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