Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multiple MIDI out issues #24944

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ const ChannelMap& FluidSequencer::channels() const
return m_channels;
}

int FluidSequencer::lastStaff() const
{
return m_lastStaff;
}

void FluidSequencer::updatePlaybackEvents(EventSequenceMap& destination, const mpe::PlaybackEventsMap& changes)
{
for (const auto& pair : changes) {
Expand All @@ -109,6 +114,9 @@ void FluidSequencer::updatePlaybackEvents(EventSequenceMap& destination, const m

timestamp_t timestampFrom = noteEvent.arrangementCtx().actualTimestamp;
timestamp_t timestampTo = timestampFrom + noteEvent.arrangementCtx().actualDuration;
// Assumption: 1:1 mapping between staff and instrument and FluidSynth and FluidSequencer instances.
// So staffLayerIndex is constant. It changes only when moving staffs.
m_lastStaff = noteEvent.arrangementCtx().staffLayerIndex;

channel_t channelIdx = channel(noteEvent);
note_idx_t noteIdx = noteIndex(noteEvent.pitchCtx().nominalPitchLevel);
Expand Down Expand Up @@ -285,7 +293,7 @@ tuning_t FluidSequencer::noteTuning(const mpe::NoteEvent& noteEvent, const int n

velocity_t FluidSequencer::noteVelocity(const mpe::NoteEvent& noteEvent) const
{
static constexpr midi::velocity_t MAX_SUPPORTED_VELOCITY = 127;
static constexpr midi::velocity_t MAX_SUPPORTED_VELOCITY = std::numeric_limits<midi::velocity_t>::max();

const mpe::ExpressionContext& expressionCtx = noteEvent.expressionCtx();

Expand All @@ -300,7 +308,7 @@ velocity_t FluidSequencer::noteVelocity(const mpe::NoteEvent& noteEvent) const
}

dynamic_level_t dynamicLevel = expressionCtx.expressionCurve.maxAmplitudeLevel();
return expressionLevel(dynamicLevel);
return expressionLevel(dynamicLevel) << 9; // midi::Event::scaleUp(7,16)
}

int FluidSequencer::expressionLevel(const mpe::dynamic_level_t dynamicLevel) const
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class FluidSequencer : public AbstractEventSequencer<midi::Event>
async::Channel<midi::channel_t, midi::Program> channelAdded() const;

const ChannelMap& channels() const;
int lastStaff() const;

private:
void updateOffStreamEvents(const mpe::PlaybackEventsMap& events, const mpe::PlaybackParamList& params) override;
Expand All @@ -66,6 +67,7 @@ class FluidSequencer : public AbstractEventSequencer<midi::Event>

mutable ChannelMap m_channels;
bool m_useDynamicEvents = false;
int m_lastStaff = -1;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ static constexpr msecs_t MIN_NOTE_LENGTH = 10;
static constexpr unsigned int FLUID_AUDIO_CHANNELS_PAIR = 1;
static constexpr unsigned int FLUID_AUDIO_CHANNELS_COUNT = FLUID_AUDIO_CHANNELS_PAIR * 2;

static constexpr bool STAFF_TO_MIDIOUT_CHANNEL = true;

struct muse::audio::synth::Fluid {
fluid_settings_t* settings = nullptr;
fluid_synth_t* synth = nullptr;
Expand Down Expand Up @@ -160,14 +162,55 @@ void FluidSynth::allNotesOff()
setControllerValue(i, midi::SUSTAIN_PEDAL_CONTROLLER, 0);
setPitchBend(i, 8192);
}

auto port = midiOutPort();
if (port->isConnected()) {
// Send all notes off to connected midi ports.
// Room for improvement:
// - We could record which groups/channels we sent something or which channels were scheduled in the sequencer.
// - We could send all events at once.
int lowerBound = 0;
int upperBound = 0;
if (STAFF_TO_MIDIOUT_CHANNEL) {
int channel = m_sequencer.lastStaff();
if (channel < 0) {
return;
}
channel = channel % 16;
lowerBound = channel;
upperBound = channel + 1;
} else {
upperBound = min(lastChannelIdx, 16);
}
for (int i = lowerBound; i < upperBound; i++) {
muse::midi::Event e(muse::midi::Event::Opcode::ControlChange, muse::midi::Event::MessageType::ChannelVoice20);
e.setChannel(i);
e.setIndex(123); // CC#123 = All notes off
port->sendEvent(e);
}
for (int i = lowerBound; i < upperBound; i++) {
muse::midi::Event e(muse::midi::Event::Opcode::ControlChange, muse::midi::Event::MessageType::ChannelVoice20);
e.setChannel(i);
e.setIndex(midi::SUSTAIN_PEDAL_CONTROLLER);
e.setData(0);
port->sendEvent(e);
}
for (int i = lowerBound; i < upperBound; i++) {
muse::midi::Event e(muse::midi::Event::Opcode::PitchBend, muse::midi::Event::MessageType::ChannelVoice20);
e.setChannel(i);
e.setData(0x80000000);
port->sendEvent(e);
}
}
}

bool FluidSynth::handleEvent(const midi::Event& event)
{
int ret = FLUID_OK;
switch (event.opcode()) {
case Event::Opcode::NoteOn: {
ret = fluid_synth_noteon(m_fluid->synth, event.channel(), event.note(), event.velocity());
// fluid_synth_noteon expects 0...127
ret = fluid_synth_noteon(m_fluid->synth, event.channel(), event.note(), event.velocity7());
m_tuning.add(event.note(), event.pitchTuningCents());
} break;
case Event::Opcode::NoteOff: {
Expand All @@ -176,24 +219,34 @@ bool FluidSynth::handleEvent(const midi::Event& event)
} break;
case Event::Opcode::ControlChange: {
if (event.index() == muse::midi::EXPRESSION_CONTROLLER) {
ret = setExpressionLevel(event.data());
ret = setExpressionLevel(event.data7());
} else {
ret = setControllerValue(event.channel(), event.index(), event.data());
ret = setControllerValue(event.channel(), event.index(), event.data7());
}
} break;
case Event::Opcode::ProgramChange: {
fluid_synth_program_change(m_fluid->synth, event.channel(), event.program());
} break;
case Event::Opcode::PitchBend: {
ret = setPitchBend(event.channel(), event.data());
ret = setPitchBend(event.channel(), event.pitchBend14());
} break;
default: {
LOGD() << "not supported event type: " << event.opcodeString();
ret = FLUID_FAILED;
}
}

midiOutPort()->sendEvent(event);
if (STAFF_TO_MIDIOUT_CHANNEL && event.isChannelVoice()) {
int staff = m_sequencer.lastStaff();
if (staff >= 0) {
int channel = staff % 16;
midi::Event me(event);
me.setChannel(channel);
midiOutPort()->sendEvent(me);
}
} else {
midiOutPort()->sendEvent(event);
}

return ret == FLUID_OK;
}
Expand Down
81 changes: 54 additions & 27 deletions src/framework/midi/internal/platform/osx/coremidiinport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@
using namespace muse;
using namespace muse::midi;

//#define DEBUG_COREMIDIINPORT
#ifdef DEBUG_COREMIDIINPORT
#define LOG_MIDI_D LOGD
#define LOG_MIDI_W LOGW
#else
#define LOG_MIDI_D LOGN
#define LOG_MIDI_W LOGN
#endif

struct muse::midi::CoreMidiInPort::Core {
MIDIClientRef client = 0;
MIDIPortRef inputPort = 0;
Expand Down Expand Up @@ -198,34 +207,40 @@ void CoreMidiInPort::initCore()
// Document Version 1.1.2
// Draft Date 2023-10-27
// Published 2023-11-10
const uint32_t message_type_to_size_in_byte[] = { 1, // 0x0
1, // 0x1
1, // 0x2
2, // 0x3
2, // 0x4
4, // 0x5
1, // 0x6
1, // 0x7
2, // 0x8
2, // 0x9
2, // 0xA
3, // 0xB
3, // 0xC
4, // 0xD
4, // 0xE
4 };// 0xF
// Section 2.1.4
const uint32_t message_type_to_size_in_words[] = {
1, // 0x0 Utility
1, // 0x1 SystemRealTime
1, // 0x2 ChannelVoice10
2, // 0x3 SystemExclusiveData
2, // 0x4 ChannelVoice20
4, // 0x5 Data
1, // 0x6 Reserved
1, // 0x7 Reserved
2, // 0x8 Reserved
2, // 0x9 Reserved
2, // 0xA Reserved
3, // 0xB Reserved
3, // 0xC Reserved
4, // 0xD Reserved
4, // 0xE Reserved
4 // 0xF Reserved
};

const MIDIEventPacket* packet = eventList->packet;
for (UInt32 index = 0; index < eventList->numPackets; index++) {
LOGD() << "midi packet size " << packet->wordCount << " bytes";
LOG_MIDI_D() << "Receiving MIDIEventPacket with " << packet->wordCount << " words";
// Handle packet
uint32_t pos = 0;
while (pos < packet->wordCount) {
uint32_t most_significant_4_bit = packet->words[pos] >> 28;
uint32_t message_size = message_type_to_size_in_byte[most_significant_4_bit];
assert(most_significant_4_bit < 6);

LOGD() << "midi message size " << message_size << " bytes";
uint32_t message_size = message_type_to_size_in_words[most_significant_4_bit];
LOG_MIDI_D() << "Receiving midi message with " << message_size << " words";
Event e = Event::fromRawData(&packet->words[pos], message_size);
if (e) {
LOG_MIDI_D() << "Received midi message:" << e.to_string();
m_eventReceived.send((tick_t)packet->timeStamp, e);
}
pos += message_size;
Expand All @@ -241,18 +256,30 @@ void CoreMidiInPort::initCore()
{
const MIDIPacket* packet = packetList->packet;
for (UInt32 index = 0; index < packetList->numPackets; index++) {
if (packet->length != 0 && packet->length <= 4) {
uint32_t message(0);
memcpy(&message, packet->data, std::min(sizeof(message), sizeof(char) * packet->length));

auto e = Event::fromMIDI10Package(message).toMIDI20();
auto len = packet->length;
int pos = 0;
const Byte* pointer = static_cast<const Byte*>(&(packet->data[0]));
while (pos < len) {
Byte status = pointer[pos] >> 4;
if (status < 8 || status >= 15) {
LOG_MIDI_W() << "Unhandled status byte:" << status;
return;
}
Event::Opcode opcode = static_cast<Event::Opcode>(status);
int msgLen = Event::midi10ByteCountForOpcode(opcode);
if (msgLen == 0) {
LOG_MIDI_W() << "Unhandled opcode:" << status;
return;
}
Event e = Event::fromMIDI10BytePackage(pointer + pos, msgLen);
LOG_MIDI_D() << "Received midi 1.0 message:" << e.to_string();
e = e.toMIDI20();
if (e) {
LOG_MIDI_D() << "Converted to midi 2.0 midi message:" << e.to_string();
m_eventReceived.send((tick_t)packet->timeStamp, e);
}
} else if (packet->length > 4) {
LOGW() << "unsupported midi message size " << packet->length << " bytes";
pos += msgLen;
}

packet = MIDIPacketNext(packet);
}
};
Expand Down
Loading