From 5e41c70ed7b7b2124678e5ad03d3bebd422a9f28 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Sun, 23 Aug 2026 18:50:37 +1000 Subject: [PATCH 1/8] Pass `muted_` to instruments --- sources/Application/Instruments/I_Instrument.h | 6 ++++-- sources/Application/Instruments/MidiInstrument.cpp | 8 ++++++-- sources/Application/Instruments/MidiInstrument.h | 2 +- sources/Application/Instruments/SampleInstrument.cpp | 8 +++++--- sources/Application/Instruments/SampleInstrument.h | 2 +- sources/Application/Player/PlayerChannel.cpp | 4 +++- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/sources/Application/Instruments/I_Instrument.h b/sources/Application/Instruments/I_Instrument.h index 4a3365d4..2c4add73 100644 --- a/sources/Application/Instruments/I_Instrument.h +++ b/sources/Application/Instruments/I_Instrument.h @@ -23,8 +23,10 @@ class I_Instrument:public VariableContainer, public Observable { virtual bool Init()=0 ; - // Start & stop the instument - virtual bool Start(int channel,unsigned char note,bool retrigger=true)=0 ; + // Start & stop the instument + // flags & 1 indicates retriggers (default) + // flags & 2 indicates muting (i.e. no trigger) + virtual bool Start(int channel,unsigned char note,int flags=1)=0 ; virtual void Stop(int channel)=0 ; // Engine playback start callback diff --git a/sources/Application/Instruments/MidiInstrument.cpp b/sources/Application/Instruments/MidiInstrument.cpp index dfdc2372..0cf408ad 100644 --- a/sources/Application/Instruments/MidiInstrument.cpp +++ b/sources/Application/Instruments/MidiInstrument.cpp @@ -39,7 +39,11 @@ void MidiInstrument::OnStart() { tableState_.Reset() ; } ; -bool MidiInstrument::Start(int c,unsigned char note,bool retrigger) { +bool MidiInstrument::Start(int c, unsigned char note, int flags) { + bool muted = flags & 2; + + if (muted) + return false; first_[c]=true ; lastNote_[c]=note ; @@ -70,7 +74,7 @@ bool MidiInstrument::Start(int c,unsigned char note,bool retrigger) { retrig_=false ; return true ; -} ; +}; void MidiInstrument::Stop(int c) { diff --git a/sources/Application/Instruments/MidiInstrument.h b/sources/Application/Instruments/MidiInstrument.h index 4490d621..0ec98c0c 100644 --- a/sources/Application/Instruments/MidiInstrument.h +++ b/sources/Application/Instruments/MidiInstrument.h @@ -26,7 +26,7 @@ class MidiInstrument:public I_Instrument { virtual bool Init() ; // Start & stop the instument - virtual bool Start(int channel,unsigned char note,bool retrigger=true) ; + virtual bool Start(int channel, unsigned char note, int flags = 1); virtual void Stop(int channel) ; // size refers to the number of samples diff --git a/sources/Application/Instruments/SampleInstrument.cpp b/sources/Application/Instruments/SampleInstrument.cpp index 33aca908..7b07ff04 100644 --- a/sources/Application/Instruments/SampleInstrument.cpp +++ b/sources/Application/Instruments/SampleInstrument.cpp @@ -166,9 +166,10 @@ void SampleInstrument::OnStart() { tableState_.Reset() ; } ; -bool SampleInstrument::Start(int channel,unsigned char midinote,bool cleanstart) -{ - // Look if we're dirty & need to update this instrument's data +bool SampleInstrument::Start(int channel, unsigned char midinote, int flags) { + bool cleanstart = flags & 1; + + // Look if we're dirty & need to update this instrument's data if (dirty_) { @@ -273,6 +274,7 @@ bool SampleInstrument::Start(int channel,unsigned char midinote,bool cleanstart) }; rp->baseSpeed_ = fl2fp((freq * length) / driverRate); rp->rendFirst_ = rp->rendLoopStart_; + if (cleanstart) { rp->position_ = float(rp->rendFirst_); } diff --git a/sources/Application/Instruments/SampleInstrument.h b/sources/Application/Instruments/SampleInstrument.h index eadefd43..ab14060a 100644 --- a/sources/Application/Instruments/SampleInstrument.h +++ b/sources/Application/Instruments/SampleInstrument.h @@ -59,7 +59,7 @@ class SampleInstrument: public I_Instrument,I_Observer { virtual ~SampleInstrument() ; // I_Instrument implementation virtual bool Init() ; - virtual bool Start(int channel,unsigned char note,bool trigger=true) ; + virtual bool Start(int channel, unsigned char note, int flags = 1); virtual void Stop(int channel) ; virtual bool Render(int channel,fixed *buffer,int size,bool updateTick) ; virtual bool IsInitialized() ; diff --git a/sources/Application/Player/PlayerChannel.cpp b/sources/Application/Player/PlayerChannel.cpp index 7efb660e..d825c6bf 100644 --- a/sources/Application/Player/PlayerChannel.cpp +++ b/sources/Application/Player/PlayerChannel.cpp @@ -29,7 +29,9 @@ void PlayerChannel::StartInstrument(I_Instrument *instr,unsigned char note,bool if (instr_) { StopInstrument() ; } - if (instr->Start(index_,note,trigger)) { // note could be refused coz it's out of the keymap + /* The note will not trigger if it is out the keymap or, in the case of a + MIDI instrument, if the channel is muted. */ + if (instr->Start(index_,note, trigger | (muted_ << 1))) { instr_=instr ; } else { instr_=0 ; From d79a2d7067ea1d6c73684e9df57c752dd23293c7 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 24 Aug 2026 17:19:14 +1000 Subject: [PATCH 2/8] Keep track of muting in rendering --- .../Application/Instruments/I_Instrument.h | 6 +- .../Instruments/MidiInstrument.cpp | 31 +++++-- .../Application/Instruments/MidiInstrument.h | 7 +- .../Instruments/SampleInstrument.cpp | 86 +++++++++---------- .../Instruments/SampleInstrument.h | 2 +- sources/Application/Player/PlayerChannel.cpp | 3 +- 6 files changed, 76 insertions(+), 59 deletions(-) diff --git a/sources/Application/Instruments/I_Instrument.h b/sources/Application/Instruments/I_Instrument.h index 2c4add73..4b1089f0 100644 --- a/sources/Application/Instruments/I_Instrument.h +++ b/sources/Application/Instruments/I_Instrument.h @@ -35,8 +35,10 @@ class I_Instrument:public VariableContainer, public Observable { // size refers to the number of samples // should always fill interleaved stereo / 16bit - - virtual bool Render(int channel,fixed *buffer,int size,bool updateTick)=0 ; + + // flags & 1 indicates 'updateTick'. + // flags & 2 indicated muting (i.e. stop notes) + virtual bool Render(int channel,fixed *buffer,int size,int flags)=0 ; virtual bool IsInitialized()=0 ; diff --git a/sources/Application/Instruments/MidiInstrument.cpp b/sources/Application/Instruments/MidiInstrument.cpp index 0cf408ad..c948b376 100644 --- a/sources/Application/Instruments/MidiInstrument.cpp +++ b/sources/Application/Instruments/MidiInstrument.cpp @@ -42,11 +42,14 @@ void MidiInstrument::OnStart() { bool MidiInstrument::Start(int c, unsigned char note, int flags) { bool muted = flags & 2; - if (muted) + if (muted) { + muted_[c] = true; return false; + } - first_[c]=true ; - lastNote_[c]=note ; + first_[c] = true; + muted_[c] = false; + lastNote_[c]=note ; Variable *v=FindVariable(MIP_CHANNEL) ; int channel=v->GetInt() ; @@ -77,7 +80,9 @@ bool MidiInstrument::Start(int c, unsigned char note, int flags) { }; void MidiInstrument::Stop(int c) { - + if (muted_[c]) + return; + Variable *v=FindVariable(MIP_CHANNEL) ; int channel=v->GetInt() ; @@ -95,13 +100,22 @@ void MidiInstrument::SetChannel(int channel) { v->SetInt(channel) ; } ; -bool MidiInstrument::Render(int channel,fixed *buffer,int size,bool updateTick) { +bool MidiInstrument::Render(int channel, fixed *buffer, int size, int flags) { + bool mute = flags & 2; + + if(mute && !muted_[channel]) + { + Stop(channel); + muted_[channel]++; + return false; + } // We do it here so we have the opportunity to send some command before Variable *v=FindVariable(MIP_CHANNEL) ; int mchannel=v->GetInt() ; - if (first_[channel]) { + + if (first_[channel]) { // send note @@ -114,7 +128,8 @@ bool MidiInstrument::Render(int channel,fixed *buffer,int size,bool updateTick) first_[channel]=false ; } - if (remainingTicks_>0) { + + if (remainingTicks_>0) { remainingTicks_-- ; if (remainingTicks_==0) { if (!retrig_) { @@ -133,7 +148,7 @@ bool MidiInstrument::Render(int channel,fixed *buffer,int size,bool updateTick) } ; } ; } ; - return false ; + return false; }; bool MidiInstrument::IsInitialized() { diff --git a/sources/Application/Instruments/MidiInstrument.h b/sources/Application/Instruments/MidiInstrument.h index 0ec98c0c..8313b11a 100644 --- a/sources/Application/Instruments/MidiInstrument.h +++ b/sources/Application/Instruments/MidiInstrument.h @@ -31,8 +31,8 @@ class MidiInstrument:public I_Instrument { // size refers to the number of samples // should always fill interleaved stereo / 16bit - virtual bool Render(int channel,fixed *buffer,int size,bool updateTick) ; - virtual void ProcessCommand(int channel,FourCC cc,ushort value) ; + virtual bool Render(int channel, fixed *buffer, int size, int flags); + virtual void ProcessCommand(int channel,FourCC cc,ushort value) ; virtual bool IsInitialized() ; @@ -65,8 +65,9 @@ class MidiInstrument:public I_Instrument { char velocity_; TableSaveState tableState_ ; bool first_[SONG_CHANNEL_COUNT] ; + bool muted_[SONG_CHANNEL_COUNT]; - static MidiService* svc_ ; + static MidiService *svc_; } ; #endif diff --git a/sources/Application/Instruments/SampleInstrument.cpp b/sources/Application/Instruments/SampleInstrument.cpp index 7b07ff04..ffa7ad93 100644 --- a/sources/Application/Instruments/SampleInstrument.cpp +++ b/sources/Application/Instruments/SampleInstrument.cpp @@ -454,46 +454,48 @@ void SampleInstrument::updateFeedback(renderParams *rp) { // Size in samples -bool SampleInstrument::Render(int channel,fixed *buffer,int size,bool updateTick) { +bool SampleInstrument::Render(int channel, fixed *buffer, int size, int flags) { - bool somethingToMix=false ; + bool somethingToMix = false; + bool updateTick = flags & 1; - // Get Current render parameters - - renderParams *rp=renderParams_+channel ; - lastMidiNote_[channel]=rp->midiNote_ ; - bool *rpFinished=&(rp->finished_) ; + // Get Current render parameters - if (source_) { + renderParams *rp = renderParams_ + channel; + lastMidiNote_[channel] = rp->midiNote_; + bool *rpFinished = &(rp->finished_); - if (*rpFinished) return false ; - - // clear the fixed point buffer + if (source_) { - SYS_MEMSET(buffer,0,size*2*sizeof(fixed)) ; + if (*rpFinished) + return false; + // clear the fixed point buffer - bool hasUpdaters=!(rp->activeUpdaters_.empty()) ; + SYS_MEMSET(buffer, 0, size * 2 * sizeof(fixed)); - int filterMix=filterMix_->GetInt() ; - FilterMode filterMode=(FilterMode)filterMode_->GetInt() ; - bool filterBoost=(filterMode==FM_SCREAM) ; - bool bassyFilter=(filterMode==FM_BASSY) ; + bool hasUpdaters = !(rp->activeUpdaters_.empty()); - // Be sure filters are properly initialized + int filterMix = filterMix_->GetInt(); + FilterMode filterMode = (FilterMode)filterMode_->GetInt(); + bool filterBoost = (filterMode == FM_SCREAM); + bool bassyFilter = (filterMode == FM_BASSY); - set_filter(channel,FLT_LOWPASS,rp->cutoff_,rp->reso_,filterMix,bassyFilter); + // Be sure filters are properly initialized - filter_t* flt =get_filter(channel) ; - bool filtering=(rp->cutoff_reso_>i2fp(0)) ; + set_filter(channel, FLT_LOWPASS, rp->cutoff_, rp->reso_, filterMix, + bassyFilter); - // Process tick-level updates - - if (updateTick) { + filter_t *flt = get_filter(channel); + bool filtering = (rp->cutoff_ < i2fp(1)) || (rp->reso_ > i2fp(0)); + + // Process tick-level updates - if (hasUpdaters) { + if (updateTick) { - doTickUpdate(channel) ; + if (hasUpdaters) { + + doTickUpdate(channel); struct RUParams rup ; rup.cutOffset_=rup.resOffset_=rup.volumeOffset_=rup.panOffset_=0 ; @@ -509,11 +511,11 @@ bool SampleInstrument::Render(int channel,fixed *buffer,int size,bool updateTick rp->volume_=rp->baseVolume_+rup.volumeOffset_ ; rp->speed_=fp_mul(rp->baseSpeed_,rup.speedOffset_) ; rp->pan_=rp->basePan_+rup.panOffset_ ; - } + } + + // Process retrig - // Process retrig - - if (rp->retrig_) { + if (rp->retrig_) { if (rp->retrigCount_==0) { int ticks=rp->retrigOffset_-rp->retrigLoop_ ; long offset=long(ticks*SyncMaster::GetInstance()->GetTickSampleCount()) ; @@ -525,20 +527,17 @@ bool SampleInstrument::Render(int channel,fixed *buffer,int size,bool updateTick } rp->retrigCount_-- ; } ; + } + // Get additional parameters from variables - } - - // Get additional parameters from variables - - - // Crush + // Crush - int shift=16-rp->crush_; - fixed mask=0xFFFFFFFF ; - if (shift !=0) { - mask<<=FIXED_SHIFT+shift ; - } + int shift = 16 - rp->crush_; + fixed mask = 0xFFFFFFFF; + if (shift != 0) { + mask <<= FIXED_SHIFT + shift; + } // Crush vol @@ -990,9 +989,8 @@ bool SampleInstrument::Render(int channel,fixed *buffer,int size,bool updateTick somethingToMix=true ; } - return somethingToMix ; -} ; - + return somethingToMix; +}; void SampleInstrument::AssignSample(int i) { diff --git a/sources/Application/Instruments/SampleInstrument.h b/sources/Application/Instruments/SampleInstrument.h index ab14060a..f481327e 100644 --- a/sources/Application/Instruments/SampleInstrument.h +++ b/sources/Application/Instruments/SampleInstrument.h @@ -61,7 +61,7 @@ class SampleInstrument: public I_Instrument,I_Observer { virtual bool Init() ; virtual bool Start(int channel, unsigned char note, int flags = 1); virtual void Stop(int channel) ; - virtual bool Render(int channel,fixed *buffer,int size,bool updateTick) ; + virtual bool Render(int channel, fixed *buffer, int size, int flags); virtual bool IsInitialized() ; virtual bool IsEmpty() ; diff --git a/sources/Application/Player/PlayerChannel.cpp b/sources/Application/Player/PlayerChannel.cpp index d825c6bf..c8fca827 100644 --- a/sources/Application/Player/PlayerChannel.cpp +++ b/sources/Application/Player/PlayerChannel.cpp @@ -48,7 +48,8 @@ void PlayerChannel::StopInstrument() { bool PlayerChannel::Render(fixed *buffer,int samplecount) { if (instr_) { bool tableSlice=SyncMaster::GetInstance()->TableSlice() ; - bool status=instr_->Render(index_,buffer,samplecount,tableSlice) ; + bool status = instr_->Render(index_, buffer, samplecount, + tableSlice | (muted_ << 1)); if (status && !muted_) { // Apply HPF if enabled if (hpfMode_ != 0) { From 9d060e2a7fb2e43fc3f40791d3c56b07f9e2b45b Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 25 Aug 2026 20:43:25 +1000 Subject: [PATCH 3/8] Minor refactor --- .../Instruments/MidiInstrument.cpp | 95 ++++++++++--------- .../Application/Instruments/MidiInstrument.h | 45 +++++---- 2 files changed, 74 insertions(+), 66 deletions(-) diff --git a/sources/Application/Instruments/MidiInstrument.cpp b/sources/Application/Instruments/MidiInstrument.cpp index c948b376..54081633 100644 --- a/sources/Application/Instruments/MidiInstrument.cpp +++ b/sources/Application/Instruments/MidiInstrument.cpp @@ -65,11 +65,8 @@ bool MidiInstrument::Start(int c, unsigned char note, int flags) { // send initial volume for this midi channel v=FindVariable(MIP_VOLUME) ; - msg.status_=MIDI_CC+channel ; - msg.data1_=7 ; - msg.data2_ = floor(static_cast(v->GetInt()+0.99)/2) ; - svc_->QueueMessage(msg) ; - + unsigned char volume = floor(static_cast(v->GetInt() + 0.99) / 2); + SetVolume(c, volume); // store initial velocity velocity_ = msg.data2_; @@ -86,12 +83,8 @@ void MidiInstrument::Stop(int c) { Variable *v=FindVariable(MIP_CHANNEL) ; int channel=v->GetInt() ; - MidiMessage msg; - msg.status_=MIDI_NOTE_OFF+channel ; - msg.data1_=lastNote_[c] ; - msg.data2_=0x00 ; - svc_->QueueMessage(msg) ; - playing_=false ; + QueueNote(false, channel, lastNote_[c], 0); + playing_=false ; } ; @@ -117,16 +110,9 @@ bool MidiInstrument::Render(int channel, fixed *buffer, int size, int flags) { if (first_[channel]) { - // send note - - MidiMessage msg ; - - msg.status_=MIDI_NOTE_ON+mchannel ; - msg.data1_=lastNote_[channel] ; - msg.data2_ = velocity_; - svc_->QueueMessage(msg) ; - - first_[channel]=false ; + // send note + QueueNote(true, mchannel, lastNote_[channel], velocity_); + first_[channel] = false; } if (remainingTicks_>0) { @@ -135,16 +121,9 @@ bool MidiInstrument::Render(int channel, fixed *buffer, int size, int flags) { if (!retrig_) { Stop(channel) ; } else { - MidiMessage msg ; - remainingTicks_=retrigLoop_ ; - msg.status_=MIDI_NOTE_OFF+mchannel ; - msg.data1_=lastNote_[channel] ; - msg.data2_=0x00 ; - svc_->QueueMessage(msg) ; - msg.status_=MIDI_NOTE_ON+mchannel ; - msg.data1_=lastNote_[channel] ; - msg.data2_=0x7F ; - svc_->QueueMessage(msg) ; + remainingTicks_ = retrigLoop_; + QueueNote(false, mchannel, lastNote_[channel], 0); + QueueNote(true, mchannel, lastNote_[channel], velocity_); } ; } ; } ; @@ -180,31 +159,23 @@ void MidiInstrument::ProcessCommand(int channel,FourCC cc,ushort value) { case I_CMD_VOLM: { - MidiMessage msg ; - msg.status_=MIDI_CC+mchannel ; - msg.data1_= 7; - msg.data2_ = floor(static_cast(value / 2)); - svc_->QueueMessage(msg) ; + unsigned char volume = floor(static_cast(value / 2)); + SetVolume(mchannel, volume); } ; break ; case I_CMD_MDCC: { - MidiMessage msg ; - msg.status_=MIDI_CC+mchannel ; - msg.data1_=(value&0x7F00)>>8 ; - msg.data2_=(value&0x7F) ; - svc_->QueueMessage(msg) ; + unsigned char id = (value & 0x7F00) >> 8; + unsigned char value = value & 0x7F; + SetCC(mchannel, id, value); }; break ; case I_CMD_MDPG: { - MidiMessage msg ; - msg.status_=MIDI_PRG+mchannel ; - msg.data1_=(value&0x7F) ; - msg.data2_=MidiMessage::UNUSED_BYTE ; - svc_->QueueMessage(msg) ; + unsigned char id = value & 0x7F; + SetPRG(mchannel, id); }; break ; } @@ -235,3 +206,35 @@ void MidiInstrument::SetTableState(TableSaveState &state) { memcpy(tableState_.hopCount_,state.hopCount_,sizeof(uchar)*TABLE_STEPS*3) ; memcpy(tableState_.position_,state.position_,sizeof(int)*3) ; } ; + +void MidiInstrument::QueueNote(bool note_on, int channel, unsigned char note, unsigned char velocity) +{ + MidiMessage msg; + msg.status_ = channel + note_on ? MIDI_NOTE_ON : MIDI_NOTE_OFF; + msg.data1_ = note; + msg.data2_ = note_on * velocity; + + svc_->QueueMessage(msg); +} + +void MidiInstrument::SetVolume(int channel, unsigned char volume) { + SetCC(channel, 7, volume); +} + +void MidiInstrument::SetCC(int channel, unsigned char id, unsigned char value) { + MidiMessage msg; + + msg.status_=MIDI_CC+channel ; + msg.data1_=id ; + msg.data2_ = value ; + svc_->QueueMessage(msg) ; +} + +void MidiInstrument::SetPRG(int channel, unsigned char id) { + MidiMessage msg; + + msg.status_=MIDI_PRG+channel ; + msg.data1_=id ; + msg.data2_ = MidiMessage::UNUSED_BYTE ; + svc_->QueueMessage(msg) ; +} diff --git a/sources/Application/Instruments/MidiInstrument.h b/sources/Application/Instruments/MidiInstrument.h index 8313b11a..2179cf24 100644 --- a/sources/Application/Instruments/MidiInstrument.h +++ b/sources/Application/Instruments/MidiInstrument.h @@ -48,26 +48,31 @@ class MidiInstrument:public I_Instrument { virtual int GetTable() ; virtual bool GetTableAutomation(); - virtual void GetTableState(TableSaveState &state) ; - virtual void SetTableState(TableSaveState &state) ; - - // external parameter list - - void SetChannel(int i); - - private: - char name_[20] ; // Instrument name - int lastNote_[SONG_CHANNEL_COUNT] ; - int remainingTicks_ ; - bool playing_ ; - bool retrig_ ; - int retrigLoop_ ; - char velocity_; - TableSaveState tableState_ ; - bool first_[SONG_CHANNEL_COUNT] ; - bool muted_[SONG_CHANNEL_COUNT]; - - static MidiService *svc_; + virtual void GetTableState(TableSaveState &state); + virtual void SetTableState(TableSaveState &state); + virtual void QueueNote(bool note_on, int channel, unsigned char note, + unsigned char velocity); + virtual void SetVolume(int channel, unsigned char volume); + virtual void SetCC(int channel, unsigned char id, unsigned char value); + virtual void SetPRG(int channel, unsigned char id); + + // external parameter list + + void SetChannel(int i); + + private: + char name_[20]; // Instrument name + int lastNote_[SONG_CHANNEL_COUNT]; + int remainingTicks_; + bool playing_; + bool retrig_; + int retrigLoop_; + char velocity_; + TableSaveState tableState_; + bool first_[SONG_CHANNEL_COUNT]; + bool muted_[SONG_CHANNEL_COUNT]; + + static MidiService *svc_; } ; #endif From dfe6f405f7b571f17e8a30b41cc3775f49130476 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 1 Sep 2026 22:41:00 +1000 Subject: [PATCH 4/8] Add MCHD command --- docs/wiki/What-is-LittlePiggyTracker.md | 5 + .../Application/Instruments/CommandList.cpp | 35 +--- sources/Application/Instruments/CommandList.h | 1 + .../Instruments/MidiInstrument.cpp | 175 +++++++++++------- .../Application/Instruments/MidiInstrument.h | 6 +- sources/Application/Utils/HelpLegend.h | 5 + sources/Foundation/T_SimpleList.cpp | 2 +- 7 files changed, 133 insertions(+), 96 deletions(-) diff --git a/docs/wiki/What-is-LittlePiggyTracker.md b/docs/wiki/What-is-LittlePiggyTracker.md index 1a3b4fe4..154eaa61 100644 --- a/docs/wiki/What-is-LittlePiggyTracker.md +++ b/docs/wiki/What-is-LittlePiggyTracker.md @@ -546,6 +546,11 @@ don't forget trying to combine it with complex hop structure ! - LPOF is absolute - you can't trigger a note with the LPOF, it has to be executed after a sample is playing - every time you trigger a sample LPOF is set back to the instrument parameters + +## MCHD aabb + +Plays the notes aa and bb semitones higher than the last played note (modulo 128). Note that the value `00` is ignored. The chords tones will last as long as the root note, i.e. until another note is played on the channel. + ## MDCC aabb **Sends a MIDI “continuous control” message. aa is the control number and bb is the value. It will be sent on the MIDI channel of the currently running instrument.** diff --git a/sources/Application/Instruments/CommandList.cpp b/sources/Application/Instruments/CommandList.cpp index 812385c2..a740c5dd 100644 --- a/sources/Application/Instruments/CommandList.cpp +++ b/sources/Application/Instruments/CommandList.cpp @@ -1,35 +1,12 @@ #include "CommandList.h" -static FourCC _all[]= { - I_CMD_NONE, - I_CMD_ARPG, - I_CMD_CRSH, - I_CMD_DLAY, - I_CMD_FBMX, - I_CMD_FBTN, - I_CMD_FCUT, - I_CMD_FLTR, - I_CMD_FRES, - I_CMD_GROV, - I_CMD_HOP, - I_CMD_IRTG, - I_CMD_KILL, - I_CMD_LEGA, - I_CMD_LPOF, - I_CMD_MDCC, - I_CMD_MDPG, - I_CMD_MVEL, - I_CMD_PAN_, - I_CMD_PFIN, - I_CMD_PLOF, - I_CMD_PTCH, - I_CMD_RTRG, - I_CMD_STOP, - I_CMD_TABL, - I_CMD_TMPO, - I_CMD_VOLM -} ; +static FourCC _all[] = { + I_CMD_NONE, I_CMD_ARPG, I_CMD_CRSH, I_CMD_DLAY, I_CMD_FBMX, I_CMD_FBTN, + I_CMD_FCUT, I_CMD_FLTR, I_CMD_FRES, I_CMD_GROV, I_CMD_HOP, I_CMD_IRTG, + I_CMD_KILL, I_CMD_LEGA, I_CMD_LPOF, I_CMD_MCHD, I_CMD_MDCC, I_CMD_MDPG, + I_CMD_MVEL, I_CMD_PAN_, I_CMD_PFIN, I_CMD_PLOF, I_CMD_PTCH, I_CMD_RTRG, + I_CMD_STOP, I_CMD_TABL, I_CMD_TMPO, I_CMD_VOLM}; int CommandList::GetCount() { return sizeof(_all) / sizeof(FourCC); } diff --git a/sources/Application/Instruments/CommandList.h b/sources/Application/Instruments/CommandList.h index 8201a72a..cd83a2a5 100644 --- a/sources/Application/Instruments/CommandList.h +++ b/sources/Application/Instruments/CommandList.h @@ -14,6 +14,7 @@ #define I_CMD_LEGA MAKE_FOURCC('L','E','G','A') #define I_CMD_RTRG MAKE_FOURCC('R','T','R','G') #define I_CMD_TMPO MAKE_FOURCC('T','M','P','O') +#define I_CMD_MCHD MAKE_FOURCC('M', 'C', 'H', 'D') #define I_CMD_MDCC MAKE_FOURCC('M','D','C','C') #define I_CMD_MDPG MAKE_FOURCC('M','D','P','G') #define I_CMD_MVEL MAKE_FOURCC('M','V','E','L') diff --git a/sources/Application/Instruments/MidiInstrument.cpp b/sources/Application/Instruments/MidiInstrument.cpp index 54081633..9dc9fcc7 100644 --- a/sources/Application/Instruments/MidiInstrument.cpp +++ b/sources/Application/Instruments/MidiInstrument.cpp @@ -14,8 +14,11 @@ MidiInstrument::MidiInstrument() { svc_=MidiService::GetInstance() ; }; - Variable *v=new Variable("channel",MIP_CHANNEL,0) ; - Insert(v) ; + for (int i = 0; i < SONG_CHANNEL_COUNT; i++) + lastNote_[i] = new T_SimpleList(true); + + Variable *v = new Variable("channel", MIP_CHANNEL, 0); + Insert(v) ; v=new Variable("note length",MIP_NOTELENGTH,0) ; Insert(v) ; v=new Variable("volume",MIP_VOLUME,255) ; @@ -40,52 +43,63 @@ void MidiInstrument::OnStart() { } ; bool MidiInstrument::Start(int c, unsigned char note, int flags) { + // Take note of requested note regardless of whethere it is sounded. + rootNote_[c] = note; + bool muted = flags & 2; if (muted) { muted_[c] = true; - return false; + return true; } + // Not muted, thus the note should be queued. + unsigned char *last_note = new unsigned char; + *last_note = note; + + lastNote_[c]->Insert(last_note); + first_[c] = true; muted_[c] = false; - lastNote_[c]=note ; - Variable *v=FindVariable(MIP_CHANNEL) ; - int channel=v->GetInt() ; + Variable *v = FindVariable(MIP_CHANNEL); + int channel=v->GetInt() ; - v=FindVariable(MIP_NOTELENGTH) ; - remainingTicks_=v->GetInt() ; + v = FindVariable(MIP_NOTELENGTH); + remainingTicks_=v->GetInt() ; if (remainingTicks_==0) { remainingTicks_=-1 ; } - MidiMessage msg ; - - // send initial volume for this midi channel + // send initial volume for this midi channel - v=FindVariable(MIP_VOLUME) ; + v = FindVariable(MIP_VOLUME); unsigned char volume = floor(static_cast(v->GetInt() + 0.99) / 2); - SetVolume(c, volume); + SetVolume(channel, volume); - // store initial velocity - velocity_ = msg.data2_; + // store initial velocity + velocity_ = volume; playing_=true ; retrig_=false ; - return true ; + return true; }; void MidiInstrument::Stop(int c) { if (muted_[c]) return; - - Variable *v=FindVariable(MIP_CHANNEL) ; - int channel=v->GetInt() ; - QueueNote(false, channel, lastNote_[c], 0); - playing_=false ; + Variable *v = FindVariable(MIP_CHANNEL); + int channel = v->GetInt(); + + IteratorPtr it(lastNote_[c]->GetIterator()); + for (it->Begin(); !it->IsDone(); it->Next()) { + unsigned char note = it->CurrentItem(); + QueueNote(false, channel, note, 0); + } + lastNote_[c]->Empty(); + playing_ = false; } ; void MidiInstrument::SetChannel(int channel) { @@ -95,37 +109,47 @@ void MidiInstrument::SetChannel(int channel) { bool MidiInstrument::Render(int channel, fixed *buffer, int size, int flags) { bool mute = flags & 2; - - if(mute && !muted_[channel]) - { - Stop(channel); - muted_[channel]++; - return false; - } - // We do it here so we have the opportunity to send some command before + if (mute && !muted_[channel]) { + Stop(channel); + muted_[channel]=true; + return false; + } else if (!mute && muted_[channel]) + muted_[channel] = false; - Variable *v=FindVariable(MIP_CHANNEL) ; - int mchannel=v->GetInt() ; + // We do it here so we have the opportunity to send some command before + + Variable *v = FindVariable(MIP_CHANNEL); + int mchannel=v->GetInt() ; if (first_[channel]) { - // send note - QueueNote(true, mchannel, lastNote_[channel], velocity_); + // send note(s) + IteratorPtr it(lastNote_[channel]->GetIterator()); + for (it->Begin(); !it->IsDone(); it->Next()) { + unsigned char note = it->CurrentItem(); + QueueNote(true, mchannel, note, velocity_); + } + first_[channel] = false; - } + } if (remainingTicks_>0) { - remainingTicks_-- ; - if (remainingTicks_==0) { - if (!retrig_) { - Stop(channel) ; - } else { + remainingTicks_--; + if (remainingTicks_ == 0) { + if (!retrig_) { + Stop(channel); + } else { remainingTicks_ = retrigLoop_; - QueueNote(false, mchannel, lastNote_[channel], 0); - QueueNote(true, mchannel, lastNote_[channel], velocity_); - } ; - } ; + IteratorPtr it( + lastNote_[channel]->GetIterator()); + for (it->Begin(); !it->IsDone(); it->Next()) { + unsigned char note = it->CurrentItem(); + QueueNote(false, mchannel, note, 0); + QueueNote(true, mchannel, note, velocity_); + } + }; + }; } ; return false; }; @@ -134,8 +158,11 @@ bool MidiInstrument::IsInitialized() { return true ; // Always initialised } ; -void MidiInstrument::ProcessCommand(int channel,FourCC cc,ushort value) { - +void MidiInstrument::ProcessCommand(int channel, FourCC cc, ushort value) { + // Do not process commands if muted. + if (muted_[channel]) + return; + Variable *v=FindVariable(MIP_CHANNEL) ; int mchannel=v->GetInt() ; @@ -161,29 +188,47 @@ void MidiInstrument::ProcessCommand(int channel,FourCC cc,ushort value) { { unsigned char volume = floor(static_cast(value / 2)); SetVolume(mchannel, volume); - } ; - break ; + }; break; + + case I_CMD_MCHD: { + if (muted_[channel]) + break; + + int notes[] = {value & 0xFF, value >> 8}; + for (unsigned int i = 0; i < 2; i++) { + unsigned char *chord_tone = new unsigned char; + *chord_tone = (rootNote_[channel] + notes[i]) % 128; + + // Make sure note hasn't already been triggered. + bool tone_sounded = *chord_tone == rootNote_[channel]; + IteratorPtr it( + lastNote_[channel]->GetIterator()); + for (it->Begin(); !it->IsDone(); it->Next()) + tone_sounded |= *chord_tone == it->CurrentItem(); + + if (!tone_sounded) { + QueueNote(true, mchannel, *chord_tone, velocity_); + lastNote_[channel]->Insert(chord_tone); + } + } + } break; - case I_CMD_MDCC: - { + case I_CMD_MDCC: { unsigned char id = (value & 0x7F00) >> 8; - unsigned char value = value & 0x7F; + value &= 0x7F; SetCC(mchannel, id, value); - }; - break ; + }; break; - case I_CMD_MDPG: - { + case I_CMD_MDPG: { unsigned char id = value & 0x7F; SetPRG(mchannel, id); - }; - break ; - } -} ; + }; break; + } +}; const char *MidiInstrument::GetName() { - Variable *v=FindVariable(MIP_CHANNEL) ; - sprintf(name_,"MIDI CH %2.2d",v->GetInt()+1) ; + Variable *v = FindVariable(MIP_CHANNEL); + sprintf(name_, "MIDI CH %2.2d", v->GetInt() + 1); return name_ ; } @@ -209,12 +254,12 @@ void MidiInstrument::SetTableState(TableSaveState &state) { void MidiInstrument::QueueNote(bool note_on, int channel, unsigned char note, unsigned char velocity) { - MidiMessage msg; - msg.status_ = channel + note_on ? MIDI_NOTE_ON : MIDI_NOTE_OFF; - msg.data1_ = note; - msg.data2_ = note_on * velocity; + MidiMessage msg; + msg.status_ = channel + (note_on ? MIDI_NOTE_ON : MIDI_NOTE_OFF); + msg.data1_ = note; + msg.data2_ = note_on * velocity; - svc_->QueueMessage(msg); + svc_->QueueMessage(msg); } void MidiInstrument::SetVolume(int channel, unsigned char volume) { diff --git a/sources/Application/Instruments/MidiInstrument.h b/sources/Application/Instruments/MidiInstrument.h index 2179cf24..0ca36df7 100644 --- a/sources/Application/Instruments/MidiInstrument.h +++ b/sources/Application/Instruments/MidiInstrument.h @@ -62,7 +62,11 @@ class MidiInstrument:public I_Instrument { private: char name_[20]; // Instrument name - int lastNote_[SONG_CHANNEL_COUNT]; + T_SimpleList + *lastNote_[SONG_CHANNEL_COUNT]; // List of played note(s). + int rootNote_[SONG_CHANNEL_COUNT]; + // Keep track of last requested note even if not played. This way, + // 'root notes' of chords are stored even when the track is muted. int remainingTicks_; bool playing_; bool retrig_; diff --git a/sources/Application/Utils/HelpLegend.h b/sources/Application/Utils/HelpLegend.h index 6551f692..caec8e72 100644 --- a/sources/Application/Utils/HelpLegend.h +++ b/sources/Application/Utils/HelpLegend.h @@ -51,6 +51,11 @@ static inline std::string* getHelpLegend(FourCC command) { result[1].assign("sets the tempo to hex"); result[2].assign("value bb"); break; + case I_CMD_MCHD: + result[0].assign("MiDiCHorD:aabb"); + result[1].assign("send notes aa and bb"); + result[2].assign("semitones higher than current note"); + break; case I_CMD_MDCC: result[0].assign("MiDiCC:aabb"); result[1].assign("CC message aa"); diff --git a/sources/Foundation/T_SimpleList.cpp b/sources/Foundation/T_SimpleList.cpp index ea29ac0d..ad360350 100644 --- a/sources/Foundation/T_SimpleList.cpp +++ b/sources/Foundation/T_SimpleList.cpp @@ -67,7 +67,7 @@ I_Iterator *T_SimpleList::GetIterator() { template I_Iterator *T_SimpleList::GetIterator(bool reverse) { - return new T_SimpleListIterator(*this,reverse) ; + return new T_SimpleListIterator(*this, reverse); } // Empties the list's content From 67ff6c901498f23725f471554cb01bfef1c32dbc Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 1 Sep 2026 23:06:20 +1000 Subject: [PATCH 5/8] Clean up help legend --- sources/Application/Utils/HelpLegend.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/Application/Utils/HelpLegend.h b/sources/Application/Utils/HelpLegend.h index caec8e72..7c832ba1 100644 --- a/sources/Application/Utils/HelpLegend.h +++ b/sources/Application/Utils/HelpLegend.h @@ -54,8 +54,8 @@ static inline std::string* getHelpLegend(FourCC command) { case I_CMD_MCHD: result[0].assign("MiDiCHorD:aabb"); result[1].assign("send notes aa and bb"); - result[2].assign("semitones higher than current note"); - break; + result[2].assign("semitones higher"); + break; case I_CMD_MDCC: result[0].assign("MiDiCC:aabb"); result[1].assign("CC message aa"); From 78a822cf9fa8a12f23ea6d272b07b6e5c1857e12 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 1 Sep 2026 23:08:56 +1000 Subject: [PATCH 6/8] Formatting --- .../Instruments/SampleInstrument.cpp | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/sources/Application/Instruments/SampleInstrument.cpp b/sources/Application/Instruments/SampleInstrument.cpp index ffa7ad93..84160e23 100644 --- a/sources/Application/Instruments/SampleInstrument.cpp +++ b/sources/Application/Instruments/SampleInstrument.cpp @@ -516,18 +516,20 @@ bool SampleInstrument::Render(int channel, fixed *buffer, int size, int flags) { // Process retrig if (rp->retrig_) { - if (rp->retrigCount_==0) { - int ticks=rp->retrigOffset_-rp->retrigLoop_ ; - long offset=long(ticks*SyncMaster::GetInstance()->GetTickSampleCount()) ; - rp->position_+=offset*fp2fl(rp->speed_) ; - if (rp->position_<0) { - rp->position_=0 ; - } ; - rp->retrigCount_=rp->retrigLoop_ ; - } - rp->retrigCount_-- ; - } ; - } + if (rp->retrigCount_ == 0) { + int ticks = rp->retrigOffset_ - rp->retrigLoop_; + long offset = + long(ticks * + SyncMaster::GetInstance()->GetTickSampleCount()); + rp->position_ += offset * fp2fl(rp->speed_); + if (rp->position_ < 0) { + rp->position_ = 0; + }; + rp->retrigCount_ = rp->retrigLoop_; + } + rp->retrigCount_-- ; + } ; + } // Get additional parameters from variables From 65db3ecfaef8eb3faadfad8e6ac6ae3a0e183c78 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 1 Sep 2026 23:27:04 +1000 Subject: [PATCH 7/8] Formatting --- sources/Application/Instruments/SampleInstrument.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/Application/Instruments/SampleInstrument.cpp b/sources/Application/Instruments/SampleInstrument.cpp index 84160e23..2fbdfd40 100644 --- a/sources/Application/Instruments/SampleInstrument.cpp +++ b/sources/Application/Instruments/SampleInstrument.cpp @@ -529,7 +529,7 @@ bool SampleInstrument::Render(int channel, fixed *buffer, int size, int flags) { } rp->retrigCount_-- ; } ; - } + } // Get additional parameters from variables From 8d7a9179eb8b3881eb280ea595391d5365053f74 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 3 Sep 2026 15:27:12 +1000 Subject: [PATCH 8/8] Update CHANGELOG and Project.h --- CHANGELOG | 4 ++++ sources/Application/Model/Project.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7145fd49..2ad2c272 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ +1.6.0-bacon19 + Fixed MIDI muting + Added MIDI chord command (MCHD) +1.6.0-bacon18 Migrate W32 to SDL2, removing need for building in vs2008 1.6.0-bacon17 diff --git a/sources/Application/Model/Project.h b/sources/Application/Model/Project.h index 7d1cd145..7f195cdd 100644 --- a/sources/Application/Model/Project.h +++ b/sources/Application/Model/Project.h @@ -21,7 +21,7 @@ #define PROJECT_NUMBER "1" #define PROJECT_RELEASE "6" -#define BUILD_COUNT "0-bacon17" +#define BUILD_COUNT "0-bacon19" #define MAX_TAP 3