source: src/mlx/soundsched.py@ 1161:38b1f077d211

python3
Last change on this file since 1161:38b1f077d211 was 1159:6229730a50ed, checked in by István Váradi <ivaradi@…>, 7 months ago

Added a taxi sound file for the Tupolev Tu-154 aircraft (re #387).

File size: 10.9 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_T154 : const.SOUND_TAXI_T154 },
149 { const.AIRCRAFT_B736 : const.SOUND_TAXI_BOEING737NG_ALT1,
150 const.AIRCRAFT_B737 : const.SOUND_TAXI_BOEING737NG_ALT1,
151 const.AIRCRAFT_B738 : const.SOUND_TAXI_BOEING737NG_ALT1,
152 const.AIRCRAFT_B738C : const.SOUND_TAXI_BOEING737NG_ALT1,
153 const.AIRCRAFT_B762 : const.SOUND_TAXI_BOEING767_ALT1,
154 const.AIRCRAFT_B763 : const.SOUND_TAXI_BOEING767_ALT1,
155 const.AIRCRAFT_F70 : const.SOUND_TAXI_F70,
156 const.AIRCRAFT_T154 : const.SOUND_TAXI_T154 },
157 ]
158
159 def __init__(self, flight, boardingSound = None):
160 """Construct the taxi sound."""
161 config = flight.config
162
163 super(TaxiSound, self).__init__(const.SOUND_MALEV,
164 const.STAGE_PUSHANDTAXI,
165 previousSound = boardingSound,
166 extraCondition = lambda _flight, state:
167 config.taxiSoundOnPushback or state.groundSpeed>5)
168
169 self._flight = flight
170
171 def _playbackDone(self, success, extra):
172 """Called when the playback is done.
173
174 It starts playing the aircraft type-specific taxi sound, if any."""
175 super(TaxiSound, self)._playbackDone(success, extra)
176 aircraftType = self._flight.aircraftType
177
178 soundSet = self._flight.config.soundSet
179 sounds = TaxiSound._sounds
180 sounds = sounds[soundSet] if soundSet<len(sounds) else sounds[-1]
181
182 if aircraftType in sounds:
183 startSound(sounds[aircraftType])
184
185#------------------------------------------------------------------------------
186
187class TouchdownApplause(Sound):
188 """An applause sound that is played after a gentle touchdown."""
189 def __init__(self):
190 super(TouchdownApplause, self).__init__(const.SOUND_APPLAUSE)
191
192 self._touchdownTime = None
193 self._played = False
194
195 def shouldPlay(self, flight, state, pilotHotkeyPressed):
196 """Determine if the sound should be played.
197
198 It should be played if we are 2 seconds after a gentle touchdown."""
199 if self._played or flight.tdRate is None:
200 return False
201
202 if self._touchdownTime is None:
203 self._touchdownTime = state.timestamp
204
205 if state.timestamp>=(self._touchdownTime + 2) and \
206 flight.tdRate>=-150 and flight.tdRate<0:
207 self._played = True
208 return True
209 else:
210 return False
211
212#------------------------------------------------------------------------------
213
214class SoundScheduler(object):
215 """A scheduler for the sounds."""
216 def __init__(self, flight):
217 """Construct the sound scheduler for the given flight."""
218 self._flight = flight
219 self._sounds = []
220
221 self._sounds.append(ScreamSound())
222
223 boardingSound = SimpleSound(const.SOUND_BOARDING,
224 const.STAGE_BOARDING,
225 delay = 10.0)
226 self._sounds.append(boardingSound)
227 self._sounds.append(TaxiSound(flight, boardingSound))
228 self._sounds.append(SimpleSound(const.SOUND_CAPTAIN_TAKEOFF,
229 const.STAGE_TAKEOFF,
230 extraCondition = lambda flight, state:
231 state.onTheGround,
232 considerHotkey = False))
233 self._sounds.append(SimpleSound(const.SOUND_CRUISE, const.STAGE_CRUISE))
234 self._sounds.append(SimpleSound(const.SOUND_DESCENT, const.STAGE_DESCENT,
235 extraCondition = lambda flight, state:
236 state.altitude<15000))
237 self._sounds.append(TouchdownApplause())
238 self._sounds.append(SimpleSound(const.SOUND_TAXIAFTERLAND,
239 const.STAGE_TAXIAFTERLAND,
240 delay = 10.0))
241
242 def schedule(self, state, pilotHotkeyPressed):
243 """Schedule any sound, if needed."""
244 flight = self._flight
245 if flight.config.enableSounds:
246 for sound in self._sounds:
247 sound.check(flight, state, pilotHotkeyPressed)
248
249#------------------------------------------------------------------------------
250
251class ChecklistScheduler(object):
252 """A scheduler for the checklist sounds"""
253 def __init__(self, flight):
254 """Construct the checklist scheduler for the given flight."""
255 self._flight = flight
256 self._checklist = None
257 self._itemIndex = 0
258
259 def hotkeyPressed(self):
260 """Called when the checklist hotkey is pressed."""
261 flight = self._flight
262 config = flight.config
263 if config.enableChecklists and flight.aircraftType is not None:
264 if self._checklist is None:
265 self._checklist = config.getChecklist(flight.aircraftType)
266
267 index = self._itemIndex
268 if index>=len(self._checklist):
269 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
270 "End of checklist")
271 else:
272 startSound(self._checklist[index])
273 self._itemIndex += 1
274
275#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.