MM & Zak : Only WA resource, old bundle Indy3ega & Loom: WA resource + AD resource directly after another, old bundle : In loom there are also roland resources, which are loaded : when VAR(VAR_SOUNDCARD) == 4. indy3vga : short header SO, WA, AD blocks. indy3 sfxs: 1: normal steps 2: open door 3: close door 4: open window 5: close window 6: library stamper / jump sound 8: water fountain 9: sound of metal post 27: water steps musics: 38: travel sound 40: venice Resource 6: 000000: 69 00 01 62 25 00 20 11 f4 2a 00 21 18 f5 0a 00 |i..b%. ..*.!....| 000010: 00 02 82 0f 94 51 2e 80 3e d4 00 88 fe 01 c5 04 |.....Q..>.......| 000020: 0e 20 04 84 1a 00 20 00 ff 0f 00 00 02 82 0f 81 |. .... .........| 000030: 15 2e 00 6a 4c 23 88 ff |...jL#.. | old bundle header: int16 size int16 old_type ??? new bundle hader: int32 size int16 type new bundle header: WA resource: header 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; } } } } AD resource: header int8 priority int8 channel_to_play blocks ... non-music blocks: sequence of blocks, 1 byte header: 01 instrument definition: uint16 freq uint8 feedback uint8 amp_0 uint8 level_0 uint8 attack_0 uint8 sustain_0 uint8 waveform_0 uint8 amp_1 uint8 level_1 uint8 attack_1 uint8 sustain_1 uint8 waveform_1 uint8 percussion: 0 none, 1 bass drum, 2 snare drum, ... 02 note definition (similar to extra_a/extra_b in backends/midi/adlib.cpp) uint8 flags 0x80:active, last 3 bits what param needs to be changed uint8 length_1<<4|new_level_1 uint8 length_2<<4|new_level_2 uint8 length_3<<4|length_4 uint8 overall duration (I think for necessary for repeating??) same for a different param uint8 flags 0x80:active, last 3 bits what param needs to be changed uint8 length_1<<4|new_level_1 uint8 length_2<<4|new_level_2 uint8 length_3<<4|length_4 uint8 overall duration (I think for necessary for repeating??) fe next track plays in parallel (up to three tracks). ff end of block 80 (must be the only block) Music uint8 tempo uint8 play_once uint16 offset into script for auto-repeat (in bytes). uint16 ??? uint8 number of channels (max 8) 8 bytes channel numbers + 1 8*16 bytes: instrument definition for each channel (see 01 block) midi script. In note-on events volume is ignored. 9x note on 8x note off ff meta: 2f: end of track 51: set tempo (2 bytes data) tick_ctr is increased by adlib_tempo every second. music_timer is increased by adlib_tempo/120 every second. adlib_tempo = 0x73000 / tempo_as_set_by_ff_51_midi_chunk timer interrupt is called with 1193000/2521 = 473 Hz every 4th time (118.25 Hz) next_cmd is called.