music resource: int16 size int16 xxx int16 priority (low byte: priority, high byte: sound is delayable */ int16 speaker_streams[4] int16 PCjr_streams[4] ... streams ... The variable xxx_streams[i] contains the offset from the beginning of the resource to the stream of commands. There are 4 channels. On every channel can play notes and there is a stream of commands associated with it. When a channel has finished playing a note it takes the next command from its stream. Stream format: 0..0xf8 means play a note of length note_length[byte&0x1f] on channel (byte>>5). next byte is note, 0x7f for pause NOTE: every script can play a note on any channel, not just the channel that executes the script. note_length is a fixed array. The actual length depends on channel->tempo. 0xf8: set hull curve next byte: 2*hull curve. There are 14 predefined hull curves six for PC speaker the remaining for PCjr. The 2* is because it is an offset into a int16 array. 0xf9: set freq mod curve next byte: curve number: There are five predefined curves: 1: freq going up->down->up->down->... 0: freq going up 2: fast warbler 3: slow warbler 4: random noise 0xfa: clear current channel clears params like hull-curve, freqmod, basefreq. 0xfb: ret from subroutine 0xfc: call subroutine next word absolute offset from beginning of resource. only one call/ret level is supported. 0xfd: select & clear another channel (otherwise same as 0xfa) next byte: channel number 0xfe: loop music next byte: pointer to parameter containing number of loops next word: relative offset. 0xff: change script parameter next byte: param offset next word: value NOTE: Some of the parameters should not be changed by the music as they are pointers to internal data structures. I don't have a complete list of params that are changed. parameters (order is important): uint16 time_left; uint16 next_cmd; uint16 base_freq; uint16 freq_delta; uint16 freq; uint16 volume; uint16 volume_delta; uint16 tempo; uint16 inter_note_pause; uint16 transpose; uint16 note_length; uint16 hull_curve; uint16 hull_offset; uint16 hull_counter; uint16 freqmod_table_ptr; uint16 freqmod_offset; uint16 freqmod_incr; uint16 freqmod_multiplier; uint16 freqmod_modulo; uint16 general_purpose_vars[5]; uint16 music_script_nr; frequency/volume manipulation (executed on every tick): volume += volume_delta; base_freq += freq_delta; freqmod_offset += freqmod_incr; if (freqmod_offset > freqmod_modulo) freqmod_offset -= freqmod_modulo; freq = (int) (freqmod_table[freqmod_table_ptr + (freqmod_offset >> 4)]) * (int) channel->freqmod_multiplier / 256 + channel->base_freq; if (note_length && !--note_length) { /* switch hull curve to decay part */ channel->hull_offset += 16; channel->hull_counter = 1; } if (!--time_left) execute_next_channel_script_command if (hull_counter && !--hull_counter) { set volume_delta and hull_counter according to next entry in hull curve. } example (sound 12, Telephone busy): 000046c0: 9a00 .. 000046d0: 00e8 6400 1600 0000 0000 0000 6a00 8200 ..d.........j... 000046e0: 0000 0000 ff0a 0300 ff04 1027 ff00 0800 ...........'.... 000046f0: ff26 0700 ff04 2a0e ff00 0300 ff04 fa0b .&....*......... 00004700: ff00 0300 fe26 ecff ff0a 0000 ff00 3c00 .....&........<. 00004710: ff0a 0300 ff04 1027 ff00 0800 ff26 0d00 .......'.....&.. 00004720: ff04 2a0e ff00 0300 ff04 fa0b ff00 0300 ..*............. 00004730: fe26 ecff fe28 d0ff ff0a a861 ff04 015b .&...(.....a...[ 00004740: ff00 6000 ff0a 0000 ff00 6000 ff00 0000 ..`.......`..... 00004750: ff0a a861 ff04 a74a ff00 6000 ff0a 0000 ...a...J..`..... 00004760: ff00 6000 ff00 0000 PC-sound: first channel starts at 0016, other channels have no script. ff 0a 0003 set volume = 3 (for PC speaker volume 3 means on, 0 means off) ff 04 2710 set base freq. to 10000 (pc speaker specific) ff 00 0008 play for eight ticks ff 26 0007 set 26 = 7 lbl1: ff 04 0e2a ff 00 0003 play for three ticks ff 04 0bfa ff 00 0003 play for three ticks fe 26 lbl1 repeat *26 (7) times (this causes the brbrbrb-sound) lbl2: ff 0a 0000 set volume = 0 ff 00 003c pause for 0x3c ticks ff 0a 0003 set volume = 3 ff 04 2710 set base freq to 10000 ff 00 0008 play for eight ticks ff 26 000d set 26 = 14 lbl3: ff 04 0e2a ff 00 0003 ff 04 0bfa ff 00 0003 fe 26 lbl3 repeat *26 (14) times (this causes the brbrbrb-sound) fe 28 lbl2 repeat for infinity as *28 is zero. priority-resolving algorithm: next_sound_nr : next sound to execute next_sound_prio: prio of next sound current_sound_nr : current sound number current_sound_prio: prio of current sound sub load_music (nr) { char * sound = get_sound(nr); int prio = sound->prio; flgPlayThis = 0; if (current_sound_nr == 0 || low(current_sound_prio) <= low(prio)) { /* the music loaded has higher priority than current sound * --> make the new sound current and try to enqueue the * previously current sound */ sound_status[nr]++; sound_status[current_sound_nr]--; swap(current_sound_nr, nr); swap(current_sound_prio, prio); flgPlayThis = 1; } if (current_sound_nr == 0) { /* 0 means silence and this clears next_sound */ nr = 0; goto set_next_sound; } if ((high(prio) != 0 /* sound is queuable */ && current_sound_nr != nr /* sound is not playing currently */ && (next_sound_nr == 0 /* sound has higher priority than next */ || low(next_sound_prio) <= low(prio)))) { /* enqueue sound in next_..., it will be played when current * sound has finished. */ set_next_sound: sound_status[nr]++; sound_status[next_sound_nr]--; next_sound_prio = prio; next_sound_nr = nr; } if (flgPlayThis) { for (i = 0; i< 4; i++) { clear channel[i]; if (sound->channels[i]) { channel[i].next_cmd = sound[channels[i]]; channel[i]->nr = current_sound_nr; } } } }