#include "driver.h" #include "nmk004.h" #include "sound/2203intf.h" #include "sound/okim6295.h" #define FM_CHANNELS 6 #define PSG_CHANNELS 3 #define EFFECTS_CHANNELS 8 #define FM_FLAG_NEED_INITIALIZATION (1<<0) #define FM_FLAG_UNKNOWN2 (1<<1) #define FM_FLAG_NOTE_IS_PAUSE (1<<2) #define FM_FLAG_UNKNOWN3 (1<<3) #define FM_FLAG_MODULATE_NOTE (1<<4) #define FM_FLAG_MUST_SEND_KEYON (1<<5) #define FM_FLAG_MUST_SEND_CONFIGURATION (1<<6) #define FM_FLAG_ACTIVE (1<<7) #define PSG_FLAG_ACTIVE (1<<0) #define PSG_FLAG_NOTE_IS_PAUSE (1<<1) #define PSG_FLAG_NEED_INITIALIZATION (1<<2) #define PSG_FLAG_INITIALIZE_VOLUME (1<<3) #define PSG_FLAG_NOTE_IS_NOISE (1<<5) #define PSG_FLAG_NOISE_NOT_ENABLED (1<<6) #define EFFECTS_FLAG_NEED_INITIALIZATION (1<<0) #define EFFECTS_FLAG_ACTIVE (1<<7) #define NOTE_PAUSE 0x0c struct psg_control { /* C220 */ UINT8 flags; /* C221-C222 */ UINT16 note_timer; /* C223-C224 */ UINT16 note_length; /* C225 */ UINT8 volume_timer; /* C227-C228 */ UINT16 current; // current position in control table /* C229-C22A */ UINT16 return_address[16]; // return address when control table calls a subtable int return_address_depth; /* C22B-C22C */ UINT16 loop_start; // first instruction of loop /* C22D */ UINT8 loop_times; // number of times to loop /* C22E */ UINT8 volume_shape; /* C22F */ UINT8 volume_position; /* C230 */ UINT8 octave; // base octave /* C231 */ UINT8 note; // note to play /* C233 */ UINT8 note_period_hi_bits; }; struct fm_control { UINT8 note; /* C020 */ UINT8 flags; /* C021 */ UINT8 slot; // for ym2203 keyon command /* C022-C039 */ UINT8 voice_params[0x18]; // parameters for the YM2203 to configure sound shape /* C03A-C03B */ UINT16 f_number; /* C03C */ UINT8 self_feedback; /* C03D */ UINT8 note_duration_table_select; /* C03E-C03F */ UINT16 current; // current position in control table /* C040-C041 */ UINT16 loop_start; // first instruction of loop /* C042 */ UINT8 loop_times; // number of times to loop /* C043-C044 */ UINT16 return_address[16]; // return address when control table calls a subtable int return_address_depth; /* C045 */ UINT8 octave; /* C046-C047 */ UINT16 timer1; /* C048-C049 */ UINT16 timer2; /* C04A-C04B */ UINT16 timer1_duration; /* C04C-C04D */ UINT16 timer2_duration; /* C04E */ UINT8 modulation_table_number; /* C04F-C050 */ UINT16 modulation_timer; /* C051-C052 */ UINT16 modulation_table; /* C053-C054 */ UINT16 modulation_table_position; /* C055-C056 */ UINT16 note_period; /* C057-C05A */ UINT8 voice_volume[4]; // parameters for the YM2203 to configure sound shape /* C05C */ UINT8 must_update_voice_params; }; struct effects_control { /* C1A0 */ UINT8 flags; /* C1BE-C1BF */ UINT16 current; // current position in control table /* C1C0-C1C1 */ UINT16 loop_start; // first instruction of loop /* C1C2 */ UINT8 loop_times; // number of times to loop /* C1C3-C1C4 */ UINT16 return_address[16]; // return address when control table calls a subtable int return_address_depth; /* C1C6-C1C7 */ UINT16 timer; /* C1CA-C1CB */ UINT16 timer_duration; }; struct { const UINT8 *rom; // NMK004 data ROM UINT8 from_main; // command from main CPU UINT8 to_main; // answer to main CPU int protection_check; /* C001 */ UINT8 last_command; // last command received /* C016 */ UINT8 oki_playing; // bitmap of active Oki channels /* C020-C19F */ struct fm_control fm_control[FM_CHANNELS]; /* C220-C2DF */ struct psg_control psg_control[PSG_CHANNELS]; /* C1A0-C21F */ struct effects_control effects_control[EFFECTS_CHANNELS]; } NMK004_state; #define SAMPLE_TABLE_0 0xefe0 #define SAMPLE_TABLE_1 0xefe2 #define FM_MODULATION_TABLE 0xefe4 #define FM_NOTE_TABLE 0xefe6 #define NOTE_LENGTH_TABLE_1 0xefe8 #define NOTE_LENGTH_TABLE_2 0xefea #define NOTE_LENGTH_TABLE_3 0xefec #define PSG_VOLUME_TABLE 0xefee #define COMMAND_TABLE 0xeff0 #define PSG_NOTE_TABLE 0xeff2 static UINT8 read8(int address) { return NMK004_state.rom[address]; } static UINT16 read16(int address) { return NMK004_state.rom[address] + 256 * NMK004_state.rom[address+1]; } /***************************** OKI6295 *****************************/ static void oki_play_sample(int sample_no) { UINT16 table_start = (sample_no & 0x80) ? read16(SAMPLE_TABLE_1) : read16(SAMPLE_TABLE_0); UINT8 byte1 = read8(table_start + 2 * (sample_no & 0x7f) + 0); UINT8 byte2 = read8(table_start + 2 * (sample_no & 0x7f) + 1); int chip = (byte1 & 0x80) >> 7; if ((byte1 & 0x7f) == 0) { // stop all channels if (chip == 0) OKIM6295_data_0_w(0, 0x78 ); else OKIM6295_data_1_w(0, 0x78 ); } else { int sample = byte1 & 0x7f; int ch = byte2 & 0x03; int force = (byte2 & 0x80) >> 7; if (!force && (NMK004_state.oki_playing & (1 << (ch + 4*chip)))) return; NMK004_state.oki_playing |= 1 << (ch + 4*chip); // stop channel if (chip == 0) OKIM6295_data_0_w(0, (0x08 << ch) ); else OKIM6295_data_1_w(0, (0x08 << ch) ); if (sample != 0) { UINT8 *rom = memory_region(REGION_SOUND1 + chip); int bank = (byte2 & 0x0c) >> 2; int vol = (byte2 & 0x70) >> 4; if (bank != 3) memcpy(rom + 0x20000,rom + 0x40000 + bank * 0x20000,0x20000); if (chip == 0) { OKIM6295_data_0_w(0, 0x80 | sample ); OKIM6295_data_0_w(0, (0x10 << ch) | vol ); } else { OKIM6295_data_1_w(0, 0x80 | sample ); OKIM6295_data_1_w(0, (0x10 << ch) | vol ); } } } } static void oki_update_state(void) { NMK004_state.oki_playing = ((OKIM6295_status_1_r(0) & 0x0f) << 4) | (OKIM6295_status_0_r(0) & 0x0f); } /***************************** EFFECTS (OKI6295) *****************************/ static void effects_update(int channel) { struct effects_control *effects = &NMK004_state.effects_control[channel]; // advance the timers if (effects->timer) effects->timer--; if (effects->flags & EFFECTS_FLAG_NEED_INITIALIZATION) { effects->flags = EFFECTS_FLAG_ACTIVE; effects->timer = 0; } if (effects->flags & EFFECTS_FLAG_ACTIVE) { if (effects->timer == 0) { UINT8 token; do { //logerror("channel %d address %04x token %02x\n",channel,effects->current,read8(effects->current)); token = read8(effects->current++); if (token == 0x0ef || (token & 0xf0) == 0xf0) { switch (token) { case 0xef: // play sample oki_play_sample(read8(effects->current++)); break; case 0xf6: // jump effects->current = read16(effects->current); break; case 0xf7: // begin repeat loop effects->loop_times = read8(effects->current++); effects->loop_start = effects->current; break; case 0xf8: // end repeat loop if (--effects->loop_times > 0) effects->current = effects->loop_start; break; case 0xf9: // call subtable effects->return_address[effects->return_address_depth++] = effects->current + 2; effects->current = read16(effects->current); break; case 0xfa: // return from subtable effects->current = effects->return_address[--effects->return_address_depth]; break; case 0xfc: // ??? (hachamf command 04) break; case 0xfd: // ??? (hachamf command 04) break; case 0xff: // end effects->flags = 0; // disable channel return; default: fatalerror("effects channel %d unsupported token %02x",channel,token); } } } while (token == 0xef || (token & 0xf0) == 0xf0); effects->current--; if ((read8(effects->current) & 0x80) == 0) { if (read8(effects->current++) != 0x0c) { // this shouldn't happen on the effects channels (but it happens e.g. hachamf command 04) logerror("effects channel %d invalid token %02x\n",channel,read8(effects->current)); } } // optional note length (otherwise use the same length as the previous one) if (read8(effects->current) & 0x80) { UINT16 table_start = read16(NOTE_LENGTH_TABLE_1); effects->timer_duration = read16(table_start + 2 * (read8(effects->current++) & 0x7f)); } effects->timer = effects->timer_duration; } } } /***************************** YM2203 - FM *****************************/ static void fm_update(int channel) { struct fm_control *fm = &NMK004_state.fm_control[channel]; // advance the timers if (fm->timer1) fm->timer1--; if (fm->timer2) fm->timer2--; if (fm->modulation_timer) fm->modulation_timer--; if (fm->flags & FM_FLAG_NEED_INITIALIZATION) { fm->flags = FM_FLAG_ACTIVE; fm->timer1 = 0; } if (fm->flags & FM_FLAG_ACTIVE) { if (fm->timer1 == 0) { UINT8 token; do { //logerror("channel %d address %04x token %02x\n",channel,fm->current,read8(fm->current)); token = read8(fm->current++); if (token == 0x0ef || (token & 0xf0) == 0xf0) { int i; switch (token) { // case 0xef: // play sample // oki_play_sample(read8(fm->current++)); // break; case 0xf0: // slot (for keyon ym2203 command) fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION; fm->slot = read8(fm->current++); if (channel < 3 || !(NMK004_state.fm_control[channel-3].flags & FM_FLAG_ACTIVE)) { YM2203_control_port_0_w(0, 0x28); // keyon/off YM2203_write_port_0_w(0, channel % 3); } break; case 0xf1: // sound shape fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION; for (i = 0x00; i < 0x04; i++) fm->voice_params[i] = read8(fm->current++); break; case 0xf2: // sound shape fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION; for (i = 0; i < 4; i++) fm->voice_volume[i] = read8(fm->current++); break; case 0xf3: // sound shape fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION; for (i = 0x08; i < 0x18; i++) fm->voice_params[i] = read8(fm->current++); break; case 0xf4: // set self-feedback fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION; fm->self_feedback = read8(fm->current++); break; case 0xf5: // select note duration table fm->note_duration_table_select = read8(fm->current++); break; case 0xf6: // jump fm->current = read16(fm->current); break; case 0xf7: // begin repeat loop fm->loop_times = read8(fm->current++); fm->loop_start = fm->current; break; case 0xf8: // end repeat loop if (--fm->loop_times > 0) fm->current = fm->loop_start; break; case 0xf9: // call subtable fm->return_address[fm->return_address_depth++] = fm->current + 2; fm->current = read16(fm->current); break; case 0xfa: // return from subtable fm->current = fm->return_address[--fm->return_address_depth]; break; case 0xfb: // set octave fm->octave = read8(fm->current++); break; case 0xfc: // ??? fm->flags |= FM_FLAG_UNKNOWN2; fm->flags |= FM_FLAG_UNKNOWN3; break; case 0xfd: // ??? fm->flags &= ~FM_FLAG_UNKNOWN2; break; case 0xfe: // set note modulation fm->modulation_table_number = read8(fm->current++); if (fm->modulation_table_number == 0) { fm->flags &= ~FM_FLAG_MODULATE_NOTE; } else { UINT16 table_start = read16(FM_MODULATION_TABLE); fm->modulation_table = read16(table_start + 2 * (fm->modulation_table_number - 1)); fm->modulation_timer = read16(fm->modulation_table); fm->modulation_table_position = fm->modulation_table + 2; fm->flags |= FM_FLAG_MODULATE_NOTE; } break; case 0xff: // end fm->flags = FM_FLAG_MUST_SEND_CONFIGURATION; // disable channel for (i = 0x04; i < 0x08; i++) fm->voice_params[i] = 0x7f; for (i = 0x14; i < 0x18; i++) fm->voice_params[i] = 0x0f; return; default: fatalerror("fm channel %d unsupported token %02x",channel,token); } } } while (token == 0xef || (token & 0xf0) == 0xf0); fm->current--; if ((read8(fm->current) & 0x80) == 0) { int note = read8(fm->current++); fm->note = note; if ((note & 0x0f) == NOTE_PAUSE) fm->flags |= FM_FLAG_NOTE_IS_PAUSE; else { UINT16 table_start = read16(FM_NOTE_TABLE); UINT16 period = read16(table_start + 2 * (note & 0x0f)); UINT8 octave = ((fm->octave << 4) + note) & 0xf0; fm->flags &= ~FM_FLAG_NOTE_IS_PAUSE; fm->note_period = period | (octave << 7); fm->must_update_voice_params = 1; } } // optional note length (otherwise use the same length as the previous one) if (read8(fm->current) & 0x80) { UINT16 table_start; UINT8 duration = read8(fm->current++) & 0x7f; table_start = read16(NOTE_LENGTH_TABLE_1); fm->timer1_duration = read16(table_start + 2 * duration); table_start = fm->note_duration_table_select ? read16(NOTE_LENGTH_TABLE_3) : read16(NOTE_LENGTH_TABLE_2); fm->timer2_duration = read16(table_start + 2 * duration); } fm->timer1 = fm->timer1_duration; fm->timer2 = fm->timer2_duration; if (!(fm->flags & FM_FLAG_NOTE_IS_PAUSE) && ((fm->flags & FM_FLAG_UNKNOWN3) || !(fm->flags & FM_FLAG_UNKNOWN2))) { fm->flags &= ~FM_FLAG_UNKNOWN3; fm->flags |= FM_FLAG_MUST_SEND_KEYON; fm->flags |= FM_FLAG_MUST_SEND_CONFIGURATION; } } } if ((fm->flags & FM_FLAG_MODULATE_NOTE) && (fm->flags & FM_FLAG_MUST_SEND_KEYON)) { fm->modulation_timer = read16(fm->modulation_table); fm->modulation_table_position = fm->modulation_table + 2; } if (!(fm->flags & FM_FLAG_MODULATE_NOTE) || (fm->flags & FM_FLAG_MUST_SEND_KEYON) || fm->must_update_voice_params) { int i; fm->must_update_voice_params = 0; for (i = 0; i < 4; i++) fm->voice_params[0x04 + i] = fm->voice_volume[i]; fm->f_number = fm->note_period; } else { if (fm->modulation_timer == 0) { int i; UINT16 a; for (i = 0; i < 4; i++) fm->voice_params[0x04 + i] = fm->voice_volume[i]; fm->modulation_table_position++; a = read8(fm->modulation_table_position++); if (a & 0x80) // sign extend a |= 0xff00; a *= 4; fm->f_number = fm->note_period + a; fm->modulation_timer = read8(fm->modulation_table_position++); // modulation_timer is UINT16 but this is just 8-bit if (read8(fm->modulation_table_position) == 0x80) // end of table - repeat { fm->modulation_table_position = fm->modulation_table + 2; } else if (read8(fm->modulation_table_position) == 0x88) // end of table - stop { fm->flags &= ~FM_FLAG_MODULATE_NOTE; } } } #if 0 popmessage("%02x %02x %02x %02x %02x %02x", NMK004_state.fm_control[0].note, NMK004_state.fm_control[1].note, NMK004_state.fm_control[2].note, NMK004_state.fm_control[3].note, NMK004_state.fm_control[4].note, NMK004_state.fm_control[5].note); #endif #if 0 popmessage("%02x %02x%02x%02x%02x %02x %02x%02x%02x%02x %02x %02x%02x%02x%02x", NMK004_state.fm_control[3].note, NMK004_state.fm_control[3].voice_volume[0], NMK004_state.fm_control[3].voice_volume[1], NMK004_state.fm_control[3].voice_volume[2], NMK004_state.fm_control[3].voice_volume[3], NMK004_state.fm_control[4].note, NMK004_state.fm_control[4].voice_volume[0], NMK004_state.fm_control[4].voice_volume[1], NMK004_state.fm_control[4].voice_volume[2], NMK004_state.fm_control[4].voice_volume[3], NMK004_state.fm_control[5].note, NMK004_state.fm_control[5].voice_volume[0], NMK004_state.fm_control[5].voice_volume[1], NMK004_state.fm_control[5].voice_volume[2], NMK004_state.fm_control[5].voice_volume[3]); #endif } static void fm_voices_update(void) { static int ym2203_registers[0x18] = { 0x30,0x38,0x34,0x3C,0x40,0x48,0x44,0x4C,0x50,0x58,0x54,0x5C,0x60,0x68,0x64,0x6C, 0x70,0x78,0x74,0x7C,0x80,0x88,0x84,0x8C }; int channel,i; for (channel = 0; channel < 3;channel++) { struct fm_control *fm1 = &NMK004_state.fm_control[channel]; struct fm_control *fm2 = &NMK004_state.fm_control[channel + 3]; if (fm1->flags & FM_FLAG_MUST_SEND_CONFIGURATION) { fm1->flags &= ~FM_FLAG_MUST_SEND_CONFIGURATION; for (i = 0; i < 0x18; i++) { YM2203_control_port_0_w(0, ym2203_registers[i] + channel); YM2203_write_port_0_w(0, fm1->voice_params[i]); } } if (fm2->flags & FM_FLAG_MUST_SEND_CONFIGURATION) { fm2->flags &= ~FM_FLAG_MUST_SEND_CONFIGURATION; if (!(fm1->flags & FM_FLAG_ACTIVE)) { for (i = 0; i < 0x18; i++) { YM2203_control_port_0_w(0, ym2203_registers[i] + channel); YM2203_write_port_0_w(0, fm2->voice_params[i]); } } } if (fm1->flags & FM_FLAG_ACTIVE) { YM2203_control_port_0_w(0, 0xb0 + channel); // self-feedback YM2203_write_port_0_w(0, fm1->self_feedback); YM2203_control_port_0_w(0, 0xa4 + channel); // F-number YM2203_write_port_0_w(0, fm1->f_number >> 8); YM2203_control_port_0_w(0, 0xa0 + channel); // F-number YM2203_write_port_0_w(0, fm1->f_number & 0xff); } else { YM2203_control_port_0_w(0, 0xb0 + channel); // self-feedback YM2203_write_port_0_w(0, fm2->self_feedback); YM2203_control_port_0_w(0, 0xa4 + channel); // F-number YM2203_write_port_0_w(0, fm2->f_number >> 8); YM2203_control_port_0_w(0, 0xa0 + channel); // F-number YM2203_write_port_0_w(0, fm2->f_number & 0xff); } if (fm1->flags & FM_FLAG_MUST_SEND_KEYON) { fm1->flags &= ~FM_FLAG_MUST_SEND_KEYON; YM2203_control_port_0_w(0, 0x28); // keyon/off YM2203_write_port_0_w(0, fm1->slot | channel); } if (fm2->flags & FM_FLAG_MUST_SEND_KEYON) { fm2->flags &= ~FM_FLAG_MUST_SEND_KEYON; if (!(fm1->flags & FM_FLAG_ACTIVE)) { YM2203_control_port_0_w(0, 0x28); // keyon/off YM2203_write_port_0_w(0, fm2->slot | channel); } } } } /***************************** YM2203 - PSG *****************************/ static void psg_update(int channel) { struct psg_control *psg = &NMK004_state.psg_control[channel]; // advance the timers if (psg->note_timer) psg->note_timer--; if (psg->volume_timer) psg->volume_timer--; if (psg->flags & PSG_FLAG_NEED_INITIALIZATION) { psg->flags &= ~PSG_FLAG_NEED_INITIALIZATION; psg->flags |= PSG_FLAG_ACTIVE; if (psg->flags & PSG_FLAG_NOTE_IS_NOISE) { int enable; psg->flags &= ~PSG_FLAG_NOTE_IS_NOISE; psg->flags &= ~PSG_FLAG_NOISE_NOT_ENABLED; // enable noise, disable tone on this channel YM2203_control_port_0_w(0, 0x07); enable = YM2203_read_port_0_r(0); enable |= (0x01 << channel); // disable tone enable &= ~(0x08 << channel); // enable noise YM2203_write_port_0_w(0, enable); } psg->note_timer = 0; psg->volume_timer = 0; psg->octave = 0; } if (psg->flags & PSG_FLAG_ACTIVE) { if (psg->note_timer == 0) { UINT8 token; do { token = read8(psg->current++); if ((token & 0xf0) == 0xf0) { int enable; switch (token) { case 0xf0: // noise psg->flags |= PSG_FLAG_NOTE_IS_NOISE; break; case 0xf1: // note psg->flags &= ~PSG_FLAG_NOTE_IS_NOISE; psg->flags &= ~PSG_FLAG_NOISE_NOT_ENABLED; // enable noise, disable tone on this channel YM2203_control_port_0_w(0, 0x07); enable = YM2203_read_port_0_r(0); enable |= (0x01 << channel); // disable tone enable &= ~(0x08 << channel); // enable noise YM2203_write_port_0_w(0, enable); break; case 0xf2: // set volume shape case 0xf3: case 0xf4: case 0xf5: psg->volume_shape = read8(psg->current++); break; case 0xf6: // jump psg->current = read16(psg->current); break; case 0xf7: // begin repeat loop psg->loop_times = read8(psg->current++); psg->loop_start = psg->current; break; case 0xf8: // end repeat loop if (--psg->loop_times > 0) psg->current = psg->loop_start; break; case 0xf9: // call subtable psg->return_address[psg->return_address_depth++] = psg->current + 2; psg->current = read16(psg->current); break; case 0xfa: // return from subtable psg->current = psg->return_address[--psg->return_address_depth]; break; case 0xfb: // set octave case 0xfc: case 0xfd: case 0xfe: psg->octave = read8(psg->current++); break; case 0xff: // end psg->flags = 0; // disable channel psg->volume_shape = 0; // mute channel YM2203_control_port_0_w(0, 8 + channel); YM2203_write_port_0_w(0, 0); return; } } } while ((token & 0xf0) == 0xf0); // token is the note to play psg->note = token; if ((psg->note & 0x0f) > NOTE_PAUSE) { fatalerror("PSG channel %d invalid note %02x",channel,psg->note); } // optional note length (otherwise use the same length as the previous one) if (read8(psg->current) & 0x80) { UINT16 table_start = read16(NOTE_LENGTH_TABLE_1); psg->note_length = read16(table_start + 2 * (read8(psg->current++) & 0x7f)); } psg->note_timer = psg->note_length; psg->volume_timer = 0; psg->flags |= PSG_FLAG_INITIALIZE_VOLUME; if (psg->note == NOTE_PAUSE) psg->flags |= PSG_FLAG_NOTE_IS_PAUSE; else { psg->flags &= ~PSG_FLAG_NOTE_IS_PAUSE; if (!(psg->flags & PSG_FLAG_NOTE_IS_NOISE)) { UINT16 table_start = read16(PSG_NOTE_TABLE); UINT16 period = read16(table_start + 2 * (psg->note & 0x0f)); UINT8 octave = psg->octave + ((psg->note & 0xf0) >> 4); period >>= octave; YM2203_control_port_0_w(0, 2 * channel + 1); YM2203_write_port_0_w(0, (period & 0x0f00) >> 8); YM2203_control_port_0_w(0, 2 * channel + 0); YM2203_write_port_0_w(0, (period & 0x00ff)); psg->note_period_hi_bits = (period & 0x0f00) >> 8; } else { if (!(psg->flags & PSG_FLAG_NOISE_NOT_ENABLED)) { int enable; psg->flags |= PSG_FLAG_NOISE_NOT_ENABLED; // disable noise, enable tone on this channel YM2203_control_port_0_w(0, 0x07); enable = YM2203_read_port_0_r(0); enable &= ~(0x01 << channel); // enable tone enable |= (0x08 << channel); // disable noise YM2203_write_port_0_w(0, enable); } YM2203_control_port_0_w(0, 0x06); // noise period YM2203_write_port_0_w(0, psg->note); psg->note_period_hi_bits = psg->note; } } } if (psg->volume_timer == 0) { UINT16 table_start = read16(PSG_VOLUME_TABLE); UINT16 vol_table_start = read16(table_start + 2 * psg->volume_shape); int volume; if (psg->flags & PSG_FLAG_INITIALIZE_VOLUME) { psg->flags &= ~PSG_FLAG_INITIALIZE_VOLUME; psg->volume_position = 0; } volume = read8(vol_table_start + psg->volume_position++); psg->volume_timer = read8(vol_table_start + psg->volume_position++); if (psg->flags & PSG_FLAG_NOTE_IS_PAUSE) volume = 0; // set volume YM2203_control_port_0_w(0, 8 + channel); YM2203_write_port_0_w(0, volume & 0x0f); } } } /***************************** Command processing *****************************/ static void get_command(void) { static UINT8 from_main[] = { 0x00,0x22,0x62,0x8c,0xc7,0x00,0x00,0x3f,0x7f,0x89,0xc7,0x00,0x00,0x2b,0x6b }; static UINT8 to_main[] = { 0x82,0xc7,0x00,0x2c,0x6c,0x00,0x9f,0xc7,0x00,0x29,0x69,0x00,0x8b,0xc7,0x00 }; UINT8 cmd = NMK004_state.from_main; if (NMK004_state.protection_check < sizeof(to_main)) { // startup handshake if (cmd == from_main[NMK004_state.protection_check]) { logerror("advance handshake to %02x\n",to_main[NMK004_state.protection_check]); NMK004_state.to_main = to_main[NMK004_state.protection_check++]; } } else { // send command back to main CPU to acknowledge reception NMK004_state.to_main = cmd; } if (NMK004_state.last_command != cmd) { UINT16 table_start = read16(COMMAND_TABLE); UINT16 cmd_table = read16(table_start + 2 * cmd); NMK004_state.last_command = cmd; if ((cmd_table & 0xff00) == 0) { oki_play_sample(cmd_table); } else { int channel; while ((channel = read8(cmd_table++)) != 0xff) { table_start = read16(cmd_table); cmd_table += 2; if (channel == 0xef) break; // bioship bug? //logerror("%04x: channel %d table %04x\n",cmd_table-3,channel,table_start); if (channel < FM_CHANNELS) { NMK004_state.fm_control[channel].current = table_start; NMK004_state.fm_control[channel].return_address_depth = 0; NMK004_state.fm_control[channel].flags |= FM_FLAG_NEED_INITIALIZATION; } else { channel -= FM_CHANNELS; if (channel < PSG_CHANNELS) { NMK004_state.psg_control[channel].current = table_start; NMK004_state.psg_control[channel].return_address_depth = 0; NMK004_state.psg_control[channel].flags |= PSG_FLAG_NEED_INITIALIZATION; } else { channel -= PSG_CHANNELS; if (channel >= EFFECTS_CHANNELS) { fatalerror("too many effects channels"); } NMK004_state.effects_control[channel].current = table_start; NMK004_state.effects_control[channel].return_address_depth = 0; NMK004_state.effects_control[channel].flags |= EFFECTS_FLAG_NEED_INITIALIZATION; } } } } } } static void update_music(void) { int channel; for (channel = 0; channel < FM_CHANNELS; channel++) fm_update(channel); fm_voices_update(); for (channel = 0; channel < PSG_CHANNELS; channel++) psg_update(channel); for (channel = 0; channel < EFFECTS_CHANNELS; channel++) effects_update(channel); } void NMK004_irq(int irq) { int status; if (!irq) return; status = YM2203_status_port_0_r(0); if (status & 1) // timer A expired { oki_update_state(); get_command(); update_music(); // restart timer YM2203_control_port_0_w(0, 0x27); YM2203_write_port_0_w(0, 0x15); } } static TIMER_CALLBACK( real_nmk004_init ) { static UINT8 ym2203_init[] = { 0x07,0x38,0x08,0x00,0x09,0x00,0x0A,0x00,0x24,0xB3,0x25,0x00,0x26,0xF9,0x27,0x15, 0x28,0x00,0x28,0x01,0x28,0x02,0x40,0x00,0x41,0x00,0x42,0x00,0x44,0x00,0x45,0x00, 0x46,0x00,0x48,0x00,0x49,0x00,0x4A,0x00,0x4C,0x00,0x4D,0x00,0x4E,0x00,0xFF, }; int i; memset(&NMK004_state, 0, sizeof(NMK004_state)); NMK004_state.rom = memory_region(REGION_CPU2); YM2203_control_port_0_w(0, 0x2f); i = 0; while (ym2203_init[i] != 0xff) { YM2203_control_port_0_w(0, ym2203_init[i++]); YM2203_write_port_0_w(0, ym2203_init[i++]); } NMK004_state.oki_playing = 0; oki_play_sample(0); NMK004_state.protection_check = 0; } void NMK004_init(void) { /* we have to do this via a timer because we get called before the sound reset */ timer_call_after_resynch(0, real_nmk004_init); } WRITE16_HANDLER( NMK004_w ) { if (ACCESSING_LSB) { logerror("%06x: NMK004_w %02x\n",activecpu_get_pc(),data); NMK004_state.from_main = data & 0xff; } } READ16_HANDLER( NMK004_r ) { static int last; int res = NMK004_state.to_main; if (res != last) logerror("%06x: NMK004_r %02x\n",activecpu_get_pc(),res); last = res; return res; }