diff --git a/clients/jack_client.cpp b/clients/jack_client.cpp index 71ac5fe3..d1d3aee6 100644 --- a/clients/jack_client.cpp +++ b/clients/jack_client.cpp @@ -89,18 +89,18 @@ int process(jack_nframes_t numFrames, void* arg [[maybe_unused]]) switch (midi::status(event.buffer[0])) { case midi::noteOff: // DBG("[MIDI] Note " << +event.buffer[1] << " OFF at time " << event.time); - synth->noteOff(event.time, midi::channel(event.buffer[0]) + 1, event.buffer[1], event.buffer[2]); + synth->noteOff(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]); break; case midi::noteOn: // DBG("[MIDI] Note " << +event.buffer[1] << " ON at time " << event.time); - synth->noteOn(event.time, midi::channel(event.buffer[0]) + 1, event.buffer[1], event.buffer[2]); + synth->noteOn(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]); break; case midi::polyphonicPressure: // DBG("[MIDI] Polyphonic pressure on at time " << event.time); break; case midi::controlChange: // DBG("[MIDI] CC " << +event.buffer[1] << " at time " << event.time); - synth->cc(event.time, midi::channel(event.buffer[0]) + 1, event.buffer[1], event.buffer[2]); + synth->cc(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]); break; case midi::programChange: // DBG("[MIDI] Program change at time " << event.time); diff --git a/lv2/sfizz.c b/lv2/sfizz.c index e15c0dbd..e2b9c7c0 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -446,7 +446,7 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev) // "[process_midi] Received note on %d/%d at time %d\n", msg[0], msg[1], ev->time.frames); sfizz_send_note_on(self->synth, (int)ev->time.frames, - (int)MIDI_CHANNEL(msg[0]) + 1, + (int)MIDI_CHANNEL(msg[0]), (int)msg[1], msg[2]); break; @@ -455,7 +455,7 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev) // "[process_midi] Received note off %d/%d at time %d\n", msg[0], msg[1], ev->time.frames); sfizz_send_note_off(self->synth, (int)ev->time.frames, - (int)MIDI_CHANNEL(msg[0]) + 1, + (int)MIDI_CHANNEL(msg[0]), (int)msg[1], msg[2]); break; @@ -464,7 +464,7 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev) // "[process_midi] Received CC %d/%d at time %d\n", msg[0], msg[1], ev->time.frames); sfizz_send_cc(self->synth, (int)ev->time.frames, - (int)MIDI_CHANNEL(msg[0]) + 1, + (int)MIDI_CHANNEL(msg[0]), (int)msg[1], msg[2]); break; diff --git a/src/sfizz.h b/src/sfizz.h index 869b10c9..3e185f17 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -145,7 +145,7 @@ void sfizz_set_sample_rate(sfizz_synth_t* synth, float sample_rate); * * @param synth The synth * @param delay the delay of the event in the block, in samples. - * @param channel the MIDI channel; currently starts at 1 (FIXME) + * @param channel the MIDI channel (starting at 0 for the first channel) * @param note_number the MIDI note number * @param velocity the MIDI velocity */ @@ -159,7 +159,7 @@ void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int channel, int note_n * * @param synth The synth * @param delay the delay of the event in the block, in samples. - * @param channel the MIDI channel; currently starts at 1 (FIXME) + * @param channel the MIDI channel (starting at 0 for the first channel) * @param note_number the MIDI note number * @param velocity the MIDI velocity */ @@ -171,7 +171,7 @@ void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int channel, int note_ * * @param synth The synth * @param delay the delay of the event in the block, in samples. - * @param channel the MIDI channel; currently starts at 1 (FIXME) + * @param channel the MIDI channel (starting at 0 for the first channel) * @param cc_number the MIDI CC number * @param cc_value the MIDI CC value */ diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 1259cfa7..b36d42ff 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -89,7 +89,7 @@ struct Region { * @brief Register a new note on event. The region may be switched on or off using keys so * this function updates the keyswitches state. * - * @param channel + * @param channel MIDI channel (1-based) * @param noteNumber * @param velocity * @param randValue a random value between 0 and 1 used to randomize a bit the region activations @@ -102,7 +102,7 @@ struct Region { * @brief Register a new note off event. The region may be switched on or off using keys so * this function updates the keyswitches state. * - * @param channel + * @param channel MIDI channel (1-based) * @param noteNumber * @param velocity * @param randValue a random value between 0 and 1 used to randomize a bit the region activations @@ -115,7 +115,7 @@ struct Region { * @brief Register a new CC event. The region may be switched on or off using CCs so * this function checks if it indeeds need to activate or not. * - * @param channel + * @param channel MIDI channel (1-based) * @param ccNumber * @param ccValue * @return true if the region should trigger on this event @@ -125,14 +125,14 @@ struct Region { /** * @brief Register a new pitch wheel event. * - * @param channel + * @param channel MIDI channel (1-based) * @param pitch */ void registerPitchWheel(int channel, int pitch) noexcept; /** * @brief Register a new aftertouch event. * - * @param channel + * @param channel MIDI channel (1-based) * @param aftertouch */ void registerAftertouch(int channel, uint8_t aftertouch) noexcept; diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 74a528f6..2290d5df 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -35,13 +35,24 @@ using CCValueArray = std::array; using CCValuePair = std::pair ; using CCNamePair = std::pair; +/** + * @brief Translates the zero-based MIDI channel to the 1-based SFZ channel + * + * @param zeroBasedChannel + * @return constexpr int + */ +inline constexpr int translateMidiChannelToSfz(int zeroBasedChannel) +{ + return zeroBasedChannel + 1; +} + /** * @brief Converts cents to a pitch ratio - * - * @tparam T - * @param cents - * @param centsPerOctave - * @return constexpr float + * + * @tparam T + * @param cents + * @param centsPerOctave + * @return constexpr float */ template inline constexpr float centsFactor(T cents, T centsPerOctave = 1200) @@ -51,10 +62,10 @@ inline constexpr float centsFactor(T cents, T centsPerOctave = 1200) /** * @brief Normalize a CC value between (T)0.0 and (T)1.0 - * - * @tparam T - * @param ccValue - * @return constexpr float + * + * @tparam T + * @param ccValue + * @return constexpr float */ template inline constexpr float normalizeCC(T ccValue) @@ -65,10 +76,10 @@ inline constexpr float normalizeCC(T ccValue) /** * @brief Normalize a percentage between 0 and 1 - * - * @tparam T - * @param percentValue - * @return constexpr float + * + * @tparam T + * @param percentValue + * @return constexpr float */ template inline constexpr float normalizePercents(T percentValue) @@ -78,10 +89,10 @@ inline constexpr float normalizePercents(T percentValue) /** * @brief Normalize a possibly negative percentage between -1 and 1 - * - * @tparam T - * @param percentValue - * @return constexpr float + * + * @tparam T + * @param percentValue + * @return constexpr float */ template inline constexpr float normalizeNegativePercents(T percentValue) @@ -91,11 +102,11 @@ inline constexpr float normalizeNegativePercents(T percentValue) /** * @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone. - * - * @param ccValues - * @param ccSwitch - * @param value - * @return float + * + * @param ccValues + * @param ccSwitch + * @param value + * @return float */ inline float ccSwitchedValue(const CCValueArray& ccValues, const absl::optional& ccSwitch, float value) noexcept { @@ -107,9 +118,9 @@ inline float ccSwitchedValue(const CCValueArray& ccValues, const absl::optional< /** * @brief Convert a note in string to its equivalent midi note number - * - * @param value - * @return absl::optional + * + * @param value + * @return absl::optional */ absl::optional readNoteValue(const absl::string_view& value); diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index c579ff31..cc00b295 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -372,6 +372,7 @@ void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); + channel = translateMidiChannelToSfz(channel); midiState.noteOn(noteNumber, velocity); AtomicGuard callbackGuard { inCallback }; @@ -401,6 +402,8 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); + channel = translateMidiChannelToSfz(channel); + AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) return; @@ -429,6 +432,8 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc ASSERT(ccNumber < 128); ASSERT(ccNumber >= 0); + channel = translateMidiChannelToSfz(channel); + AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) return; @@ -449,13 +454,13 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc } } -void sfz::Synth::pitchWheel(int /* delay */, int /* channel */, int /* pitch */) noexcept +void sfz::Synth::pitchWheel(int /* delay */, int channel, int /* pitch */) noexcept { - + channel = translateMidiChannelToSfz(channel); } -void sfz::Synth::aftertouch(int /* delay */, int /* channel */, uint8_t /* aftertouch */) noexcept +void sfz::Synth::aftertouch(int /* delay */, int channel, uint8_t /* aftertouch */) noexcept { - + channel = translateMidiChannelToSfz(channel); } void sfz::Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept { diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 70397b2d..41cd4600 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -191,7 +191,7 @@ public: * * @param delay the delay at which the event occurs; this should be lower * than the size of the block in the next call to renderBlock(). - * @param channel the midi channel for the event + * @param channel the midi channel for the event (0-based) * @param noteNumber the midi note number * @param velocity the midi note velocity */ @@ -201,7 +201,7 @@ public: * * @param delay the delay at which the event occurs; this should be lower * than the size of the block in the next call to renderBlock(). - * @param channel the midi channel for the event + * @param channel the midi channel for the event (0-based) * @param noteNumber the midi note number * @param velocity the midi note velocity */ @@ -211,7 +211,7 @@ public: * * @param delay the delay at which the event occurs; this should be lower than the size of * the block in the next call to renderBlock(). - * @param channel the midi channel for the event + * @param channel the midi channel for the event (0-based) * @param ccNumber the cc number * @param ccValue the cc value */ @@ -222,7 +222,7 @@ public: * @param delay the delay at which the event occurs; this should be lower * than the size of the block in the next call to * renderBlock(). - * @param channel the midi channel for the event + * @param channel the midi channel for the event (0-based) * @param pitch the pitch value */ void pitchWheel(int delay, int channel, int pitch) noexcept; @@ -231,7 +231,7 @@ public: * * @param delay the delay at which the event occurs; this should be lower than the size of * the block in the next call to renderBlock(). - * @param channel the midi channel for the event + * @param channel the midi channel for the event (0-based) * @param aftertouch the aftertouch value */ void aftertouch(int delay, int channel, uint8_t aftertouch) noexcept; @@ -240,7 +240,6 @@ public: * * @param delay the delay at which the event occurs; this should be lower than the size of * the block in the next call to renderBlock(). - * @param channel the midi channel for the event * @param secondsPerQuarter the new period of the quarter note */ void tempo(int delay, float secondsPerQuarter) noexcept; diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 3697f445..67e70af7 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -324,3 +324,39 @@ TEST_CASE("[Files] Specific bug: relative path with backslashes") REQUIRE(synth.getNumRegions() == 1); REQUIRE(synth.getRegionView(0)->sample == R"(closedhat.wav)"); } + +TEST_CASE("[Synth] Testing channel 1") +{ + sfz::Synth synth; + synth.setNumVoices(1); + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/midi_channels.sfz"); + synth.noteOn(0, 0, 60, 63); + const auto* region = synth.getVoiceView(0)->getRegion(); + REQUIRE( region != nullptr ); + REQUIRE( region->sample == "dummy1.wav" ); +} + + +TEST_CASE("[Synth] Testing channel 2") +{ + sfz::Synth synth; + synth.setNumVoices(1); + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/midi_channels.sfz"); + synth.noteOn(0, 1, 60, 63); + const auto* region = synth.getVoiceView(0)->getRegion(); + REQUIRE( region != nullptr ); + REQUIRE( region->sample == "dummy2.wav" ); +} + + + +TEST_CASE("[Synth] Testing channel 16") +{ + sfz::Synth synth; + synth.setNumVoices(1); + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/midi_channels.sfz"); + synth.noteOn(0, 15, 60, 63); + const auto* region = synth.getVoiceView(0)->getRegion(); + REQUIRE( region != nullptr ); + REQUIRE( region->sample == "dummy16.wav" ); +} diff --git a/tests/TestFiles/dummy1.wav b/tests/TestFiles/dummy1.wav new file mode 100755 index 00000000..d31835e5 Binary files /dev/null and b/tests/TestFiles/dummy1.wav differ diff --git a/tests/TestFiles/dummy16.wav b/tests/TestFiles/dummy16.wav new file mode 100755 index 00000000..d31835e5 Binary files /dev/null and b/tests/TestFiles/dummy16.wav differ diff --git a/tests/TestFiles/dummy2.wav b/tests/TestFiles/dummy2.wav new file mode 100755 index 00000000..d31835e5 Binary files /dev/null and b/tests/TestFiles/dummy2.wav differ diff --git a/tests/TestFiles/midi_channels.sfz b/tests/TestFiles/midi_channels.sfz new file mode 100644 index 00000000..16f174b8 --- /dev/null +++ b/tests/TestFiles/midi_channels.sfz @@ -0,0 +1,3 @@ + lochan=1 hichan=1 sample=dummy1.wav + lochan=2 hichan=2 sample=dummy2.wav + lochan=16 hichan=16 sample=dummy16.wav