Remove the channels from the inner engine

This commit is contained in:
Paul Ferrand 2019-12-23 01:25:20 +01:00
parent 24f52b255f
commit 8e39ffc18e
22 changed files with 417 additions and 626 deletions

View file

@ -94,18 +94,18 @@ int process(jack_nframes_t numFrames, void* arg [[maybe_unused]])
switch (midi::status(event.buffer[0])) { switch (midi::status(event.buffer[0])) {
case midi::noteOff: case midi::noteOff:
// DBG("[MIDI] Note " << +event.buffer[1] << " OFF at time " << event.time); // DBG("[MIDI] Note " << +event.buffer[1] << " OFF at time " << event.time);
synth->noteOff(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]); synth->noteOff(event.time, event.buffer[1], event.buffer[2]);
break; break;
case midi::noteOn: case midi::noteOn:
// DBG("[MIDI] Note " << +event.buffer[1] << " ON at time " << event.time); // DBG("[MIDI] Note " << +event.buffer[1] << " ON at time " << event.time);
synth->noteOn(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]); synth->noteOn(event.time, event.buffer[1], event.buffer[2]);
break; break;
case midi::polyphonicPressure: case midi::polyphonicPressure:
// DBG("[MIDI] Polyphonic pressure on at time " << event.time); // DBG("[MIDI] Polyphonic pressure on at time " << event.time);
break; break;
case midi::controlChange: case midi::controlChange:
// DBG("[MIDI] CC " << +event.buffer[1] << " at time " << event.time); // DBG("[MIDI] CC " << +event.buffer[1] << " at time " << event.time);
synth->cc(event.time, midi::channel(event.buffer[0]), event.buffer[1], event.buffer[2]); synth->cc(event.time, event.buffer[1], event.buffer[2]);
break; break;
case midi::programChange: case midi::programChange:
// DBG("[MIDI] Program change at time " << event.time); // DBG("[MIDI] Program change at time " << event.time);
@ -114,7 +114,7 @@ int process(jack_nframes_t numFrames, void* arg [[maybe_unused]])
// DBG("[MIDI] Channel pressure at time " << event.time); // DBG("[MIDI] Channel pressure at time " << event.time);
break; break;
case midi::pitchBend: case midi::pitchBend:
synth->pitchWheel(event.time, midi::channel(event.buffer[0]), midi::buildAndCenterPitch(event.buffer[1], event.buffer[2])); synth->pitchWheel(event.time, midi::buildAndCenterPitch(event.buffer[1], event.buffer[2]));
// DBG("[MIDI] Pitch bend at time " << event.time); // DBG("[MIDI] Pitch bend at time " << event.time);
break; break;
case midi::systemMessage: case midi::systemMessage:
@ -163,7 +163,7 @@ static void done(int sig [[maybe_unused]])
// exit(0); // exit(0);
} }
ABSL_FLAG(std::string, oversampling, "1x", "Internal oversampling factor (value values are 1x, 2x, 4x, 8x)"); ABSL_FLAG(std::string, oversampling, "1x", "Internal oversampling factor (value values are x1, x2, x4, x8)");
ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded value"); ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded value");
int main(int argc, char** argv) int main(int argc, char** argv)

View file

@ -495,7 +495,6 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
// LV2_DEBUG("[process_midi] Received note on %d/%d at time %ld\n", msg[0], msg[1], ev->time.frames); // LV2_DEBUG("[process_midi] Received note on %d/%d at time %ld\n", msg[0], msg[1], ev->time.frames);
sfizz_send_note_on(self->synth, sfizz_send_note_on(self->synth,
(int)ev->time.frames, (int)ev->time.frames,
(int)MIDI_CHANNEL(msg[0]),
(int)msg[1], (int)msg[1],
msg[2]); msg[2]);
break; break;
@ -503,7 +502,6 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
// LV2_DEBUG("[process_midi] Received note off %d/%d at time %ld\n", msg[0], msg[1], ev->time.frames); // LV2_DEBUG("[process_midi] Received note off %d/%d at time %ld\n", msg[0], msg[1], ev->time.frames);
sfizz_send_note_off(self->synth, sfizz_send_note_off(self->synth,
(int)ev->time.frames, (int)ev->time.frames,
(int)MIDI_CHANNEL(msg[0]),
(int)msg[1], (int)msg[1],
msg[2]); msg[2]);
break; break;
@ -511,7 +509,6 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
// LV2_DEBUG("[process_midi] Received CC %d/%d at time %ld\n", msg[0], msg[1], ev->time.frames); // LV2_DEBUG("[process_midi] Received CC %d/%d at time %ld\n", msg[0], msg[1], ev->time.frames);
sfizz_send_cc(self->synth, sfizz_send_cc(self->synth,
(int)ev->time.frames, (int)ev->time.frames,
(int)MIDI_CHANNEL(msg[0]),
(int)msg[1], (int)msg[1],
msg[2]); msg[2]);
break; break;
@ -519,7 +516,6 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
// LV2_DEBUG("[process_midi] Received pitch bend %d on channel %d at time %ld\n", PITCH_BUILD_AND_CENTER(msg[1], msg[2]), MIDI_CHANNEL(msg[0]), ev->time.frames); // LV2_DEBUG("[process_midi] Received pitch bend %d on channel %d at time %ld\n", PITCH_BUILD_AND_CENTER(msg[1], msg[2]), MIDI_CHANNEL(msg[0]), ev->time.frames);
sfizz_send_pitch_wheel(self->synth, sfizz_send_pitch_wheel(self->synth,
(int)ev->time.frames, (int)ev->time.frames,
(int)MIDI_CHANNEL(msg[0]),
PITCH_BUILD_AND_CENTER(msg[1], msg[2])); PITCH_BUILD_AND_CENTER(msg[1], msg[2]));
break; break;
default: default:
@ -697,7 +693,7 @@ lv2_get_options(LV2_Handle instance, LV2_Options_Option *options)
for (LV2_Options_Option *opt = options; opt->value; ++opt) for (LV2_Options_Option *opt = options; opt->value; ++opt)
{ {
if (self->unmap) { if (self->unmap) {
LV2_DEBUG("[DEBUG] Called for an option with key (subject): %s (%s) \n", LV2_DEBUG("[DEBUG] Called for an option with key (subject): %s (%s) \n",
self->unmap->unmap(self->unmap->handle, opt->key), self->unmap->unmap(self->unmap->handle, opt->key),
self->unmap->unmap(self->unmap->handle, opt->subject)); self->unmap->unmap(self->unmap->handle, opt->subject));
} }
@ -963,7 +959,7 @@ work_response(LV2_Handle instance,
const LV2_Atom *atom = (const LV2_Atom *)data; const LV2_Atom *atom = (const LV2_Atom *)data;
if (atom->type == self->sfizz_sfz_file_uri) if (atom->type == self->sfizz_sfz_file_uri)
{ {
self->changing_state = false; self->changing_state = false;
} }
else if (atom->type == self->sfizz_num_voices_uri) else if (atom->type == self->sfizz_num_voices_uri)

View file

@ -141,64 +141,59 @@ void sfizz_set_sample_rate(sfizz_synth_t* synth, float sample_rate);
/** /**
* @brief Send a note on event to the synth. As with all MIDI events, this * @brief Send a note on event to the synth. As with all MIDI events, this
* needs to happen before the call to sfizz_render_block in each * needs to happen before the call to sfizz_render_block in each
* block and should appear in order of the delays, at least within a channel. * block and should appear in order of the delays.
* *
* @param synth The synth * @param synth The synth
* @param delay the delay of the event in the block, in samples. * @param delay the delay of the event in the block, in samples.
* @param channel the MIDI channel (starting at 0 for the first channel)
* @param note_number the MIDI note number * @param note_number the MIDI note number
* @param velocity the MIDI velocity * @param velocity the MIDI velocity
*/ */
void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int channel, int note_number, char velocity); void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int note_number, char velocity);
/** /**
* @brief Send a note off event to the synth. As with all MIDI events, this * @brief Send a note off event to the synth. As with all MIDI events, this
* needs to happen before the call to sfizz_render_block in each * needs to happen before the call to sfizz_render_block in each
* block and should appear in order of the delays, at least within a channel. * block and should appear in order of the delays.
* As per the SFZ spec the velocity of note-off events is usually replaced by * As per the SFZ spec the velocity of note-off events is usually replaced by
* the note-on velocity. * the note-on velocity.
* *
* @param synth The synth * @param synth The synth
* @param delay the delay of the event in the block, in samples. * @param delay the delay of the event in the block, in samples.
* @param channel the MIDI channel (starting at 0 for the first channel)
* @param note_number the MIDI note number * @param note_number the MIDI note number
* @param velocity the MIDI velocity * @param velocity the MIDI velocity
*/ */
void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int channel, int note_number, char velocity); void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int note_number, char velocity);
/** /**
* @brief Send a CC event to the synth. As with all MIDI events, this needs * @brief Send a CC event to the synth. As with all MIDI events, this needs
* to happen before the call to sfizz_render_block in each block and * to happen before the call to sfizz_render_block in each block and
* should appear in order of the delays, at least within a channel. * should appear in order of the delays.
* *
* @param synth The synth * @param synth The synth
* @param delay the delay of the event in the block, in samples. * @param delay the delay of the event in the block, in samples.
* @param channel the MIDI channel (starting at 0 for the first channel)
* @param cc_number the MIDI CC number * @param cc_number the MIDI CC number
* @param cc_value the MIDI CC value * @param cc_value the MIDI CC value
*/ */
void sfizz_send_cc(sfizz_synth_t* synth, int delay, int channel, int cc_number, char cc_value); void sfizz_send_cc(sfizz_synth_t* synth, int delay, int cc_number, char cc_value);
/** /**
* @brief Send a pitch wheel event. As with all MIDI events, this needs * @brief Send a pitch wheel event. As with all MIDI events, this needs
* to happen before the call to sfizz_render_block in each block and * to happen before the call to sfizz_render_block in each block and
* should appear in order of the delays, at least within a channel. * should appear in order of the delays.
* *
* @param synth The synth * @param synth The synth
* @param delay The delay * @param delay The delay
* @param channel The channel
* @param pitch The pitch * @param pitch The pitch
*/ */
void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay, int channel, int pitch); void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay, int pitch);
/** /**
* @brief Send an aftertouch event. (CURRENTLY UNIMPLEMENTED) * @brief Send an aftertouch event. (CURRENTLY UNIMPLEMENTED)
* *
* @param synth * @param synth
* @param delay * @param delay
* @param channel
* @param aftertouch * @param aftertouch
*/ */
void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, int channel, char aftertouch); void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, char aftertouch);
/** /**
* @brief Send a tempo event. (CURRENTLY UNIMPLEMENTED) * @brief Send a tempo event. (CURRENTLY UNIMPLEMENTED)

View file

@ -6,99 +6,84 @@ sfz::MidiState::MidiState()
reset(); reset();
} }
void sfz::MidiState::noteOnEvent(int channel, int noteNumber, uint8_t velocity) noexcept void sfz::MidiState::noteOnEvent(int noteNumber, uint8_t velocity) noexcept
{ {
ASSERT(channel >= 0 && channel < 16);
ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(noteNumber >= 0 && noteNumber <= 127);
ASSERT(velocity >= 0 && velocity <= 127); ASSERT(velocity >= 0 && velocity <= 127);
if (noteNumber >= 0 && noteNumber < 128) { if (noteNumber >= 0 && noteNumber < 128) {
lastNoteVelocities[channel][noteNumber] = velocity; lastNoteVelocities[noteNumber] = velocity;
noteOnTimes[channel][noteNumber] = std::chrono::steady_clock::now(); noteOnTimes[noteNumber] = std::chrono::steady_clock::now();
} }
} }
float sfz::MidiState::getNoteDuration(int channel, int noteNumber) const float sfz::MidiState::getNoteDuration(int noteNumber) const
{ {
ASSERT(channel >= 0 && channel < 16);
ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(noteNumber >= 0 && noteNumber <= 127);
if (noteNumber >= 0 && noteNumber < 128) { if (noteNumber >= 0 && noteNumber < 128) {
const auto noteOffTime = std::chrono::steady_clock::now(); const auto noteOffTime = std::chrono::steady_clock::now();
const auto duration = std::chrono::duration_cast<std::chrono::duration<float>>(noteOffTime - noteOnTimes[channel][noteNumber]); const auto duration = std::chrono::duration_cast<std::chrono::duration<float>>(noteOffTime - noteOnTimes[noteNumber]);
return duration.count(); return duration.count();
} }
return 0.0f; return 0.0f;
} }
uint8_t sfz::MidiState::getNoteVelocity(int channel, int noteNumber) const noexcept uint8_t sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
{ {
ASSERT(channel >= 0 && channel < 16);
ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(noteNumber >= 0 && noteNumber <= 127);
return lastNoteVelocities[channel][noteNumber]; return lastNoteVelocities[noteNumber];
} }
void sfz::MidiState::pitchBendEvent(int channel, int pitchBendValue) noexcept void sfz::MidiState::pitchBendEvent(int pitchBendValue) noexcept
{ {
ASSERT(channel >= 0 && channel < 16);
ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192); ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192);
pitchBends[channel] = pitchBendValue; pitchBend = pitchBendValue;
} }
int sfz::MidiState::getPitchBend(int channel) const noexcept int sfz::MidiState::getPitchBend() const noexcept
{ {
ASSERT(channel >= 0 && channel < 16); return pitchBend;
return pitchBends[channel];
} }
void sfz::MidiState::ccEvent(int channel, int ccNumber, uint8_t ccValue) noexcept void sfz::MidiState::ccEvent(int ccNumber, uint8_t ccValue) noexcept
{ {
ASSERT(channel >= 0 && channel < 16);
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
ASSERT(ccValue >= 0 && ccValue <= 127); ASSERT(ccValue >= 0 && ccValue <= 127);
cc[channel][ccNumber] = ccValue; cc[ccNumber] = ccValue;
} }
uint8_t sfz::MidiState::getCCValue(int channel, int ccNumber) const noexcept uint8_t sfz::MidiState::getCCValue(int ccNumber) const noexcept
{ {
ASSERT(channel >= 0 && channel < 16);
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
return cc[channel][ccNumber]; return cc[ccNumber];
} }
const sfz::SfzCCArray& sfz::MidiState::getCCArray(int channel) const noexcept const sfz::SfzCCArray& sfz::MidiState::getCCArray() const noexcept
{ {
ASSERT(channel >= 0 && channel < 16); return cc;
return cc[channel];
} }
void sfz::MidiState::reset() noexcept void sfz::MidiState::reset() noexcept
{ {
for (auto& channelArray: lastNoteVelocities) for (auto& velocity: lastNoteVelocities)
for (auto& velocity: channelArray) velocity = 0;
velocity = 0;
for (auto& channelArray: cc) for (auto& ccValue: cc)
for (auto& ccValue: channelArray) ccValue = 0;
ccValue = 0;
for (auto& bendValue: pitchBends) pitchBend = 0;
bendValue = 0;
} }
void sfz::MidiState::resetAllControllers(int channel) noexcept void sfz::MidiState::resetAllControllers() noexcept
{ {
ASSERT(channel >= 0 && channel < 16);
for (int idx = 0; idx < config::numCCs; idx++) for (int idx = 0; idx < config::numCCs; idx++)
cc[channel][idx] = 0; cc[idx] = 0;
pitchBends[channel] = 0; pitchBend = 0;
} }

View file

@ -18,88 +18,76 @@ public:
/** /**
* @brief Update the state after a note on event * @brief Update the state after a note on event
* *
* @param channel (0-based)
* @param noteNumber * @param noteNumber
* @param velocity * @param velocity
*/ */
void noteOnEvent(int channel, int noteNumber, uint8_t velocity) noexcept; void noteOnEvent(int noteNumber, uint8_t velocity) noexcept;
/** /**
* @brief Register a note off and get the note duration * @brief Register a note off and get the note duration
* *
* @param channel (0-based)
* @param noteNumber * @param noteNumber
* @return float * @return float
*/ */
float getNoteDuration(int channel, int noteNumber) const; float getNoteDuration(int noteNumber) const;
/** /**
* @brief Get the note on velocity for a given note * @brief Get the note on velocity for a given note
* *
* @param channel (0-based)
* @param noteNumber * @param noteNumber
* @return uint8_t * @return uint8_t
*/ */
uint8_t getNoteVelocity(int channel, int noteNumber) const noexcept; uint8_t getNoteVelocity(int noteNumber) const noexcept;
/** /**
* @brief Register a pitch bend event * @brief Register a pitch bend event
* *
* @param channel * @param pitchBendValue
* @param pitchBendValue
*/ */
void pitchBendEvent(int channel, int pitchBendValue) noexcept; void pitchBendEvent(int pitchBendValue) noexcept;
/** /**
* @brief Get the pitch bend status on a channel * @brief Get the pitch bend status
*
* @param channel * @return int
* @return int
*/ */
int getPitchBend(int channel) const noexcept; int getPitchBend() const noexcept;
/** /**
* @brief Register a CC event * @brief Register a CC event
* *
* @param channel * @param ccNumber
* @param ccNumber * @param ccValue
* @param ccValue
*/ */
void ccEvent(int channel, int ccNumber, uint8_t ccValue) noexcept; void ccEvent(int ccNumber, uint8_t ccValue) noexcept;
/** /**
* @brief Get the CC value for a specific channel and cc number * @brief Get the CC value for CC number
* *
* @param channel * @param ccNumber
* @param ccNumber * @return uint8_t
* @return uint8_t
*/ */
uint8_t getCCValue(int channel, int ccNumber) const noexcept; uint8_t getCCValue(int ccNumber) const noexcept;
/** /**
* @brief Get the full CC status for a specific channel * @brief Get the full CC status
* *
* @param channel * @return const SfzCCArray&
* @return const SfzCCArray&
*/ */
const SfzCCArray& getCCArray(int channel) const noexcept; const SfzCCArray& getCCArray() const noexcept;
/** /**
* @brief Reset the midi state (does not impact the last note on time) * @brief Reset the midi state (does not impact the last note on time)
* *
*/ */
void reset() noexcept; void reset() noexcept;
/** /**
* @brief Res * @brief Reset all the controllers
*
* @param channel
*/ */
void resetAllControllers(int channel) noexcept; void resetAllControllers() noexcept;
private: private:
template<class T>
using ChannelArray = std::array<T, 16>;
template<class T> template<class T>
using MidiNoteArray = std::array<T, 128>; using MidiNoteArray = std::array<T, 128>;
using NoteOnTime = std::chrono::steady_clock::time_point; using NoteOnTime = std::chrono::steady_clock::time_point;
@ -107,21 +95,21 @@ private:
* @brief Stores the note on times. * @brief Stores the note on times.
* *
*/ */
ChannelArray<MidiNoteArray<NoteOnTime>> noteOnTimes { }; MidiNoteArray<NoteOnTime> noteOnTimes { };
/** /**
* @brief Stores the velocity of the note ons for currently * @brief Stores the velocity of the note ons for currently
* depressed notes. * depressed notes.
* *
*/ */
ChannelArray<MidiNoteArray<uint8_t>> lastNoteVelocities { }; MidiNoteArray<uint8_t> lastNoteVelocities { };
/** /**
* @brief Current known values for the CCs. * @brief Current known values for the CCs.
* *
*/ */
ChannelArray<SfzCCArray> cc; SfzCCArray cc;
/** /**
* Pitch bend status * Pitch bend status
*/ */
ChannelArray<int> pitchBends { 0 }; int pitchBend { 0 };
}; };
} }

View file

@ -144,20 +144,6 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
break; break;
// Region logic: MIDI conditions // Region logic: MIDI conditions
case hash("lochan"):
{
const auto value = readOpcode(opcode.value, Default::channelRange);
if (value)
channelRange.setStart(*value - 1);
}
break;
case hash("hichan"):
{
const auto value = readOpcode(opcode.value, Default::channelRange);
if (value)
channelRange.setEnd(*value - 1);
}
break;
case hash("lobend"): case hash("lobend"):
setRangeStartFromOpcode(opcode, bendRange, Default::bendRange); setRangeStartFromOpcode(opcode, bendRange, Default::bendRange);
break; break;
@ -500,6 +486,8 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
break; break;
// Ignored opcodes // Ignored opcodes
case hash("hichan"):
case hash("lochan"):
case hash("ampeg_depth"): case hash("ampeg_depth"):
case hash("ampeg_vel2depth"): case hash("ampeg_vel2depth"):
break; break;
@ -515,12 +503,8 @@ bool sfz::Region::isSwitchedOn() const noexcept
return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all(); return keySwitched && previousKeySwitched && sequenceSwitched && pitchSwitched && bpmSwitched && aftertouchSwitched && ccSwitched.all();
} }
bool sfz::Region::registerNoteOn(int channel, int noteNumber, uint8_t velocity, float randValue) noexcept bool sfz::Region::registerNoteOn(int noteNumber, uint8_t velocity, float randValue) noexcept
{ {
const bool chanOk = channelRange.containsWithEnd(channel);
if (!chanOk)
return false;
if (keyswitchRange.containsWithEnd(noteNumber)) { if (keyswitchRange.containsWithEnd(noteNumber)) {
if (keyswitch) { if (keyswitch) {
if (*keyswitch == noteNumber) if (*keyswitch == noteNumber)
@ -571,15 +555,11 @@ bool sfz::Region::registerNoteOn(int channel, int noteNumber, uint8_t velocity,
const bool attackTrigger = (trigger == SfzTrigger::attack); const bool attackTrigger = (trigger == SfzTrigger::attack);
const bool notFirstLegatoNote = (trigger == SfzTrigger::legato && activeNotesInRange > 0); const bool notFirstLegatoNote = (trigger == SfzTrigger::legato && activeNotesInRange > 0);
return keyOk && velOk && chanOk && randOk && (attackTrigger || firstLegatoNote || notFirstLegatoNote); return keyOk && velOk && randOk && (attackTrigger || firstLegatoNote || notFirstLegatoNote);
} }
bool sfz::Region::registerNoteOff(int channel, int noteNumber, uint8_t velocity [[maybe_unused]], float randValue) noexcept bool sfz::Region::registerNoteOff(int noteNumber, uint8_t velocity [[maybe_unused]], float randValue) noexcept
{ {
const bool chanOk = channelRange.containsWithEnd(channel);
if (!chanOk)
return false;
if (keyswitchRange.containsWithEnd(noteNumber)) { if (keyswitchRange.containsWithEnd(noteNumber)) {
if (keyswitchDown && *keyswitchDown == noteNumber) if (keyswitchDown && *keyswitchDown == noteNumber)
keySwitched = false; keySwitched = false;
@ -603,14 +583,11 @@ bool sfz::Region::registerNoteOff(int channel, int noteNumber, uint8_t velocity
const bool velOk = velocityRange.containsWithEnd(velocity); const bool velOk = velocityRange.containsWithEnd(velocity);
const bool randOk = randRange.contains(randValue); const bool randOk = randRange.contains(randValue);
const bool releaseTrigger = (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key); const bool releaseTrigger = (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key);
return keyOk && velOk && chanOk && randOk && releaseTrigger; return keyOk && velOk && randOk && releaseTrigger;
} }
bool sfz::Region::registerCC(int channel, int ccNumber, uint8_t ccValue) noexcept bool sfz::Region::registerCC(int ccNumber, uint8_t ccValue) noexcept
{ {
if (!channelRange.containsWithEnd(channel))
return false;
if (ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue)) if (ccConditions.getWithDefault(ccNumber).containsWithEnd(ccValue))
ccSwitched.set(ccNumber, true); ccSwitched.set(ccNumber, true);
else else
@ -628,22 +605,16 @@ bool sfz::Region::registerCC(int channel, int ccNumber, uint8_t ccValue) noexcep
return false; return false;
} }
void sfz::Region::registerPitchWheel(int channel, int pitch) noexcept void sfz::Region::registerPitchWheel(int pitch) noexcept
{ {
if (!channelRange.containsWithEnd(channel))
return;
if (bendRange.containsWithEnd(pitch)) if (bendRange.containsWithEnd(pitch))
pitchSwitched = true; pitchSwitched = true;
else else
pitchSwitched = false; pitchSwitched = false;
} }
void sfz::Region::registerAftertouch(int channel, uint8_t aftertouch) noexcept void sfz::Region::registerAftertouch(uint8_t aftertouch) noexcept
{ {
if (!channelRange.containsWithEnd(channel))
return;
if (aftertouchRange.containsWithEnd(aftertouch)) if (aftertouchRange.containsWithEnd(aftertouch))
aftertouchSwitched = true; aftertouchSwitched = true;
else else
@ -669,11 +640,11 @@ float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) noexc
return centsFactor(pitchVariationInCents); return centsFactor(pitchVariationInCents);
} }
float sfz::Region::getBaseVolumedB(int channel, int noteNumber) noexcept float sfz::Region::getBaseVolumedB(int noteNumber) noexcept
{ {
auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator); auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator);
if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key) if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key)
baseVolumedB -= rtDecay * midiState.getNoteDuration(channel, noteNumber); baseVolumedB -= rtDecay * midiState.getNoteDuration(noteNumber);
return baseVolumedB; return baseVolumedB;
} }

View file

@ -90,7 +90,6 @@ struct Region {
* @brief Register a new note on event. The region may be switched on or off using keys so * @brief Register a new note on event. The region may be switched on or off using keys so
* this function updates the keyswitches state. * this function updates the keyswitches state.
* *
* @param channel MIDI channel (0-based)
* @param noteNumber * @param noteNumber
* @param velocity * @param velocity
* @param randValue a random value between 0 and 1 used to randomize a bit the region activations * @param randValue a random value between 0 and 1 used to randomize a bit the region activations
@ -98,12 +97,11 @@ struct Region {
* @return true if the region should trigger on this event. * @return true if the region should trigger on this event.
* @return false * @return false
*/ */
bool registerNoteOn(int channel, int noteNumber, uint8_t velocity, float randValue) noexcept; bool registerNoteOn(int noteNumber, uint8_t velocity, float randValue) noexcept;
/** /**
* @brief Register a new note off event. The region may be switched on or off using keys so * @brief Register a new note off event. The region may be switched on or off using keys so
* this function updates the keyswitches state. * this function updates the keyswitches state.
* *
* @param channel MIDI channel (0-based)
* @param noteNumber * @param noteNumber
* @param velocity * @param velocity
* @param randValue a random value between 0 and 1 used to randomize a bit the region activations * @param randValue a random value between 0 and 1 used to randomize a bit the region activations
@ -111,32 +109,29 @@ struct Region {
* @return true if the region should trigger on this event. * @return true if the region should trigger on this event.
* @return false * @return false
*/ */
bool registerNoteOff(int channel, int noteNumber, uint8_t velocity, float randValue) noexcept; bool registerNoteOff(int noteNumber, uint8_t velocity, float randValue) noexcept;
/** /**
* @brief Register a new CC event. The region may be switched on or off using CCs so * @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. * this function checks if it indeeds need to activate or not.
* *
* @param channel MIDI channel (0-based)
* @param ccNumber * @param ccNumber
* @param ccValue * @param ccValue
* @return true if the region should trigger on this event * @return true if the region should trigger on this event
* @return false * @return false
*/ */
bool registerCC(int channel, int ccNumber, uint8_t ccValue) noexcept; bool registerCC(int ccNumber, uint8_t ccValue) noexcept;
/** /**
* @brief Register a new pitch wheel event. * @brief Register a new pitch wheel event.
* *
* @param channel MIDI channel (0-based)
* @param pitch * @param pitch
*/ */
void registerPitchWheel(int channel, int pitch) noexcept; void registerPitchWheel(int pitch) noexcept;
/** /**
* @brief Register a new aftertouch event. * @brief Register a new aftertouch event.
* *
* @param channel MIDI channel (0-based)
* @param aftertouch * @param aftertouch
*/ */
void registerAftertouch(int channel, uint8_t aftertouch) noexcept; void registerAftertouch(uint8_t aftertouch) noexcept;
/** /**
* @brief Register tempo * @brief Register tempo
* *
@ -174,11 +169,10 @@ struct Region {
* @brief Get the base volume of the region depending on which note has been * @brief Get the base volume of the region depending on which note has been
* pressed to trigger the region. * pressed to trigger the region.
* *
* @param channel (0-based)
* @param noteNumber * @param noteNumber
* @return float * @return float
*/ */
float getBaseVolumedB(int channel, int noteNumber) noexcept; float getBaseVolumedB(int noteNumber) noexcept;
/** /**
* @brief Get the base gain of the region. * @brief Get the base gain of the region.
* *
@ -246,7 +240,6 @@ struct Region {
Range<uint8_t> velocityRange { Default::velocityRange }; // hivel and lovel Range<uint8_t> velocityRange { Default::velocityRange }; // hivel and lovel
// Region logic: MIDI conditions // Region logic: MIDI conditions
Range<uint8_t> channelRange { Default::midiChannelRange }; //lochan and hichan
Range<int> bendRange { Default::bendRange }; // hibend and lobend Range<int> bendRange { Default::bendRange }; // hibend and lobend
CCMap<Range<uint8_t>> ccConditions { Default::ccValueRange }; CCMap<Range<uint8_t>> ccConditions { Default::ccValueRange };
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey

View file

@ -173,8 +173,7 @@ void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
case hash("set_cc"): case hash("set_cc"):
if (member.parameter && Default::ccNumberRange.containsWithEnd(*member.parameter)) { if (member.parameter && Default::ccNumberRange.containsWithEnd(*member.parameter)) {
const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0); const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0);
for (int channel = 0; channel < 16; channel++) midiState.ccEvent(*member.parameter, ccValue);
midiState.ccEvent(channel, *member.parameter, ccValue);
} }
break; break;
case hash("Label_cc"): case hash("Label_cc"):
@ -272,18 +271,17 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename)
// Defaults // Defaults
for (int ccIndex = 0; ccIndex < config::numCCs; ccIndex++) { for (int ccIndex = 0; ccIndex < config::numCCs; ccIndex++) {
const auto channel = region->channelRange.getStart(); region->registerCC(ccIndex, midiState.getCCValue(ccIndex));
region->registerCC(channel, ccIndex, midiState.getCCValue(channel, ccIndex));
} }
if (defaultSwitch) { if (defaultSwitch) {
region->registerNoteOn(region->channelRange.getStart(), *defaultSwitch, 127, 1.0); region->registerNoteOn(*defaultSwitch, 127, 1.0);
region->registerNoteOff(region->channelRange.getStart(), *defaultSwitch, 0, 1.0); region->registerNoteOff(*defaultSwitch, 0, 1.0);
} }
addEndpointsToVelocityCurve(*region); addEndpointsToVelocityCurve(*region);
region->registerPitchWheel(region->channelRange.getStart(), 0); region->registerPitchWheel(0);
region->registerAftertouch(region->channelRange.getStart(), 0); region->registerAftertouch(0);
region->registerTempo(2.0f); region->registerTempo(2.0f);
currentRegion++; currentRegion++;
@ -386,12 +384,12 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
buffer.applyGain(db2mag(volume)); buffer.applyGain(db2mag(volume));
} }
void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity) noexcept void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept
{ {
ASSERT(noteNumber < 128); ASSERT(noteNumber < 128);
ASSERT(noteNumber >= 0); ASSERT(noteNumber >= 0);
midiState.noteOnEvent(channel, noteNumber, velocity); midiState.noteOnEvent(noteNumber, velocity);
AtomicGuard callbackGuard { inCallback }; AtomicGuard callbackGuard { inCallback };
if (!canEnterCallback) if (!canEnterCallback)
@ -400,22 +398,22 @@ void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity
auto randValue = randNoteDistribution(Random::randomGenerator); auto randValue = randNoteDistribution(Random::randomGenerator);
for (auto& region : noteActivationLists[noteNumber]) { for (auto& region : noteActivationLists[noteNumber]) {
if (region->registerNoteOn(channel, noteNumber, velocity, randValue)) { if (region->registerNoteOn(noteNumber, velocity, randValue)) {
for (auto& voice : voices) { for (auto& voice : voices) {
if (voice->checkOffGroup(delay, region->group)) if (voice->checkOffGroup(delay, region->group))
noteOff(delay, voice->getTriggerChannel(), voice->getTriggerNumber(), 0); noteOff(delay, voice->getTriggerNumber(), 0);
} }
auto voice = findFreeVoice(); auto voice = findFreeVoice();
if (voice == nullptr) if (voice == nullptr)
continue; continue;
voice->startVoice(region, delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOn); voice->startVoice(region, delay, noteNumber, velocity, Voice::TriggerType::NoteOn);
} }
} }
} }
void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocity [[maybe_unused]]) noexcept void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity [[maybe_unused]]) noexcept
{ {
ASSERT(noteNumber < 128); ASSERT(noteNumber < 128);
ASSERT(noteNumber >= 0); ASSERT(noteNumber >= 0);
@ -427,24 +425,24 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit
// FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a // FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a
// way in sfz to specify that a release trigger should NOT use the note-on velocity? // way in sfz to specify that a release trigger should NOT use the note-on velocity?
// auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity); // auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity);
auto replacedVelocity = midiState.getNoteVelocity(channel, noteNumber); auto replacedVelocity = midiState.getNoteVelocity(noteNumber);
auto randValue = randNoteDistribution(Random::randomGenerator); auto randValue = randNoteDistribution(Random::randomGenerator);
for (auto& voice : voices) for (auto& voice : voices)
voice->registerNoteOff(delay, channel, noteNumber, replacedVelocity); voice->registerNoteOff(delay, noteNumber, replacedVelocity);
for (auto& region : noteActivationLists[noteNumber]) { for (auto& region : noteActivationLists[noteNumber]) {
if (region->registerNoteOff(channel, noteNumber, replacedVelocity, randValue)) { if (region->registerNoteOff(noteNumber, replacedVelocity, randValue)) {
auto voice = findFreeVoice(); auto voice = findFreeVoice();
if (voice == nullptr) if (voice == nullptr)
continue; continue;
voice->startVoice(region, delay, channel, noteNumber, replacedVelocity, Voice::TriggerType::NoteOff); voice->startVoice(region, delay, noteNumber, replacedVelocity, Voice::TriggerType::NoteOff);
} }
} }
} }
void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexcept void sfz::Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept
{ {
ASSERT(ccNumber < config::numCCs); ASSERT(ccNumber < config::numCCs);
ASSERT(ccNumber >= 0); ASSERT(ccNumber >= 0);
@ -454,36 +452,36 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc
return; return;
if (ccNumber == config::resetCC) { if (ccNumber == config::resetCC) {
resetAllControllers(delay, channel); resetAllControllers(delay);
return; return;
} }
midiState.ccEvent(channel, ccNumber, ccValue); midiState.ccEvent(ccNumber, ccValue);
for (auto& voice : voices) for (auto& voice : voices)
voice->registerCC(delay, channel, ccNumber, ccValue); voice->registerCC(delay, ccNumber, ccValue);
for (auto& region : ccActivationLists[ccNumber]) { for (auto& region : ccActivationLists[ccNumber]) {
if (region->registerCC(channel, ccNumber, ccValue)) { if (region->registerCC(ccNumber, ccValue)) {
auto voice = findFreeVoice(); auto voice = findFreeVoice();
if (voice == nullptr) if (voice == nullptr)
continue; continue;
voice->startVoice(region, delay, channel, ccNumber, ccValue, Voice::TriggerType::CC); voice->startVoice(region, delay, ccNumber, ccValue, Voice::TriggerType::CC);
} }
} }
} }
void sfz::Synth::pitchWheel(int delay, int channel, int pitch) noexcept void sfz::Synth::pitchWheel(int delay, int pitch) noexcept
{ {
ASSERT(pitch <= 8192); ASSERT(pitch <= 8192);
ASSERT(pitch >= -8192); ASSERT(pitch >= -8192);
midiState.pitchBendEvent(channel, pitch); midiState.pitchBendEvent(pitch);
for (auto& voice: voices) { for (auto& voice: voices) {
voice->registerPitchWheel(delay, channel, pitch); voice->registerPitchWheel(delay, pitch);
} }
} }
void sfz::Synth::aftertouch(int /* delay */, int /*channel*/, uint8_t /* aftertouch */) noexcept void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept
{ {
} }
void sfz::Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept void sfz::Synth::tempo(int /* delay */, float /* secondsPerQuarter */) noexcept
@ -617,21 +615,21 @@ void sfz::Synth::disableFreeWheeling() noexcept
} }
} }
void sfz::Synth::resetAllControllers(int delay, int channel) noexcept void sfz::Synth::resetAllControllers(int delay) noexcept
{ {
AtomicGuard callbackGuard { inCallback }; AtomicGuard callbackGuard { inCallback };
if (!canEnterCallback) if (!canEnterCallback)
return; return;
midiState.resetAllControllers(channel); midiState.resetAllControllers();
for (auto& voice: voices) { for (auto& voice: voices) {
voice->registerPitchWheel(delay, channel, 0); voice->registerPitchWheel(delay, 0);
for (int cc = 0; cc < config::numCCs; ++cc) for (int cc = 0; cc < config::numCCs; ++cc)
voice->registerCC(delay, channel, cc, 0); voice->registerCC(delay, cc, 0);
} }
for (auto& region: regions) { for (auto& region: regions) {
for (int cc = 0; cc < config::numCCs; ++cc) for (int cc = 0; cc < config::numCCs; ++cc)
region->registerCC(channel, cc, 0); region->registerCC(cc, 0);
} }
} }

View file

@ -69,7 +69,7 @@ namespace sfz {
* synth should have a delay parameter strictly lower than 256. Events beyond 256 * synth should have a delay parameter strictly lower than 256. Events beyond 256
* may be completely ignored by the synth as the incoming event buffer is cleared * may be completely ignored by the synth as the incoming event buffer is cleared
* during the renderBlock() call. You SHOULD also feed the midi events in the correct * during the renderBlock() call. You SHOULD also feed the midi events in the correct
* order, at least within a channel. * order.
* *
* The jack_client.cpp file contains examples of the most classical usage of the * The jack_client.cpp file contains examples of the most classical usage of the
* synth and can be used as a reference. * synth and can be used as a reference.
@ -196,50 +196,45 @@ public:
* *
* @param delay the delay at which the event occurs; this should be lower * @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(). * than the size of the block in the next call to renderBlock().
* @param channel the midi channel for the event (0-based)
* @param noteNumber the midi note number * @param noteNumber the midi note number
* @param velocity the midi note velocity * @param velocity the midi note velocity
*/ */
void noteOn(int delay, int channel, int noteNumber, uint8_t velocity) noexcept; void noteOn(int delay, int noteNumber, uint8_t velocity) noexcept;
/** /**
* @brief Send a note off event to the synth * @brief Send a note off event to the synth
* *
* @param delay the delay at which the event occurs; this should be lower * @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(). * than the size of the block in the next call to renderBlock().
* @param channel the midi channel for the event (0-based)
* @param noteNumber the midi note number * @param noteNumber the midi note number
* @param velocity the midi note velocity * @param velocity the midi note velocity
*/ */
void noteOff(int delay, int channel, int noteNumber, uint8_t velocity) noexcept; void noteOff(int delay, int noteNumber, uint8_t velocity) noexcept;
/** /**
* @brief Send a CC event to the synth * @brief Send a CC event to the synth
* *
* @param delay the delay at which the event occurs; this should be lower than the size of * @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(). * the block in the next call to renderBlock().
* @param channel the midi channel for the event (0-based)
* @param ccNumber the cc number * @param ccNumber the cc number
* @param ccValue the cc value * @param ccValue the cc value
*/ */
void cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexcept; void cc(int delay, int ccNumber, uint8_t ccValue) noexcept;
/** /**
* @brief Send a pitch bend event to the synth * @brief Send a pitch bend event to the synth
* *
* @param delay the delay at which the event occurs; this should be lower * @param delay the delay at which the event occurs; this should be lower
* than the size of the block in the next call to * than the size of the block in the next call to
* renderBlock(). * renderBlock().
* @param channel the midi channel for the event (0-based)
* @param pitch the pitch value centered between -8192 and 8192 * @param pitch the pitch value centered between -8192 and 8192
*/ */
void pitchWheel(int delay, int channel, int pitch) noexcept; void pitchWheel(int delay, int pitch) noexcept;
/** /**
* @brief Send a aftertouch event to the synth * @brief Send a aftertouch event to the synth
* *
* @param delay the delay at which the event occurs; this should be lower than the size of * @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(). * the block in the next call to renderBlock().
* @param channel the midi channel for the event (0-based)
* @param aftertouch the aftertouch value * @param aftertouch the aftertouch value
*/ */
void aftertouch(int delay, int channel, uint8_t aftertouch) noexcept; void aftertouch(int delay, uint8_t aftertouch) noexcept;
/** /**
* @brief Send a tempo event to the synth * @brief Send a tempo event to the synth
* *
@ -363,10 +358,9 @@ private:
* @brief Reset all CCs; to be used on CC 121 * @brief Reset all CCs; to be used on CC 121
* *
* @param delay the delay for the controller reset * @param delay the delay for the controller reset
* @param channel the channel on which to reset the controller
* *
*/ */
void resetAllControllers(int delay, int channel) noexcept; void resetAllControllers(int delay) noexcept;
int numGroups { 0 }; int numGroups { 0 };
int numMasters { 0 }; int numMasters { 0 };

View file

@ -36,11 +36,10 @@ sfz::Voice::Voice(const sfz::MidiState& midiState, sfz::Resources& resources)
{ {
} }
void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, uint8_t value, sfz::Voice::TriggerType triggerType) noexcept void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value, sfz::Voice::TriggerType triggerType) noexcept
{ {
this->triggerType = triggerType; this->triggerType = triggerType;
triggerNumber = number; triggerNumber = number;
triggerChannel = channel;
triggerValue = value; triggerValue = value;
this->region = region; this->region = region;
@ -60,39 +59,39 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number,
} }
pitchRatio = region->getBasePitchVariation(number, value); pitchRatio = region->getBasePitchVariation(number, value);
baseVolumedB = region->getBaseVolumedB(channel, number); baseVolumedB = region->getBaseVolumedB(number);
auto volumedB { baseVolumedB }; auto volumedB { baseVolumedB };
if (region->volumeCC) if (region->volumeCC)
volumedB += normalizeCC(midiState.getCCValue(channel, region->volumeCC->first)) * region->volumeCC->second; volumedB += normalizeCC(midiState.getCCValue(region->volumeCC->first)) * region->volumeCC->second;
volumeEnvelope.reset(db2mag(volumedB)); volumeEnvelope.reset(db2mag(volumedB));
baseGain = region->getBaseGain(); baseGain = region->getBaseGain();
baseGain *= region->getCrossfadeGain(midiState.getCCArray(channel)); baseGain *= region->getCrossfadeGain(midiState.getCCArray());
if (triggerType != TriggerType::CC) if (triggerType != TriggerType::CC)
baseGain *= region->getNoteGain(number, value); baseGain *= region->getNoteGain(number, value);
float gain { baseGain }; float gain { baseGain };
if (region->amplitudeCC) if (region->amplitudeCC)
gain *= normalizeCC(midiState.getCCValue(channel, region->amplitudeCC->first)) * normalizePercents(region->amplitudeCC->second); gain *= normalizeCC(midiState.getCCValue(region->amplitudeCC->first)) * normalizePercents(region->amplitudeCC->second);
amplitudeEnvelope.reset(gain); amplitudeEnvelope.reset(gain);
basePan = normalizeNegativePercents(region->pan); basePan = normalizeNegativePercents(region->pan);
auto pan { basePan }; auto pan { basePan };
if (region->panCC) if (region->panCC)
pan += normalizeCC(midiState.getCCValue(channel, region->panCC->first)) * normalizeNegativePercents(region->panCC->second); pan += normalizeCC(midiState.getCCValue(region->panCC->first)) * normalizeNegativePercents(region->panCC->second);
panEnvelope.reset(pan); panEnvelope.reset(pan);
basePosition = normalizeNegativePercents(region->position); basePosition = normalizeNegativePercents(region->position);
auto position { basePosition }; auto position { basePosition };
if (region->positionCC) if (region->positionCC)
position += normalizeCC(midiState.getCCValue(channel, region->positionCC->first)) * normalizeNegativePercents(region->positionCC->second); position += normalizeCC(midiState.getCCValue(region->positionCC->first)) * normalizeNegativePercents(region->positionCC->second);
positionEnvelope.reset(position); positionEnvelope.reset(position);
baseWidth = normalizeNegativePercents(region->width); baseWidth = normalizeNegativePercents(region->width);
auto width { baseWidth }; auto width { baseWidth };
if (region->widthCC) if (region->widthCC)
width += normalizeCC(midiState.getCCValue(channel, region->widthCC->first)) * normalizeNegativePercents(region->widthCC->second); width += normalizeCC(midiState.getCCValue(region->widthCC->first)) * normalizeNegativePercents(region->widthCC->second);
widthEnvelope.reset(width); widthEnvelope.reset(width);
pitchBendEnvelope.setFunction([region](float pitchValue){ pitchBendEnvelope.setFunction([region](float pitchValue){
@ -100,21 +99,21 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number,
const auto bendInCents = normalizedBend > 0 ? normalizedBend * region->bendUp : -normalizedBend * region->bendDown; const auto bendInCents = normalizedBend > 0 ? normalizedBend * region->bendUp : -normalizedBend * region->bendDown;
return centsFactor(bendInCents); return centsFactor(bendInCents);
}); });
pitchBendEnvelope.reset(midiState.getPitchBend(channel)); pitchBendEnvelope.reset(midiState.getPitchBend());
sourcePosition = region->getOffset(); sourcePosition = region->getOffset();
initialDelay = delay + static_cast<uint32_t>(region->getDelay() * sampleRate); initialDelay = delay + static_cast<uint32_t>(region->getDelay() * sampleRate);
baseFrequency = midiNoteFrequency(number); baseFrequency = midiNoteFrequency(number);
bendStepFactor = centsFactor(region->bendStep); bendStepFactor = centsFactor(region->bendStep);
prepareEGEnvelope(channel, initialDelay, value); prepareEGEnvelope(initialDelay, value);
} }
void sfz::Voice::prepareEGEnvelope(int channel, int delay, uint8_t velocity) noexcept void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept
{ {
auto secondsToSamples = [this](auto timeInSeconds) { auto secondsToSamples = [this](auto timeInSeconds) {
return static_cast<int>(timeInSeconds * sampleRate); return static_cast<int>(timeInSeconds * sampleRate);
}; };
const auto& ccArray = midiState.getCCArray(channel); const auto& ccArray = midiState.getCCArray();
egEnvelope.reset( egEnvelope.reset(
secondsToSamples(region->amplitudeEG.getAttack(ccArray, velocity)), secondsToSamples(region->amplitudeEG.getAttack(ccArray, velocity)),
secondsToSamples(region->amplitudeEG.getRelease(ccArray, velocity)), secondsToSamples(region->amplitudeEG.getRelease(ccArray, velocity)),
@ -143,7 +142,7 @@ void sfz::Voice::release(int delay, bool fastRelease) noexcept
} }
} }
void sfz::Voice::registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity [[maybe_unused]]) noexcept void sfz::Voice::registerNoteOff(int delay, int noteNumber, uint8_t velocity [[maybe_unused]]) noexcept
{ {
if (region == nullptr) if (region == nullptr)
return; return;
@ -151,18 +150,18 @@ void sfz::Voice::registerNoteOff(int delay, int channel, int noteNumber, uint8_t
if (state != State::playing) if (state != State::playing)
return; return;
if (triggerChannel == channel && triggerNumber == noteNumber) { if (triggerNumber == noteNumber) {
noteIsOff = true; noteIsOff = true;
if (region->loopMode == SfzLoopMode::one_shot) if (region->loopMode == SfzLoopMode::one_shot)
return; return;
if (!region->checkSustain || midiState.getCCValue(channel, config::sustainCC) < config::halfCCThreshold) if (!region->checkSustain || midiState.getCCValue(config::sustainCC) < config::halfCCThreshold)
release(delay); release(delay);
} }
} }
void sfz::Voice::registerCC(int delay, int channel, int ccNumber, uint8_t ccValue) noexcept void sfz::Voice::registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept
{ {
if (region == nullptr) if (region == nullptr)
return; return;
@ -170,9 +169,6 @@ void sfz::Voice::registerCC(int delay, int channel, int ccNumber, uint8_t ccValu
if (state == State::idle) if (state == State::idle)
return; return;
if (triggerChannel != channel)
return;
if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) { if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) {
reset(); reset();
return; return;
@ -207,21 +203,15 @@ void sfz::Voice::registerCC(int delay, int channel, int ccNumber, uint8_t ccValu
} }
} }
void sfz::Voice::registerPitchWheel(int delay, int channel, int pitch) noexcept void sfz::Voice::registerPitchWheel(int delay, int pitch) noexcept
{ {
if (channel != triggerChannel)
return;
if (state == State::idle) if (state == State::idle)
return; return;
if (triggerChannel != channel)
return;
pitchBendEnvelope.registerEvent(delay, pitch); pitchBendEnvelope.registerEvent(delay, pitch);
} }
void sfz::Voice::registerAftertouch(int delay [[maybe_unused]], int channel [[maybe_unused]], uint8_t aftertouch [[maybe_unused]]) noexcept void sfz::Voice::registerAftertouch(int delay [[maybe_unused]], uint8_t aftertouch [[maybe_unused]]) noexcept
{ {
// TODO // TODO
} }
@ -511,11 +501,6 @@ int sfz::Voice::getTriggerNumber() const noexcept
return triggerNumber; return triggerNumber;
} }
int sfz::Voice::getTriggerChannel() const noexcept
{
return triggerChannel;
}
uint8_t sfz::Voice::getTriggerValue() const noexcept uint8_t sfz::Voice::getTriggerValue() const noexcept
{ {
return triggerValue; return triggerValue;

View file

@ -92,54 +92,48 @@ public:
* *
* @param region * @param region
* @param delay * @param delay
* @param channel (0-based)
* @param number * @param number
* @param value * @param value
* @param triggerType * @param triggerType
*/ */
void startVoice(Region* region, int delay, int channel, int number, uint8_t value, TriggerType triggerType) noexcept; void startVoice(Region* region, int delay, int number, uint8_t value, TriggerType triggerType) noexcept;
/** /**
* @brief Register a note-off event; this may trigger a release. * @brief Register a note-off event; this may trigger a release.
* *
* @param delay * @param delay
* @param channel (0-based)
* @param noteNumber * @param noteNumber
* @param velocity * @param velocity
*/ */
void registerNoteOff(int delay, int channel, int noteNumber, uint8_t velocity) noexcept; void registerNoteOff(int delay, int noteNumber, uint8_t velocity) noexcept;
/** /**
* @brief Register a CC event; this may trigger a release. If the voice is playing and its * @brief Register a CC event; this may trigger a release. If the voice is playing and its
* region has CC modifiers, it will use this value to compute the CC envelope to apply to the * region has CC modifiers, it will use this value to compute the CC envelope to apply to the
* parameter. * parameter.
* *
* @param delay * @param delay
* @param channel (0-based)
* @param ccNumber * @param ccNumber
* @param ccValue * @param ccValue
*/ */
void registerCC(int delay, int channel, int ccNumber, uint8_t ccValue) noexcept; void registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept;
/** /**
* @brief Register a pitch wheel event; for now this does nothing * @brief Register a pitch wheel event; for now this does nothing
* *
* @param delay * @param delay
* @param channel (0-based)
* @param pitch * @param pitch
*/ */
void registerPitchWheel(int delay, int channel, int pitch) noexcept; void registerPitchWheel(int delay, int pitch) noexcept;
/** /**
* @brief Register an aftertouch event; for now this does nothing * @brief Register an aftertouch event; for now this does nothing
* *
* @param delay * @param delay
* @param channel (0-based)
* @param aftertouch * @param aftertouch
*/ */
void registerAftertouch(int delay, int channel, uint8_t aftertouch) noexcept; void registerAftertouch(int delay, uint8_t aftertouch) noexcept;
/** /**
* @brief Register a tempo event; for now this does nothing * @brief Register a tempo event; for now this does nothing
* *
* @param delay * @param delay
* @param channel (0-based)
* @param pitch * @param pitch
*/ */
void registerTempo(int delay, float secondsPerQuarter) noexcept; void registerTempo(int delay, float secondsPerQuarter) noexcept;
@ -181,12 +175,6 @@ public:
* @return int * @return int
*/ */
int getTriggerNumber() const noexcept; int getTriggerNumber() const noexcept;
/**
* @brief Get the channel that triggered the voice
*
* @return int
*/
int getTriggerChannel() const noexcept;
/** /**
* @brief Get the value that triggered the voice (note velocity or cc value) * @brief Get the value that triggered the voice (note velocity or cc value)
* *
@ -243,11 +231,10 @@ private:
/** /**
* @brief Computes the values for the envelope depending on the note or CC number and the velocity/cc value * @brief Computes the values for the envelope depending on the note or CC number and the velocity/cc value
* *
* @param channel (0-based)
* @param delay * @param delay
* @param velocity * @param velocity
*/ */
void prepareEGEnvelope(int channel, int delay, uint8_t velocity) noexcept; void prepareEGEnvelope(int delay, uint8_t velocity) noexcept;
/** /**
* @brief The function processing a mono sample source * @brief The function processing a mono sample source
* *
@ -279,7 +266,6 @@ private:
TriggerType triggerType; TriggerType triggerType;
int triggerNumber; int triggerNumber;
int triggerChannel;
uint8_t triggerValue; uint8_t triggerValue;
float speedRatio { 1.0 }; float speedRatio { 1.0 };

View file

@ -88,30 +88,30 @@ void sfizz_set_sample_rate(sfizz_synth_t* synth, float sample_rate)
self->setSampleRate(sample_rate); self->setSampleRate(sample_rate);
} }
void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int channel, int note_number, char velocity) void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int note_number, char velocity)
{ {
auto self = reinterpret_cast<sfz::Synth*>(synth); auto self = reinterpret_cast<sfz::Synth*>(synth);
self->noteOn(delay, channel, note_number, velocity); self->noteOn(delay, note_number, velocity);
} }
void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int channel, int note_number, char velocity) void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int note_number, char velocity)
{ {
auto self = reinterpret_cast<sfz::Synth*>(synth); auto self = reinterpret_cast<sfz::Synth*>(synth);
self->noteOff(delay, channel, note_number, velocity); self->noteOff(delay, note_number, velocity);
} }
void sfizz_send_cc(sfizz_synth_t* synth, int delay, int channel, int cc_number, char cc_value) void sfizz_send_cc(sfizz_synth_t* synth, int delay, int cc_number, char cc_value)
{ {
auto self = reinterpret_cast<sfz::Synth*>(synth); auto self = reinterpret_cast<sfz::Synth*>(synth);
self->cc(delay, channel, cc_number, cc_value); self->cc(delay, cc_number, cc_value);
} }
void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay, int channel, int pitch) void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay, int pitch)
{ {
auto self = reinterpret_cast<sfz::Synth*>(synth); auto self = reinterpret_cast<sfz::Synth*>(synth);
self->pitchWheel(delay, channel, pitch); self->pitchWheel(delay, pitch);
} }
void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, int channel, char aftertouch) void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, char aftertouch)
{ {
auto self = reinterpret_cast<sfz::Synth*>(synth); auto self = reinterpret_cast<sfz::Synth*>(synth);
self->aftertouch(delay, channel, aftertouch); self->aftertouch(delay, aftertouch);
} }
void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float seconds_per_quarter) void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float seconds_per_quarter)
{ {

View file

@ -50,7 +50,7 @@ TEST_CASE("[Files] Basic opcodes (regions_opcodes.sfz)")
sfz::Synth synth; sfz::Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_opcodes.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Regions/regions_opcodes.sfz");
REQUIRE(synth.getNumRegions() == 1); REQUIRE(synth.getNumRegions() == 1);
REQUIRE(synth.getRegionView(0)->channelRange == sfz::Range<uint8_t>(1, 13)); REQUIRE(synth.getRegionView(0)->keyRange == sfz::Range<uint8_t>(2, 14));
} }
TEST_CASE("[Files] Underscore opcodes (underscore_opcodes.sfz)") TEST_CASE("[Files] Underscore opcodes (underscore_opcodes.sfz)")
@ -283,20 +283,20 @@ TEST_CASE("[Files] sw_default and playing with switches")
REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); REQUIRE( synth.getRegionView(1)->isSwitchedOn() );
REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(2)->isSwitchedOn() );
REQUIRE( synth.getRegionView(3)->isSwitchedOn() ); REQUIRE( synth.getRegionView(3)->isSwitchedOn() );
synth.noteOn(0, 1, 41, 64); synth.noteOn(0, 41, 64);
synth.noteOff(0, 1, 41, 0); synth.noteOff(0, 41, 0);
REQUIRE( synth.getRegionView(0)->isSwitchedOn() ); REQUIRE( synth.getRegionView(0)->isSwitchedOn() );
REQUIRE( !synth.getRegionView(1)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(1)->isSwitchedOn() );
REQUIRE( synth.getRegionView(2)->isSwitchedOn() ); REQUIRE( synth.getRegionView(2)->isSwitchedOn() );
REQUIRE( !synth.getRegionView(3)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(3)->isSwitchedOn() );
synth.noteOn(0, 1, 42, 64); synth.noteOn(0, 42, 64);
synth.noteOff(0, 1, 42, 0); synth.noteOff(0, 42, 0);
REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(0)->isSwitchedOn() );
REQUIRE( !synth.getRegionView(1)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(1)->isSwitchedOn() );
REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(2)->isSwitchedOn() );
REQUIRE( !synth.getRegionView(3)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(3)->isSwitchedOn() );
synth.noteOn(0, 1, 40, 64); synth.noteOn(0, 40, 64);
synth.noteOff(0, 1, 40, 64); synth.noteOff(0, 40, 64);
REQUIRE( !synth.getRegionView(0)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(0)->isSwitchedOn() );
REQUIRE( synth.getRegionView(1)->isSwitchedOn() ); REQUIRE( synth.getRegionView(1)->isSwitchedOn() );
REQUIRE( !synth.getRegionView(2)->isSwitchedOn() ); REQUIRE( !synth.getRegionView(2)->isSwitchedOn() );
@ -355,14 +355,12 @@ TEST_CASE("[Files] Default path is ignored for generators")
REQUIRE(synth.getRegionView(0)->sample == R"(*sine)"); REQUIRE(synth.getRegionView(0)->sample == R"(*sine)");
} }
TEST_CASE("[Files] Set CC applies properly to all channels") TEST_CASE("[Files] Set CC applies properly")
{ {
sfz::Synth synth; sfz::Synth synth;
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_cc.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_cc.sfz");
for (int channel = 0; channel < 16; channel++) { REQUIRE(synth.getMidiState().getCCValue(142) == 63);
REQUIRE(synth.getMidiState().getCCValue(channel, 142) == 63); REQUIRE(synth.getMidiState().getCCValue(61) == 122);
REQUIRE(synth.getMidiState().getCCValue(channel, 61) == 122);
}
} }
TEST_CASE("[Files] Note and octave offsets") TEST_CASE("[Files] Note and octave offsets")
@ -412,7 +410,7 @@ TEST_CASE("[Files] Off modes")
synth.setSamplesPerBlock(256); synth.setSamplesPerBlock(256);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/off_mode.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/off_mode.sfz");
REQUIRE( synth.getNumRegions() == 3 ); REQUIRE( synth.getNumRegions() == 3 );
synth.noteOn(0, 0, 64, 63); synth.noteOn(0, 64, 63);
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
const auto* fastVoice = const auto* fastVoice =
synth.getVoiceView(0)->getRegion()->offMode == SfzOffMode::fast ? synth.getVoiceView(0)->getRegion()->offMode == SfzOffMode::fast ?
@ -422,7 +420,7 @@ TEST_CASE("[Files] Off modes")
synth.getVoiceView(0)->getRegion()->offMode == SfzOffMode::fast ? synth.getVoiceView(0)->getRegion()->offMode == SfzOffMode::fast ?
synth.getVoiceView(1) : synth.getVoiceView(1) :
synth.getVoiceView(0) ; synth.getVoiceView(0) ;
synth.noteOn(100, 0, 63, 63); synth.noteOn(100, 63, 63);
REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( synth.getNumActiveVoices() == 3 );
sfz::AudioBuffer<float> buffer { 2, 256 }; sfz::AudioBuffer<float> buffer { 2, 256 };
synth.renderBlock(buffer); synth.renderBlock(buffer);

View file

@ -35,69 +35,56 @@ using namespace Catch::literals;
TEST_CASE("[MidiState] Initial values") TEST_CASE("[MidiState] Initial values")
{ {
sfz::MidiState state; sfz::MidiState state;
for (auto& cc: state.getCCArray(1)) for (auto& cc: state.getCCArray())
REQUIRE( cc == 0 ); REQUIRE( cc == 0 );
for (auto& cc: state.getCCArray(6)) REQUIRE( state.getPitchBend() == 0 );
REQUIRE( cc == 0 );
for (int channel = 0; channel < 16; ++channel)
REQUIRE( state.getPitchBend(channel) == 0 );
} }
TEST_CASE("[MidiState] Set and get CCs") TEST_CASE("[MidiState] Set and get CCs")
{ {
sfz::MidiState state; sfz::MidiState state;
const auto& cc0 = state.getCCArray(0); const auto& cc = state.getCCArray();
const auto& cc6 = state.getCCArray(6); state.ccEvent(24, 23);
const auto& cc12 = state.getCCArray(12); state.ccEvent(123, 124);
state.ccEvent(0, 24, 23); REQUIRE(state.getCCValue(24) == 23);
state.ccEvent(6, 123, 124); REQUIRE(cc[24] == 23);
REQUIRE(state.getCCValue(0, 24) == 23); REQUIRE(state.getCCValue(123) == 124);
REQUIRE(cc0[24] == 23); REQUIRE(cc[123] == 124);
REQUIRE(state.getCCValue(6, 123) == 124);
REQUIRE(cc6[123] == 124);
REQUIRE(+state.getCCValue(12, 24) == 0);
REQUIRE(+state.getCCValue(12, 123) == 0);
REQUIRE(cc12[24] == 0);
REQUIRE(cc12[123] == 0);
} }
TEST_CASE("[MidiState] Set and get pitch bends") TEST_CASE("[MidiState] Set and get pitch bends")
{ {
sfz::MidiState state; sfz::MidiState state;
state.pitchBendEvent(0, 894); state.pitchBendEvent(894);
REQUIRE(state.getPitchBend(0) == 894); REQUIRE(state.getPitchBend() == 894);
REQUIRE(state.getPitchBend(6) == 0); state.pitchBendEvent(0);
state.pitchBendEvent(0, 0); REQUIRE(state.getPitchBend() == 0);
REQUIRE(state.getPitchBend(0) == 0);
REQUIRE(state.getPitchBend(6) == 0);
} }
TEST_CASE("[MidiState] Reset") TEST_CASE("[MidiState] Reset")
{ {
sfz::MidiState state; sfz::MidiState state;
state.pitchBendEvent(0, 894); state.pitchBendEvent(894);
state.noteOnEvent(6, 64, 24); state.noteOnEvent(64, 24);
state.ccEvent(15, 123, 124); state.ccEvent(123, 124);
state.reset(); state.reset();
REQUIRE(state.getPitchBend(0) == 0); REQUIRE(state.getPitchBend() == 0);
REQUIRE(state.getNoteVelocity(6, 64) == 0); REQUIRE(state.getNoteVelocity(64) == 0);
REQUIRE(state.getCCValue(15, 123) == 0); REQUIRE(state.getCCValue(123) == 0);
} }
TEST_CASE("[MidiState] Set and get note velocities") TEST_CASE("[MidiState] Set and get note velocities")
{ {
sfz::MidiState state; sfz::MidiState state;
state.noteOnEvent(0, 64, 24); state.noteOnEvent(64, 24);
REQUIRE(+state.getNoteVelocity(0, 64) == 24); REQUIRE(+state.getNoteVelocity(64) == 24);
REQUIRE(+state.getNoteVelocity(1, 64) == 0); state.noteOnEvent(64, 123);
state.noteOnEvent(0, 64, 123); REQUIRE(+state.getNoteVelocity(64) == 123);
REQUIRE(+state.getNoteVelocity(0, 64) == 123);
REQUIRE(+state.getNoteVelocity(15, 64) == 0);
} }
TEST_CASE("[MidiState] Extended CCs") TEST_CASE("[MidiState] Extended CCs")
{ {
sfz::MidiState state; sfz::MidiState state;
REQUIRE(state.getCCArray(1).size() >= 142); REQUIRE(state.getCCArray().size() >= 142);
state.ccEvent(6, 142, 64); // should not trap state.ccEvent(142, 64); // should not trap
} }

View file

@ -29,11 +29,11 @@ TEST_CASE("Region activation", "Region tests")
{ {
sfz::MidiState midiState; sfz::MidiState midiState;
sfz::Region region { midiState }; sfz::Region region { midiState };
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
SECTION("Basic state") SECTION("Basic state")
{ {
region.registerCC(1, 4, 0); region.registerCC(4, 0);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
} }
@ -41,19 +41,19 @@ TEST_CASE("Region activation", "Region tests")
{ {
region.parseOpcode({ "locc4", "56" }); region.parseOpcode({ "locc4", "56" });
region.parseOpcode({ "hicc4", "59" }); region.parseOpcode({ "hicc4", "59" });
region.registerCC(1, 4, 0); region.registerCC(4, 0);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerCC(1, 4, 57); region.registerCC(4, 57);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 4, 56); region.registerCC(4, 56);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 4, 59); region.registerCC(4, 59);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 4, 43); region.registerCC(4, 43);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerCC(1, 4, 65); region.registerCC(4, 65);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerCC(1, 6, 57); region.registerCC(6, 57);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
} }
@ -63,26 +63,26 @@ TEST_CASE("Region activation", "Region tests")
region.parseOpcode({ "hicc4", "59" }); region.parseOpcode({ "hicc4", "59" });
region.parseOpcode({ "locc54", "18" }); region.parseOpcode({ "locc54", "18" });
region.parseOpcode({ "hicc54", "27" }); region.parseOpcode({ "hicc54", "27" });
region.registerCC(1, 4, 0); region.registerCC(4, 0);
region.registerCC(1, 54, 0); region.registerCC(54, 0);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerCC(1, 4, 57); region.registerCC(4, 57);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerCC(1, 54, 19); region.registerCC(54, 19);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 54, 18); region.registerCC(54, 18);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 54, 27); region.registerCC(54, 27);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 4, 56); region.registerCC(4, 56);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 4, 59); region.registerCC(4, 59);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 54, 2); region.registerCC(54, 2);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerCC(1, 54, 26); region.registerCC(54, 26);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerCC(1, 4, 65); region.registerCC(4, 65);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
} }
@ -90,13 +90,13 @@ TEST_CASE("Region activation", "Region tests")
{ {
region.parseOpcode({ "lobend", "56" }); region.parseOpcode({ "lobend", "56" });
region.parseOpcode({ "hibend", "243" }); region.parseOpcode({ "hibend", "243" });
region.registerPitchWheel(1, 0); region.registerPitchWheel(0);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerPitchWheel(1, 56); region.registerPitchWheel(56);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerPitchWheel(1, 243); region.registerPitchWheel(243);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerPitchWheel(1, 245); region.registerPitchWheel(245);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
} }
@ -104,13 +104,13 @@ TEST_CASE("Region activation", "Region tests")
{ {
region.parseOpcode({ "lochanaft", "56" }); region.parseOpcode({ "lochanaft", "56" });
region.parseOpcode({ "hichanaft", "68" }); region.parseOpcode({ "hichanaft", "68" });
region.registerAftertouch(1, 0); region.registerAftertouch(0);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerAftertouch(1, 56); region.registerAftertouch(56);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerAftertouch(1, 68); region.registerAftertouch(68);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerAftertouch(1, 98); region.registerAftertouch(98);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
} }
@ -133,13 +133,13 @@ TEST_CASE("Region activation", "Region tests")
{ {
region.parseOpcode({ "sw_last", "40" }); region.parseOpcode({ "sw_last", "40" });
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 64, 0.5f); region.registerNoteOff(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 41, 64, 0.5f); region.registerNoteOn(41, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 41, 0, 0.5f); region.registerNoteOff(41, 0, 0.5f);
} }
SECTION("Keyswitches: sw_last with non-default keyswitch range") SECTION("Keyswitches: sw_last with non-default keyswitch range")
@ -148,20 +148,20 @@ TEST_CASE("Region activation", "Region tests")
region.parseOpcode({ "sw_hikey", "50" }); region.parseOpcode({ "sw_hikey", "50" });
region.parseOpcode({ "sw_last", "40" }); region.parseOpcode({ "sw_last", "40" });
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 60, 64, 0.5f); region.registerNoteOn(60, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 60, 0, 0.5f); region.registerNoteOff(60, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 60, 64, 0.5f); region.registerNoteOn(60, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 60, 0, 0.5f); region.registerNoteOff(60, 0, 0.5f);
region.registerNoteOn(1, 41, 64, 0.5f); region.registerNoteOn(41, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 41, 0, 0.5f); region.registerNoteOff(41, 0, 0.5f);
} }
SECTION("Keyswitches: sw_down with non-default keyswitch range") SECTION("Keyswitches: sw_down with non-default keyswitch range")
@ -170,20 +170,20 @@ TEST_CASE("Region activation", "Region tests")
region.parseOpcode({ "sw_hikey", "50" }); region.parseOpcode({ "sw_hikey", "50" });
region.parseOpcode({ "sw_down", "40" }); region.parseOpcode({ "sw_down", "40" });
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 60, 64, 0.5f); region.registerNoteOn(60, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 60, 0, 0.5f); region.registerNoteOff(60, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 60, 64, 0.5f); region.registerNoteOn(60, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 60, 0, 0.5f); region.registerNoteOff(60, 0, 0.5f);
region.registerNoteOn(1, 41, 64, 0.5f); region.registerNoteOn(41, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 41, 0, 0.5f); region.registerNoteOff(41, 0, 0.5f);
} }
SECTION("Keyswitches: sw_up with non-default keyswitch range") SECTION("Keyswitches: sw_up with non-default keyswitch range")
@ -192,16 +192,16 @@ TEST_CASE("Region activation", "Region tests")
region.parseOpcode({ "sw_hikey", "50" }); region.parseOpcode({ "sw_hikey", "50" });
region.parseOpcode({ "sw_up", "40" }); region.parseOpcode({ "sw_up", "40" });
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 41, 64, 0.5f); region.registerNoteOn(41, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
region.registerNoteOff(1, 41, 0, 0.5f); region.registerNoteOff(41, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
} }
@ -209,20 +209,20 @@ TEST_CASE("Region activation", "Region tests")
{ {
region.parseOpcode({ "sw_previous", "40" }); region.parseOpcode({ "sw_previous", "40" });
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 41, 64, 0.5f); region.registerNoteOn(41, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
region.registerNoteOff(1, 41, 0, 0.5f); region.registerNoteOff(41, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 41, 64, 0.5f); region.registerNoteOn(41, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 41, 0, 0.5f); region.registerNoteOff(41, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
} }
@ -232,17 +232,17 @@ TEST_CASE("Region activation", "Region tests")
region.parseOpcode({ "seq_position", "1" }); region.parseOpcode({ "seq_position", "1" });
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
} }
SECTION("Sequences: length 2, position 2") SECTION("Sequences: length 2, position 2")
@ -251,17 +251,17 @@ TEST_CASE("Region activation", "Region tests")
region.parseOpcode({ "seq_position", "2" }); region.parseOpcode({ "seq_position", "2" });
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
} }
SECTION("Sequences: length 3, position 2") SECTION("Sequences: length 3, position 2")
@ -270,21 +270,21 @@ TEST_CASE("Region activation", "Region tests")
region.parseOpcode({ "seq_position", "2" }); region.parseOpcode({ "seq_position", "2" });
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(!region.isSwitchedOn()); REQUIRE(!region.isSwitchedOn());
region.registerNoteOn(1, 40, 64, 0.5f); region.registerNoteOn(40, 64, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
region.registerNoteOff(1, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
REQUIRE(region.isSwitchedOn()); REQUIRE(region.isSwitchedOn());
} }
} }

View file

@ -236,23 +236,6 @@ TEST_CASE("[Region] Parsing opcodes")
REQUIRE(region.velocityRange == sfz::Range<uint8_t>(0, 127)); REQUIRE(region.velocityRange == sfz::Range<uint8_t>(0, 127));
} }
SECTION("lochan, hichan")
{
REQUIRE(region.channelRange == sfz::Range<uint8_t>(0, 15));
region.parseOpcode({ "lochan", "4" });
REQUIRE(region.channelRange == sfz::Range<uint8_t>(3, 15));
region.parseOpcode({ "lochan", "128" });
REQUIRE(region.channelRange == sfz::Range<uint8_t>(15, 15));
region.parseOpcode({ "lochan", "-3" });
REQUIRE(region.channelRange == sfz::Range<uint8_t>(0, 15));
region.parseOpcode({ "hichan", "13" });
REQUIRE(region.channelRange == sfz::Range<uint8_t>(0, 12));
region.parseOpcode({ "hichan", "-1" });
REQUIRE(region.channelRange == sfz::Range<uint8_t>(0, 0));
region.parseOpcode({ "hichan", "128" });
REQUIRE(region.channelRange == sfz::Range<uint8_t>(0, 15));
}
SECTION("lobend, hibend") SECTION("lobend, hibend")
{ {
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192)); REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192));

View file

@ -34,44 +34,44 @@ TEST_CASE("Basic triggers", "Region triggers")
SECTION("key") SECTION("key")
{ {
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
REQUIRE(region.registerNoteOn(0, 40, 64, 0.5f)); REQUIRE(region.registerNoteOn(40, 64, 0.5f));
REQUIRE(!region.registerNoteOff(0, 40, 64, 0.5f)); REQUIRE(!region.registerNoteOff(40, 64, 0.5f));
REQUIRE(!region.registerNoteOn(0, 41, 64, 0.5f)); REQUIRE(!region.registerNoteOn(41, 64, 0.5f));
REQUIRE(!region.registerCC(0, 63, 64)); REQUIRE(!region.registerCC(63, 64));
} }
SECTION("lokey and hikey") SECTION("lokey and hikey")
{ {
region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "lokey", "40" });
region.parseOpcode({ "hikey", "42" }); region.parseOpcode({ "hikey", "42" });
REQUIRE(!region.registerNoteOn(0, 39, 64, 0.5f)); REQUIRE(!region.registerNoteOn(39, 64, 0.5f));
REQUIRE(region.registerNoteOn(0, 40, 64, 0.5f)); REQUIRE(region.registerNoteOn(40, 64, 0.5f));
REQUIRE(!region.registerNoteOff(0, 40, 64, 0.5f)); REQUIRE(!region.registerNoteOff(40, 64, 0.5f));
REQUIRE(region.registerNoteOn(0, 41, 64, 0.5f)); REQUIRE(region.registerNoteOn(41, 64, 0.5f));
REQUIRE(region.registerNoteOn(0, 42, 64, 0.5f)); REQUIRE(region.registerNoteOn(42, 64, 0.5f));
REQUIRE(!region.registerNoteOn(0, 43, 64, 0.5f)); REQUIRE(!region.registerNoteOn(43, 64, 0.5f));
REQUIRE(!region.registerNoteOff(0, 42, 64, 0.5f)); REQUIRE(!region.registerNoteOff(42, 64, 0.5f));
REQUIRE(!region.registerNoteOff(0, 42, 64, 0.5f)); REQUIRE(!region.registerNoteOff(42, 64, 0.5f));
REQUIRE(!region.registerCC(0, 63, 64)); REQUIRE(!region.registerCC(63, 64));
} }
SECTION("key and release trigger") SECTION("key and release trigger")
{ {
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "trigger", "release" });
REQUIRE(!region.registerNoteOn(0, 40, 64, 0.5f)); REQUIRE(!region.registerNoteOn(40, 64, 0.5f));
REQUIRE(region.registerNoteOff(0, 40, 64, 0.5f)); REQUIRE(region.registerNoteOff(40, 64, 0.5f));
REQUIRE(!region.registerNoteOn(0, 41, 64, 0.5f)); REQUIRE(!region.registerNoteOn(41, 64, 0.5f));
REQUIRE(!region.registerNoteOff(0, 41, 64, 0.5f)); REQUIRE(!region.registerNoteOff(41, 64, 0.5f));
REQUIRE(!region.registerCC(0, 63, 64)); REQUIRE(!region.registerCC(63, 64));
} }
SECTION("key and release_key trigger") SECTION("key and release_key trigger")
{ {
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
region.parseOpcode({ "trigger", "release_key" }); region.parseOpcode({ "trigger", "release_key" });
REQUIRE(!region.registerNoteOn(0, 40, 64, 0.5f)); REQUIRE(!region.registerNoteOn(40, 64, 0.5f));
REQUIRE(region.registerNoteOff(0, 40, 64, 0.5f)); REQUIRE(region.registerNoteOff(40, 64, 0.5f));
REQUIRE(!region.registerNoteOn(0, 41, 64, 0.5f)); REQUIRE(!region.registerNoteOn(41, 64, 0.5f));
REQUIRE(!region.registerNoteOff(0, 41, 64, 0.5f)); REQUIRE(!region.registerNoteOff(41, 64, 0.5f));
REQUIRE(!region.registerCC(0, 63, 64)); REQUIRE(!region.registerCC(63, 64));
} }
// TODO: first and legato triggers // TODO: first and legato triggers
SECTION("lovel and hivel") SECTION("lovel and hivel")
@ -79,22 +79,11 @@ TEST_CASE("Basic triggers", "Region triggers")
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
region.parseOpcode({ "lovel", "60" }); region.parseOpcode({ "lovel", "60" });
region.parseOpcode({ "hivel", "70" }); region.parseOpcode({ "hivel", "70" });
REQUIRE(region.registerNoteOn(0, 40, 64, 0.5f)); REQUIRE(region.registerNoteOn(40, 64, 0.5f));
REQUIRE(region.registerNoteOn(0, 40, 60, 0.5f)); REQUIRE(region.registerNoteOn(40, 60, 0.5f));
REQUIRE(region.registerNoteOn(0, 40, 70, 0.5f)); REQUIRE(region.registerNoteOn(40, 70, 0.5f));
REQUIRE(!region.registerNoteOn(0, 41, 71, 0.5f)); REQUIRE(!region.registerNoteOn(41, 71, 0.5f));
REQUIRE(!region.registerNoteOn(0, 41, 59, 0.5f)); REQUIRE(!region.registerNoteOn(41, 59, 0.5f));
}
SECTION("lochan and hichan")
{
region.parseOpcode({ "key", "40" });
region.parseOpcode({ "lochan", "2" });
region.parseOpcode({ "hichan", "4" });
REQUIRE(!region.registerNoteOn(0, 40, 64, 0.5f));
REQUIRE(region.registerNoteOn(1, 40, 64, 0.5f));
REQUIRE(region.registerNoteOn(2, 40, 64, 0.5f));
REQUIRE(region.registerNoteOn(3, 40, 64, 0.5f));
REQUIRE(!region.registerNoteOn(4, 40, 64, 0.5f));
} }
SECTION("lorand and hirand") SECTION("lorand and hirand")
@ -102,54 +91,54 @@ TEST_CASE("Basic triggers", "Region triggers")
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
region.parseOpcode({ "lorand", "0.35" }); region.parseOpcode({ "lorand", "0.35" });
region.parseOpcode({ "hirand", "0.40" }); region.parseOpcode({ "hirand", "0.40" });
REQUIRE(!region.registerNoteOn(0, 40, 64, 0.34f)); REQUIRE(!region.registerNoteOn(40, 64, 0.34f));
REQUIRE(region.registerNoteOn(0, 40, 64, 0.35f)); REQUIRE(region.registerNoteOn(40, 64, 0.35f));
REQUIRE(region.registerNoteOn(0, 40, 64, 0.36f)); REQUIRE(region.registerNoteOn(40, 64, 0.36f));
REQUIRE(region.registerNoteOn(0, 40, 64, 0.37f)); REQUIRE(region.registerNoteOn(40, 64, 0.37f));
REQUIRE(region.registerNoteOn(0, 40, 64, 0.38f)); REQUIRE(region.registerNoteOn(40, 64, 0.38f));
REQUIRE(region.registerNoteOn(0, 40, 64, 0.39f)); REQUIRE(region.registerNoteOn(40, 64, 0.39f));
REQUIRE(!region.registerNoteOn(0, 40, 64, 0.40f)); REQUIRE(!region.registerNoteOn(40, 64, 0.40f));
REQUIRE(!region.registerNoteOn(0, 40, 64, 0.41f)); REQUIRE(!region.registerNoteOn(40, 64, 0.41f));
} }
SECTION("lorand and hirand on 1.0f") SECTION("lorand and hirand on 1.0f")
{ {
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
region.parseOpcode({ "lorand", "0.35" }); region.parseOpcode({ "lorand", "0.35" });
REQUIRE(!region.registerNoteOn(0, 40, 64, 0.34f)); REQUIRE(!region.registerNoteOn(40, 64, 0.34f));
REQUIRE(region.registerNoteOn(0, 40, 64, 0.35f)); REQUIRE(region.registerNoteOn(40, 64, 0.35f));
REQUIRE(region.registerNoteOn(0, 40, 64, 1.0f)); REQUIRE(region.registerNoteOn(40, 64, 1.0f));
} }
SECTION("Disable key trigger") SECTION("Disable key trigger")
{ {
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
REQUIRE(region.registerNoteOn(0, 40, 64, 1.0f)); REQUIRE(region.registerNoteOn(40, 64, 1.0f));
region.parseOpcode({ "hikey", "-1" }); region.parseOpcode({ "hikey", "-1" });
REQUIRE(!region.registerNoteOn(0, 40, 64, 1.0f)); REQUIRE(!region.registerNoteOn(40, 64, 1.0f));
region.parseOpcode({ "hikey", "40" }); region.parseOpcode({ "hikey", "40" });
REQUIRE(region.registerNoteOn(0, 40, 64, 1.0f)); REQUIRE(region.registerNoteOn(40, 64, 1.0f));
region.parseOpcode({ "key", "-1" }); region.parseOpcode({ "key", "-1" });
REQUIRE(!region.registerNoteOn(0, 40, 64, 1.0f)); REQUIRE(!region.registerNoteOn(40, 64, 1.0f));
region.parseOpcode({ "key", "40" }); region.parseOpcode({ "key", "40" });
REQUIRE(region.registerNoteOn(0, 40, 64, 1.0f)); REQUIRE(region.registerNoteOn(40, 64, 1.0f));
} }
SECTION("on_loccN, on_hiccN") SECTION("on_loccN, on_hiccN")
{ {
region.parseOpcode({ "on_locc47", "64" }); region.parseOpcode({ "on_locc47", "64" });
region.parseOpcode({ "on_hicc47", "68" }); region.parseOpcode({ "on_hicc47", "68" });
REQUIRE(!region.registerCC(0, 47, 63)); REQUIRE(!region.registerCC(47, 63));
REQUIRE(!region.registerCC(0, 47, 64)); REQUIRE(!region.registerCC(47, 64));
REQUIRE(!region.registerCC(0, 47, 65)); REQUIRE(!region.registerCC(47, 65));
region.parseOpcode({ "hikey", "-1" }); region.parseOpcode({ "hikey", "-1" });
REQUIRE(region.registerCC(0, 47, 64)); REQUIRE(region.registerCC(47, 64));
REQUIRE(region.registerCC(0, 47, 65)); REQUIRE(region.registerCC(47, 65));
REQUIRE(region.registerCC(0, 47, 66)); REQUIRE(region.registerCC(47, 66));
REQUIRE(region.registerCC(0, 47, 67)); REQUIRE(region.registerCC(47, 67));
REQUIRE(region.registerCC(0, 47, 68)); REQUIRE(region.registerCC(47, 68));
REQUIRE(!region.registerCC(0, 47, 69)); REQUIRE(!region.registerCC(47, 69));
REQUIRE(!region.registerCC(0, 40, 64)); REQUIRE(!region.registerCC(40, 64));
} }
} }
@ -163,11 +152,11 @@ TEST_CASE("Legato triggers", "Region triggers")
region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "lokey", "40" });
region.parseOpcode({ "hikey", "50" }); region.parseOpcode({ "hikey", "50" });
region.parseOpcode({ "trigger", "first" }); region.parseOpcode({ "trigger", "first" });
REQUIRE(region.registerNoteOn(0, 40, 64, 0.5f)); REQUIRE(region.registerNoteOn(40, 64, 0.5f));
REQUIRE(!region.registerNoteOn(0, 41, 64, 0.5f)); REQUIRE(!region.registerNoteOn(41, 64, 0.5f));
region.registerNoteOff(0, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
region.registerNoteOff(0, 41, 0, 0.5f); region.registerNoteOff(41, 0, 0.5f);
REQUIRE(region.registerNoteOn(0, 42, 64, 0.5f)); REQUIRE(region.registerNoteOn(42, 64, 0.5f));
} }
SECTION("Second note playing") SECTION("Second note playing")
@ -175,10 +164,10 @@ TEST_CASE("Legato triggers", "Region triggers")
region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "lokey", "40" });
region.parseOpcode({ "hikey", "50" }); region.parseOpcode({ "hikey", "50" });
region.parseOpcode({ "trigger", "legato" }); region.parseOpcode({ "trigger", "legato" });
REQUIRE(!region.registerNoteOn(0, 40, 64, 0.5f)); REQUIRE(!region.registerNoteOn(40, 64, 0.5f));
REQUIRE(region.registerNoteOn(0, 41, 64, 0.5f)); REQUIRE(region.registerNoteOn(41, 64, 0.5f));
region.registerNoteOff(0, 40, 0, 0.5f); region.registerNoteOff(40, 0, 0.5f);
region.registerNoteOff(0, 41, 0, 0.5f); region.registerNoteOff(41, 0, 0.5f);
REQUIRE(!region.registerNoteOn(0, 42, 64, 0.5f)); REQUIRE(!region.registerNoteOn(42, 64, 0.5f));
} }
} }

View file

@ -183,13 +183,13 @@ TEST_CASE("[Region] Crossfade in on CC")
region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_locc24", "20" });
region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "xfin_hicc24", "24" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
midiState.ccEvent(1, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a ); midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a );
midiState.ccEvent(1, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a ); midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a );
midiState.ccEvent(1, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.5_a ); midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a );
midiState.ccEvent(1, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.70711_a ); midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a );
midiState.ccEvent(1, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.86603_a ); midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a );
midiState.ccEvent(1, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a ); midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a );
midiState.ccEvent(1, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a ); midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a );
} }
TEST_CASE("[Region] Crossfade in on CC - gain") TEST_CASE("[Region] Crossfade in on CC - gain")
@ -201,13 +201,13 @@ TEST_CASE("[Region] Crossfade in on CC - gain")
region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "xfin_hicc24", "24" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
region.parseOpcode({ "xf_cccurve", "gain" }); region.parseOpcode({ "xf_cccurve", "gain" });
midiState.ccEvent(1, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a ); midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a );
midiState.ccEvent(1, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a ); midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a );
midiState.ccEvent(1, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.25_a ); midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a );
midiState.ccEvent(1, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.5_a ); midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a );
midiState.ccEvent(1, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.75_a ); midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a );
midiState.ccEvent(1, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a ); midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a );
midiState.ccEvent(1, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a ); midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a );
} }
TEST_CASE("[Region] Crossfade out on CC") TEST_CASE("[Region] Crossfade out on CC")
{ {
@ -217,13 +217,13 @@ TEST_CASE("[Region] Crossfade out on CC")
region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_locc24", "20" });
region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "xfout_hicc24", "24" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
midiState.ccEvent(1, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a ); midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a );
midiState.ccEvent(1, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a ); midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a );
midiState.ccEvent(1, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.86603_a ); midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a );
midiState.ccEvent(1, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.70711_a ); midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a );
midiState.ccEvent(1, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.5_a ); midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a );
midiState.ccEvent(1, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a ); midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a );
midiState.ccEvent(1, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a ); midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a );
} }
TEST_CASE("[Region] Crossfade out on CC - gain") TEST_CASE("[Region] Crossfade out on CC - gain")
@ -235,13 +235,13 @@ TEST_CASE("[Region] Crossfade out on CC - gain")
region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "xfout_hicc24", "24" });
region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "amp_veltrack", "0" });
region.parseOpcode({ "xf_cccurve", "gain" }); region.parseOpcode({ "xf_cccurve", "gain" });
midiState.ccEvent(1, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a ); midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a );
midiState.ccEvent(1, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a ); midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a );
midiState.ccEvent(1, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.75_a ); midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a );
midiState.ccEvent(1, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.5_a ); midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a );
midiState.ccEvent(1, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.25_a ); midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a );
midiState.ccEvent(1, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a ); midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a );
midiState.ccEvent(1, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a ); midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a );
} }
TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
@ -282,17 +282,17 @@ TEST_CASE("[Region] rt_decay")
region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "sample", "*sine" });
region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "trigger", "release" });
region.parseOpcode({ "rt_decay", "10" }); region.parseOpcode({ "rt_decay", "10" });
midiState.noteOnEvent(1, 64, 64); midiState.noteOnEvent(64, 64);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
REQUIRE( region.getBaseVolumedB(1, 64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) );
region.parseOpcode({ "rt_decay", "20" }); region.parseOpcode({ "rt_decay", "20" });
midiState.noteOnEvent(1, 64, 64); midiState.noteOnEvent(64, 64);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
REQUIRE( region.getBaseVolumedB(1, 64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) );
region.parseOpcode({ "trigger", "attack" }); region.parseOpcode({ "trigger", "attack" });
midiState.noteOnEvent(1, 64, 64); midiState.noteOnEvent(64, 64);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
REQUIRE( region.getBaseVolumedB(1, 64) == Approx(sfz::Default::volume).margin(0.1) ); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) );
} }
TEST_CASE("[Region] Base delay") TEST_CASE("[Region] Base delay")

View file

@ -35,8 +35,8 @@ TEST_CASE("[Synth] Play and check active voices")
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, blockSize };
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz");
synth.noteOn(0, 1, 36, 24); synth.noteOn(0, 36, 24);
synth.noteOn(0, 1, 36, 89); synth.noteOn(0, 36, 89);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
// Render for a while // Render for a while
for (int i = 0; i < 200; ++i) for (int i = 0; i < 200; ++i)
@ -51,8 +51,8 @@ TEST_CASE("[Synth] Change the number of voice while playing")
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, blockSize };
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz");
synth.noteOn(0, 1, 36, 24); synth.noteOn(0, 36, 24);
synth.noteOn(0, 1, 36, 89); synth.noteOn(0, 36, 89);
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
synth.setNumVoices(8); synth.setNumVoices(8);
@ -99,8 +99,8 @@ TEST_CASE("[Synth] Check that we can change the size of the preload before and a
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz");
synth.setPreloadSize(1024); synth.setPreloadSize(1024);
synth.noteOn(0, 1, 36, 24); synth.noteOn(0, 36, 24);
synth.noteOn(0, 1, 36, 89); synth.noteOn(0, 36, 89);
synth.renderBlock(buffer); synth.renderBlock(buffer);
synth.setPreloadSize(2048); synth.setPreloadSize(2048);
synth.renderBlock(buffer); synth.renderBlock(buffer);
@ -114,95 +114,39 @@ TEST_CASE("[Synth] Check that we can change the oversampling factor before and a
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz");
synth.setOversamplingFactor(sfz::Oversampling::x4); synth.setOversamplingFactor(sfz::Oversampling::x4);
synth.noteOn(0, 1, 36, 24); synth.noteOn(0, 36, 24);
synth.noteOn(0, 1, 36, 89); synth.noteOn(0, 36, 89);
synth.renderBlock(buffer); synth.renderBlock(buffer);
synth.setOversamplingFactor(sfz::Oversampling::x2); synth.setOversamplingFactor(sfz::Oversampling::x2);
synth.renderBlock(buffer); synth.renderBlock(buffer);
} }
TEST_CASE("[Synth] Testing channel 1") TEST_CASE("[Synth] All notes offs/all sounds off")
{
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" );
}
TEST_CASE("[Synth] All notes offs/all sounds off (single channel)")
{ {
sfz::Synth synth; sfz::Synth synth;
synth.setNumVoices(8); synth.setNumVoices(8);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/midi_channels.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/sound_off.sfz");
synth.noteOn(0, 0, 60, 63); synth.noteOn(0, 60, 63);
synth.noteOn(1, 0, 60, 63); synth.noteOn(0, 62, 63);
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
synth.cc(0, 0, 120, 63); synth.cc(0, 120, 63);
REQUIRE( synth.getNumActiveVoices() == 0 ); REQUIRE( synth.getNumActiveVoices() == 0 );
synth.noteOn(0, 0, 60, 63); synth.noteOn(0, 62, 63);
synth.noteOn(1, 0, 60, 63); synth.noteOn(0, 60, 63);
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
synth.cc(0, 0, 123, 63); synth.cc(0, 123, 63);
REQUIRE( synth.getNumActiveVoices() == 0 );
}
TEST_CASE("[Synth] All notes offs/all sounds off (multi channel)")
{
sfz::Synth synth;
synth.setNumVoices(8);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/midi_channels.sfz");
synth.noteOn(0, 0, 60, 63);
synth.noteOn(1, 1, 60, 63);
REQUIRE( synth.getNumActiveVoices() == 2 );
synth.cc(0, 0, 120, 63);
REQUIRE( synth.getNumActiveVoices() == 1 );
synth.noteOn(0, 0, 60, 63);
REQUIRE( synth.getNumActiveVoices() == 2 );
synth.cc(0, 0, 123, 63);
REQUIRE( synth.getNumActiveVoices() == 1 );
synth.cc(0, 1, 123, 63);
REQUIRE( synth.getNumActiveVoices() == 0 ); REQUIRE( synth.getNumActiveVoices() == 0 );
} }
TEST_CASE("[Synth] Reset all controllers") TEST_CASE("[Synth] Reset all controllers")
{ {
sfz::Synth synth; sfz::Synth synth;
synth.cc(0, 0, 12, 64); synth.cc(0, 12, 64);
synth.cc(0, 1, 12, 64); REQUIRE( synth.getMidiState().getCCValue(12) == 64 );
REQUIRE( synth.getMidiState().getCCValue(0, 12) == 64 ); synth.cc(0, 121, 64);
synth.cc(0, 0, 121, 64); REQUIRE( synth.getMidiState().getCCValue(12) == 0 );
REQUIRE( synth.getMidiState().getCCValue(0, 12) == 0 );
REQUIRE( synth.getMidiState().getCCValue(1, 12) == 64 );
} }
TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice") TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice")
@ -211,13 +155,13 @@ TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kil
synth.setSamplesPerBlock(1024); synth.setSamplesPerBlock(1024);
synth.setNumVoices(1); synth.setNumVoices(1);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/delay_release.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/delay_release.sfz");
synth.noteOn(0, 1, 60, 63); synth.noteOn(0, 60, 63);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
synth.noteOff(100, 1, 60, 63); synth.noteOff(100, 60, 63);
REQUIRE( synth.getVoiceView(0)->isFree() ); REQUIRE( synth.getVoiceView(0)->isFree() );
synth.noteOn(200, 1, 60, 63); synth.noteOn(200, 60, 63);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
synth.noteOff(1000, 1, 60, 63); synth.noteOff(1000, 60, 63);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
} }
@ -228,11 +172,11 @@ TEST_CASE("[Synth] Releasing after the initial and normal mode does not trigger
sfz::AudioBuffer<float> buffer(2, 1024); sfz::AudioBuffer<float> buffer(2, 1024);
synth.setNumVoices(1); synth.setNumVoices(1);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/delay_release.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/delay_release.sfz");
synth.noteOn(200, 1, 60, 63); synth.noteOn(200, 60, 63);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
synth.noteOff(0, 1, 60, 63); synth.noteOff(0, 60, 63);
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
} }

View file

@ -1 +1 @@
<region> lochan=2 hichan=14 sample=dummy.wav <region> lokey=2 hikey=14 sample=dummy.wav

View file

@ -1,3 +0,0 @@
<region> lochan=1 hichan=1 sample=dummy1.wav
<region> lochan=2 hichan=2 sample=dummy2.wav
<region> lochan=16 hichan=16 sample=dummy16.wav

View file

@ -0,0 +1,2 @@
<region> key=60 sample=dummy1.wav
<region> key=62 sample=dummy2.wav