source: src/mlx/soundsched.py

python3
Last change on this file was 1123:f0334593281d, checked in by István Váradi <ivaradi@…>, 4 months ago

Support for an alternative sound set

File size: 10.7 KB
Line 
1
2from .sound import startSound
3from . import const
4from . import fs
5
6import threading
7
8#------------------------------------------------------------------------------
9
10## @package mlx.soundsched
11#
12# Sound playback scheduling.
13#
14# The logger may play certain sound files when certain conditions hold. For
15# example, if the landing or the strobe lights are switched on during taxi, and
16# we go to the takeoff state, a voice says "Cabin crew, please take your seats
17# for takeoff". Some sounds are played only when the pilot presses a hotkey, if
18# the program is set up that way.
19#
20# Each sound is represented by an instance of a subclass of \ref Sound. If a
21# new \ref mlx.fs.AircraftState "aircraft state" comes in from the simulator,
22# the \ref Sound.shouldPlay "shouldPlay" functions of the registered sound
23# objects are called with that state, and if the function returns \c True, the
24# corresponding sound file is played.
25#
26# The \ref ChecklistScheduler class handles the playback of the checklists,
27# which are basically sequences of sound files played back one-by-one when a
28# certain hotkey is pressed.
29
30#------------------------------------------------------------------------------
31
32class Sound(object):
33 """A sound (file) that should be played in certain circumstances."""
34
35 # Common lock for sounds
36 _lock = threading.Lock()
37
38 def __init__(self, name):
39 """Construct the sound object for the sound file with the given
40 name."""
41 self._name = name
42
43 self._playing = 0
44
45 @property
46 def playing(self):
47 """Determine if sound is playing or not."""
48 return self._playing>0
49
50 def check(self, flight, state, pilotHotkeyPressed):
51 """Check if the file should be played, and if it should be, play it."""
52 if self.shouldPlay(flight, state, pilotHotkeyPressed):
53 with Sound._lock:
54 self._playing += 1
55 startSound(self._name, finishCallback = self._playbackDone)
56
57 def shouldPlay(self, flight, state, pilotHotkeyPressed):
58 """Determine if the sound should be played.
59
60 This default implementation returns False."""
61 return False
62
63 def _playbackDone(self, success, extra):
64 """Called when the playback of thee sound has finished (or failed)."""
65 if success is None:
66 print("Failed to start sound", self._name)
67 elif not success:
68 print("Failed to finish sound", self._name)
69 with Sound._lock:
70 self._playing -= 1
71
72#------------------------------------------------------------------------------
73
74class ScreamSound(Sound):
75 """A screaming sound that is played under certain circumstance."""
76 def __init__(self):
77 """Construct the screaming sound."""
78 super(ScreamSound, self).__init__(const.SOUND_SCREAM)
79
80 def shouldPlay(self, flight, state, pilotHotkeyPressed):
81 """Determine if the sound should be played.
82
83 It should be played if it is not being played and the absolute value of
84 the vertical speed is greater than 6000 fpm."""
85 return not self.playing and abs(state.vs)>6000
86
87#------------------------------------------------------------------------------
88
89class SimpleSound(Sound):
90 """A simple sound that should be played only once, in a certain flight
91 stage when the hotkey has been pressed or it is not the pilot who controls
92 the sounds.
93
94 If it is not the pilot who controls the sounds, it may be delayed relative
95 to the first detection of a certain flight stage."""
96 def __init__(self, name, stage, delay = 0.0, extraCondition = None,
97 considerHotkey = True, previousSound = None):
98 """Construct the simple sound."""
99 super(SimpleSound, self).__init__(name)
100
101 self._stage = stage
102 self._delay = delay
103 self._extraCondition = extraCondition
104 self._considerHotkey = considerHotkey
105 self._previousSound = previousSound
106
107 self._played = False
108 self._stageDetectionTime = None
109
110 def shouldPlay(self, flight, state, pilotHotkeyPressed):
111 """Determine if the sound should be played."""
112 if flight.stage!=self._stage or self._played or \
113 (self._previousSound is not None and
114 self._previousSound.playing):
115 return False
116
117 toPlay = False
118 if flight.config.pilotControlsSounds and self._considerHotkey:
119 toPlay = pilotHotkeyPressed
120 else:
121 if self._stageDetectionTime is None:
122 self._stageDetectionTime = state.timestamp
123 toPlay = state.timestamp>=(self._stageDetectionTime + self._delay)
124 if toPlay and self._extraCondition is not None:
125 toPlay = self._extraCondition(flight, state)
126
127 if toPlay:
128 self._played = True
129
130 return toPlay
131
132#------------------------------------------------------------------------------
133
134class TaxiSound(SimpleSound):
135 """The taxi sound.
136
137 It first plays the Malev theme song, then an aircraft-specific taxi
138 sound. The playback is started only, if the boarding sound is not being
139 played."""
140
141 _sounds = [{ const.AIRCRAFT_B736 : const.SOUND_TAXI_BOEING737NG,
142 const.AIRCRAFT_B737 : const.SOUND_TAXI_BOEING737NG,
143 const.AIRCRAFT_B738 : const.SOUND_TAXI_BOEING737NG,
144 const.AIRCRAFT_B738C : const.SOUND_TAXI_BOEING737NG,
145 const.AIRCRAFT_B762 : const.SOUND_TAXI_BOEING767,
146 const.AIRCRAFT_B763 : const.SOUND_TAXI_BOEING767,
147 const.AIRCRAFT_F70 : const.SOUND_TAXI_F70 },
148 { const.AIRCRAFT_B736 : const.SOUND_TAXI_BOEING737NG_ALT1,
149 const.AIRCRAFT_B737 : const.SOUND_TAXI_BOEING737NG_ALT1,
150 const.AIRCRAFT_B738 : const.SOUND_TAXI_BOEING737NG_ALT1,
151 const.AIRCRAFT_B738C : const.SOUND_TAXI_BOEING737NG_ALT1,
152 const.AIRCRAFT_B762 : const.SOUND_TAXI_BOEING767_ALT1,
153 const.AIRCRAFT_B763 : const.SOUND_TAXI_BOEING767_ALT1,
154 const.AIRCRAFT_F70 : const.SOUND_TAXI_F70 },
155 ]
156
157 def __init__(self, flight, boardingSound = None):
158 """Construct the taxi sound."""
159 config = flight.config
160
161 super(TaxiSound, self).__init__(const.SOUND_MALEV,
162 const.STAGE_PUSHANDTAXI,
163 previousSound = boardingSound,
164 extraCondition = lambda _flight, state:
165 config.taxiSoundOnPushback or state.groundSpeed>5)
166
167 self._flight = flight
168
169 def _playbackDone(self, success, extra):
170 """Called when the playback is done.
171
172 It starts playing the aircraft type-specific taxi sound, if any."""
173 super(TaxiSound, self)._playbackDone(success, extra)
174 aircraftType = self._flight.aircraftType
175
176 soundSet = self._flight.config.soundSet
177 sounds = TaxiSound._sounds
178 sounds = sounds[soundSet] if soundSet<len(sounds) else sounds[-1]
179
180 if aircraftType in sounds:
181 startSound(sounds[aircraftType])
182
183#------------------------------------------------------------------------------
184
185class TouchdownApplause(Sound):
186 """An applause sound that is played after a gentle touchdown."""
187 def __init__(self):
188 super(TouchdownApplause, self).__init__(const.SOUND_APPLAUSE)
189
190 self._touchdownTime = None
191 self._played = False
192
193 def shouldPlay(self, flight, state, pilotHotkeyPressed):
194 """Determine if the sound should be played.
195
196 It should be played if we are 2 seconds after a gentle touchdown."""
197 if self._played or flight.tdRate is None:
198 return False
199
200 if self._touchdownTime is None:
201 self._touchdownTime = state.timestamp
202
203 if state.timestamp>=(self._touchdownTime + 2) and \
204 flight.tdRate>=-150 and flight.tdRate<0:
205 self._played = True
206 return True
207 else:
208 return False
209
210#------------------------------------------------------------------------------
211
212class SoundScheduler(object):
213 """A scheduler for the sounds."""
214 def __init__(self, flight):
215 """Construct the sound scheduler for the given flight."""
216 self._flight = flight
217 self._sounds = []
218
219 self._sounds.append(ScreamSound())
220
221 boardingSound = SimpleSound(const.SOUND_BOARDING,
222 const.STAGE_BOARDING,
223 delay = 10.0)
224 self._sounds.append(boardingSound)
225 self._sounds.append(TaxiSound(flight, boardingSound))
226 self._sounds.append(SimpleSound(const.SOUND_CAPTAIN_TAKEOFF,
227 const.STAGE_TAKEOFF,
228 extraCondition = lambda flight, state:
229 state.onTheGround,
230 considerHotkey = False))
231 self._sounds.append(SimpleSound(const.SOUND_CRUISE, const.STAGE_CRUISE))
232 self._sounds.append(SimpleSound(const.SOUND_DESCENT, const.STAGE_DESCENT,
233 extraCondition = lambda flight, state:
234 state.altitude<15000))
235 self._sounds.append(TouchdownApplause())
236 self._sounds.append(SimpleSound(const.SOUND_TAXIAFTERLAND,
237 const.STAGE_TAXIAFTERLAND,
238 delay = 10.0))
239
240 def schedule(self, state, pilotHotkeyPressed):
241 """Schedule any sound, if needed."""
242 flight = self._flight
243 if flight.config.enableSounds:
244 for sound in self._sounds:
245 sound.check(flight, state, pilotHotkeyPressed)
246
247#------------------------------------------------------------------------------
248
249class ChecklistScheduler(object):
250 """A scheduler for the checklist sounds"""
251 def __init__(self, flight):
252 """Construct the checklist scheduler for the given flight."""
253 self._flight = flight
254 self._checklist = None
255 self._itemIndex = 0
256
257 def hotkeyPressed(self):
258 """Called when the checklist hotkey is pressed."""
259 flight = self._flight
260 config = flight.config
261 if config.enableChecklists and flight.aircraftType is not None:
262 if self._checklist is None:
263 self._checklist = config.getChecklist(flight.aircraftType)
264
265 index = self._itemIndex
266 if index>=len(self._checklist):
267 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
268 "End of checklist")
269 else:
270 startSound(self._checklist[index])
271 self._itemIndex += 1
272
273#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.