diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index e52746309..81f005140 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -101,13 +101,70 @@ class DivDispatch { * the engine shall resample to the output rate. */ int rate; + + /** + * fill a buffer with sound data. + * @param bufL the left or mono channel buffer. + * @param bufR the right channel buffer. + * @param start the start offset. + * @param len the amount of samples to fill. + */ virtual void acquire(short* bufL, short* bufR, size_t start, size_t len); + + /** + * send a command to this dispatch. + * @param c a DivCommand. + * @return a return value which varies depending on the command. + */ virtual int dispatch(DivCommand c); + + /** + * reset the state of this dispatch. + */ virtual void reset(); + + /** + * ticks this dispatch. + */ virtual void tick(); + /** + * get this dispatch's state. + * @return a pointer to the dispatch's state. must be deallocated manually! + */ + virtual void* getState(); + + /** + * set this dispatch's state. + * @param state a pointer to a state pertaining to this dispatch, + * or NULL if this dispatch does not support state saves. + */ + virtual void setState(void* state); + + /** + * mute a channel. + * @param ch the channel to mute. + * @param mute whether to mute or unmute. + */ + virtual void muteChannel(int ch, bool mute); + + /** + * test whether this dispatch outputs audio in two channels. + * @return whether it does. + */ virtual bool isStereo(); + + /** + * test whether sending a key off command to a channel should reset arp too. + * @param ch the channel in question. + * @return whether it does. + */ virtual bool keyOffAffectsArp(int ch); + + /** + * set the region to PAL. + * @param pal whether to set it to PAL. + */ virtual void setPAL(bool pal); /** diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 080e69a99..f77ba395c 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1271,6 +1271,55 @@ bool DivEngine::isPlaying() { return playing; } +bool DivEngine::isChannelMuted(int chan) { + return isMuted[chan]; +} + +void DivEngine::toggleMute(int chan) { + muteChannel(chan,!isMuted[chan]); +} + +void DivEngine::toggleSolo(int chan) { + bool solo=false; + for (int i=0; imuteChannel(i,isMuted[i]); + } + } + } else { + for (int i=0; imuteChannel(i,isMuted[i]); + } + } + } + isBusy.unlock(); +} + +void DivEngine::muteChannel(int chan, bool mute) { + isBusy.lock(); + isMuted[chan]=mute; + if (dispatch!=NULL) { + dispatch->muteChannel(chan,isMuted[chan]); + } + isBusy.unlock(); +} + int DivEngine::addInstrument() { isBusy.lock(); DivInstrument* ins=new DivInstrument; @@ -1509,6 +1558,9 @@ void DivEngine::quitDispatch() { totalCmds=0; lastCmds=0; cmdsPerSecond=0; + for (int i=0; i<17; i++) { + isMuted[i]=0; + } isBusy.unlock(); } @@ -1586,6 +1638,10 @@ bool DivEngine::init(String outName) { vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI)); } + for (int i=0; i<17; i++) { + isMuted[i]=0; + } + initDispatch(); reset(); diff --git a/src/engine/engine.h b/src/engine/engine.h index 1536f5cf4..abdd810a9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -77,6 +77,7 @@ class DivEngine { DivStatusView view; DivChannelState chan[17]; DivAudioEngines audioEngine; + bool isMuted[17]; std::mutex isBusy; short vibTable[64]; @@ -132,6 +133,18 @@ class DivEngine { // is STD system bool isSTDSystem(DivSystem sys); + // is channel muted + bool isChannelMuted(int chan); + + // toggle mute + void toggleMute(int chan); + + // toggle solo + void toggleSolo(int chan); + + // set mute status + void muteChannel(int chan, bool mute); + // get channel name const char* getChannelName(int chan); diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index 992c46a3e..7012cc5db 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -6,6 +6,16 @@ void DivDispatch::acquire(short* bufL, short* bufR, size_t start, size_t len) { void DivDispatch::tick() { } +void* DivDispatch::getState() { + return NULL; +} + +void DivDispatch::setState(void* state) { +} + +void DivDispatch::muteChannel(int ch, bool mute) { +} + int DivDispatch::dispatch(DivCommand c) { return 1; } diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 6b43d27c9..882da7495 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -118,12 +118,14 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz for (int i=8; i<13; i++) { if (chan[i].pcm.sample>=0 && chan[i].pcm.samplesong.sampleLen) { DivSample* s=parent->song.sample[chan[i].pcm.sample]; - if (s->depth==8) { - pcmL+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolL); - pcmR+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolR); - } else { - pcmL+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolL)>>8; - pcmR+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolR)>>8; + if (!isMuted[i]) { + if (s->depth==8) { + pcmL+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolL); + pcmR+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolR); + } else { + pcmL+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolL)>>8; + pcmR+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolR)>>8; + } } chan[i].pcm.pos+=chan[i].pcm.freq; if (chan[i].pcm.pos>=(s->rendLength<<8)) { @@ -191,6 +193,18 @@ void DivPlatformArcade::tick() { } } +void DivPlatformArcade::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch<8) { + DivInstrument* ins=parent->getIns(chan[ch].ins); + if (isMuted[ch]) { + rWrite(chanOffs[ch]+0x20,(ins->fm.alg&7)|(ins->fm.fb<<3)); + } else { + rWrite(chanOffs[ch]+0x20,(ins->fm.alg&7)|(ins->fm.fb<<3)|((chan[ch].chVolL&1)<<6)|((chan[ch].chVolR&1)<<7)); + } + } +} + int DivPlatformArcade::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { @@ -227,7 +241,11 @@ int DivPlatformArcade::dispatch(DivCommand c) { } } if (chan[c.chan].insChanged) { - rWrite(chanOffs[c.chan]+0x20,(ins->fm.alg&7)|(ins->fm.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7)); + if (isMuted[c.chan]) { + rWrite(chanOffs[c.chan]+0x20,(ins->fm.alg&7)|(ins->fm.fb<<3)); + } else { + rWrite(chanOffs[c.chan]+0x20,(ins->fm.alg&7)|(ins->fm.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7)); + } rWrite(chanOffs[c.chan]+0x38,((ins->fm.fms&7)<<4)|(ins->fm.ams&3)); } chan[c.chan].insChanged=false; @@ -283,7 +301,11 @@ int DivPlatformArcade::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(chan[c.chan].ins); chan[c.chan].chVolL=((c.value>>4)==1); chan[c.chan].chVolR=((c.value&15)==1); - rWrite(chanOffs[c.chan]+0x20,(ins->fm.alg&7)|(ins->fm.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7)); + if (isMuted[c.chan]) { + rWrite(chanOffs[c.chan]+0x20,(ins->fm.alg&7)|(ins->fm.fb<<3)); + } else { + rWrite(chanOffs[c.chan]+0x20,(ins->fm.alg&7)|(ins->fm.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7)); + } } break; } @@ -443,6 +465,9 @@ void DivPlatformArcade::setYMFM(bool use) { int DivPlatformArcade::init(DivEngine* p, int channels, int sugRate, bool pal) { parent=p; + for (int i=0; i<13; i++) { + isMuted[i]=false; + } if (useYMFM) { rate=447443/8; fm_ymfm=new ymfm::ym2151(iface); diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index b8a46d6c7..43074bd31 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -48,6 +48,8 @@ class DivPlatformArcade: public DivDispatch { DivArcadeInterface iface; bool extMode, useYMFM; + + bool isMuted[13]; short oldWrites[256]; short pendingWrites[256]; @@ -63,6 +65,7 @@ class DivPlatformArcade: public DivDispatch { int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); bool isStereo(); void setYMFM(bool use); int init(DivEngine* parent, int channels, int sugRate, bool pal); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 78515e29b..ce6887af7 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -55,16 +55,16 @@ void DivPlatformC64::tick() { } if (chan[i].testWhen>0) { if (--chan[i].testWhen<1) { - if (!chan[i].resetMask) { + if (!chan[i].resetMask && !isMuted[i]) { sid.write(i*7+5,0); sid.write(i*7+6,0); - sid.write(i*7+4,(chan[i].wave<<4)|8|(chan[i].ring<<2)|(chan[i].sync<<1)); + sid.write(i*7+4,(isMuted[i]?0:(chan[i].wave<<4))|8|(chan[i].ring<<2)|(chan[i].sync<<1)); } } } if (chan[i].std.hadWave) { chan[i].wave=chan[i].std.wave; - sid.write(i*7+4,(chan[i].wave<<4)|(chan[i].ring<<2)|(chan[i].sync<<1)|chan[i].active); + sid.write(i*7+4,(isMuted[i]?0:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|chan[i].active); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=(chan[i].baseFreq*(ONE_SEMITONE+chan[i].pitch))/ONE_SEMITONE; @@ -72,12 +72,12 @@ void DivPlatformC64::tick() { if (chan[i].keyOn) { sid.write(i*7+5,(chan[i].attack<<4)|(chan[i].decay)); sid.write(i*7+6,(chan[i].sustain<<4)|(chan[i].release)); - sid.write(i*7+4,(chan[i].wave<<4)|(chan[i].ring<<2)|(chan[i].sync<<1)|1); + sid.write(i*7+4,(isMuted[i]?0:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|1); } - if (chan[i].keyOff) { + if (chan[i].keyOff && !isMuted[i]) { sid.write(i*7+5,(chan[i].attack<<4)|(chan[i].decay)); sid.write(i*7+6,(chan[i].sustain<<4)|(chan[i].release)); - sid.write(i*7+4,(chan[i].wave<<4)|(chan[i].ring<<2)|(chan[i].sync<<1)|0); + sid.write(i*7+4,(isMuted[i]?0:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|0); } sid.write(i*7,chan[i].freq&0xff); sid.write(i*7+1,chan[i].freq>>8); @@ -278,6 +278,11 @@ int DivPlatformC64::dispatch(DivCommand c) { return 1; } +void DivPlatformC64::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + sid.write(ch*7+4,(isMuted[ch]?0:(chan[ch].wave<<4))|(chan[ch].ring<<2)|(chan[ch].sync<<1)|chan[ch].active); +} + void DivPlatformC64::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformC64::Channel(); @@ -312,6 +317,9 @@ void DivPlatformC64::setPAL(bool pal) { int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, bool pal) { parent=p; + for (int i=0; i<3; i++) { + isMuted[i]=false; + } setPAL(pal); reset(); diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index 599be4596..67b0a3e28 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -45,6 +45,7 @@ class DivPlatformC64: public DivDispatch { vol(15) {} }; Channel chan[3]; + bool isMuted[3]; unsigned char filtControl, filtRes, vol; int filtCut, resetTime; @@ -57,6 +58,7 @@ class DivPlatformC64: public DivDispatch { int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); void setPAL(bool pal); int init(DivEngine* parent, int channels, int sugRate, bool pal); void setChipModel(bool is6581); diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp index 57890996f..390363dda 100644 --- a/src/engine/platform/dummy.cpp +++ b/src/engine/platform/dummy.cpp @@ -7,17 +7,21 @@ void DivPlatformDummy::acquire(short* bufL, short* bufR, size_t start, size_t le bufL[i]=0; for (unsigned char j=0; j=0x8000)?chan[j].vol:-chan[j].vol)*chan[j].amp; + if (!isMuted[j]) bufL[i]+=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>13; chan[j].pos+=chan[j].freq; } } } } +void DivPlatformDummy::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + void DivPlatformDummy::tick() { for (unsigned char i=0; i5) { + psg.muteChannel(ch,mute); + return; + } + isMuted[ch]=mute; + DivInstrument* ins=parent->getIns(chan[ch].ins); + rWrite(chanOffs[ch]+0xb4,(isMuted[ch]?0:(chan[ch].pan<<6))|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); +} + int DivPlatformGenesis::dispatch(DivCommand c) { if (c.chan>5) { c.chan-=6; @@ -195,7 +205,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } if (chan[c.chan].insChanged) { rWrite(chanOffs[c.chan]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); - rWrite(chanOffs[c.chan]+0xb4,(chan[c.chan].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[c.chan]+0xb4,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); } chan[c.chan].insChanged=false; @@ -249,7 +259,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } DivInstrument* ins=parent->getIns(chan[c.chan].ins); - rWrite(chanOffs[c.chan]+0xb4,(chan[c.chan].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[c.chan]+0xb4,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -410,6 +420,9 @@ void DivPlatformGenesis::setPAL(bool pal) { int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, bool pal) { parent=p; + for (int i=0; i<10; i++) { + isMuted[i]=false; + } setPAL(pal); // PSG psg.init(p,4,sugRate,pal); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 09580be5f..b40c87c30 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -19,6 +19,7 @@ class DivPlatformGenesis: public DivDispatch { Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} }; Channel chan[10]; + bool isMuted[10]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -53,6 +54,7 @@ class DivPlatformGenesis: public DivDispatch { int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); void setPAL(bool pal); diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 7f54f6146..63aa10aaa 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -20,7 +20,9 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; - if (isOutput[ins->fm.alg][ordch]) { + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { if (!opChan[ch].active || opChan[ch].insChanged) { rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); } @@ -58,7 +60,9 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(opChan[ch].ins); unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; - if (isOutput[ins->fm.alg][ordch]) { + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); } else { rWrite(baseAddr+0x40,op.tl); @@ -182,6 +186,30 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { return 1; } +void DivPlatformGenesisExt::muteChannel(int ch, bool mute) { + if (ch<2) { + DivPlatformGenesis::muteChannel(ch,mute); + return; + } + if (ch>5) { + DivPlatformGenesis::muteChannel(ch-3,mute); + return; + } + isOpMuted[ch-2]=mute; + + int ordch=orderedOps[ch]; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } +} + static int opChanOffsL[4]={ 0xa9, 0xaa, 0xa8, 0xa2 }; @@ -273,6 +301,9 @@ bool DivPlatformGenesisExt::keyOffAffectsArp(int ch) { int DivPlatformGenesisExt::init(DivEngine* parent, int channels, int sugRate, bool pal) { DivPlatformGenesis::init(parent,channels,sugRate,pal); + for (int i=0; i<4; i++) { + isOpMuted[i]=false; + } reset(); return 13; diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index c68ed1928..506a3e66b 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -14,10 +14,12 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} }; OpChannel opChan[4]; + bool isOpMuted[4]; public: int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); int init(DivEngine* parent, int channels, int sugRate, bool pal); void quit(); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 491a43bd3..a26c24072 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -12,10 +12,12 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) dacPeriod+=dacRate; if (dacPeriod>=rate) { DivSample* s=parent->song.sample[dacSample]; - if (s->depth==8) { - apu_wr_reg(0x4011,((unsigned char)s->rendData[dacPos++]+0x80)>>1); - } else { - apu_wr_reg(0x4011,((unsigned short)s->rendData[dacPos++]+0x8000)>>9); + if (!isMuted[4]) { + if (s->depth==8) { + apu_wr_reg(0x4011,((unsigned char)s->rendData[dacPos++]+0x80)>>1); + } else { + apu_wr_reg(0x4011,((unsigned short)s->rendData[dacPos++]+0x8000)>>9); + } } if (dacPos>=s->rendLength) { dacSample=-1; @@ -272,6 +274,14 @@ int DivPlatformNES::dispatch(DivCommand c) { return 1; } +void DivPlatformNES::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + apu_wr_reg(0x4015,(!isMuted[0])|((!isMuted[1])<<1)|((!isMuted[2])<<2)|((!isMuted[3])<<3)|((!isMuted[4])<<4)); + if (isMuted[4]) { + apu_wr_reg(0x4011,0); + } +} + void DivPlatformNES::reset() { for (int i=0; i<5; i++) { chan[i]=DivPlatformNES::Channel(); @@ -287,7 +297,7 @@ void DivPlatformNES::reset() { apu.cpu_cycles=0; apu.cpu_opcode_cycle=0; - apu_wr_reg(0x4015,0x1f); + apu_wr_reg(0x4015,(!isMuted[0])|((!isMuted[1])<<1)|((!isMuted[2])<<2)|((!isMuted[3])<<3)|((!isMuted[4])<<4)); apu_wr_reg(0x4001,0x08); apu_wr_reg(0x4005,0x08); } @@ -308,6 +318,9 @@ void DivPlatformNES::setPAL(bool pal) { int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, bool pal) { parent=p; + for (int i=0; i<5; i++) { + isMuted[i]=false; + } setPAL(pal); init_nla_table(500,500); diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index b1e916b70..115f39371 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -32,6 +32,7 @@ class DivPlatformNES: public DivDispatch { wave(-1) {} }; Channel chan[5]; + bool isMuted[5]; int dacPeriod, dacRate; unsigned int dacPos; int dacSample; @@ -46,6 +47,7 @@ class DivPlatformNES: public DivDispatch { int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); void setPAL(bool pal); int init(DivEngine* parent, int channels, int sugRate, bool pal); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index fe3126cb3..63916c037 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -239,7 +239,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { chan[c.chan].pan=c.value; - chWrite(c.chan,0x05,chan[c.chan].pan); + chWrite(c.chan,0x05,isMuted[c.chan]?0:chan[c.chan].pan); break; } case DIV_CMD_LEGATO: @@ -263,6 +263,11 @@ int DivPlatformPCE::dispatch(DivCommand c) { return 1; } +void DivPlatformPCE::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chWrite(ch,0x05,isMuted[ch]?0:chan[ch].pan); +} + void DivPlatformPCE::reset() { while (!writes.empty()) writes.pop(); for (int i=0; i<6; i++) { @@ -280,7 +285,7 @@ void DivPlatformPCE::reset() { rWrite(0x01,0xff); // set per-channel initial panning for (int i=0; i<6; i++) { - chWrite(i,0x05,chan[i].pan); + chWrite(i,0x05,isMuted[i]?0:chan[i].pan); } delay=500; } @@ -303,6 +308,9 @@ void DivPlatformPCE::setPAL(bool pal) { int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, bool pal) { parent=p; + for (int i=0; i<6; i++) { + isMuted[i]=false; + } setPAL(pal); pce=new PCE_PSG(&tempL,&tempR,PCE_PSG::REVISION_HUC6280); reset(); diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 6673335ff..f96ae6983 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -40,6 +40,7 @@ class DivPlatformPCE: public DivDispatch { wave(-1) {} }; Channel chan[6]; + bool isMuted[6]; struct QueuedWrite { unsigned char addr; unsigned char val; @@ -57,6 +58,7 @@ class DivPlatformPCE: public DivDispatch { int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); void setPAL(bool pal); diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index c444c5d4a..0f9cedc0a 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -17,7 +17,7 @@ void DivPlatformSMS::tick() { chan[i].std.next(); if (chan[i].std.hadVol) { chan[i].outVol=(chan[i].vol*chan[i].std.vol)>>4; - sn->write(0x90|(i<<5)|(15-(chan[i].outVol&15))); + sn->write(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15)))); } if (chan[i].std.hadArp) { if (chan[i].std.arpMode) { @@ -84,7 +84,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; chan[c.chan].active=true; - sn->write(0x90|c.chan<<5|(15-(chan[c.chan].vol&15))); + sn->write(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); break; case DIV_CMD_NOTE_OFF: @@ -102,7 +102,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; } - sn->write(0x90|c.chan<<5|(15-(chan[c.chan].vol&15))); + sn->write(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); } break; case DIV_CMD_GET_VOLUME: @@ -159,6 +159,11 @@ int DivPlatformSMS::dispatch(DivCommand c) { return 1; } +void DivPlatformSMS::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (chan[ch].active) sn->write(0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15)))); +} + void DivPlatformSMS::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformSMS::Channel(); @@ -182,6 +187,9 @@ void DivPlatformSMS::setPAL(bool pal) { int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, bool pal) { parent=p; + for (int i=0; i<4; i++) { + isMuted[i]=false; + } setPAL(pal); sn=new sn76496_device(rate); reset(); diff --git a/src/engine/platform/sms.h b/src/engine/platform/sms.h index 2f0e8c534..3109e82b5 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -27,6 +27,7 @@ class DivPlatformSMS: public DivDispatch { outVol(15) {} }; Channel chan[4]; + bool isMuted[4]; unsigned char snNoiseMode; bool updateSNMode; sn76496_device* sn; @@ -36,6 +37,7 @@ class DivPlatformSMS: public DivDispatch { int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); void setPAL(bool pal); int init(DivEngine* parent, int channels, int sugRate, bool pal); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index a5bb5af1d..7167de51c 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -46,7 +46,11 @@ void DivPlatformYM2610::tick() { if (chan[i].std.hadVol) { chan[i].outVol=chan[i].std.vol-(15-chan[i].vol); if (chan[i].outVol<0) chan[i].outVol=0; - rWrite(0x04+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + if (isMuted[i]) { + rWrite(0x04+i,0); + } else { + rWrite(0x04+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + } } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -187,7 +191,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { int end=s->rendOff+s->adpcmRendLength-1; writes.emplace(0x120+c.chan-7,(end>>8)&0xff); writes.emplace(0x128+c.chan-7,end>>16); - writes.emplace(0x108+(c.chan-7),(chan[c.chan].pan<<6)|chan[c.chan].vol); + writes.emplace(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); writes.emplace(0x100,0x00|(1<<(c.chan-7))); break; } @@ -200,7 +204,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].std.init(ins); - rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + if (isMuted[c.chan]) { + rWrite(0x04+c.chan,0); + } else { + rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } break; } @@ -227,7 +235,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } if (chan[c.chan].insChanged) { rWrite(chanOffs[c.chan]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); - rWrite(chanOffs[c.chan]+0xb4,(chan[c.chan].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[c.chan]+0xb4,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); } chan[c.chan].insChanged=false; @@ -250,14 +258,18 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].vol=c.value; DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (c.chan>6) { // ADPCM - writes.emplace(0x108+(c.chan-7),(chan[c.chan].pan<<6)|chan[c.chan].vol); + writes.emplace(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } if (c.chan>3) { // PSG if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; } - rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + if (isMuted[c.chan]) { + rWrite(0x04+c.chan,0); + } else { + rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } break; } for (int i=0; i<4; i++) { @@ -294,12 +306,12 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } if (c.chan>6) { - writes.emplace(0x108+(c.chan-7),(chan[c.chan].pan<<6)|chan[c.chan].vol); + writes.emplace(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } if (c.chan>3) break; DivInstrument* ins=parent->getIns(chan[c.chan].ins); - rWrite(chanOffs[c.chan]+0xb4,(chan[c.chan].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[c.chan]+0xb4,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -428,7 +440,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } else { chan[c.chan].psgMode&=~4; } - rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + if (isMuted[c.chan]) { + rWrite(0x04+c.chan,0); + } else { + rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } break; case DIV_CMD_AY_ENVELOPE_LOW: if (c.chan<4 || c.chan>6) break; @@ -467,6 +483,25 @@ int DivPlatformYM2610::dispatch(DivCommand c) { return 1; } +void DivPlatformYM2610::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>6) { // ADPCM + writes.emplace(0x108+(ch-7),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + return; + } + if (ch>3) { // PSG + if (isMuted[ch]) { + rWrite(0x04+ch,0); + } else { + rWrite(0x04+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); + } + return; + } + // FM + DivInstrument* ins=parent->getIns(chan[ch].ins); + rWrite(chanOffs[ch]+0xb4,(isMuted[ch]?0:(chan[ch].pan<<6))|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); +} + void DivPlatformYM2610::reset() { while (!writes.empty()) writes.pop(); fm->reset(); @@ -518,6 +553,9 @@ bool DivPlatformYM2610::keyOffAffectsArp(int ch) { int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, bool pal) { parent=p; + for (int i=0; i<13; i++) { + isMuted[i]=false; + } if (pal) { rate=500000; } else { diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 1e9fc25f2..65199efaf 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -28,6 +28,7 @@ class DivPlatformYM2610: public DivDispatch { Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), note(0), psgMode(1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {} }; Channel chan[13]; + bool isMuted[13]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -63,6 +64,7 @@ class DivPlatformYM2610: public DivDispatch { int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); bool isStereo(); bool keyOffAffectsArp(int ch); int init(DivEngine* parent, int channels, int sugRate, bool pal); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index b123520b4..b392e9507 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -20,7 +20,9 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; - if (isOutput[ins->fm.alg][ordch]) { + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { if (!opChan[ch].active || opChan[ch].insChanged) { rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); } @@ -58,7 +60,9 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(opChan[ch].ins); unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; - if (isOutput[ins->fm.alg][ordch]) { + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); } else { rWrite(baseAddr+0x40,op.tl); @@ -233,6 +237,30 @@ void DivPlatformYM2610Ext::tick() { } } +void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) { + if (ch<1) { + DivPlatformYM2610::muteChannel(ch,mute); + return; + } + if (ch>4) { + DivPlatformYM2610::muteChannel(ch-3,mute); + return; + } + isOpMuted[ch-1]=mute; + + int ordch=orderedOps[ch]; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } +} + void DivPlatformYM2610Ext::reset() { DivPlatformYM2610::reset(); @@ -252,6 +280,9 @@ bool DivPlatformYM2610Ext::keyOffAffectsArp(int ch) { int DivPlatformYM2610Ext::init(DivEngine* parent, int channels, int sugRate, bool pal) { DivPlatformYM2610::init(parent,channels,sugRate,pal); + for (int i=0; i<4; i++) { + isOpMuted[i]=false; + } reset(); return 16; diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index dd8942d7f..b2050a1df 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -14,10 +14,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} }; OpChannel opChan[4]; + bool isOpMuted[4]; public: int dispatch(DivCommand c); void reset(); void tick(); + void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); int init(DivEngine* parent, int channels, int sugRate, bool pal); void quit(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index cbc9fd2c1..b891b175c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -782,9 +782,16 @@ void FurnaceGUI::drawPattern() { } ImGui::TableNextRow(); ImGui::TableNextColumn(); + char chanID[256]; for (int i=0; igetChannelName(i)); + snprintf(chanID,256," %s##_CH%d",e->getChannelName(i),i); + if (ImGui::Selectable(chanID,!e->isChannelMuted(i),ImGuiSelectableFlags_NoPadWithHalfSpacing)) { + e->toggleMute(i); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + e->toggleSolo(i); + } } float oneCharSize=ImGui::CalcTextSize("A").x; float lineHeight=(ImGui::GetTextLineHeight()+2*dpiScale);