fix the way mute operates for MIDI tracks

our policy is that "mute works on outputs", which means that mute should have
no effect on what an instrument plugin does. However, MidiTrack::act_on_mute()
used to inject sustain=0 messages into the data flow, which would affect
the instrument plugin(s) in the track.

now, MidiTrack::act_on_mute() simply notifies all Delivery objects in the track
that a MIDI mute is needed via a channel mask. The Delivery objects notice this
during their ::run() method, and deliver the required MIDI events to their
output ports.

There is still a potential issue that Amp objects which notice they have been
muted also send a similar set of messages. This needs more investigation and
possibly other changes. But this commit allows a sustained note to return after
the track is muted midway through it.
This commit is contained in:
Paul Davis 2025-06-19 07:58:04 -06:00
parent 919ce6309c
commit 8b09becf1d
4 changed files with 69 additions and 15 deletions

View file

@ -30,6 +30,7 @@
#include "ardour/types.h"
#include "ardour/chan_count.h"
#include "ardour/io_processor.h"
#include "ardour/midi_buffer.h"
#include "ardour/gain_control.h"
namespace ARDOUR {
@ -89,6 +90,8 @@ public:
void run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool);
void set_midi_mute_mask (int);
/* supplemental method used with MIDI */
void flush_buffers (samplecnt_t nframes);
@ -157,6 +160,7 @@ protected:
std::shared_ptr<Amp> _amp;
gain_t target_gain ();
void maybe_merge_midi_mute (BufferSet&);
private:
bool _no_outs_cuz_we_no_monitor;
@ -176,6 +180,8 @@ private:
void output_changed (IOChange, void*);
bool _no_panner_reset;
std::atomic<int> _midi_mute_mask;
MidiBuffer _midi_mute_buffer;
};

View file

@ -65,6 +65,8 @@ Delivery::Delivery (Session& s, std::shared_ptr<IO> io, std::shared_ptr<Pannable
, _mute_master (mm)
, _rta_active (false)
, _no_panner_reset (false)
, _midi_mute_mask (0)
, _midi_mute_buffer (0)
{
if (pannable) {
bool is_send = false;
@ -74,6 +76,11 @@ Delivery::Delivery (Session& s, std::shared_ptr<IO> io, std::shared_ptr<Pannable
_display_to_user = false;
const size_t stamp_size = sizeof(samplepos_t);
const size_t etype_size = sizeof(Evoral::EventType);
const size_t mmb_size = 16 * (stamp_size + etype_size + 3);
_midi_mute_buffer.resize (mmb_size);
if (_output) {
_output->changed.connect_same_thread (*this, std::bind (&Delivery::output_changed, this, _1, _2));
}
@ -90,6 +97,8 @@ Delivery::Delivery (Session& s, std::shared_ptr<Pannable> pannable, std::shared_
, _mute_master (mm)
, _rta_active (false)
, _no_panner_reset (false)
, _midi_mute_mask (0)
, _midi_mute_buffer (0)
{
if (pannable) {
bool is_send = false;
@ -98,6 +107,10 @@ Delivery::Delivery (Session& s, std::shared_ptr<Pannable> pannable, std::shared_
}
_display_to_user = false;
const size_t stamp_size = sizeof(samplepos_t);
const size_t etype_size = sizeof(Evoral::EventType);
const size_t mmb_size = 16 * (stamp_size + etype_size + 3);
_midi_mute_buffer.resize (mmb_size);
if (_output) {
_output->changed.connect_same_thread (*this, std::bind (&Delivery::output_changed, this, _1, _2));
@ -267,6 +280,38 @@ Delivery::configure_io (ChanCount in, ChanCount out)
return true;
}
void
Delivery::maybe_merge_midi_mute (BufferSet& bufs)
{
if (bufs.available().n_midi()) {
int mask = _midi_mute_mask.load(); /* atomic */
MidiBuffer& pmbuf (bufs.get_midi (0));
if (mask && (_current_gain < GAIN_COEFF_SMALL)) {
/* mask set, and we have just been muted */
_midi_mute_buffer.clear ();
for (uint8_t channel = 0; channel <= 0xF; channel++) {
if ((1<<channel) & mask) {
uint8_t buf[3] = { ((uint8_t) (MIDI_CMD_CONTROL | channel)), MIDI_CTL_SUSTAIN, 0 };
Evoral::Event<samplepos_t> ev (Evoral::MIDI_EVENT, 0, 3, buf);
_midi_mute_buffer.push_back (ev);
/* Note we do not send MIDI_CTL_ALL_NOTES_OFF here, since this may
silence notes that came from another non-muted track. */
}
}
pmbuf.merge_from (_midi_mute_buffer, 0, 0, 0); /* last 3 args do not matter for MIDI */
_midi_mute_mask = 0;
}
}
}
void
Delivery::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool result_required)
{
@ -343,6 +388,8 @@ Delivery::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample
_amp->run (bufs, start_sample, end_sample, speed, nframes, true);
}
maybe_merge_midi_mute (bufs);
RTABufferListPtr rtabuffers = _rtabuffers;
if (_rta_active.load () && rtabuffers && !rtabuffers->empty ()) {
uint32_t n_audio = bufs.count().n_audio();
@ -721,3 +768,8 @@ Delivery::panner () const
}
}
void
Delivery::set_midi_mute_mask (int mask)
{
_midi_mute_mask = mask; /* atomic */
}

View file

@ -333,6 +333,8 @@ InternalSend::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sa
Amp::apply_simple_gain (mixbufs, nframes, tgain);
}
maybe_merge_midi_mute (mixbufs);
/* apply fader gain automation */
_amp->set_gain_automation_buffer (_session.send_gain_automation_buffer ());
_amp->setup_gain_automation (start_sample + latency, end_sample + latency, nframes);

View file

@ -915,24 +915,18 @@ MidiTrack::act_on_mute ()
if (muted() || _mute_master->muted_by_others_soloing_at (MuteMaster::AllPoints)) {
/* only send messages for channels we are using */
uint16_t mask = _playback_filter.get_channel_mask();
for (uint8_t channel = 0; channel <= 0xF; channel++) {
if ((1<<channel) & mask) {
DEBUG_TRACE (DEBUG::MidiIO, string_compose ("%1 delivers mute message to channel %2\n", name(), channel+1));
uint8_t ev[3] = { ((uint8_t) (MIDI_CMD_CONTROL | channel)), MIDI_CTL_SUSTAIN, 0 };
write_immediate_event (Evoral::MIDI_EVENT, 3, ev);
/* Note we do not send MIDI_CTL_ALL_NOTES_OFF here, since this may
silence notes that came from another non-muted track. */
foreach_processor ([this](std::weak_ptr<Processor> p) {
std::shared_ptr<Delivery> delivery = std::dynamic_pointer_cast<Delivery> (p.lock());
if (delivery) {
delivery->set_midi_mute_mask (_playback_filter.get_channel_mask());
}
}
});
/* Resolve active notes. */
_disk_reader->resolve_tracker (_immediate_events, 0);
if (!the_instrument()) {
_disk_reader->resolve_tracker (_immediate_events, 0);
}
}
}