// $Id: Y8950.cc 5993 2007-01-12 22:12:12Z m9710797 $ /* * Based on: * emu8950.c -- Y8950 emulator written by Mitsutaka Okazaki 2001 * heavily rewritten to fit openMSX structure */ #include "Y8950.hh" #include "Y8950Adpcm.hh" #include "Y8950KeyboardConnector.hh" #include "Y8950Periphery.hh" #include "SimpleDebuggable.hh" #include "MSXMotherBoard.hh" #include "DACSound16S.hh" #include namespace openmsx { class Y8950Debuggable : public SimpleDebuggable { public: Y8950Debuggable(MSXMotherBoard& motherBoard, Y8950& y8950); virtual byte read(unsigned address); virtual void write(unsigned address, byte value, const EmuTime& time); private: Y8950& y8950; }; static const double EG_STEP = 0.1875; static const double SL_STEP = 3.0; static const double TL_STEP = 0.75; static const double DB_STEP = 0.1875; // PM speed(Hz) and depth(cent) static const double PM_SPEED = 6.4; static const double PM_DEPTH = 13.75 / 2; static const double PM_DEPTH2 = 13.75; // AM speed(Hz) and depth(dB) static const double AM_SPEED = 3.7; static const double AM_DEPTH = 1.0; static const double AM_DEPTH2 = 4.8; short Y8950::dB2LinTab[(2*DB_MUTE)*2]; int Y8950::Slot::sintable[PG_WIDTH]; int Y8950::Slot::tllTable[16][8][1<> b; } // Leave the lower b bits int Y8950::LOWBITS(int c, int b) { return c & ((1<> 1; } // Table for dB(0 -- (1<> (21 - DP_BITS)), sampleRate); } void Y8950::Slot::makeTllTable() { #define dB2(x) (int)((x)*2) static int kltable[16] = { dB2( 0.000),dB2( 9.000),dB2(12.000),dB2(13.875), dB2(15.000),dB2(16.125),dB2(16.875),dB2(17.625), dB2(18.000),dB2(18.750),dB2(19.125),dB2(19.500), dB2(19.875),dB2(20.250),dB2(20.625),dB2(21.000) }; for (int fnum=0; fnum<16; fnum++) for (int block=0; block<8; block++) for (int TL=0; TL<64; TL++) for (int KL=0; KL<4; KL++) { if (KL==0) { tllTable[fnum][block][TL][KL] = ALIGN(TL, TL_STEP, EG_STEP); } else { int tmp = kltable[fnum] - dB2(3.000) * (7 - block); if (tmp <= 0) tllTable[fnum][block][TL][KL] = ALIGN(TL, TL_STEP, EG_STEP); else tllTable[fnum][block][TL][KL] = (int)((tmp>>(3-KL))/EG_STEP) + ALIGN(TL, TL_STEP, EG_STEP); } } } // Rate Table for Attack void Y8950::Slot::makeDphaseARTable(int sampleRate) { for (int AR=0; AR<16; AR++) for (int Rks=0; Rks<16; Rks++) { int RM = AR + (Rks>>2); int RL = Rks&3; if (RM>15) RM=15; switch (AR) { case 0: dphaseARTable[AR][Rks] = 0; break; case 15: dphaseARTable[AR][Rks] = EG_DP_WIDTH; break; default: dphaseARTable[AR][Rks] = rate_adjust((3*(RL+4) << (RM+1)), sampleRate); break; } } } // Rate Table for Decay void Y8950::Slot::makeDphaseDRTable(int sampleRate) { for (int DR=0; DR<16; DR++) for (int Rks=0; Rks<16; Rks++) { int RM = DR + (Rks>>2); int RL = Rks&3; if (RM>15) RM=15; switch (DR) { case 0: dphaseDRTable[DR][Rks] = 0; break; default: dphaseDRTable[DR][Rks] = rate_adjust((RL+4) << (RM-1), sampleRate); break; } } } void Y8950::Slot::makeRksTable() { for (int fnum9=0; fnum9<2; fnum9++) for (int block=0; block<8; block++) for (int KR=0; KR<2; KR++) { rksTable[fnum9][block][KR] = (KR != 0) ? (block<<1) + fnum9: block>>1; } } //**********************************************************// // // // Patch // // // //**********************************************************// Y8950::Patch::Patch() { reset(); } void Y8950::Patch::reset() { AM = false; PM = false; EG = false; KR = 0; ML = 0; KL = 0; TL = 0; FB = 0; AR = 0; DR = 0; SL = 0; RR = 0; } //**********************************************************// // // // Slot // // // //**********************************************************// Y8950::Slot::Slot() { } Y8950::Slot::~Slot() { } void Y8950::Slot::reset() { phase = 0; dphase = 0; output[0] = 0; output[1] = 0; feedback = 0; eg_mode = FINISH; eg_phase = EG_DP_WIDTH; eg_dphase = 0; rks = 0; tll = 0; fnum = 0; block = 0; pgout = 0; egout = 0; slotStatus = false; patch.reset(); updateAll(); } void Y8950::Slot::updatePG() { dphase = dphaseTable[fnum][block][patch.ML]; } void Y8950::Slot::updateTLL() { tll = tllTable[fnum>>6][block][patch.TL][patch.KL]; } void Y8950::Slot::updateRKS() { rks = rksTable[fnum>>9][block][patch.KR]; } void Y8950::Slot::updateEG() { switch (eg_mode) { case ATTACK: eg_dphase = dphaseARTable[patch.AR][rks]; break; case DECAY: eg_dphase = dphaseDRTable[patch.DR][rks]; break; case SUSTINE: eg_dphase = dphaseDRTable[patch.RR][rks]; break; case RELEASE: eg_dphase = patch.EG ? dphaseDRTable[patch.RR][rks]: dphaseDRTable[7] [rks]; break; case SUSHOLD: case FINISH: eg_dphase = 0; break; } } void Y8950::Slot::updateAll() { updatePG(); updateTLL(); updateRKS(); updateEG(); // EG should be last } // Slot key on void Y8950::Slot::slotOn() { if (!slotStatus) { slotStatus = true; eg_mode = ATTACK; phase = 0; eg_phase = 0; } } // Slot key off void Y8950::Slot::slotOff() { if (slotStatus) { slotStatus = false; if (eg_mode == ATTACK) eg_phase = EXPAND_BITS(AR_ADJUST_TABLE[HIGHBITS(eg_phase, EG_DP_BITS-EG_BITS)], EG_BITS, EG_DP_BITS); eg_mode = RELEASE; } } //**********************************************************// // // // Channel // // // //**********************************************************// Y8950::Channel::Channel() { reset(); } Y8950::Channel::~Channel() { } void Y8950::Channel::reset() { mod.reset(); car.reset(); alg = false; } // Set F-Number ( fnum : 10bit ) void Y8950::Channel::setFnumber(int fnum) { car.fnum = fnum; mod.fnum = fnum; } // Set Block data (block : 3bit ) void Y8950::Channel::setBlock(int block) { car.block = block; mod.block = block; } // Channel key on void Y8950::Channel::keyOn() { mod.slotOn(); car.slotOn(); } // Channel key off void Y8950::Channel::keyOff() { mod.slotOff(); car.slotOff(); } //**********************************************************// // // // Y8950 // // // //**********************************************************// Y8950::Y8950(MSXMotherBoard& motherBoard, const std::string& name, const XMLElement& config, unsigned sampleRam, const EmuTime& time, Y8950Periphery& perihery_) : SoundDevice(motherBoard.getMSXMixer(), name, "MSX-AUDIO") , irq(motherBoard.getCPU()) , perihery(perihery_) , timer1(motherBoard.getScheduler(), *this) , timer2(motherBoard.getScheduler(), *this) , adpcm(new Y8950Adpcm(*this, motherBoard, name, sampleRam)) , connector(new Y8950KeyboardConnector(motherBoard.getPluggingController())) , dac13(new DACSound16S(motherBoard.getMSXMixer(), name + " DAC", "MSX-AUDIO 13-bit DAC", config, time)) , debuggable(new Y8950Debuggable(motherBoard, *this)) { makePmTable(); makeAmTable(); Slot::makeAdjustTable(); Slot::makeDB2LinTable(); Slot::makeTllTable(); Slot::makeRksTable(); Slot::makeSinTable(); for (int i=0; i<9; i++) { // TODO cleanup slot[i*2+0] = &(ch[i].mod); slot[i*2+1] = &(ch[i].car); ch[i].mod.plfo_am = &lfo_am; ch[i].mod.plfo_pm = &lfo_pm; ch[i].car.plfo_am = &lfo_am; ch[i].car.plfo_pm = &lfo_pm; } reset(time); registerSound(config); } Y8950::~Y8950() { unregisterSound(); } void Y8950::setSampleRate(int sampleRate) { adpcm->setSampleRate(sampleRate); Y8950::Slot::makeDphaseTable(sampleRate); Y8950::Slot::makeDphaseARTable(sampleRate); Y8950::Slot::makeDphaseDRTable(sampleRate); makeDphaseNoiseTable(sampleRate); pm_dphase = rate_adjust(PM_SPEED * PM_DP_WIDTH / (CLK_FREQ/72), sampleRate); am_dphase = rate_adjust(AM_SPEED * AM_DP_WIDTH / (CLK_FREQ/72), sampleRate); } // Reset whole of opl except patch datas. void Y8950::reset(const EmuTime &time) { for (int i=0; i<9; i++) ch[i].reset(); output[0] = 0; output[1] = 0; rythm_mode = false; am_mode = 0; pm_mode = 0; pm_phase = 0; am_phase = 0; noise_seed = 0xffff; noiseA = 0; noiseB = 0; noiseA_phase = 0; noiseB_phase = 0; noiseA_dphase = 0; noiseB_dphase = 0; // update the output buffer before changing the register updateStream(time); for (int i = 0; i < 0x100; ++i) reg[i] = 0x00; reg[0x04] = 0x18; reg[0x19] = 0x0F; // fixes 'Thunderbirds are Go' status = 0x00; statusMask = 0; irq.reset(); adpcm->reset(time); setMute(true); // muted } // Drum key on void Y8950::keyOn_BD() { ch[6].keyOn(); } void Y8950::keyOn_HH() { ch[7].mod.slotOn(); } void Y8950::keyOn_SD() { ch[7].car.slotOn(); } void Y8950::keyOn_TOM() { ch[8].mod.slotOn(); } void Y8950::keyOn_CYM() { ch[8].car.slotOn(); } // Drum key off void Y8950::keyOff_BD() { ch[6].keyOff(); } void Y8950::keyOff_HH() { ch[7].mod.slotOff(); } void Y8950::keyOff_SD() { ch[7].car.slotOff(); } void Y8950::keyOff_TOM(){ ch[8].mod.slotOff(); } void Y8950::keyOff_CYM(){ ch[8].car.slotOff(); } // Change Rhythm Mode void Y8950::setRythmMode(int data) { bool newMode = (data & 32) != 0; if (rythm_mode != newMode) { rythm_mode = newMode; if (!rythm_mode) { // ON->OFF ch[6].mod.eg_mode = FINISH; // BD1 ch[6].mod.slotStatus = false; ch[6].car.eg_mode = FINISH; // BD2 ch[6].car.slotStatus = false; ch[7].mod.eg_mode = FINISH; // HH ch[7].mod.slotStatus = false; ch[7].car.eg_mode = FINISH; // SD ch[7].car.slotStatus = false; ch[8].mod.eg_mode = FINISH; // TOM ch[8].mod.slotStatus = false; ch[8].car.eg_mode = FINISH; // CYM ch[8].car.slotStatus = false; } } } //********************************************************// // // // Generate wave data // // // //********************************************************// // Convert Amp(0 to EG_HEIGHT) to Phase(0 to 4PI). int Y8950::Slot::wave2_4pi(int e) { int shift = SLOT_AMP_BITS - PG_BITS - 1; if (shift > 0) return e >> shift; else return e << -shift; } // Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI). int Y8950::Slot::wave2_8pi(int e) { int shift = SLOT_AMP_BITS - PG_BITS - 2; if (shift > 0) return e >> shift; else return e << -shift; } void Y8950::update_noise() { if (noise_seed & 1) noise_seed ^= 0x24000; noise_seed >>= 1; whitenoise = noise_seed&1 ? DB_POS(6) : DB_NEG(6); noiseA_phase += noiseA_dphase; noiseB_phase += noiseB_dphase; noiseA_phase &= (0x40<<11) - 1; if ((noiseA_phase>>11)==0x3f) noiseA_phase = 0; noiseA = noiseA_phase&(0x03<<11)?DB_POS(6):DB_NEG(6); noiseB_phase &= (0x10<<11) - 1; noiseB = noiseB_phase&(0x0A<<11)?DB_POS(6):DB_NEG(6); } void Y8950::update_ampm() { pm_phase = (pm_phase + pm_dphase)&(PM_DP_WIDTH - 1); am_phase = (am_phase + am_dphase)&(AM_DP_WIDTH - 1); lfo_am = amtable[am_mode][HIGHBITS(am_phase, AM_DP_BITS - AM_PG_BITS)]; lfo_pm = pmtable[pm_mode][HIGHBITS(pm_phase, PM_DP_BITS - PM_PG_BITS)]; } void Y8950::Slot::calc_phase() { if (patch.PM) phase += (dphase * (*plfo_pm)) >> PM_AMP_BITS; else phase += dphase; phase &= (DP_WIDTH - 1); pgout = HIGHBITS(phase, DP_BASE_BITS); } void Y8950::Slot::calc_envelope() { #define S2E(x) (ALIGN((unsigned int)(x/SL_STEP),SL_STEP,EG_STEP)<<(EG_DP_BITS-EG_BITS)) static unsigned int SL[16] = { S2E( 0), S2E( 3), S2E( 6), S2E( 9), S2E(12), S2E(15), S2E(18), S2E(21), S2E(24), S2E(27), S2E(30), S2E(33), S2E(36), S2E(39), S2E(42), S2E(93) }; switch (eg_mode) { case ATTACK: eg_phase += eg_dphase; if (EG_DP_WIDTH & eg_phase) { egout = 0; eg_phase= 0; eg_mode = DECAY; updateEG(); } else { egout = AR_ADJUST_TABLE[HIGHBITS(eg_phase, EG_DP_BITS - EG_BITS)]; } break; case DECAY: eg_phase += eg_dphase; egout = HIGHBITS(eg_phase, EG_DP_BITS - EG_BITS); if (eg_phase >= SL[patch.SL]) { if (patch.EG) { eg_phase = SL[patch.SL]; eg_mode = SUSHOLD; updateEG(); } else { eg_phase = SL[patch.SL]; eg_mode = SUSTINE; updateEG(); } egout = HIGHBITS(eg_phase, EG_DP_BITS - EG_BITS); } break; case SUSHOLD: egout = HIGHBITS(eg_phase, EG_DP_BITS - EG_BITS); if (!patch.EG) { eg_mode = SUSTINE; updateEG(); } break; case SUSTINE: case RELEASE: eg_phase += eg_dphase; egout = HIGHBITS(eg_phase, EG_DP_BITS - EG_BITS); if (egout >= (1<= DB_MUTE) egout = DB_MUTE-1; } int Y8950::Slot::calc_slot_car(int fm) { calc_envelope(); calc_phase(); if (egout>=(DB_MUTE-1)) return 0; return dB2LinTab[sintable[(pgout+wave2_8pi(fm))&(PG_WIDTH-1)] + egout]; } int Y8950::Slot::calc_slot_mod() { output[1] = output[0]; calc_envelope(); calc_phase(); if (egout>=(DB_MUTE-1)) { output[0] = 0; } else if (patch.FB!=0) { int fm = wave2_4pi(feedback) >> (7-patch.FB); output[0] = dB2LinTab[sintable[(pgout+fm)&(PG_WIDTH-1)] + egout]; } else output[0] = dB2LinTab[sintable[pgout] + egout]; feedback = (output[1] + output[0])>>1; return feedback; } // TOM int Y8950::Slot::calc_slot_tom() { calc_envelope(); calc_phase(); if (egout>=(DB_MUTE-1)) return 0; return dB2LinTab[sintable[pgout] + egout]; } // SNARE int Y8950::Slot::calc_slot_snare(int whitenoise) { calc_envelope(); calc_phase(); if (egout>=(DB_MUTE-1)) return 0; if (pgout & (1<<(PG_BITS-1))) { return (dB2LinTab[egout] + dB2LinTab[egout+whitenoise]) >> 1; } else { return (dB2LinTab[2*DB_MUTE + egout] + dB2LinTab[egout+whitenoise]) >> 1; } } // TOP-CYM int Y8950::Slot::calc_slot_cym(int a, int b) { calc_envelope(); if (egout>=(DB_MUTE-1)) { return 0; } else { return (dB2LinTab[egout+a] + dB2LinTab[egout+b]) >> 1; } } // HI-HAT int Y8950::Slot::calc_slot_hat(int a, int b, int whitenoise) { calc_envelope(); if (egout>=(DB_MUTE-1)) { return 0; } else { return (dB2LinTab[egout+whitenoise] + dB2LinTab[egout+a] + dB2LinTab[egout+b]) >>2; } } int Y8950::calcSample(int channelMask) { // while muted update_ampm() and update_noise() aren't called, probably ok update_ampm(); update_noise(); int mix = 0; if (rythm_mode) { // TODO wasn't in original source either ch[7].mod.calc_phase(); ch[8].car.calc_phase(); if (channelMask & (1 << 6)) mix += ch[6].car.calc_slot_car(ch[6].mod.calc_slot_mod()); if (ch[7].mod.eg_mode != FINISH) mix += ch[7].mod.calc_slot_hat(noiseA, noiseB, whitenoise); if (channelMask & (1 << 7)) mix += ch[7].car.calc_slot_snare(whitenoise); if (ch[8].mod.eg_mode != FINISH) mix += ch[8].mod.calc_slot_tom(); if (channelMask & (1 << 8)) mix += ch[8].car.calc_slot_cym(noiseA, noiseB); channelMask &= (1<< 6) - 1; mix *= 2; } for (Channel *cp = ch; channelMask; channelMask >>=1, cp++) { if (channelMask & 1) { if (cp->alg) mix += cp->car.calc_slot_car(0) + cp->mod.calc_slot_mod(); else mix += cp->car.calc_slot_car( cp->mod.calc_slot_mod()); } } mix += adpcm->calcSample(); return (mix*maxVolume) >> DB2LIN_AMP_BITS; } void Y8950::checkMute() { bool mute = checkMuteHelper(); //PRT_DEBUG("Y8950: muted " << mute); setMute(mute); } bool Y8950::checkMuteHelper() { for (int i = 0; i < 6; i++) { if (ch[i].car.eg_mode != FINISH) return false; } if (!rythm_mode) { for(int i = 6; i < 9; i++) { if (ch[i].car.eg_mode != FINISH) return false; } } else { if (ch[6].car.eg_mode != FINISH) return false; if (ch[7].mod.eg_mode != FINISH) return false; if (ch[7].car.eg_mode != FINISH) return false; if (ch[8].mod.eg_mode != FINISH) return false; if (ch[8].car.eg_mode != FINISH) return false; } return adpcm->muted(); } void Y8950::updateBuffer(unsigned length, int* buffer, const EmuTime& /*time*/, const EmuDuration& /*sampDur*/) { int channelMask = 0; for (int i = 9; i--; ) { channelMask <<= 1; if (ch[i].car.eg_mode != FINISH) channelMask |= 1; } while (length--) { *(buffer++) = calcSample(channelMask); } checkMute(); } void Y8950::setVolume(int newVolume) { maxVolume = newVolume; } //**************************************************// // // // I/O Ctrl // // // //**************************************************// void Y8950::writeReg(byte rg, byte data, const EmuTime& time) { //PRT_DEBUG("Y8950 write " << (int)rg << " " << (int)data); int stbl[32] = { 0, 2, 4, 1, 3, 5,-1,-1, 6, 8,10, 7, 9,11,-1,-1, 12,14,16,13,15,17,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1 }; //TODO only for registers that influence sound //TODO also ADPCM //if (rg>=0x20) { // update the output buffer before changing the register updateStream(time); //} //std::cout << "write: " << (int)rg << " " << (int)data << std::endl; switch (rg & 0xe0) { case 0x00: { switch (rg) { case 0x01: // TEST // TODO // Y8950 MSX-AUDIO Test register $01 (write only) // // Bit Description // // 7 Reset LFOs - seems to force the LFOs to their initial values (eg. // maximum amplitude, zero phase deviation) // // 6 something to do with ADPCM - bit 0 of the status register is // affected by setting this bit (PCM BSY) // // 5 No effect? - Waveform select enable in YM3812 OPL2 so seems // reasonable that this bit wouldn't have been used in OPL // // 4 No effect? // // 3 Faster LFOs - increases the frequencies of the LFOs and (maybe) // the timers (cf. YM2151 test register) // // 2 Reset phase generators - No phase generator output, but envelope // generators still work (can hear a transient when they are gated) // // 1 No effect? // // 0 Reset envelopes - Envelope generator outputs forced to maximum, // so all enabled voices sound at maximum reg[rg] = data; break; case 0x02: // TIMER1 (reso. 80us) timer1.setValue(data); reg[rg] = data; break; case 0x03: // TIMER2 (reso. 320us) timer2.setValue(data); reg[rg] = data; break; case 0x04: // FLAG CONTROL if (data & R04_IRQ_RESET) { resetStatus(0x78); // reset all flags } else { changeStatusMask((~data) & 0x78); timer1.setStart(data & R04_ST1, time); timer2.setStart(data & R04_ST2, time); reg[rg] = data; } break; case 0x06: // (KEYBOARD OUT) connector->write(data, time); reg[rg] = data; break; case 0x07: // START/REC/MEM DATA/REPEAT/SP-OFF/-/-/RESET case 0x08: // CSM/KEY BOARD SPLIT/-/-/SAMPLE/DA AD/64K/ROM case 0x09: // START ADDRESS (L) case 0x0A: // START ADDRESS (H) case 0x0B: // STOP ADDRESS (L) case 0x0C: // STOP ADDRESS (H) case 0x0D: // PRESCALE (L) case 0x0E: // PRESCALE (H) case 0x0F: // ADPCM-DATA case 0x10: // DELTA-N (L) case 0x11: // DELTA-N (H) case 0x12: // ENVELOP CONTROL case 0x1A: // PCM-DATA reg[rg] = data; adpcm->writeReg(rg, data, time); break; case 0x15: // DAC-DATA (bit9-2) reg[rg] = data; if (reg[0x08] & 0x04) { int tmp = ((signed char)reg[0x15]) * 256 + reg[0x16]; tmp = (tmp * 4) >> (7 - reg[0x17]); if (tmp > 32767) tmp = 32767; else if (tmp < -32768) tmp = -32768; dac13->writeDAC(tmp, time); } break; case 0x16: // (bit1-0) reg[rg] = data & 0xC0; break; case 0x17: // (exponent) reg[rg] = data & 0x07; break; case 0x18: // I/O-CONTROL (bit3-0) // 0 -> input // 1 -> output reg[rg] = data; perihery.write(reg[0x18], reg[0x19], time); break; case 0x19: // I/O-DATA (bit3-0) reg[rg] = data; perihery.write(reg[0x18], reg[0x19], time); break; } break; } case 0x20: { int s = stbl[rg&0x1f]; if (s >= 0) { slot[s]->patch.AM = (data>>7)&1; slot[s]->patch.PM = (data>>6)&1; slot[s]->patch.EG = (data>>5)&1; slot[s]->patch.KR = (data>>4)&1; slot[s]->patch.ML = (data)&15; slot[s]->updateAll(); } reg[rg] = data; break; } case 0x40: { int s = stbl[rg&0x1f]; if (s >= 0) { slot[s]->patch.KL = (data>>6)&3; slot[s]->patch.TL = (data)&63; slot[s]->updateAll(); } reg[rg] = data; break; } case 0x60: { int s = stbl[rg&0x1f]; if (s >= 0) { slot[s]->patch.AR = (data>>4)&15; slot[s]->patch.DR = (data)&15; slot[s]->updateEG(); } reg[rg] = data; break; } case 0x80: { int s = stbl[rg&0x1f]; if (s >= 0) { slot[s]->patch.SL = (data>>4)&15; slot[s]->patch.RR = (data)&15; slot[s]->updateEG(); } reg[rg] = data; break; } case 0xa0: { if (rg==0xbd) { am_mode = (data>>7)&1; pm_mode = (data>>6)&1; setRythmMode(data); if (rythm_mode) { if (data&0x10) keyOn_BD(); else keyOff_BD(); if (data&0x08) keyOn_SD(); else keyOff_SD(); if (data&0x04) keyOn_TOM(); else keyOff_TOM(); if (data&0x02) keyOn_CYM(); else keyOff_CYM(); if (data&0x01) keyOn_HH(); else keyOff_HH(); } ch[6].mod.updateAll(); ch[6].car.updateAll(); ch[7].mod.updateAll(); ch[7].car.updateAll(); ch[8].mod.updateAll(); ch[8].car.updateAll(); reg[rg] = data; break; } if ((rg&0xf) > 8) { // 0xa9-0xaf 0xb9-0xbf break; } if (!(rg&0x10)) { // 0xa0-0xa8 int c = rg-0xa0; int fNum = data + ((reg[rg+0x10]&3)<<8); int block = (reg[rg+0x10]>>2)&7; ch[c].setFnumber(fNum); switch (c) { case 7: noiseA_dphase = dphaseNoiseTable[fNum][block]; break; case 8: noiseB_dphase = dphaseNoiseTable[fNum][block]; break; } ch[c].car.updateAll(); ch[c].mod.updateAll(); reg[rg] = data; } else { // 0xb0-0xb8 int c = rg-0xb0; int fNum = ((data&3)<<8) + reg[rg-0x10]; int block = (data>>2)&7; ch[c].setFnumber(fNum); ch[c].setBlock(block); switch (c) { case 7: noiseA_dphase = dphaseNoiseTable[fNum][block]; break; case 8: noiseB_dphase = dphaseNoiseTable[fNum][block]; break; } if (data&0x20) ch[c].keyOn(); else ch[c].keyOff(); ch[c].mod.updateAll(); ch[c].car.updateAll(); reg[rg] = data; } break; } case 0xc0: { if (rg > 0xc8) break; int c = rg-0xC0; slot[c*2]->patch.FB = (data>>1)&7; ch[c].alg = data&1; reg[rg] = data; } } //TODO only for registers that influence sound checkMute(); } byte Y8950::readReg(byte rg, const EmuTime &time) { updateStream(time); // TODO only when necessary byte result; switch (rg) { case 0x0F: // ADPCM-DATA case 0x13: // ??? case 0x14: // ??? case 0x1A: // PCM-DATA result = adpcm->readReg(rg); default: result = peekReg(rg, time); } //std::cout << "read: " << (int)rg << " " << (int)result << std::endl; return result; } byte Y8950::peekReg(byte rg, const EmuTime &time) const { switch (rg) { case 0x05: // (KEYBOARD IN) return connector->read(time); // TODO peek iso read case 0x0F: // ADPCM-DATA case 0x13: // ??? case 0x14: // ??? case 0x1A: // PCM-DATA return adpcm->peekReg(rg); case 0x19: { // I/O DATA byte input = perihery.read(time); byte output = reg[0x19]; byte enable = reg[0x18]; return (output & enable) | (input & ~enable) | 0xF0; } default: return 255; } } byte Y8950::readStatus() { setStatus(STATUS_BUF_RDY); // temp hack byte result = peekStatus(); //std::cout << "status: " << (int)result << std::endl; return result; } byte Y8950::peekStatus() const { return (status & (0x80 | statusMask)) | 0x06; // bit 1 and 2 are always 1 } void Y8950::callback(byte flag) { setStatus(flag); } void Y8950::setStatus(byte flags) { status |= flags; if (status & statusMask) { status |= 0x80; irq.set(); } } void Y8950::resetStatus(byte flags) { status &= ~flags; if (!(status & statusMask)) { status &= 0x7f; irq.reset(); } } void Y8950::changeStatusMask(byte newMask) { statusMask = newMask; status &= statusMask; if (status) { status |= 0x80; irq.set(); } else { status &= 0x7f; irq.reset(); } } // SimpleDebuggable Y8950Debuggable::Y8950Debuggable(MSXMotherBoard& motherBoard, Y8950& y8950_) : SimpleDebuggable(motherBoard, y8950_.getName() + " regs", "MSX-AUDIO", 0x100) , y8950(y8950_) { } byte Y8950Debuggable::read(unsigned address) { return y8950.reg[address]; } void Y8950Debuggable::write(unsigned address, byte value, const EmuTime& time) { y8950.writeReg(address, value, time); } } // namespace openmsx