/* spk2wav -- Scumm V2 sound resource to wav converter. * Copyright (C) 2002-2003 Jochen Hoenicke, Jamieson Christian * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ */ #include #include #include #include #include #include #include #include "scummsys.h" #if !defined(__GNUC__) #pragma START_PACK_STRUCTS #endif #define SAMPLE_RATE 22050 #define FREQ_HZ 236 /* Don't change! */ #define FRAGMENTS ((2<<16) | 7) #define SAMPLE_LENGTH (SAMPLE_RATE / FREQ_HZ) #define AUTHENTIC_SOUND #define DECAY 0xf400 /* Depends on sample rate */ #define PCJR_DECAY 0xd000 /* Depends on sample rate */ FILE *sound_fd; int pc_jr = 0; int sample_length = 0; typedef unsigned int uint; typedef unsigned short uint16; typedef short int16; typedef unsigned char uint8; typedef signed char int8; struct channel_data { uint16 time_left; // 00 uint16 next_cmd; // 02 uint16 base_freq; // 04 uint16 freq_delta; // 06 uint16 freq; // 08 uint16 volume; // 10 uint16 volume_delta; // 12 uint16 tempo; // 14 uint16 inter_note_pause; // 16 uint16 transpose; // 18 uint16 note_length; // 20 uint16 hull_curve; // 22 uint16 hull_offset; // 24 uint16 hull_counter; // 26 uint16 freqmod_table; // 28 uint16 freqmod_offset; // 30 uint16 freqmod_incr; // 32 uint16 freqmod_multiplier; // 34 uint16 freqmod_modulo; // 36 uint16 unknown[5]; // 38 - 46 uint16 music_script_nr; // 48 } GCC_PACK; union channel_info { channel_data d; uint16 array[sizeof(channel_data)/2]; }; void debug_channel(channel_info *channel) { fprintf(stdout, "chn: %5d, %5d, %5d, %5d, %5d, %5d, %5d, %5d\n", channel->d.time_left, channel->d.next_cmd, channel->d.base_freq, channel->d.freq_delta, channel->d.freq, channel->d.volume, channel->d.volume_delta, channel->d.tempo); fprintf(stdout, " %5d, %5d, %5d, %5d, %5d, %5d, %5d, %5d\n", channel->d.inter_note_pause, channel->d.transpose, channel->d.note_length, channel->d.hull_curve, channel->d.hull_offset, channel->d.hull_counter, channel->d.freqmod_table, channel->d.freqmod_offset); fprintf(stdout, " %5d, %5d, %5d, %5d, %5d, %5d, %5d, %5d\n", channel->d.freqmod_incr, channel->d.freqmod_multiplier, channel->d.freqmod_modulo, channel->d.unknown[0], channel->d.unknown[1], channel->d.unknown[2], channel->d.unknown[3], channel->d.unknown[4]); fprintf(stdout, " %5d\n", channel->d.music_script_nr); } channel_info channels[8]; uint8 * retaddr; uint8 m_lfl[128]; uint16 m_off[128]; uint8 * music_script[128]; uint8 note_lengths[] = { 0, 0, 0, 2, 0, 3, 4, 0, 6, 8, 0, 12, 16, 0, 24, 32, 0, 48, 64 }; uint16 hull_offsets[] = { 0, 12, 24, 36, 48, 60, 72, 88, 104, 120, 136, 240, 152, 164 }; int16 hulls[] = { // hull 0 3, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, // hull 1 (staccato) 3, -1, 0, 30, 0, -1, 0, 0, 0, -1, 0, 0, // hull 2 (legato) 3, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // hull 3 (staccatissimo) 3, -1, 0, 2, 0, -1, 0, 0, 0, -1, 0, 0, // hull 4 3, -1, 0, 6, 0, -1, 0, 0, 0, -1, 0, 0, // hull 5 3, -1, 0, 10, 0, -1, 0, 0, 0, -1, 0, 0, // hull 6 (int16) 60000, -1, -1000, 20, 0, 0, 0, 0, (int16) 40000, -1, -5000, 5, 0, -1, 0, 0, // hull 7 (int16) 50000, -1, 0, 8, 30000, -1, 0, 0, 28000, -1, -5000, 5, 0, -1, 0, 0, // hull 8 (int16) 60000, -1, -2000, 16, 0, 0, 0, 0, 28000, -1, -6000, 5, 0, -1, 0, 0, // hull 9 (int16) 55000, -1, 0, 8, (int16) 35000, -1, 0, 0, (int16) 40000, -1, -2000, 10, 0, -1, 0, 0, // hull 10 (int16) 60000, -1, 0, 4, -2000, 8, 0, 0, (int16) 40000, -1, -6000, 5, 0, -1, 0, 0, // hull 12 0, -1, 150, 340, -150, 340, 0, -1, 0, -1, 0, 0, // hull 13 == 164 20000, -1, 4000, 7, 1000, 15, 0, 0, (int16) 35000, -1, -2000, 15, 0, -1, 0, 0, // hull misc = 180 (int16) 44000, -1, -4400, 10, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 53000, -1, -5300, 10, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 63000, -1, -6300, 10, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 44000, -1, -1375, 32, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 53000, -1, -1656, 32, 0, -1, 0, 0, 0, -1, 0, 0, // hull 11 == 240 (int16) 63000, -1, -1968, 32, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 44000, -1, - 733, 60, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 53000, -1, - 883, 60, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 63000, -1, -1050, 60, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 44000, -1, - 488, 90, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 53000, -1, - 588, 90, 0, -1, 0, 0, 0, -1, 0, 0, (int16) 63000, -1, - 700, 90, 0, -1, 0, 0, 0, -1, 0, 0 }; uint16 freqmod_lengths[] = { 0x1000, 0x1000, 0x20, 0x2000, 0x1000 }; uint16 freqmod_offsets[] = { 0, 0x100, 0x200, 0x302, 0x202 }; int8 freqmod_table[0x502] = { 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 59, 62, 65, 67, 70, 73, 75, 78, 80, 82, 85, 87, 89, 91, 94, 96, 98, 100, 102, 103, 105, 107, 108, 110, 112, 113, 114, 116, 117, 118, 119, 120, 121, 122, 123, 123, 124, 125, 125, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 125, 125, 124, 123, 123, 122, 121, 120, 119, 118, 117, 116, 114, 113, 112, 110, 108, 107, 105, 103, 102, 100, 98, 96, 94, 91, 89, 87, 85, 82, 80, 78, 75, 73, 70, 67, 65, 62, 59, 57, 54, 51, 48, 45, 42, 39, 36, 33, 30, 27, 24, 21, 18, 15, 12, 9, 6, 3, 0, -3, -6, -9, -12, -15, -18, -21, -24, -27, -30, -33, -36, -39, -42, -45, -48, -51, -54, -57, -59, -62, -65, -67, -70, -73, -75, -78, -80, -82, -85, -87, -89, -91, -94, -96, -98,-100,-102,-103, -105,-107,-108,-110,-112,-113,-114,-116, -117,-118,-119,-120,-121,-122,-123,-123, -124,-125,-125,-126,-126,-126,-126,-126, -126,-126,-126,-126,-126,-126,-125,-125, -124,-123,-123,-122,-121,-120,-119,-118, -117,-116,-114,-113,-112,-110,-108,-107, -105,-103,-102,-100, -98, -96, -94, -91, -89, -87, -85, -82, -80, -78, -75, -73, -70, -67, -65, -62, -59, -57, -54, -51, -48, -45, -42, -39, -36, -33, -30, -27, -24, -21, -18, -15, -12, -9, -6, -3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, -128,-127,-126,-125,-124,-123,-122,-121, -120,-119,-118,-117,-116,-115,-114,-113, -112,-111,-110,-109,-108,-107,-106,-105, -104,-103,-102,-101,-100, -99, -98, -97, -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, -82, -81, -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, -64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, -120, 120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, -120,-120,-120,-120,-120,-120,-120,-120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 41, 35, -66,-124, -31, 108, -42, -82, 82,-112, 73, -15, -15, -69, -23, -21, -77, -90, -37, 60,-121, 12, 62,-103, 36, 94, 13, 28, 6, -73, 71, -34, -77, 18, 77, -56, 67, -69,-117, -90, 31, 3, 90, 125, 9, 56, 37, 31, 93, -44, -53, -4,-106, -11, 69, 59, 19, 13,-119, 10, 28, -37, -82, 50, 32,-102, 80, -18, 64, 120, 54, -3, 18, 73, 50, -10, -98, 125, 73, -36, -83, 79, 20, -14, 68, 64, 102, -48, 107, -60, 48, -73, 50, 59, -95, 34, -10, 34,-111, -99, -31,-117, 31, -38, -80, -54,-103, 2, -71, 114, -99, 73, 44,-128, 126, -59,-103, -43, -23,-128, -78, -22, -55, -52, 83, -65, 103, -42, -65, 20, -42, 126, 45, -36,-114, 102, -125, -17, 87, 73, 97, -1, 105,-113, 97, -51, -47, 30, -99,-100, 22, 114, 114, -26, 29, -16,-124, 79, 74, 119, 2, -41, -24, 57, 44, 83, -53, -55, 18, 30, 51, 116, -98, 12, -12, -43, -44, -97, -44, -92, 89, 126, 53, -49, 50, 34, -12, -52, -49, -45,-112, 45, 72, -45,-113, 117, -26, -39, 29, 42, -27, -64, -9, 43, 120,-127,-121, 68, 14, 95, 80, 0, -44, 97,-115, -66, 123, 5, 21, 7, 59, 51,-126, 31, 24, 112,-110, -38, 100, 84, -50, -79, -123, 62, 105, 21, -8, 70, 106, 4, -106, 115, 14, -39, 22, 47, 103, 104, -44, -9, 74, 74, -48, 87, 104, 118, }; uint16 *freqs_table; uint16 spk_freq_table[12] = { 36484, 34436, 32503, 30679, 29007, 27332, 25798, 24350, 22983, 21693, 20476, 19326 }; uint16 pcjr_freq_table[12] = { 65472, 61760, 58304, 55040, 52032, 49024, 46272, 43648, 41216, 38912, 36736, 34624 }; void execute_cmd(channel_info *channel) { uint16 value; int16 offset; uint8 *script_start; uint8 *script_ptr; channel_info * current_channel; channel_info * dest_channel; current_channel = channel; if (channel->d.next_cmd == 0) return; script_start = music_script[channel->d.music_script_nr]; script_ptr = script_start + channel->d.next_cmd; for (;;) { uint8 opcode = *script_ptr++; if (opcode >= 0xf8) { switch (opcode) { case 0xf8: // set hull curve #if 0 printf("channel[%d]: hull curve %2d\n", channel - channels, (int) *script_ptr); #endif channel->d.hull_curve = hull_offsets[(*script_ptr)/2]; script_ptr++; break; case 0xf9: // set freqmod curve #if 0 printf("channel[%d]: freqmod curve %2d\n", channel - channels, (int) *script_ptr); #endif channel->d.freqmod_table = freqmod_offsets[(*script_ptr)/4]; channel->d.freqmod_modulo = freqmod_lengths[(*script_ptr)/4]; script_ptr++; break; case 0xfd: // clear other channel value = READ_LE_UINT16 (script_ptr); printf("clear channel %d\n", (int) (value / sizeof(channel_info))); script_ptr += 2; channel = &channels[value / sizeof(channel_info)]; // fall through case 0xfa: // clear current channel if (opcode == 0xfa) printf("clear channel\n"); channel->d.next_cmd = 0; channel->d.base_freq = 0; channel->d.freq_delta = 0; channel->d.freq = 0; channel->d.volume = 0; channel->d.volume_delta = 0; channel->d.inter_note_pause = 0; channel->d.transpose = 0; channel->d.hull_curve = 0; channel->d.hull_offset = 0; channel->d.hull_counter = 0; channel->d.freqmod_table = 0; channel->d.freqmod_offset = 0; channel->d.freqmod_incr = 0; channel->d.freqmod_multiplier = 0; channel->d.freqmod_modulo = 0; break; case 0xfb: // ret from subroutine printf("ret from sub %p\n", script_ptr); script_ptr = retaddr; break; case 0xfc: // call subroutine offset = READ_LE_UINT16 (script_ptr); printf("subroutine %d\n", offset); script_ptr += 2; retaddr = script_ptr; // FIXME: I don't think the following original code was quite right! // script_ptr = (uint8 *) ((void*)(int)offset); script_ptr = script_start + offset; printf("XXX1: %p -> %04x", script_ptr, (int) offset); break; case 0xfe: // loop music opcode = *script_ptr++; offset = READ_LE_UINT16 (script_ptr); script_ptr += 2; printf("loop if %d to %d\n", opcode, offset); if (!channel->array[opcode/2] || --channel->array[opcode/2]) script_ptr += offset; break; case 0xff: // set parameter opcode = *script_ptr++; value = READ_LE_UINT16 (script_ptr); channel->array[opcode/2] = value; // #if 0 printf("channel[%d]: set param %2d = %5d\n", channel - &channels[0], (int) opcode, (int) value); // #endif if (opcode == 18) printf (" Transpose = %d\n", (signed short) channel->d.transpose); script_ptr += 2; if (opcode == 0) goto end; break; } } else { // opcode < 0xf8 for (;;) { int16 note, octave; dest_channel = &(channels[opcode >> 5]); printf (" channels = %p, sizeof(channels[0]) = %d, channel = %d, dest_channel = %p\n", channels, (int) sizeof (channels[0]), (int) (opcode >> 5), dest_channel); channel->d.time_left = channel->d.tempo * note_lengths[opcode & 0x1f]; #if 0 printf("Set time_left to %d (tempo %d, note length %d)\n", (int) channel->d.time_left, (int) channel->d.tempo, (int) note_lengths[opcode & 0x1f]); #endif // #if 0 fprintf(stdout, "channel[%d]: @%04x note: %3d+%d len: %2d hull: %d mod: %d/%d/%d %s\n", (int) opcode>>5, (int) (script_ptr ? script_ptr - music_script[channel->d.music_script_nr] : 0), (int) *script_ptr & 0x7f, (signed short) dest_channel->d.transpose, opcode & 0x1f, (int) dest_channel->d.hull_curve, (int) dest_channel->d.freqmod_table, (int) dest_channel->d.freqmod_incr, (int) dest_channel->d.freqmod_multiplier, *script_ptr & 0x80 ? "last":""); // #endif opcode = *script_ptr++; note = opcode & 0x7f; if (note != 0x7f) { uint16 freq; dest_channel->d.time_left = channel->d.time_left; dest_channel->d.note_length = channel->d.time_left - dest_channel->d.inter_note_pause; note += dest_channel->d.transpose; while (note < 0) note += 12; octave = note / 12; note = note % 12; dest_channel->d.hull_offset = 0; dest_channel->d.hull_counter = 1; if (pc_jr && dest_channel == &channels[3]) { dest_channel->d.hull_curve = 180 + note * 12; freq = 384 - 64 * octave; } else { freq = freqs_table[note] >> octave; } dest_channel->d.freq = dest_channel->d.base_freq = freq; } if ((opcode & 0x80) != 0) goto end; opcode = *script_ptr++; } } } end: channel = current_channel; if (channel->d.time_left) goto finish; #if 0 script_ptr = 0; if (--fc8_c) goto finish; channel->d.next_cmd = 0; sound_status[fc8_4]--; fc8_4 = 0; look_for_pending_sound(); #endif return; finish: channel->d.next_cmd = script_ptr - music_script[channel->d.music_script_nr]; return; } void next_freqs(channel_info *channel) { channel->d.volume += channel->d.volume_delta; channel->d.base_freq += channel->d.freq_delta; channel->d.freqmod_offset += channel->d.freqmod_incr; if (channel->d.freqmod_offset > channel->d.freqmod_modulo) channel->d.freqmod_offset -= channel->d.freqmod_modulo; channel->d.freq = (int) (freqmod_table[channel->d.freqmod_table + (channel->d.freqmod_offset >> 4)]) * (int) channel->d.freqmod_multiplier / 256 + channel->d.base_freq; #if 0 printf("Freq: %d/%d, %d/%d/%d*%d %d\n", channel->d.base_freq, (int16)channel->d.freq_delta, channel->d.freqmod_table, channel->d.freqmod_offset, channel->d.freqmod_incr, channel->d.freqmod_multiplier, channel->d.freq); #endif if (channel->d.note_length && !--channel->d.note_length) { channel->d.hull_offset += 16; channel->d.hull_counter = 1; } if (!--channel->d.time_left) { execute_cmd(channel); } #if 0 fprintf(stdout, "channel[%d]: freq %d hull %d/%d/%d\n", channel - &channels[0], channel->d.freq, channel->d.hull_curve, channel->d.hull_offset, channel->d.hull_counter); #endif if (channel->d.hull_counter && !--channel->d.hull_counter) { for (;;) { int16 *hull_ptr = hulls + channel->d.hull_curve + channel->d.hull_offset/2; if (hull_ptr[1] == -1) { channel->d.volume = hull_ptr[0]; if (hull_ptr[0] == 0) channel->d.volume_delta = 0; channel->d.hull_offset += 4; } else { channel->d.volume_delta = hull_ptr[0]; channel->d.hull_counter = hull_ptr[1]; channel->d.hull_offset += 4; break; } } } } #ifdef AUTHENTIC_SOUND /** * This simulates the pc speaker sound, which is driven * by the 8253 (square wave generator) and a low-band filter. */ void produce_spk_sound() { int8 sample[SAMPLE_LENGTH]; static int sample_offset = 0; static int last_freq=100; int winning_channel = -1; int i, j; int freq; // , volume; static uint level = 0; for (j = 0; j < SAMPLE_LENGTH; j++) { sample[j] = 0; } for (i = 0; i < 4; i++) { if (!channels[i].d.time_left) continue; next_freqs(&channels[i]); if (winning_channel == -1 && channels[i].d.volume && channels[i].d.time_left) { winning_channel = i; } } if (winning_channel != -1) { freq = channels[winning_channel].d.freq; } else { freq = 0; } for (j = 0; j < SAMPLE_LENGTH; j++) { level = (level * DECAY) >> 16; if (sample_offset < freq*500) level += 0xffff-DECAY; sample[j] = (level >> 8); sample_offset += 1193000*1000 / SAMPLE_RATE; if (sample_offset >= last_freq*1000) { sample_offset -= last_freq*1000; last_freq = freq; } } #if 0 fprintf(stdout, "channel[%d]: freq %d ; %d\n", winning_channel, freq, sample_offset); #endif fflush(stdout); for (j = 0; j < SAMPLE_LENGTH;) { j += fwrite (sample + j, 1, SAMPLE_LENGTH - j, sound_fd); if (j < SAMPLE_LENGTH) { fprintf(stderr, "Incomplete write: %d < %d!\n", j, SAMPLE_LENGTH); exit(0); } } sample_length += SAMPLE_LENGTH; } #else void produce_spk_sound() { int8 sample[SAMPLE_LENGTH]; static int sample_offset = 0; int winning_channel = -1; int i, j; float freq, volume; for (j = 0; j < SAMPLE_LENGTH; j++) { sample[j] = 0; } for (i = 0; i < 4; i++) { if (!channels[i].d.time_left) continue; next_freqs(&channels[i]); if (winning_channel == -1 && channels[i].volume && channels[i].d.time_left) { winning_channel = i; } } if (winning_channel != -1) { i = winning_channel; freq = channels[i].freq; volume = channels[i].volume; volume /= 3.0; freq = 1193000 / freq; for (j = 0; j < SAMPLE_LENGTH; j++) { sample[j] += (int8) (volume * 100 #ifdef SAEGEZAHN * (sample_offset < SAMPLE_RATE/2 ? sample_offset - SAMPLE_RATE/4 : 3*SAMPLE_RATE/4 - sample_offset) /(SAMPLE_RATE/4) #elif defined(RECHTECK) * (sample_offset < SAMPLE_RATE/2 ? 1 : -1) #else * sin((double)sample_offset*2*3.145926 / SAMPLE_RATE) #endif ); sample[j] = sample[j] + 0x80; sample_offset += freq; sample_offset %= SAMPLE_RATE; } #if 0 fprintf(stdout, "channel[%d]: freq %d = %0f ; volume %f %d\n", i, channels[i].freq, freq, volume, sample_offset); #endif } fflush(stdout); for (j = 0; j < SAMPLE_LENGTH;) j += fwrite(sample + j, 1, SAMPLE_LENGTH - j, sound_fd); sample_length += SAMPLE_LENGTH; } #endif #define STEP 0x10000 #define UPDATESTEP ((STEP * SAMPLE_RATE) / (111860*2)) #define MAX_OUTPUT 0x7fff #define FB_WNOISE 0x12000 /* bit15.d(16bits) = bit0(out) ^ bit2 */ #define FB_PNOISE 0x08000 /* JH 981127 - fixes Do Run Run */ #define NG_PRESET 0x0f35 int SN76496_count[4]; int SN76496_output[4]; int SN76496_voltable[16]; void init_pcjr() { int i; double out; int gain; // increase max output basing on gain (0.2 dB per step) out = MAX_OUTPUT / 3; gain = 10; while (gain-- > 0) out *= 1.023292992; // = (10 ^ (0.2/20)) // build volume table (2dB per step) for (i = 0; i < 15; i++) { // limit volume to avoid clipping if (out > MAX_OUTPUT / 3) SN76496_voltable[i] = MAX_OUTPUT / 3; else SN76496_voltable[i] = (int) out; printf("%2d: %4x\n", i, SN76496_voltable[i]); out /= 1.258925412; // = 10 ^ (2/20) = 2dB } SN76496_voltable[15] = 0; } void produce_pcjr_sound() { uint16 sample[SAMPLE_LENGTH]; int i, j; uint16 freq, vol; static int16 lastvol = 0; memset(sample, 0, sizeof(sample)); for (i = 0; i < 4; i++) { if (!channels[i].d.time_left) continue; next_freqs(&channels[i]); freq = channels[i].d.freq >> 6; vol = (65535 - channels[i].d.volume) >> 12; if (!channels[i].d.volume && !channels[i].d.time_left) { if (SN76496_count[i] >= SAMPLE_LENGTH * STEP) SN76496_count[i] -= STEP * SAMPLE_LENGTH; } else if (i < 3) { int period = UPDATESTEP * freq; if (period == 0) period = UPDATESTEP; for (j = 0; j < SAMPLE_LENGTH; j++) { unsigned int volume = 0; if (SN76496_output[i]) volume += SN76496_count[i]; SN76496_count[i] -= STEP; while (SN76496_count[i] <= 0) { SN76496_count[i] += period; if (SN76496_count[i] > 0) { SN76496_output[i] ^= 1; if (SN76496_output[i]) volume += period; break; } SN76496_count[i] += period; volume += period; } if (SN76496_output[i]) volume -= SN76496_count[i]; volume *= SN76496_voltable[vol]; sample[j] += volume / STEP; } } else { int period, RNG, NoiseFB; // , left int n = (freq & 3); if (n == 3) { period = 2 * UPDATESTEP * (channels[2].d.freq>>6); if (!period) period = 2 * UPDATESTEP; } else period = UPDATESTEP << (5 + n); RNG = NG_PRESET; NoiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE; SN76496_output[3] = RNG & 1; for (j = 0; j < SAMPLE_LENGTH; j++) { unsigned int volume = 0; if (SN76496_output[3]) volume += SN76496_count[3]; SN76496_count[3] -= STEP; while (SN76496_count[3] <= 0) { if (RNG & 1) RNG ^= NoiseFB; RNG >>= 1; SN76496_output[3] = RNG & 1; SN76496_count[3] += period; if (SN76496_output[3]) volume += period; } if (SN76496_output[3]) volume -= SN76496_count[3]; volume *= SN76496_voltable[vol]; sample[j] += volume / STEP; } } #if 0 fprintf(stdout, "channel[%d]: freq %d %.1f ; volume %d \n", i, freq, 111860.0/freq, vol); #endif } for (j = 0; j < SAMPLE_LENGTH; j++) { lastvol = ((int)lastvol * PCJR_DECAY + (int)sample[j] * (0x10000-PCJR_DECAY)) >> 16; sample[j] = lastvol; } fflush(stdout); for (j = 0; j < 2*SAMPLE_LENGTH;) j += fwrite(sample + j, 1, 2*SAMPLE_LENGTH - j, sound_fd); #if 0 for (j = 0; j < 2*SAMPLE_LENGTH;) j += fwrite(sample + j, 1, 2*SAMPLE_LENGTH - j, stdout); #endif sample_length += 2*SAMPLE_LENGTH; } void init_channel(channel_info *channel) { channel->d.next_cmd = 0; channel->d.base_freq = 0; channel->d.freq_delta = 0; channel->d.freq = 0; channel->d.volume = 0; channel->d.volume_delta = 0; channel->d.inter_note_pause = 0; channel->d.transpose = 0; channel->d.hull_curve = 0; channel->d.hull_offset = 0; channel->d.hull_counter = 0; channel->d.freqmod_table = 0; channel->d.freqmod_offset = 0; channel->d.freqmod_incr = 0; channel->d.freqmod_multiplier = 0; channel->d.freqmod_modulo = 0; channel->d.unknown[0] = 0; channel->d.unknown[1] = 0; channel->d.unknown[2] = 0; channel->d.unknown[3] = 0; channel->d.unknown[4] = 0; channel->d.tempo = 0; channel->d.note_length = 0; } void load_music(int nr) { int i; uint16 *music = (uint16*) music_script[nr]; int offset = pc_jr ? 7 : 3; for (i = 0; i < 4; i++) { init_channel(&channels[i]); if (music [offset + i]) { channels[i].d.music_script_nr = nr; channels[i].d.next_cmd = READ_LE_UINT16 (&music[offset+i]); channels[i].d.time_left = 1; } } while (channels[0].d.time_left || channels[1].d.time_left || channels[2].d.time_left || channels[3].d.time_left) { if (pc_jr) produce_pcjr_sound(); else produce_spk_sound(); } } uint8 readByte(FILE *file) { uint8 a; fread(&a, 1, 1, file); return a; } uint16 readUint16LE(FILE *file) { uint8 a = readByte(file); uint8 b = readByte(file); return a | (b << 8); } void WRITE_LE_UINT16(uint8 *ptr, uint16 val) { *ptr = (val & 0xff); *(ptr+1) = ((val >> 8) & 0xff); } void WRITE_LE_UINT32(uint8 *ptr, uint32 val) { WRITE_LE_UINT16(ptr, val & 0xffff); WRITE_LE_UINT16(ptr+2, val >> 16); } void read_level0() { FILE * file; uint8 b; uint16 w; int i; file = fopen("00.lfl", "rb"); w = readUint16LE(file) ^ 0xffff; /* skip object */ w = readUint16LE(file) ^ 0xffff; fseek(file, w, SEEK_CUR); /* skip unknowns */ b = readByte(file) ^ 0xff; fseek(file, 3*b, SEEK_CUR); /* skip costumes */ b = readByte(file) ^ 0xff; fseek(file, 3*b, SEEK_CUR); /* skip scripts */ b = readByte(file) ^ 0xff; fseek(file, 3*b, SEEK_CUR); /* read music */ b = readByte(file) ^ 0xff; fread(&m_lfl, 1, b, file); for (i = 0; i < b; i++) { m_lfl[i] ^= 0xff; m_off[i] = readUint16LE(file) ^ 0xffff; } fclose(file); } void read_music(int nr) { char lvl[20]; FILE * file; int i; uint16 len; sprintf(lvl, "%02d.lfl", m_lfl[nr]); printf("Opening %s\n", lvl); file = fopen(lvl, "rb"); if (!file) { printf ("Could not open file\n"); exit (-1); } fseek(file, m_off[nr], SEEK_CUR); len = readUint16LE(file) ^ 0xffff; music_script[nr] = (uint8 *) malloc(len); fseek(file, -2, SEEK_CUR); fread(music_script[nr], 1, len, file); for (i = 0; i < len; i++) music_script[nr][i] ^= 0xff; fclose(file); } void open_sound() { long size = -1; long length = -1; sound_fd = fopen("output.wav", "wb"); uint8 outbuff[4]; int bps = 1; if (pc_jr) bps = 2; memcpy (outbuff, "RIFF", 4); fwrite (outbuff, 1, 4, sound_fd); outbuff[0] = size & 0xFF; outbuff[1] = (size >> 8) & 0xFF; outbuff[2] = (size >> 16) & 0xFF; outbuff[3] = (size >> 24) & 0xFF; fwrite (outbuff, 1, 4, sound_fd); memcpy (outbuff, "WAVE", 4); fwrite (outbuff, 1, 4, sound_fd); memcpy (outbuff, "fmt ", 4); fwrite (outbuff, 1, 4, sound_fd); memcpy (outbuff, "\x10\x00\x00\x00", 4); fwrite (outbuff, 1, 4, sound_fd); memcpy (outbuff, "\x01\x00\x01\x00", 4); fwrite (outbuff, 1, 4, sound_fd); WRITE_LE_UINT32(outbuff, SAMPLE_RATE); fwrite (outbuff, 1, 4, sound_fd); WRITE_LE_UINT32(outbuff, bps*SAMPLE_RATE); fwrite (outbuff, 1, 4, sound_fd); WRITE_LE_UINT16(outbuff, bps); WRITE_LE_UINT16(outbuff+2, bps*8); fwrite (outbuff, 1, 4, sound_fd); memcpy (outbuff, "data", 4); fwrite (outbuff, 1, 4, sound_fd); outbuff[0] = length & 0xFF; outbuff[1] = (length >> 8) & 0xFF; outbuff[2] = (length >> 16) & 0xFF; outbuff[3] = (length >> 24) & 0xFF; fwrite (outbuff, 1, 4, sound_fd); } void write_length() { char outbuff[4]; long length = sample_length; long size = length + 36; printf ("Input file size: %d\n", length); fseek(sound_fd, 4, SEEK_SET); outbuff[0] = size & 0xFF; outbuff[1] = (size >> 8) & 0xFF; outbuff[2] = (size >> 16) & 0xFF; outbuff[3] = (size >> 24) & 0xFF; fwrite (outbuff, 1, 4, sound_fd); fseek(sound_fd, 32, SEEK_CUR); outbuff[0] = length & 0xFF; outbuff[1] = (length >> 8) & 0xFF; outbuff[2] = (length >> 16) & 0xFF; outbuff[3] = (length >> 24) & 0xFF; fwrite (outbuff, 1, 4, sound_fd); } int main(int argc, char** argv) { int nr; // , i; if (argc > 2) pc_jr = 1; open_sound(); read_level0(); if (argc > 1) nr = atoi(argv[1]); else nr = 95; srand(1234); /* for (i = 0x302; i < 0x502; i++) freqmod_table[i] = (rand()>> 8) & 0xff; */ read_music(nr); if (pc_jr) init_pcjr(); freqs_table = pc_jr ? pcjr_freq_table : spk_freq_table; load_music(nr); fclose (sound_fd); write_length(); return 0; }