1 /*
2 ** sndseq.cpp
3 **
4 **---------------------------------------------------------------------------
5 ** Copyright 2013 Braden Obrzut
6 ** All rights reserved.
7 **
8 ** Redistribution and use in source and binary forms, with or without
9 ** modification, are permitted provided that the following conditions
10 ** are met:
11 **
12 ** 1. Redistributions of source code must retain the above copyright
13 ** notice, this list of conditions and the following disclaimer.
14 ** 2. Redistributions in binary form must reproduce the above copyright
15 ** notice, this list of conditions and the following disclaimer in the
16 ** documentation and/or other materials provided with the distribution.
17 ** 3. The name of the author may not be used to endorse or promote products
18 ** derived from this software without specific prior written permission.
19 **
20 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 **---------------------------------------------------------------------------
31 **
32 **
33 */
34
35 #include "id_sd.h"
36 #include "m_random.h"
37 #include "scanner.h"
38 #include "sndseq.h"
39 #include "sndinfo.h"
40 #include "w_wad.h"
41 #include "wl_game.h"
42
43 #include <climits>
44
45 enum ESndSeqFlag
46 {
47 SSF_NoStopCutOff = 0x1
48 };
49
50 // We used a fixed size instruction. What the instruction does is determined
51 // by what flags are set, so a single instruction can play a sound and delay
52 // for example.
53 enum ESndSeqInstruction
54 {
55 SSI_PlaySound = 0x1,
56 SSI_Delay = 0x2,
57 SSI_End = 0x4,
58 SSI_WaitForFinish = 0x8,
59 SSI_Repeat = 0x10
60 };
61
SoundSequence()62 SoundSequence::SoundSequence() : Flags(0)
63 {
64 }
65
AddInstruction(const SndSeqInstruction & instr)66 void SoundSequence::AddInstruction(const SndSeqInstruction &instr)
67 {
68 Instructions.Push(instr);
69 }
70
Clear()71 void SoundSequence::Clear()
72 {
73 for(unsigned int i = 0;i < NUM_SEQ_TYPES;++i)
74 AltSequences[i] = NAME_None;
75 Instructions.Clear();
76 }
77
GetSequence(SequenceType type) const78 const SoundSequence &SoundSequence::GetSequence(SequenceType type) const
79 {
80 if(AltSequences[type] == NAME_None)
81 return *this;
82 return SoundSeq(AltSequences[type], type);
83 }
84
SetFlag(unsigned int flag,bool set)85 void SoundSequence::SetFlag(unsigned int flag, bool set)
86 {
87 if(set)
88 Flags |= flag;
89 else
90 Flags &= ~flag;
91 }
92
SetSequence(SequenceType type,FName sequence)93 void SoundSequence::SetSequence(SequenceType type, FName sequence)
94 {
95 AltSequences[type] = sequence;
96 }
97
Start() const98 const SndSeqInstruction *SoundSequence::Start() const
99 {
100 if(Instructions.Size() == 0)
101 return NULL;
102 return &Instructions[0];
103 }
104
105 //------------------------------------------------------------------------------
106
107 SndSeqTable SoundSeq;
108
Init()109 void SndSeqTable::Init()
110 {
111 Printf("S_Init: Reading SNDSEQ defintions.\n");
112
113 int lastLump = 0;
114 int lump = 0;
115 while((lump = Wads.FindLump("SNDSEQ", &lastLump)) != -1)
116 {
117 ParseSoundSequence(lump);
118 }
119 }
120
ParseSoundSequence(int lumpnum)121 void SndSeqTable::ParseSoundSequence(int lumpnum)
122 {
123 FMemLump lump = Wads.ReadLump(lumpnum);
124 Scanner sc((const char*)(lump.GetMem()), lump.GetSize());
125 sc.SetScriptIdentifier(Wads.GetLumpFullName(lumpnum));
126
127 while(sc.TokensLeft())
128 {
129 if(sc.CheckToken('['))
130 {
131 sc.MustGetToken(TK_Identifier);
132 SoundSequence &seq = Sequences[sc->str];
133 seq.Name = sc->str;
134 seq.Clear();
135
136 while(!sc.CheckToken(']'))
137 {
138 sc.MustGetToken(TK_IntConst);
139 SequenceType type = static_cast<SequenceType>(sc->number);
140
141 if(!sc.GetNextString())
142 sc.ScriptMessage(Scanner::ERROR, "Expected logical sound name.");
143 seq.SetSequence(type, sc->str);
144 }
145 }
146 else
147 {
148 sc.MustGetToken(':');
149 sc.MustGetToken(TK_Identifier);
150 SoundSequence &seq = Sequences[sc->str];
151 seq.Name = sc->str;
152 seq.Clear();
153
154 do
155 {
156 sc.MustGetToken(TK_Identifier);
157
158 if(sc->str.CompareNoCase("end") == 0)
159 {
160 SndSeqInstruction instr;
161 instr.Instruction = SSI_End;
162 seq.AddInstruction(instr);
163 break;
164 }
165 else if(sc->str.CompareNoCase("delay") == 0)
166 {
167 SndSeqInstruction instr;
168 instr.Instruction = SSI_Delay;
169 instr.ArgumentRand = 0;
170
171 sc.MustGetToken(TK_IntConst);
172 instr.Argument = sc->number;
173
174 seq.AddInstruction(instr);
175 }
176 else if(sc->str.CompareNoCase("delayrand") == 0)
177 {
178 SndSeqInstruction instr;
179 instr.Instruction = SSI_Delay;
180
181 sc.MustGetToken(TK_IntConst);
182 instr.Argument = sc->number;
183
184 sc.MustGetToken(TK_IntConst);
185 instr.ArgumentRand = sc->number - instr.Argument;
186
187 seq.AddInstruction(instr);
188 }
189 else if(sc->str.CompareNoCase("play") == 0)
190 {
191 SndSeqInstruction instr;
192 instr.Instruction = SSI_PlaySound;
193
194 if(!sc.GetNextString())
195 sc.ScriptMessage(Scanner::ERROR, "Expected logical sound name.");
196 instr.Sound = sc->str;
197
198 seq.AddInstruction(instr);
199 }
200 else if(sc->str.CompareNoCase("playrepeat") == 0)
201 {
202 SndSeqInstruction instr;
203 instr.Instruction = SSI_PlaySound|SSI_WaitForFinish|SSI_Repeat;
204
205 if(!sc.GetNextString())
206 sc.ScriptMessage(Scanner::ERROR, "Expected logical sound name.");
207 instr.Sound = sc->str;
208
209 seq.AddInstruction(instr);
210 }
211 else if(sc->str.CompareNoCase("nostopcutoff") == 0)
212 {
213 seq.SetFlag(SSF_NoStopCutOff, true);
214 }
215 else if(sc->str.CompareNoCase("stopsound") == 0)
216 {
217 if(!sc.GetNextString())
218 sc.ScriptMessage(Scanner::ERROR, "Expected logical sound name.");
219 seq.StopSound = sc->str;
220 }
221 else
222 {
223 sc.ScriptMessage(Scanner::ERROR, "Unknown sound sequence command '%s'.", sc->str.GetChars());
224 }
225 }
226 while(sc.TokensLeft());
227 }
228 }
229 }
230
operator ()(FName sequence,SequenceType type) const231 const SoundSequence &SndSeqTable::operator() (FName sequence, SequenceType type) const
232 {
233 static const SoundSequence NullSequence;
234
235 const SoundSequence *seq = Sequences.CheckKey(sequence);
236 if(seq)
237 return seq->GetSequence(type);
238 return NullSequence;
239 }
240
241 //------------------------------------------------------------------------------
242
SndSeqPlayer(const SoundSequence & sequence,MapSpot Source)243 SndSeqPlayer::SndSeqPlayer(const SoundSequence &sequence, MapSpot Source) :
244 Sequence(sequence), Source(Source), Delay(0), Playing(true), WaitForDone(false)
245 {
246 Current = Sequence.Start();
247 if(Current == NULL)
248 Playing = false;
249 }
250
~SndSeqPlayer()251 SndSeqPlayer::~SndSeqPlayer()
252 {
253 if(Playing)
254 Stop();
255 }
256
257 // SD_SoundPlaying() seems to intentionally be for adlib/pc speaker only. At
258 // least it has been like that since the beginning of ECWolf.
259 extern FString SoundPlaying;
Tick()260 void SndSeqPlayer::Tick()
261 {
262 if(!Playing || (Delay != 0 && --Delay > 0))
263 return;
264
265 if(WaitForDone)
266 {
267 if(SoundPlaying.IsNotEmpty())
268 return;
269 else
270 WaitForDone = false;
271 }
272
273 do
274 {
275 if(Current->Instruction & SSI_PlaySound)
276 {
277 PlaySoundLocMapSpot(Current->Sound, Source);
278 }
279
280 if(Current->Instruction & SSI_Delay)
281 {
282 Delay = Current->Argument + (Current->ArgumentRand ? (M_Random.GenRand32() % Current->ArgumentRand) : 0);
283 }
284
285 if(Current->Instruction & SSI_End)
286 {
287 Playing = false;
288 }
289
290 if(Current->Instruction & SSI_WaitForFinish)
291 {
292 WaitForDone = true;
293 if(Delay == 0)
294 Delay = 1;
295 }
296
297 if(!(Current->Instruction & SSI_Repeat))
298 {
299 ++Current;
300 }
301 }
302 while(Delay == 0 && Playing);
303 }
304
Stop()305 void SndSeqPlayer::Stop()
306 {
307 Playing = false;
308
309 // Unfortunately due to limitations of the sound code we can't determine
310 // what sound is playing much less stop the sound.
311
312 if(Sequence.GetStopSound() != NAME_None)
313 PlaySoundLocMapSpot(Sequence.GetStopSound(), Source);
314 }
315
operator <<(FArchive & arc,SndSeqPlayer * & seqplayer)316 FArchive &operator<< (FArchive &arc, SndSeqPlayer *&seqplayer)
317 {
318 FName seqname;
319 MapSpot source;
320 unsigned int offs;
321
322 if(arc.IsStoring())
323 {
324 if(seqplayer == NULL)
325 {
326 // Can't do much here, so flag and move on.
327 offs = UINT_MAX;
328 arc << offs;
329 return arc;
330 }
331
332 seqname = seqplayer->Sequence.GetSeqName();
333 source = seqplayer->Source;
334 offs = static_cast<unsigned int>(seqplayer->Current - seqplayer->Sequence.Start());
335 }
336
337 // First check that we actually stored a sequence player.
338 arc << offs;
339 if(arc.IsLoading() && offs == UINT_MAX)
340 {
341 delete seqplayer;
342 seqplayer = NULL;
343 return arc;
344 }
345
346 arc << seqname << source;
347
348 if(arc.IsLoading())
349 {
350 // We don't need to worry about the sequence type here since seqname
351 // should have already resolved to the proper sequence.
352 delete seqplayer;
353 seqplayer = new SndSeqPlayer(SoundSeq(seqname, SEQ_OpenNormal), source);
354 seqplayer->Current += offs;
355 }
356
357 arc << seqplayer->Delay
358 << seqplayer->Playing
359 << seqplayer->WaitForDone;
360
361 return arc;
362 }
363