Decoded: Sopwith (1984) by David L. Clark
Source file: SWSOUND.C
Beginner friendly, line-by-line code walkthrough by MaiZure

SWSOUND.C brings us all the PC speaker goodness used in Sopwith

Original code:
http://davidlclark.com/page/sopwith-source-code

Original code with line numbers
http://www.maizure.org/projects/decoded-sopwith/SWSOUND_linenum.txt

1     COMMENT
2     COMMENT
3     BLANK
4     COMMENT
5     COMMENT
6     COMMENT
7     COMMENT
8     COMMENT
9     COMMENT
10    COMMENT
11    COMMENT
12    COMMENT
13    COMMENT
14    BLANK
15    COMMENT
16    BLANK
17    COMMENT
18    COMMENT
19    COMMENT
20    COMMENT
21    COMMENT
22    Include main game header (sw.h)
23    BLANK
24    BLANK
25    BLANK
26    Define the timer controller port to 0x40
27    Define the speaker controller port at 0x61 (really keyboard controller)
28    Define the number of tones to index the tone table
29    BLANK
30    BLANK
31    Import the sound-enabled flag
32    Import the debu value for possible display (not used)
33    BLANK
34    BLANK
35    Declares the current sound type mask (0xFFFF = off)  
36    Declares the current sound parameter (part of current frequency)
37    Declares an object pointer to the sound making object
38    Declares a variable to track the last frequency (no need to recalc same)
39    Declares a pointer to the last object making the sound
40    Declares the function pointer to adjust tone (depends on sound type)
41    BLANK
42    Declares an array of structs (linked list) for tones values
43    Declares pointers to the first tone and the first unassigned tone
44    Declares tick counters for tracking the tone length (title only)
45    BLANK
46    Declares a counter for the number of active explosions
47    COMMENT
48    Declares a varaible for the current place within the explosion lines
49    Declares a variable for the current line in the tune (0-6)
50    Declares a variable for the current tone based on the place/line
51    Declares a tone length counter
52    Declares a variable for the current octave in the explosion
53    BLOCK START - expltune, Defines the tune used for explosions (and the
      title too!), see lines 493-499 for symbol definitions.
54    Line 0 of the explosion/title tune (11 notes)
55    Line 1 of the explosion/title tune (8 notes)
56    Line 2 of the explosion/title tune (11 notes)
57    Line 3 of the explosion/title tune (8 notes)
58    Line 4 of the explosion/title tune (12 notes)
59    Line 5 of the explosion/title tune (9 notes)
60    Line 6 of the explosion/title tune (null)
61    BLOCK END - expltune
62    BLANK
63    Declares flag for playing the title music
64    Declares location within a line of the title tune
65    Declares the current line of the title tune (0-6)
66    Declares the current tone within the line
67    Declares the current note length remaining
68    Declares the current octave within the title
69    BLANK
70    BLANK
71    BLANK
72    Declares a pointer to the current tune (always expltone)
73    Declares a variable to track the current line
74    Declares a variable to track the current place in a line
75    Declares a variable to track the current frequency
76    Declares a variable to track the current tone length
77    Declares a variable to track the current octave
78    BLANK
79    BLANK
80    BLANK
81    BLANK

INITIALIZE SOUND SYSTEM
82    Declare initsnd with zero arguments
83    BLOCK START - initsndt, initializes the tone table on start up
84    Declares a pointer to the tone table
85    Declares an iterator
86    BLANK
87    Loop through the tone table starting at the base
88    Connect each tone in sequence
89    The final tone is NULL
90    The table has no tone yet
91    The free tone is the first tone in the table
92    BLOCK END - initsndt
93    BLANK
94    BLANK
95    BLANK

SET NEXT SOUND TO PLAY
96    Declares and defines sound with 3 arguments
97    Arguments 1 and 2 provide flags for the sound type and frequency base
98    Argument 3 a pointer to the sound object
99    BLOCK START - sound, initializes a specific sound effect (procedural instance)
100   BLANK
101   If the input type is valid (not 0xFFFF)...
102   Set the playing sound to input type
103   Set the current parameter (freq) to the input parameter
104   Set the current object to the input object
105   Otherwise, the type may not be valid but...
106   If the input parameter is not 0xFFFF then..
107   Match the current parameter to the input...
108   And match the sound object pointers to the input..
109   End case for setting up sound
110   BLOCK END - sound
111   BLANK
112   BLANK
113   BLANK
114   BLANK

MAIN SOUND GAME LOOP PROCEDURE
115   Declares and defines swsound with no arguments
116   BLOCK START - swsound, sound procedure tied in to the game loop
117   Declares the rand function for use in this function, defined on line 477
118   Declares sound adjustment functions, defined on lines 221 and 232
119   Declares a pointer to the tone table
120   BLANK
121   Disables interrupts while we play with interrupt-driven sound
122   Pointers the tone table to the first assigned tone
123   BLOCK START - Update continuous tones for the current tick
124   Apply the tone update based on time since last update and the delta time
125   Point to the next tone
126   BLOCK END - Update continuous tones
127   BLANK
128   Reset the time tick now that updates are complete
129   Clear the title playing flag
130   BLANK
131   SWITCH on current sound type
132   BLANK
133   CASE off
134   CASE initialized by no sound assigned
135   CASE default
136   Turn off sound
137   Clear sound object pointer
138   Clear tone pointer
139   End default/off cases
140   BLANK
141   CASE airplane sound
142   If no input parameter (no speed, plane is parked)
143   Then output a specific continuous tone
144   Otherwise, the airplane is moving (param is non-zero see SWDISP.C)
145   Offset the continuous tone based on the plane speed
146   Clear last object reference
147   Clear adjustment reference
148   End of plane case
149   BLANK
150   CASE bomb sound
151   If the sound is already playing...
152   End case, which enables interrupts and returns to game loop
153   Assign tone adjust function to adjust continuous sound
154   Assign last sound object to the input object
155   Adjust the continuous tone
156   End case for bomb sounds
157   BLANK
158   CASE plane is falling
159   If the sound is already playing...
160   End case, which enables interrupts and returns to game loop
161   Assign tone adjust function to adjust continuous sound
162   Assign last sound object to the input object
163   Adjust the continuous tone
164   End case for plane falling
165   BLANK
166   CASE plane was hit
167   Choose between two random sound values, high and low sounds
168   Clear the last object sound pointer
169   Clear the last tone pointer
170   End case for plane hit
171   BLANK
172   CASE explosion
173   Kick off explosion sound
174   Clear the last tone pointer
175   Clear the last object sound pointer
176   End case for explosion
177   BLANK
178   CASE plane bullets
179   plane a very high pitched sound
180   Assign a tone adjustment function
181   Clear the last object sound pointer
182   End case for plane bullets
183   BLANK
184   CASE Title music
185   Reset the title line to 0
186   Reset the place in the line to 0
187   Initialize the title music sequence
188   Clear the tone adjustment function pointer
189   Clear the last object sound pointer
190   Set title playing to true
191   End case for title
192   BLANK
193   END SWITCH on sound type
194   BLANK
195   Enable interrupts
196   Reset sound parameters and type to ready
197   BLOCK END - swsound
198   BLANK
199   BLANK
200   BLANK
201   BLANK
202   BLANK

ADJUST SOUND EFFECT
203   Declare soundadj with no arguments
204   BLOCK START - soundadj, updates sound properties as needed
205   BLANK
206   Increments the tick counter
207   BLANK
208   If there is still a sound playing...
209   Call the appropriate sound adjust function (adjcont, adjshot, etc)
210   BLANK
211   If there are explosions occurring
212   Call explosion adjust
213   BLANK
214   If we're playing the title theme
215   Call title adjust
216   BLOCK END - soundadj
217   BLANK
218   BLANK
219   BLANK
220   BLANK

ADJUST CONTINUOUS TONE
221   Declare adjcont with no arguments
222   BLOCK START - adjcont, updates the tone table based on ticks and delta
223   Declare a local pointer to the continuous tone table
224   BLANK
225   If the last object is pointing to a valid place in the tone table
226   Adjust the tone based on delta tick and tone delta value
227   BLOCK END - adjdont
228   BLANK
229   BLANK
230   BLANK
231   BLANK

ADJUST SHOT SOUND EFFECT
232   Declare adjshot with no arguments
233   BLOCK START - adjshot, adjusts the sound of shots
234   Declare a local variable to save the last frequency played
235   BLANK
236   Checks if the last sound was silence
237   If so, it plays the shot sounds (0x1000 tone)
238   Otherwise...
239   It was playing the shot sound (cycle it off)...
240   Save the shot sound...
241   Change the tone to silence
242   BLOCK END - adjshot
243   BLANK
244   BLANK
245   BLANK
246   BLANK

ADJUST EXPLOSION SOUND
247   Declare adjexpl with no arguments
248   BLOCK START - adjexpl, check to update the explosion sound
249   If the update explosion tick counter hasn't expired..
250   No updates necessary
251   BLANK
252   Otherwise, update the explosion sound
253   BLOCK END - adjexpl
254   BLANK
255   BLANK
256   BLANK
257   BLANK

PLAY NEXT EXPLOSION NOTE
258   Declare explnote with no arguments
259   BLOCK START - explnote, Update the explosion sound
260   Get the current line in the explosion sound
261   Get the current position within the line
262   Point to the explosion sound
263   Set the octave
264   Play the note with the current settings (this changes the settings)
265   Store the new line
266   Store the new place
267   Store the new tone
268   Disable interrupts
269   Add back new note duration
270   Enable interrupts
271   Store the new octave
272   BLOCK END explnote
273   BLANK
274   BLANK
275   BLANK
276   BLANK

ADJUST TITLE SOUND
277   Declare adjtitl with no arguments
278   BLOCK START - adjtitl, check to update title music
279   If the title tick timer hasn't expired..
280   No need to update
281   Otherwise, update the title note
282   BLOCK END - adjtitl
283   BLANK
284   BLANK
285   BLANK
286   BLANK

PLAY NEXT TITLE NOTE
287   Declare titlnote with no arguments
288   BLOCK START - titlnote, updates the next note in the music title
289   BLANK
290   Get the current line in the title music score
291   Get the current position on the line
292   Get the current tone we're pointing to
293   Get the octave
294   Play that note (this updates the settings we just got)
295   Store the new line
296   Store the new position
297   Store the new tone
298   Disable interrupts
299   Update the play duration
300   Enable interrupts
301   Store the new octave
302   Disable sound
303   Pay the title tone
304   BOCK END - titlnote
305   BLANK
306   BLANK
307   BLANK
308   BLANK
309   BLANK

START A NEW SOUND
310   Declare initsound with 2 arguments
311   Argument 1 is the pointer to the object making the sound
312   Argument 2 is the type of sound
313   BLOCK START - initsound, prepares a sound effect for playing
314   Declare a local pointer the object
315   Declare a local pointer to the tone able
316   Initialize the tone table
317   BLANK
318   Point to the input object and check if it already has a sound
319   If so, then return -- it's already playing
320   BLANK
321   BLOCK START - Explosion sound, if the input object is an explosion..
322   Then disable interrupts (we're about to play with sound/timer)
323   BLOCK START - First explosion
324   Initialize the sound sequence to the first line
325   And initialize to the first position
326   Set up the explosion sound
327   BLOCK END - First explosion
328   Save a temporary pointer to a sound object (not valid until below)
329   Enable interrupts
330   Return to game play
331   BLOCK END - Explosion sound
332   BLANK
333   BLOCK START - New tone table, allocate tone table and if successful...
334   Disable interrupts
335   SWITCH on type of sound
336   CASE Bomb
337   Set the base tone to a high pitched sound
338   The sound should decrease pitch each tick
339   End case for bomb
340   CASE falling plane
341   Set the base tone to a high, but lower pitch than the bomb
342   The tone should increase pitch as the plane falls
343   End case for falling plane
344   CASE default
345   No sound, nothing happens
346   BLOCK END - New tone table
347   Set the object's sound pointer to the tone table entry
348   Enable interrupts
349   Return to gameplay
350   BK
351   BLOCK END - initsound, prepares a sound effect for playing
352   BLANK
353   BLANK
354   BLANK
355   BLANK

INITIALIZE TONE TABLE
356   Declare allocton with no arguments
357   BLOCK START - allocton, initializes the tone table to empty
358   Declare a local pointer to a tone table entry
359   BLANK
360   If there isn't space in the tone table
361   Return falure
362   BLANK
363   Set the tone table pointer to the first free entry
364   Set the free entry to the subsequent entry
365   BLANK
366   Set the next pointer as the global first pointer
367   There is no previous tone
368   BLANK
369   If the first tone exists then...
370   Link it back to this tone
371   BLANK
372   Return success (first tone is this tone)
373   BLOCK END - allocton
374   BLANK
375   BLANK
376   BLANK

DEALLOCATE TONE TABLE
377   Declare and define deallton with one argument
378   Argument is the tone table pointer
379   BLOCK START - deallton, deallocates the tone entry
380   Declare local pointers to the input tone and its previous tone
381   BLANK
382   BLANK
383   If the tone has a previous...
384   Then map around the tone we're about to deallocate
385   Otherwise...
386   This is the first tone
387   BLANK
388   If the there is a next tone...
389   Then again, map around the current
390   BLANK
391   Set the next done to free
392   Put the next tone at the head of the free list
393   BLOCK END - deallton
394   BLANK
395   BLANK
396   BLANK
397   BLANK
398   BLANK
399   BLANK
400   BLANK
401   BLANK

STOP CURRENT SOUND
402   Declare stopsound with 1 argument
403   Declare a pointer to the object making the sound
404   BLOCK START - stopsound
405   Delcare a local pointer to a tone table entry
406   BLANK
407   If the object has no associated sound
408   Then return -- no sound to play
409   BLANK
410   Disable interrupts
411   If the object is of type explosion...
412   Decrement explosions because it's finished playing
413   Otherwise...
414   Deallocate the tone type
415   In all cases, clear the object sound type
416   Enable interrupts
417   BLOCK END - stopsound
418   BLANK
419   BLANK
420   BLANK
421   BLANK
422   BLANK
423   BLANK

NOTE ON THE PC SPEAKER
We're operating the PC Speaker using the system timer chip (8253) in square
      wave, mode 3. This means that any value we write to the PIT determines the duration
      between square state changes. Although Sopwith uses the variable name 'freq',
      it should be interpreted as duration. The LOWER the value, the higher pitch
      tone we hear. 


OUTPUT TONE TO PC SPEAKER
424   Declares and defines tone with 1 argument
425   Argument 1 is the frequency in two bytes
426   BLOCK START - tone, sets the PC's 8253 to output
427   BLANK
428   If sound isn't playing..
429   Then why are we here?
430   BLANK
431   If the previously set frequency hasn't changed...
432   Then we don't need to change anything
433   BLANK
434   If this is an IBM PC...
435   If we aren't playing any sounds right now..
436   Set the PIT to use channel 2 (0x42) with two byte writes and square waves
      non BCD
437   Writes the first byte of the count to the PIT
438   Writes the second byte of the count to the PIT
439   If we aren't playing any sounds right now...
440   Enable timer to speaker and speaker data. Writes current status back to
      port with bits 0 and 1 forced
441   End case for IBM PC
442   BLANK
443   Save the frequency
444   Prepare the frequence as a debug value
445   BLOCK END - tone
446   BLANK
447   BLANK
448   BLANK
449   BLANK

DISABLE SOUND
450   Declares soundoff with no arguments
451   BLOCK START - soundoff, disables sound
452   BLOCK START - Sound active, if we are playing a sound...
453   and this is an IBM PC...
454   Disable the PC speaker
455   End IBM PC case
456   Set the last frequency to 0
457   Clear the debug value
458   BLOCK END - Sound active
459   BLOCK END - soundoff
460   BLANK
461   BLANK
462   BLANK
463   BLANK
464   BLANK

RANDOM SEED
465   Defines seed, a high entropy 50-byte sequence 
466   Defines bytes 0-7 of the seed
467   Defines bytes 8-15 of the seed
468   Defines bytes 16-23 of the seed
469   Defines bytes 24-31 of the seed
470   Defines bytes 32-39 of the seed
471   Defines bytes 40-47 of the seed
472   Defines bytes 48 and 49 of the seed
473   Ends seed definition
474   BLANK
475   BLANK
476   BLANK

RANDOMIZE FUNCTION
477   Declares rand with one argument
478   The arugmnet is the modulus for the seed (random between 0 and mod - 1)
479   BLOCK START - rand, returns a random number between 0 and input - 1
480   Initializes index to 1
481   BLANK
482   If index is higher than 50
483   Reset it to zero
484   Look up random number in seed and return mod seed
485   BLOCK END - rand
486   BLANK
487   BLANK
488   BLANK
489   BLANK
490   BLANK
491   BLANK
492   BLANK

DEFINE SOUND ENCODINGS
493   Defines macro for the end of note symbol (used to switch by symbol)
494   Defines macro for the octave increase symbol
495   Defines macro for the octave decrease symbol
496   Defines macro for sharp notes symbol
497   Defines macro for flat notes symbol
498   Defines macro for dotted note symbol
499   Defines macro for rest symbol
500   BLANK
501   BLANK
502   BLANK
503   BLANK
504   BLANK

UPDATE CURRENT SOUND EFFECT TONE
505   Declares playnote with no arguments
506   BLOCK START - playnote, finds the tone to play and sets sound variables
507   BLANK
508   Defines a note index of semi-tones based on the major scale in the key
      of C
509   Defines the frequency of the notes, note A = 440
510   BLANK
511   Declares variables for note properties
512   Declares an index
513   Declares another index
514   BLANK
515   Declares an array to hold duration
516   Declare more note variables
517   BLANK
518   Declares an octave scalar
519   Declares a dotted note flag
520   BLANK
521   Intializes a starting flag to true
522   BLANK
523   Initializes one index to 0
524   Initializes the moment duration to 0
525   Initializes dotted note value to 2
526   Initializes octave scalar to 256
527   BLANK
528   START BLOCK - Loop on notes
529   If we at the start of the tone sequence...
530   Then reset the scaling factor to base 256
531   BLANK
532   Iterate place in the tone...if we're at the end of the line...
533   Iterate lines in the tone...if we're at the end of all lines..
534   Go back to the start of the tone
535   End tone movement
536   BLANK
537   If we're at the start of the tone
538   Skip to next interator
539   End this loop (go to line 541)
540   End tone iteration
541   Set first place flag to falg
542   If we're at the end of the note, then we don't need to do anything else
543   End this iteration
544   BLANK
545   If the next character is a letter, then it's probably a note reference
546   Set the index to the offset value of the letter from A
547   save the characer value
548   BLOCK START - Sound function, handles encoded sound functions
549   SWITCH on sound function
550   If it's >, then double the octave factor
551   If it's <, then halve the octave factor
552   If its +, then move forward in the index by 1 semi-tone
553   IF its -, then move backward in the index by 1 semi-tone
554   If it's a dot, then extend the note by half
555   Otherwise...
556   If it's a rest then...
557   Point in to the duration string array
558   End case
559   BLOCK END - sound function
560   BLANK
561   BLOCK END - Loop on notes
562   BLANK
563   Nullterm the string
564   Convert it to a number (it should be a rest duration)
565   If it's less than set, set it to 4 (a whole note)
566   Convert the duration to ticks  
567   BLANK
568   If the note is a rest then
569   The frequency is 32000 (duration is nil)
570   otherwise, there's a note so...
571   Offset the index by the adjustment
572   If the index went below the current octave then loop while it's below
      0
573   Move back up the whole scale
574   But drop the octave
575   End case for low octaves
576   Same thing for going high...if the note is higher than the scale
577   Subtract 12 semi-tones
578   But raise the octave factor
579   End case for high octaves
580   Calculate the frequency (Duration) using the note, frequency, and duration
581   End check for non-rests
582   Save the frequency for the next tick
583   Save the duration for the next tick
584   BLOCK END - playnote
585   EOF