channel mute/solo!

This commit is contained in:
tildearrow 2021-12-18 03:25:42 -05:00
parent c4c019e593
commit b3b66ecbdb
27 changed files with 416 additions and 48 deletions

View file

@ -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);
/**

View file

@ -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; i<chans; i++) {
if (i==chan) {
solo=true;
continue;
} else {
if (!isMuted[i]) {
solo=false;
break;
}
}
}
isBusy.lock();
if (!solo) {
for (int i=0; i<chans; i++) {
isMuted[i]=(i!=chan);
if (dispatch!=NULL) {
dispatch->muteChannel(i,isMuted[i]);
}
}
} else {
for (int i=0; i<chans; i++) {
isMuted[i]=false;
if (dispatch!=NULL) {
dispatch->muteChannel(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();

View file

@ -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);

View file

@ -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;
}

View file

@ -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.sample<parent->song.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);

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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<chans; j++) {
if (chan[j].active) {
bufL[i]+=((chan[j].pos>=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; i<chans; i++) {
chan[i].amp--;
if (chan[i].amp<0) chan[i].amp=0;
chan[i].amp-=3;
if (chan[i].amp<16) chan[i].amp=16;
if (chan[i].freqChanged) {
chan[i].freqChanged=false;
@ -29,7 +33,7 @@ void DivPlatformDummy::tick() {
int DivPlatformDummy::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
chan[c.chan].baseFreq=16.4f*pow(2.0f,((float)c.value/12.0f));
chan[c.chan].baseFreq=65.6f*pow(2.0f,((float)c.value/12.0f));
chan[c.chan].freqChanged=true;
chan[c.chan].active=true;
chan[c.chan].amp=64;
@ -49,7 +53,7 @@ int DivPlatformDummy::dispatch(DivCommand c) {
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=16.4f*pow(2.0f,((float)c.value/12.0f));
chan[c.chan].baseFreq=65.6f*pow(2.0f,((float)c.value/12.0f));
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_GET_VOLMAX:
@ -70,6 +74,9 @@ void DivPlatformDummy::reset() {
int DivPlatformDummy::init(DivEngine* p, int channels, int sugRate, bool pal) {
parent=p;
for (int i=0; i<17; i++) {
isMuted[i]=false;
}
rate=65536;
chans=channels;
reset();

View file

@ -1,6 +1,6 @@
#include "../dispatch.h"
// the dummy platform outputs square waves, interprets STD instruments and plays samples.
// the dummy platform outputs saw waves.
// used when a DivDispatch for a system is not found.
class DivPlatformDummy: public DivDispatch {
struct Channel {
@ -13,9 +13,11 @@ class DivPlatformDummy: public DivDispatch {
Channel(): freq(0), baseFreq(0), pitch(0), pos(0), active(false), freqChanged(false), vol(0), amp(64) {}
};
Channel chan[17];
bool isMuted[17];
unsigned char chans;
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
void muteChannel(int ch, bool mute);
int dispatch(DivCommand c);
void reset();
void tick();

View file

@ -24,6 +24,17 @@ void DivPlatformGB::updateWave() {
}
}
static unsigned char chanMuteMask[4]={
0xee, 0xdd, 0xbb, 0x77
};
unsigned char DivPlatformGB::procMute() {
return lastPan&(isMuted[0]?chanMuteMask[0]:0xff)
&(isMuted[1]?chanMuteMask[1]:0xff)
&(isMuted[2]?chanMuteMask[2]:0xff)
&(isMuted[3]?chanMuteMask[3]:0xff);
}
static unsigned char gbVolMap[16]={
0x00, 0x00, 0x00, 0x00,
0x60, 0x60, 0x60, 0x60,
@ -153,6 +164,11 @@ void DivPlatformGB::tick() {
}
}
void DivPlatformGB::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
rWrite(0x25,procMute());
}
int DivPlatformGB::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
@ -233,7 +249,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
lastPan&=~(0x11<<c.chan);
if (c.value==0) c.value=0x11;
lastPan|=c.value<<c.chan;
rWrite(0x25,lastPan);
rWrite(0x25,procMute());
break;
}
case DIV_CMD_LEGATO:
@ -280,8 +296,8 @@ void DivPlatformGB::reset() {
GB_set_sample_rate(gb,rate);
// enable all channels
GB_apu_write(gb,0x26,0x80);
GB_apu_write(gb,0x25,0xff);
lastPan=0xff;
GB_apu_write(gb,0x25,procMute());
}
bool DivPlatformGB::isStereo() {
@ -289,6 +305,9 @@ bool DivPlatformGB::isStereo() {
}
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, bool pal) {
for (int i=0; i<4; i++) {
isMuted[i]=false;
}
parent=p;
rate=262144;
gb=new GB_gameboy_t;

View file

@ -31,15 +31,18 @@ class DivPlatformGB: public DivDispatch {
wave(-1) {}
};
Channel chan[4];
bool isMuted[4];
unsigned char lastPan;
GB_gameboy_t* gb;
unsigned char procMute();
void updateWave();
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void reset();
void tick();
void muteChannel(int ch, bool mute);
bool isStereo();
int init(DivEngine* parent, int channels, int sugRate, bool pal);
void quit();

View file

@ -151,6 +151,16 @@ int DivPlatformGenesis::toFreq(int freq) {
}
}
void DivPlatformGenesis::muteChannel(int ch, bool mute) {
if (ch>5) {
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);

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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 {

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -782,9 +782,16 @@ void FurnaceGUI::drawPattern() {
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
char chanID[256];
for (int i=0; i<chans; i++) {
ImGui::TableNextColumn();
ImGui::Text("%s",e->getChannelName(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);