Test and API doc update w.r.t. ordering

Updated tests mostly to call on `renderBlock()` before testing things like number of active voices and such, in case we buffer/reorder events in the library later on.
This commit is contained in:
Paul Fd 2021-04-05 00:18:33 +02:00
parent d0cd21acf9
commit cfb9eae68f
10 changed files with 611 additions and 294 deletions

View file

@ -325,11 +325,12 @@ SFIZZ_EXPORTED_API void sfizz_set_sample_rate(sfizz_synth_t* synth, float sample
/** /**
* @brief Send a note on event to the synth. * @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 block and should appear in order of the delays.
* @since 0.2.0 * @since 0.2.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @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 note_number The MIDI note number. * @param note_number The MIDI note number.
@ -342,12 +343,14 @@ SFIZZ_EXPORTED_API void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int
/** /**
* @brief Send a note off event to the synth. * @brief Send a note off event to the synth.
* @since 0.2.0
*
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
* *
* As with all MIDI events, this needs to happen before the call to
* sfizz_render_block() in each 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.
* @since 0.2.0
* *
* @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.
@ -361,11 +364,12 @@ SFIZZ_EXPORTED_API void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int
/** /**
* @brief Send a CC event to the synth. * @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 should appear in order of the delays.
* @since 0.2.0 * @since 0.2.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @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 cc_number The MIDI CC number. * @param cc_number The MIDI CC number.
@ -378,11 +382,12 @@ SFIZZ_EXPORTED_API void sfizz_send_cc(sfizz_synth_t* synth, int delay, int cc_nu
/** /**
* @brief Send a high precision CC event to the synth. * @brief Send a high precision 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 should appear in order of the delays.
* @since 0.4.0 * @since 0.4.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @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 cc_number The MIDI CC number. * @param cc_number The MIDI CC number.
@ -395,13 +400,14 @@ SFIZZ_EXPORTED_API void sfizz_send_hdcc(sfizz_synth_t* synth, int delay, int cc_
/** /**
* @brief Send a high precision CC automation to the synth. * @brief Send a high precision CC automation to the synth.
* @since 0.6.0
* *
* This updates the CC value known to the synth, but without performing * This updates the CC value known to the synth, but without performing
* additional MIDI-specific interpretations. (eg. the CC 120 and up) * additional MIDI-specific interpretations. (eg. the CC 120 and up)
* *
* As with all MIDI events, this needs to happen before the call to * This command should be delay-ordered with all other midi-type events
* sfizz_render_block() in each block and should appear in order of the delays. * (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* @since 0.6.0 * synth is undefined.
* *
* @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.
@ -415,11 +421,12 @@ SFIZZ_EXPORTED_API void sfizz_automate_hdcc(sfizz_synth_t* synth, int delay, int
/** /**
* @brief Send a pitch wheel event. * @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 should appear in order of the delays.
* @since 0.4.0 * @since 0.4.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @param synth The synth. * @param synth The synth.
* @param delay The delay. * @param delay The delay.
* @param pitch The pitch. * @param pitch The pitch.
@ -433,6 +440,10 @@ SFIZZ_EXPORTED_API void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay,
* @brief Send an aftertouch event. * @brief Send an aftertouch event.
* @since 0.2.0 * @since 0.2.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @param synth The synth. * @param synth 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().
@ -448,6 +459,10 @@ SFIZZ_EXPORTED_API void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, c
* in the internal engine. * in the internal engine.
* @since 0.6.0 * @since 0.6.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @param synth The synth. * @param synth 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().
@ -461,6 +476,11 @@ SFIZZ_EXPORTED_API void sfizz_send_poly_aftertouch(sfizz_synth_t* synth, int del
/** /**
* @brief Send a tempo event. * @brief Send a tempo event.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
*
* @since 0.2.0 * @since 0.2.0
* *
* @param synth The synth. * @param synth The synth.
@ -474,6 +494,11 @@ SFIZZ_EXPORTED_API void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float
/** /**
* @brief Send the time signature. * @brief Send the time signature.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
*
* @since 0.5.0 * @since 0.5.0
* *
* @param synth The synth. * @param synth The synth.
@ -488,6 +513,11 @@ SFIZZ_EXPORTED_API void sfizz_send_time_signature(sfizz_synth_t* synth, int dela
/** /**
* @brief Send the time position. * @brief Send the time position.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
*
* @since 0.5.0 * @since 0.5.0
* *
* @param synth The synth. * @param synth The synth.
@ -502,6 +532,11 @@ SFIZZ_EXPORTED_API void sfizz_send_time_position(sfizz_synth_t* synth, int delay
/** /**
* @brief Send the playback state. * @brief Send the playback state.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
*
* @since 0.5.0 * @since 0.5.0
* *
* @param synth The synth. * @param synth The synth.
@ -521,6 +556,7 @@ SFIZZ_EXPORTED_API void sfizz_send_playback_state(sfizz_synth_t* synth, int dela
* for the block (midi notes, CCs, ...) before rendering each block. * for the block (midi notes, CCs, ...) before rendering each block.
* The synth will memorize the inputs and render sample accurates envelopes * The synth will memorize the inputs and render sample accurates envelopes
* depending on the input events passed to it. * depending on the input events passed to it.
*
* @since 0.2.0 * @since 0.2.0
* *
* @param synth The synth. * @param synth The synth.

View file

@ -334,9 +334,12 @@ public:
/** /**
* @brief Send a note on event to the synth. * @brief Send a note on event to the synth.
*
* @since 0.2.0 * @since 0.2.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @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 noteNumber the midi note number. * @param noteNumber the midi note number.
@ -349,9 +352,12 @@ public:
/** /**
* @brief Send a note off event to the synth. * @brief Send a note off event to the synth.
*
* @since 0.2.0 * @since 0.2.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @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 noteNumber the midi note number. * @param noteNumber the midi note number.
@ -364,9 +370,12 @@ public:
/** /**
* @brief Send a CC event to the synth * @brief Send a CC event to the synth
*
* @since 0.2.0 * @since 0.2.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @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 ccNumber the cc number. * @param ccNumber the cc number.
@ -379,9 +388,12 @@ public:
/** /**
* @brief Send a high precision CC event to the synth * @brief Send a high precision CC event to the synth
*
* @since 0.4.0 * @since 0.4.0
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @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 ccNumber the cc number. * @param ccNumber the cc number.
@ -398,6 +410,10 @@ public:
* This updates the CC value known to the synth, but without performing * This updates the CC value known to the synth, but without performing
* additional MIDI-specific interpretations. (eg. the CC 120 and up) * additional MIDI-specific interpretations. (eg. the CC 120 and up)
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @since 0.6.0 * @since 0.6.0
* *
* @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
@ -413,6 +429,10 @@ public:
/** /**
* @brief Send a pitch bend event to the synth * @brief Send a pitch bend event to the synth
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @since 0.2.0 * @since 0.2.0
* *
* @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
@ -427,6 +447,10 @@ public:
/** /**
* @brief Send an aftertouch event to the synth. * @brief Send an aftertouch event to the synth.
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @since 0.2.0 * @since 0.2.0
* *
* @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
@ -442,6 +466,10 @@ public:
* @brief Send a polyphonic aftertouch event to the synth. This feature is * @brief Send a polyphonic aftertouch event to the synth. This feature is
* experimental and needs more testing in the internal engine. * experimental and needs more testing in the internal engine.
* *
* This command should be delay-ordered with all other midi-type events
* (notes, CCs, aftertouch and pitch-wheel), otherwise the behavior of the
* synth is undefined.
*
* @since 0.6.0 * @since 0.6.0
* *
* @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
@ -457,6 +485,10 @@ public:
/** /**
* @brief Send a tempo event to the synth. * @brief Send a tempo event to the synth.
* *
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
*
* @since 0.2.0 * @since 0.2.0
* *
* @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
@ -471,6 +503,10 @@ public:
/** /**
* @brief Send the time signature. * @brief Send the time signature.
* *
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
*
* @since 0.5.0 * @since 0.5.0
* *
* @param delay The delay. * @param delay The delay.
@ -485,6 +521,10 @@ public:
/** /**
* @brief Send the time position. * @brief Send the time position.
* *
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
*
* @since 0.5.0 * @since 0.5.0
* *
* @param delay The delay. * @param delay The delay.
@ -499,6 +539,10 @@ public:
/** /**
* @brief Send the playback state. * @brief Send the playback state.
* *
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
*
* @since 0.5.0 * @since 0.5.0
* *
* @param delay The delay. * @param delay The delay.

View file

@ -417,7 +417,8 @@ public:
* @brief Send a tempo event to the synth * @brief Send a tempo 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(), and ordered with respect to
* calls to tempo(), timeSignature(), timePosition(), and playbackState().
* @param secondsPerQuarter the new period of the quarter note * @param secondsPerQuarter the new period of the quarter note
*/ */
void tempo(int delay, float secondsPerQuarter) noexcept; void tempo(int delay, float secondsPerQuarter) noexcept;
@ -438,28 +439,35 @@ public:
*/ */
void hdPolyAftertouch(int delay, int noteNumber, float normAftertouch) noexcept; void hdPolyAftertouch(int delay, int noteNumber, float normAftertouch) noexcept;
/** /**
* @brief Send the time signature. * @brief Send the time signature.
* *
* @param delay The delay. * @param delay the delay at which the event occurs; this should be lower than the size of
* @param beats_per_bar The number of beats per bar, or time signature numerator. * the block in the next call to renderBlock(), and ordered with respect to
* @param beat_unit The note corresponding to one beat, or time signature denominator. * calls to tempo(), timeSignature(), timePosition(), and playbackState().
* @param beats_per_bar The number of beats per bar, or time signature numerator.
* @param beat_unit The note corresponding to one beat, or time signature denominator.
*/ */
void timeSignature(int delay, int beatsPerBar, int beatUnit); void timeSignature(int delay, int beatsPerBar, int beatUnit);
/** /**
* @brief Send the time position. * @brief Send the time position.
* *
* @param delay The delay. * @param delay the delay at which the event occurs; this should be lower than the size of
* @param bar The current bar. * the block in the next call to renderBlock(), and ordered with respect to
* @param bar_beat The fractional position of the current beat within the bar. * calls to tempo(), timeSignature(), timePosition(), and playbackState().
* @param bar The current bar.
* @param bar_beat The fractional position of the current beat within the bar.
*/ */
void timePosition(int delay, int bar, double barBeat); void timePosition(int delay, int bar, double barBeat);
/** /**
* @brief Send the playback state. * @brief Send the playback state.
* *
* @param delay The delay. * @param delay the delay at which the event occurs; this should be lower than the size of
* @param playback_state The playback state, 1 if playing, 0 if stopped. * the block in the next call to renderBlock(), and ordered with respect to
* calls to tempo(), timeSignature(), timePosition(), and playbackState().
* @param playback_state The playback state, 1 if playing, 0 if stopped.
*/ */
void playbackState(int delay, int playbackState); void playbackState(int delay, int playbackState);
/** /**
* @brief Render an block of audio data in the buffer. This call will reset * @brief Render an block of audio data in the buffer. This call will reset
* the synth in its waiting state for the next batch of events. The size of * the synth in its waiting state for the next batch of events. The size of
@ -534,16 +542,16 @@ public:
uint32_t getPreloadSize() const noexcept; uint32_t getPreloadSize() const noexcept;
/** /**
* @brief Gets the number of allocated buffers. * @brief Gets the number of allocated buffers.
* *
* @return The allocated buffers. * @return The allocated buffers.
*/ */
int getAllocatedBuffers() const noexcept { return Buffer<float>::counter().getNumBuffers(); } int getAllocatedBuffers() const noexcept { return Buffer<float>::counter().getNumBuffers(); }
/** /**
* @brief Gets the number of bytes allocated through the buffers * @brief Gets the number of bytes allocated through the buffers
* *
* @return The allocated bytes. * @return The allocated bytes.
*/ */
int getAllocatedBytes() const noexcept { return Buffer<float>::counter().getTotalBytes(); } int getAllocatedBytes() const noexcept { return Buffer<float>::counter().getTotalBytes(); }
@ -610,15 +618,15 @@ public:
void allSoundOff() noexcept; void allSoundOff() noexcept;
/** /**
* @brief Get the parser. * @brief Get the parser.
* *
* @return A reference to the parser. * @return A reference to the parser.
*/ */
Parser& getParser() noexcept; Parser& getParser() noexcept;
/** /**
* @brief Get the parser. * @brief Get the parser.
* *
* @return A reference to the parser. * @return A reference to the parser.
*/ */
const Parser& getParser() const noexcept; const Parser& getParser() const noexcept;

View file

@ -500,10 +500,14 @@ TEST_CASE("[Files] Note and octave offsets")
TEST_CASE("[Files] Off modes") TEST_CASE("[Files] Off modes")
{ {
Synth synth; Synth synth;
AudioBuffer<float> buffer { 2, 256 };
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, 64, 63); synth.noteOn(0, 64, 63);
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
const auto* fastVoice = const auto* fastVoice =
synth.getVoiceView(0)->getRegion()->offMode == OffMode::fast ? synth.getVoiceView(0)->getRegion()->offMode == OffMode::fast ?
@ -514,9 +518,10 @@ TEST_CASE("[Files] Off modes")
synth.getVoiceView(1) : synth.getVoiceView(1) :
synth.getVoiceView(0) ; synth.getVoiceView(0) ;
synth.noteOn(100, 63, 63); synth.noteOn(100, 63, 63);
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( numPlayingVoices(synth) == 1 );
AudioBuffer<float> buffer { 2, 256 };
for (unsigned i = 0; i < 20; ++i) // Not enough for the "normal" voice to die for (unsigned i = 0; i < 20; ++i) // Not enough for the "normal" voice to die
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );

View file

@ -17,8 +17,6 @@
using namespace Catch::literals; using namespace Catch::literals;
using namespace sfz::literals; using namespace sfz::literals;
constexpr int blockSize { 256 };
TEST_CASE("[Polyphony] Polyphony in hierarchy") TEST_CASE("[Polyphony] Polyphony in hierarchy")
{ {
sfz::Synth synth; sfz::Synth synth;
@ -76,72 +74,72 @@ TEST_CASE("[Polyphony] Polyphony groups")
TEST_CASE("[Polyphony] group polyphony limits") TEST_CASE("[Polyphony] group polyphony limits")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<group> group=1 polyphony=2 <group> group=1 polyphony=2
<region> sample=*sine key=65 <region> sample=*sine key=65
)"); )");
synth.noteOn(0, 65, 64); synth.noteOn(0, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(1, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(2, 65, 64);
REQUIRE( synth.getNumActiveVoices() == 3 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing
} }
TEST_CASE("[Polyphony] Hierarchy polyphony limits") TEST_CASE("[Polyphony] Hierarchy polyphony limits")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<group> polyphony=2 <group> polyphony=2
<region> sample=*sine key=65 <region> sample=*sine key=65
)"); )");
synth.noteOn(0, 65, 64); synth.noteOn(0, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(1, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(2, 65, 64);
REQUIRE( synth.getNumActiveVoices() == 3 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing
} }
TEST_CASE("[Polyphony] Hierarchy polyphony limits (group)") TEST_CASE("[Polyphony] Hierarchy polyphony limits (group)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<group> polyphony=2 <group> polyphony=2
<region> sample=*sine key=65 <region> sample=*sine key=65
)"); )");
synth.noteOn(0, 65, 64); synth.noteOn(0, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(1, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(2, 65, 64);
REQUIRE( synth.getNumActiveVoices() == 3 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing
} }
TEST_CASE("[Polyphony] Hierarchy polyphony limits (master)") TEST_CASE("[Polyphony] Hierarchy polyphony limits (master)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<master> polyphony=2 <master> polyphony=2
<group> polyphony=5 <group> polyphony=5
<region> sample=*sine key=65 <region> sample=*sine key=65
)"); )");
synth.noteOn(0, 65, 64); synth.noteOn(0, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(1, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(2, 65, 64);
REQUIRE( synth.getNumActiveVoices() == 3 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing
} }
TEST_CASE("[Polyphony] Hierarchy polyphony limits (limit in another master)") TEST_CASE("[Polyphony] Hierarchy polyphony limits (limit in another master)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<master> polyphony=2 <master> polyphony=2
<region> sample=*saw key=65 <region> sample=*saw key=65
@ -150,38 +148,37 @@ TEST_CASE("[Polyphony] Hierarchy polyphony limits (limit in another master)")
<region> sample=*sine key=66 <region> sample=*sine key=66
)"); )");
synth.noteOn(0, 65, 64); synth.noteOn(0, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(1, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(2, 65, 64);
synth.noteOn(0, 66, 64); synth.noteOn(3, 66, 64);
synth.noteOn(0, 66, 64); synth.noteOn(4, 66, 64);
synth.noteOn(0, 66, 64); synth.noteOn(5, 66, 64);
REQUIRE( synth.getNumActiveVoices() == 6);
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 6);
REQUIRE( numPlayingVoices(synth) == 5); // One is releasing REQUIRE( numPlayingVoices(synth) == 5); // One is releasing
} }
TEST_CASE("[Polyphony] Hierarchy polyphony limits (global)") TEST_CASE("[Polyphony] Hierarchy polyphony limits (global)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<global> polyphony=2 <global> polyphony=2
<group> polyphony=5 <group> polyphony=5
<region> sample=*sine key=65 <region> sample=*sine key=65
)"); )");
synth.noteOn(0, 65, 64); synth.noteOn(0, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(1, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(2, 65, 64);
REQUIRE( synth.getNumActiveVoices() == 3 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing
} }
TEST_CASE("[Polyphony] Polyphony in master") TEST_CASE("[Polyphony] Polyphony in master")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.setSamplesPerBlock(blockSize);
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<master> polyphony=2 <master> polyphony=2
<group> group=2 <group> group=2
@ -192,28 +189,28 @@ TEST_CASE("[Polyphony] Polyphony in master")
<region> sample=*sine key=61 <region> sample=*sine key=61
)"); )");
synth.noteOn(0, 65, 64); synth.noteOn(0, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(1, 65, 64);
synth.noteOn(0, 65, 64); synth.noteOn(2, 65, 64);
REQUIRE( synth.getNumActiveVoices() == 3 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing
synth.allSoundOff(); synth.allSoundOff();
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 0); REQUIRE( synth.getNumActiveVoices() == 0);
synth.noteOn(0, 63, 64); synth.noteOn(0, 63, 64);
synth.noteOn(0, 63, 64); synth.noteOn(1, 63, 64);
synth.noteOn(0, 63, 64); synth.noteOn(2, 63, 64);
REQUIRE( synth.getNumActiveVoices() == 3 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing
synth.allSoundOff(); synth.allSoundOff();
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 0); REQUIRE( synth.getNumActiveVoices() == 0);
synth.noteOn(0, 61, 64); synth.noteOn(0, 61, 64);
synth.noteOn(0, 61, 64); synth.noteOn(1, 61, 64);
synth.noteOn(0, 61, 64); synth.noteOn(2, 61, 64);
REQUIRE( synth.getNumActiveVoices() == 3 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 3 ); REQUIRE( numPlayingVoices(synth) == 3 );
} }
@ -221,15 +218,15 @@ TEST_CASE("[Polyphony] Polyphony in master")
TEST_CASE("[Polyphony] Self-masking") TEST_CASE("[Polyphony] Self-masking")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> sample=*sine key=64 note_polyphony=2 <region> sample=*sine key=64 note_polyphony=2
)"); )");
synth.noteOn(0, 64, 63 ); synth.noteOn(0, 64, 63 );
synth.noteOn(0, 64, 62 ); synth.noteOn(1, 64, 62 );
synth.noteOn(0, 64, 64); synth.noteOn(2, 64, 64);
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); REQUIRE(!synth.getVoiceView(0)->releasedOrFree());
@ -242,15 +239,15 @@ TEST_CASE("[Polyphony] Self-masking")
TEST_CASE("[Polyphony] Not self-masking") TEST_CASE("[Polyphony] Not self-masking")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> sample=*sine key=66 note_polyphony=2 note_selfmask=off <region> sample=*sine key=66 note_polyphony=2 note_selfmask=off
)"); )");
synth.noteOn(0, 66, 63 ); synth.noteOn(0, 66, 63 );
synth.noteOn(0, 66, 62 ); synth.noteOn(1, 66, 62 );
synth.noteOn(0, 66, 64); synth.noteOn(2, 66, 64);
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); REQUIRE( synth.getVoiceView(0)->releasedOrFree());
@ -263,15 +260,15 @@ TEST_CASE("[Polyphony] Not self-masking")
TEST_CASE("[Polyphony] Self-masking with the exact same velocity") TEST_CASE("[Polyphony] Self-masking with the exact same velocity")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path(), R"( synth.loadSfzString(fs::current_path(), R"(
<region> sample=*sine key=64 note_polyphony=2 <region> sample=*sine key=64 note_polyphony=2
)"); )");
synth.noteOn(0, 64, 64); synth.noteOn(0, 64, 64);
synth.noteOn(0, 64, 63 ); synth.noteOn(1, 64, 63 );
synth.noteOn(0, 64, 63 ); synth.noteOn(2, 64, 63 );
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 64_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 64_norm);
REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); REQUIRE(!synth.getVoiceView(0)->releasedOrFree());
@ -284,11 +281,13 @@ TEST_CASE("[Polyphony] Self-masking with the exact same velocity")
TEST_CASE("[Polyphony] Self-masking only works from low to high") TEST_CASE("[Polyphony] Self-masking only works from low to high")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> sample=*sine key=64 note_polyphony=1 <region> sample=*sine key=64 note_polyphony=1
)"); )");
synth.noteOn(0, 64, 63 ); synth.noteOn(0, 64, 63 );
synth.noteOn(0, 64, 62 ); synth.noteOn(1, 64, 62 );
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 ); // Both notes are playing REQUIRE( synth.getNumActiveVoices() == 2 ); // Both notes are playing
REQUIRE( numPlayingVoices(synth) == 2 ); // id REQUIRE( numPlayingVoices(synth) == 2 ); // id
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
@ -300,14 +299,13 @@ TEST_CASE("[Polyphony] Self-masking only works from low to high")
TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default)") TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> sample=*saw key=64 note_polyphony=1 <region> sample=*saw key=64 note_polyphony=1
<region> sample=*sine key=64 note_polyphony=1 <region> sample=*sine key=64 note_polyphony=1
)"); )");
synth.noteOn(0, 64, 62 ); synth.noteOn(0, 64, 62 );
synth.noteOn(0, 64, 63 ); synth.noteOn(1, 64, 63 );
REQUIRE( synth.getNumActiveVoices() == 4);
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( numPlayingVoices(synth) == 1 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm);
@ -323,20 +321,21 @@ TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same po
TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default, with keyswitches)") TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default, with keyswitches)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<global> sw_lokey=36 sw_hikey=37 sw_default=36 <global> sw_lokey=36 sw_hikey=37 sw_default=36
<region> sw_last=36 key=48 note_polyphony=1 sample=*saw <region> sw_last=36 key=48 note_polyphony=1 sample=*saw
<region> sw_last=37 key=48 transpose=12 note_polyphony=1 sample=*tri <region> sw_last=37 key=48 transpose=12 note_polyphony=1 sample=*tri
)"); )");
synth.noteOn(0, 48, 63 ); synth.noteOn(0, 48, 63 );
REQUIRE( synth.getNumActiveVoices() == 1);
synth.cc(0, 64, 127);
synth.noteOn(0, 37, 127);
synth.noteOff(0, 37, 0);
synth.noteOn(0, 48, 64);
REQUIRE( synth.getNumActiveVoices() == 2 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 1);
synth.cc(1, 64, 127);
synth.noteOn(2, 37, 127);
synth.noteOff(3, 37, 0);
synth.noteOn(4, 48, 64);
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( numPlayingVoices(synth) == 1 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); REQUIRE( synth.getVoiceView(0)->releasedOrFree());
@ -348,15 +347,15 @@ TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same po
TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups") TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> group=1 sample=*saw key=64 note_polyphony=1 <region> group=1 sample=*saw key=64 note_polyphony=1
<region> group=2 sample=*sine key=64 note_polyphony=1 <region> group=2 sample=*sine key=64 note_polyphony=1
)"); )");
synth.noteOn(0, 64, 62 ); synth.noteOn(0, 64, 62 );
synth.noteOn(0, 64, 63 ); synth.noteOn(1, 64, 63 );
REQUIRE( synth.getNumActiveVoices() == 4); // Both notes are playing
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 4); // Both notes are playing
REQUIRE(numPlayingVoices(synth) == 2 ); REQUIRE(numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); // got killed REQUIRE( synth.getVoiceView(0)->releasedOrFree()); // got killed
@ -371,20 +370,21 @@ TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups")
TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups (with keyswitches)") TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups (with keyswitches)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<global> sw_lokey=36 sw_hikey=37 sw_default=36 <global> sw_lokey=36 sw_hikey=37 sw_default=36
<region> group=1 sw_last=36 key=48 note_polyphony=1 sample=*saw <region> group=1 sw_last=36 key=48 note_polyphony=1 sample=*saw
<region> group=2 sw_last=37 key=48 transpose=12 note_polyphony=1 sample=*tri <region> group=2 sw_last=37 key=48 transpose=12 note_polyphony=1 sample=*tri
)"); )");
synth.noteOn(0, 48, 63 ); synth.noteOn(0, 48, 63 );
REQUIRE( synth.getNumActiveVoices() == 1);
synth.cc(0, 64, 127);
synth.noteOn(0, 37, 127);
synth.noteOff(0, 37, 0);
synth.noteOn(0, 48, 64);
REQUIRE( synth.getNumActiveVoices() == 2 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 1);
synth.cc(1, 64, 127);
synth.noteOn(2, 37, 127);
synth.noteOff(3, 37, 0);
synth.noteOn(4, 48, 64);
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE(numPlayingVoices(synth) == 2 ); REQUIRE(numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); REQUIRE(!synth.getVoiceView(0)->releasedOrFree());
@ -395,17 +395,18 @@ TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups (wi
TEST_CASE("[Polyphony] Note polyphony operates on release voices") TEST_CASE("[Polyphony] Note polyphony operates on release voices")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> key=48 note_polyphony=1 sample=*saw trigger=release_key ampeg_attack=1 ampeg_decay=1 <region> key=48 note_polyphony=1 sample=*saw trigger=release_key ampeg_attack=1 ampeg_decay=1
)"); )");
synth.noteOn(0, 48, 63 ); synth.noteOn(0, 48, 63 );
synth.noteOff(10, 48, 0 ); synth.noteOff(10, 48, 0 );
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 1); REQUIRE( synth.getNumActiveVoices() == 1);
synth.noteOn(20, 48, 65 ); synth.noteOn(20, 48, 65 );
synth.noteOff(30, 48, 10 ); synth.noteOff(30, 48, 10 );
REQUIRE( synth.getNumActiveVoices() == 2 );
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE(numPlayingVoices(synth) == 1 ); REQUIRE(numPlayingVoices(synth) == 1 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); REQUIRE( synth.getVoiceView(0)->releasedOrFree());
@ -416,16 +417,18 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices")
TEST_CASE("[Polyphony] Note polyphony operates on release voices (masking works from low to high but takes into account the replaced velocity)") TEST_CASE("[Polyphony] Note polyphony operates on release voices (masking works from low to high but takes into account the replaced velocity)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> key=48 note_polyphony=1 sample=*saw trigger=release_key ampeg_attack=1 ampeg_decay=1 <region> key=48 note_polyphony=1 sample=*saw trigger=release_key ampeg_attack=1 ampeg_decay=1
)"); )");
synth.noteOn(0, 48, 63 ); synth.noteOn(0, 48, 63 );
synth.noteOff(10, 48, 0 ); synth.noteOff(10, 48, 0 );
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 1); REQUIRE( synth.getNumActiveVoices() == 1);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( numPlayingVoices(synth) == 1 );
synth.noteOn(20, 48, 61 ); synth.noteOn(20, 48, 61 );
synth.noteOff(30, 48, 10 ); synth.noteOff(30, 48, 10 );
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( numPlayingVoices(synth) == 2 );
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm);
@ -437,7 +440,7 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices (masking works
TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal") TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> key=48 sample=*sine <region> key=48 sample=*sine
<region> key=48 note_polyphony=1 sample=*saw trigger=release ampeg_attack=1 ampeg_decay=1 <region> key=48 note_polyphony=1 sample=*saw trigger=release ampeg_attack=1 ampeg_decay=1
@ -449,37 +452,21 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain ped
synth.noteOff(3, 48, 0 ); synth.noteOff(3, 48, 0 );
synth.noteOn(4, 48, 63 ); synth.noteOn(4, 48, 63 );
synth.noteOff(5, 48, 0 ); synth.noteOff(5, 48, 0 );
REQUIRE( synth.getNumActiveVoices() == 3);
REQUIRE( synth.getVoiceView(0)->getRegion()->sampleId->filename() == "*sine");
REQUIRE( synth.getVoiceView(1)->getRegion()->sampleId->filename() == "*sine");
REQUIRE( synth.getVoiceView(2)->getRegion()->sampleId->filename() == "*sine");
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 3 ); std::vector<std::string> expectedSamples { "*sine", "*sine", "*sine" };
REQUIRE( playingSamples(synth) == expectedSamples );
synth.cc(20, 64, 0); synth.cc(20, 64, 0);
REQUIRE( synth.getNumActiveVoices() == 6 );
REQUIRE( synth.getVoiceView(3)->getRegion()->sampleId->filename() == "*saw");
REQUIRE( synth.getVoiceView(4)->getRegion()->sampleId->filename() == "*saw");
REQUIRE( synth.getVoiceView(5)->getRegion()->sampleId->filename() == "*saw");
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); std::vector<std::string> expectedSamples2 { "*saw" };
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 61_norm); std::vector<float> expectedVelocities { 63_norm };
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); REQUIRE( playingSamples(synth) == expectedSamples2 );
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); REQUIRE( playingVelocities(synth) == expectedVelocities );
REQUIRE( synth.getVoiceView(1)->releasedOrFree());
REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm);
REQUIRE( synth.getVoiceView(2)->releasedOrFree());
REQUIRE( synth.getVoiceView(3)->getTriggerEvent().value == 61_norm);
REQUIRE( synth.getVoiceView(3)->releasedOrFree());
REQUIRE( synth.getVoiceView(4)->getTriggerEvent().value == 62_norm);
REQUIRE( synth.getVoiceView(4)->releasedOrFree());
REQUIRE( synth.getVoiceView(5)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(5)->releasedOrFree());
} }
TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal (masking)") TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal (masking)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<region> key=48 sample=*sine <region> key=48 sample=*sine
<region> key=48 note_polyphony=1 sample=*saw trigger=release ampeg_attack=1 ampeg_decay=1 <region> key=48 note_polyphony=1 sample=*saw trigger=release ampeg_attack=1 ampeg_decay=1
@ -491,75 +478,53 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain ped
synth.noteOff(3, 48, 0 ); synth.noteOff(3, 48, 0 );
synth.noteOn(4, 48, 61 ); synth.noteOn(4, 48, 61 );
synth.noteOff(5, 48, 0 ); synth.noteOff(5, 48, 0 );
REQUIRE( synth.getNumActiveVoices() == 3);
REQUIRE( synth.getVoiceView(0)->getRegion()->sampleId->filename() == "*sine");
REQUIRE( synth.getVoiceView(1)->getRegion()->sampleId->filename() == "*sine");
REQUIRE( synth.getVoiceView(2)->getRegion()->sampleId->filename() == "*sine");
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 3 ); std::vector<std::string> expectedSamples { "*sine", "*sine", "*sine" };
REQUIRE( playingSamples(synth) == expectedSamples );
synth.cc(20, 64, 0); synth.cc(20, 64, 0);
REQUIRE( synth.getNumActiveVoices() == 6 );
REQUIRE( synth.getVoiceView(3)->getRegion()->sampleId->filename() == "*saw");
REQUIRE( synth.getVoiceView(4)->getRegion()->sampleId->filename() == "*saw");
REQUIRE( synth.getVoiceView(5)->getRegion()->sampleId->filename() == "*saw");
synth.renderBlock(buffer); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 3 ); std::vector<std::string> expectedSamples2 { "*saw", "*saw", "*saw" };
REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); std::vector<float> expectedVelocities { 63_norm, 62_norm, 61_norm };
REQUIRE( synth.getVoiceView(0)->releasedOrFree()); REQUIRE( playingSamples(synth) == expectedSamples2 );
REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); REQUIRE( playingVelocities(synth) == expectedVelocities );
REQUIRE( synth.getVoiceView(1)->releasedOrFree());
REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 61_norm);
REQUIRE( synth.getVoiceView(2)->releasedOrFree());
REQUIRE( synth.getVoiceView(3)->getTriggerEvent().value == 63_norm);
REQUIRE(!synth.getVoiceView(3)->releasedOrFree());
REQUIRE( synth.getVoiceView(4)->getTriggerEvent().value == 62_norm);
REQUIRE(!synth.getVoiceView(4)->releasedOrFree());
REQUIRE( synth.getVoiceView(5)->getTriggerEvent().value == 61_norm);
REQUIRE(!synth.getVoiceView(5)->releasedOrFree());
} }
TEST_CASE("[Polyphony] Bi-directional choking (with polyphony)") TEST_CASE("[Polyphony] Bi-directional choking (with polyphony)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<group> key=60 polyphony=1 <group> key=60 polyphony=1
<region> sample=kick.wav loop_mode=one_shot <region> sample=kick.wav loop_mode=one_shot
<region> sample=snare.wav trigger=release <region> sample=snare.wav trigger=release
)"); )");
synth.noteOn(0, 60, 63 ); synth.noteOn(0, 60, 63 );
REQUIRE( synth.getNumActiveVoices() == 1); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( playingSamples(synth) == std::vector<std::string> { "kick.wav" } );
REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "kick.wav" );
synth.noteOff(10, 60, 63 ); synth.noteOff(10, 60, 63 );
REQUIRE( synth.getNumActiveVoices() == 2); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( playingSamples(synth) == std::vector<std::string> { "snare.wav" } );
REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "snare.wav" );
synth.noteOn(20, 60, 63 ); synth.noteOn(20, 60, 63 );
REQUIRE( synth.getNumActiveVoices() == 3); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( playingSamples(synth) == std::vector<std::string> { "kick.wav" } );
REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "kick.wav" );
} }
TEST_CASE("[Polyphony] Bi-directional choking (with note_polyphony)") TEST_CASE("[Polyphony] Bi-directional choking (with note_polyphony)")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, blockSize }; sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
<group> key=60 note_polyphony=1 <group> key=60 note_polyphony=1
<region> sample=kick.wav loop_mode=one_shot <region> sample=kick.wav loop_mode=one_shot
<region> sample=snare.wav trigger=release <region> sample=snare.wav trigger=release
)"); )");
synth.noteOn(0, 60, 63 ); synth.noteOn(0, 60, 63 );
REQUIRE( synth.getNumActiveVoices() == 1); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( playingSamples(synth) == std::vector<std::string> { "kick.wav" } );
REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "kick.wav" );
synth.noteOff(10, 60, 63 ); synth.noteOff(10, 60, 63 );
REQUIRE( synth.getNumActiveVoices() == 2); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( playingSamples(synth) == std::vector<std::string> { "snare.wav" } );
REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "snare.wav" );
synth.noteOn(20, 60, 63 ); synth.noteOn(20, 60, 63 );
REQUIRE( synth.getNumActiveVoices() == 3); synth.renderBlock(buffer);
REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( playingSamples(synth) == std::vector<std::string> { "kick.wav" } );
REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "kick.wav" );
} }

View file

@ -190,159 +190,200 @@ TEST_CASE("Region activation", "Region tests")
TEST_CASE("[Keyswitches] Normal lastKeyswitch range") TEST_CASE("[Keyswitches] Normal lastKeyswitch range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=40 sw_hikey=42 sw_default=40 <global> sw_lokey=40 sw_hikey=42 sw_default=40
<region> sw_last=40 key=60 sample=*sine <region> sw_last=40 key=60 sample=*sine
<region> sw_last=41 key=62 sample=*saw <region> sw_last=41 key=62 sample=*saw
)"); )");
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 41, 64); synth.noteOn(0, 41, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
} }
TEST_CASE("[Keyswitches] No lastKeyswitch range") TEST_CASE("[Keyswitches] No lastKeyswitch range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<region> sw_last=40 key=60 sample=*sine <region> sw_last=40 key=60 sample=*sine
<region> sw_last=41 key=62 sample=*saw <region> sw_last=41 key=62 sample=*saw
)"); )");
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getNumActiveVoices() == 0);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getNumActiveVoices() == 0);
synth.noteOn(0, 40, 64); synth.noteOn(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 41, 64); synth.noteOn(0, 41, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
} }
TEST_CASE("[Keyswitches] Out of lastKeyswitch range") TEST_CASE("[Keyswitches] Out of lastKeyswitch range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=40 sw_hikey=42 sw_default=40 <global> sw_lokey=40 sw_hikey=42 sw_default=40
<region> sw_last=40 key=60 sample=*sine <region> sw_last=40 key=60 sample=*sine
<region> sw_last=43 key=62 sample=*saw <region> sw_last=43 key=62 sample=*saw
)"); )");
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 43, 64); synth.noteOn(0, 43, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
} }
TEST_CASE("[Keyswitches] Overlapping key and lastKeyswitch range") TEST_CASE("[Keyswitches] Overlapping key and lastKeyswitch range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=1 sw_hikey=127 sw_default=40 <global> sw_lokey=1 sw_hikey=127 sw_default=40
<region> sw_last=40 key=60 sample=*sine <region> sw_last=40 key=60 sample=*sine
<region> sw_last=41 key=62 sample=*saw <region> sw_last=41 key=62 sample=*saw
)"); )");
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 41, 64); synth.noteOn(0, 41, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
synth.noteOn(0, 43, 64); synth.noteOn(0, 43, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 3); REQUIRE(synth.getNumActiveVoices() == 3);
} }
TEST_CASE("[Keyswitches] sw_down, in range") TEST_CASE("[Keyswitches] sw_down, in range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=1 sw_hikey=127 sw_default=40 <global> sw_lokey=1 sw_hikey=127 sw_default=40
<region> sw_down=40 key=60 sample=*sine <region> sw_down=40 key=60 sample=*sine
)"); )");
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getNumActiveVoices() == 0);
synth.noteOn(0, 40, 64); synth.noteOn(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOff(0, 40, 64); synth.noteOff(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
} }
TEST_CASE("[Keyswitches] sw_down, out of range") TEST_CASE("[Keyswitches] sw_down, out of range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=1 sw_hikey=10 sw_default=40 <global> sw_lokey=1 sw_hikey=10 sw_default=40
<region> sw_down=40 key=60 sample=*sine <region> sw_down=40 key=60 sample=*sine
)"); )");
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getNumActiveVoices() == 0);
synth.noteOn(0, 40, 64); synth.noteOn(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOff(0, 40, 64); synth.noteOff(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
} }
TEST_CASE("[Keyswitches] sw_up, in range") TEST_CASE("[Keyswitches] sw_up, in range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=1 sw_hikey=127 sw_default=40 <global> sw_lokey=1 sw_hikey=127 sw_default=40
<region> sw_up=40 key=60 sample=*sine <region> sw_up=40 key=60 sample=*sine
)"); )");
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 40, 64); synth.noteOn(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOff(0, 40, 64); synth.noteOff(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
} }
TEST_CASE("[Keyswitches] sw_up, out of range") TEST_CASE("[Keyswitches] sw_up, out of range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
<global> sw_lokey=1 sw_hikey=127 sw_default=40 <global> sw_lokey=1 sw_hikey=127 sw_default=40
<region> sw_up=40 key=60 sample=*sine <region> sw_up=40 key=60 sample=*sine
)"); )");
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOn(0, 40, 64); synth.noteOn(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
synth.noteOff(0, 40, 64); synth.noteOff(0, 40, 64);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
} }
TEST_CASE("[Keyswitches] sw_default") TEST_CASE("[Keyswitches] sw_default")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"(
<global> sw_lokey=30 sw_hikey=50 sw_default=40 <global> sw_lokey=30 sw_hikey=50 sw_default=40
<region> sw_last=41 key=51 sample=*sine <region> sw_last=41 key=51 sample=*sine
@ -360,6 +401,7 @@ TEST_CASE("[Keyswitches] sw_default")
TEST_CASE("[Keyswitches] sw_default and playing with switches") TEST_CASE("[Keyswitches] sw_default and playing with switches")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"(
<global> sw_lokey=30 sw_hikey=50 sw_default=40 <global> sw_lokey=30 sw_hikey=50 sw_default=40
<region> sw_last=41 key=51 sample=*sine <region> sw_last=41 key=51 sample=*sine
@ -374,12 +416,14 @@ TEST_CASE("[Keyswitches] sw_default and playing with switches")
REQUIRE( synth.getLayerView(3)->isSwitchedOn() ); REQUIRE( synth.getLayerView(3)->isSwitchedOn() );
synth.noteOn(0, 41, 64); synth.noteOn(0, 41, 64);
synth.noteOff(0, 41, 0); synth.noteOff(0, 41, 0);
synth.renderBlock(buffer);
REQUIRE( synth.getLayerView(0)->isSwitchedOn() ); REQUIRE( synth.getLayerView(0)->isSwitchedOn() );
REQUIRE( !synth.getLayerView(1)->isSwitchedOn() ); REQUIRE( !synth.getLayerView(1)->isSwitchedOn() );
REQUIRE( synth.getLayerView(2)->isSwitchedOn() ); REQUIRE( synth.getLayerView(2)->isSwitchedOn() );
REQUIRE( !synth.getLayerView(3)->isSwitchedOn() ); REQUIRE( !synth.getLayerView(3)->isSwitchedOn() );
synth.noteOn(0, 40, 64); synth.noteOn(0, 40, 64);
synth.noteOff(0, 40, 64); synth.noteOff(0, 40, 64);
synth.renderBlock(buffer);
REQUIRE( !synth.getLayerView(0)->isSwitchedOn() ); REQUIRE( !synth.getLayerView(0)->isSwitchedOn() );
REQUIRE( synth.getLayerView(1)->isSwitchedOn() ); REQUIRE( synth.getLayerView(1)->isSwitchedOn() );
REQUIRE( !synth.getLayerView(2)->isSwitchedOn() ); REQUIRE( !synth.getLayerView(2)->isSwitchedOn() );
@ -389,6 +433,7 @@ TEST_CASE("[Keyswitches] sw_default and playing with switches")
TEST_CASE("[Keyswitches] sw_previous in range") TEST_CASE("[Keyswitches] sw_previous in range")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<region> sample=*saw sw_previous=60 lokey=50 hikey=70 <region> sample=*saw sw_previous=60 lokey=50 hikey=70
)"); )");
@ -397,17 +442,22 @@ TEST_CASE("[Keyswitches] sw_previous in range")
// the test assumes that sw_previous regions are disabled by default // the test assumes that sw_previous regions are disabled by default
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
synth.noteOn(0, 51, 64); synth.noteOn(0, 51, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getNumActiveVoices() == 0);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getNumActiveVoices() == 0);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
synth.noteOn(0, 51, 64); synth.noteOn(0, 51, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 2); REQUIRE(synth.getNumActiveVoices() == 2);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
} }
@ -416,28 +466,35 @@ TEST_CASE("[Keyswitches] sw_previous out of range")
{ {
// The behavior is the same in this case, regardless of the keyrange // The behavior is the same in this case, regardless of the keyrange
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<region> sample=*saw sw_previous=60 lokey=50 hikey=55 <region> sample=*saw sw_previous=60 lokey=50 hikey=55
)"); )");
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
synth.noteOn(0, 51, 64); synth.noteOn(0, 51, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getNumActiveVoices() == 0);
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getNumActiveVoices() == 0);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
synth.noteOn(0, 51, 64); synth.noteOn(0, 51, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getNumActiveVoices() == 1);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
synth.noteOn(0, 61, 64); synth.noteOn(0, 61, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
} }
TEST_CASE("[Keyswitches] sw_lolast and sw_hilast") TEST_CASE("[Keyswitches] sw_lolast and sw_hilast")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<region> sw_lolast=57 sw_hilast=59 key=70 sample=*saw <region> sw_lolast=57 sw_hilast=59 key=70 sample=*saw
<region> sw_lolast=60 sw_hilast=62 key=72 sample=*sine <region> sw_lolast=60 sw_hilast=62 key=72 sample=*sine
@ -445,24 +502,31 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast")
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 51, 64); synth.noteOn(0, 51, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 57, 64); synth.noteOn(0, 57, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(synth.getLayerView(1)->isSwitchedOn()); REQUIRE(synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 58, 64); synth.noteOn(0, 58, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 61, 64); synth.noteOn(0, 61, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(synth.getLayerView(1)->isSwitchedOn()); REQUIRE(synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 59, 64); synth.noteOn(0, 59, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 62, 64); synth.noteOn(0, 62, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(synth.getLayerView(1)->isSwitchedOn()); REQUIRE(synth.getLayerView(1)->isSwitchedOn());
} }
@ -470,6 +534,7 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast")
TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last") TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<region> sw_last=40 sw_lolast=57 sw_hilast=59 key=70 sample=*saw <region> sw_last=40 sw_lolast=57 sw_hilast=59 key=70 sample=*saw
<region> sw_lolast=60 sw_hilast=62 sw_last=41 key=72 sample=*sine <region> sw_lolast=60 sw_hilast=62 sw_last=41 key=72 sample=*sine
@ -477,21 +542,27 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last")
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 40, 64); synth.noteOn(0, 40, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 41, 64); synth.noteOn(0, 41, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 57, 64); synth.noteOn(0, 57, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 41, 64); synth.noteOn(0, 41, 64);
synth.renderBlock(buffer);
REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(0)->isSwitchedOn());
REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 60, 64); synth.noteOn(0, 60, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(synth.getLayerView(1)->isSwitchedOn()); REQUIRE(synth.getLayerView(1)->isSwitchedOn());
synth.noteOn(0, 40, 64); synth.noteOn(0, 40, 64);
synth.renderBlock(buffer);
REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(0)->isSwitchedOn());
REQUIRE(synth.getLayerView(1)->isSwitchedOn()); REQUIRE(synth.getLayerView(1)->isSwitchedOn());
} }
@ -499,6 +570,7 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_last")
TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_default") TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_default")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<global> sw_default=58 <global> sw_default=58
<region> sw_lolast=57 sw_hilast=59 key=70 sample=*saw <region> sw_lolast=57 sw_hilast=59 key=70 sample=*saw
@ -511,6 +583,7 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_default")
TEST_CASE("[Keyswitches] Multiple sw_default") TEST_CASE("[Keyswitches] Multiple sw_default")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<global> sw_default=60 <global> sw_default=60
<region> sw_last=60 key=70 sample=*saw <region> sw_last=60 key=70 sample=*saw
@ -528,6 +601,7 @@ TEST_CASE("[Keyswitches] Multiple sw_default")
TEST_CASE("[Keyswitches] Multiple sw_default, in region") TEST_CASE("[Keyswitches] Multiple sw_default, in region")
{ {
sfz::Synth synth; sfz::Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
<global> sw_default=60 <global> sw_default=60
<region> sw_last=58 key=70 sample=*saw <region> sw_last=58 key=70 sample=*saw

View file

@ -227,6 +227,7 @@ TEST_CASE("Legato triggers", "Region triggers")
TEST_CASE("[Triggers] sw_vel, basic") TEST_CASE("[Triggers] sw_vel, basic")
{ {
Synth synth; Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
std::vector<std::string> messageList; std::vector<std::string> messageList;
Client client(&messageList); Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver); client.setReceiveCallback(&simpleMessageReceiver);
@ -236,6 +237,7 @@ TEST_CASE("[Triggers] sw_vel, basic")
)"); )");
synth.noteOn(0, 60, 127); synth.noteOn(0, 60, 127);
synth.noteOn(10, 62, 10); synth.noteOn(10, 62, 10);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr);
synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr);
synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr);
@ -250,6 +252,7 @@ TEST_CASE("[Triggers] sw_vel, basic")
TEST_CASE("[Triggers] sw_vel, without sw_previous") TEST_CASE("[Triggers] sw_vel, without sw_previous")
{ {
Synth synth; Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
std::vector<std::string> messageList; std::vector<std::string> messageList;
Client client(&messageList); Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver); client.setReceiveCallback(&simpleMessageReceiver);
@ -259,6 +262,7 @@ TEST_CASE("[Triggers] sw_vel, without sw_previous")
)"); )");
synth.noteOn(0, 60, 127); synth.noteOn(0, 60, 127);
synth.noteOn(10, 62, 10); synth.noteOn(10, 62, 10);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr);
synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr);
synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr);
@ -273,6 +277,7 @@ TEST_CASE("[Triggers] sw_vel, without sw_previous")
TEST_CASE("[Triggers] sw_vel, with a note in between") TEST_CASE("[Triggers] sw_vel, with a note in between")
{ {
Synth synth; Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
std::vector<std::string> messageList; std::vector<std::string> messageList;
Client client(&messageList); Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver); client.setReceiveCallback(&simpleMessageReceiver);
@ -284,6 +289,7 @@ TEST_CASE("[Triggers] sw_vel, with a note in between")
synth.noteOn(0, 60, 127); synth.noteOn(0, 60, 127);
synth.noteOn(5, 64, 63); synth.noteOn(5, 64, 63);
synth.noteOn(10, 62, 10); synth.noteOn(10, 62, 10);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr);
synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr);
synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr);
@ -300,6 +306,7 @@ TEST_CASE("[Triggers] sw_vel, with a note in between")
TEST_CASE("[Triggers] sw_vel, with a note in between and sw_previous") TEST_CASE("[Triggers] sw_vel, with a note in between and sw_previous")
{ {
Synth synth; Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
std::vector<std::string> messageList; std::vector<std::string> messageList;
Client client(&messageList); Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver); client.setReceiveCallback(&simpleMessageReceiver);
@ -311,6 +318,7 @@ TEST_CASE("[Triggers] sw_vel, with a note in between and sw_previous")
synth.noteOn(0, 60, 127); synth.noteOn(0, 60, 127);
synth.noteOn(5, 64, 63); synth.noteOn(5, 64, 63);
synth.noteOn(10, 62, 10); synth.noteOn(10, 62, 10);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr);
synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr);
synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr);
@ -325,6 +333,7 @@ TEST_CASE("[Triggers] sw_vel, with a note in between and sw_previous")
TEST_CASE("[Triggers] sw_vel, consider the previous velocity for triggers") TEST_CASE("[Triggers] sw_vel, consider the previous velocity for triggers")
{ {
Synth synth; Synth synth;
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
std::vector<std::string> messageList; std::vector<std::string> messageList;
Client client(&messageList); Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver); client.setReceiveCallback(&simpleMessageReceiver);
@ -336,6 +345,7 @@ TEST_CASE("[Triggers] sw_vel, consider the previous velocity for triggers")
SECTION("Should trigger") { SECTION("Should trigger") {
synth.noteOn(0, 60, 127); synth.noteOn(0, 60, 127);
synth.noteOn(10, 62, 10); synth.noteOn(10, 62, 10);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr);
synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr);
synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr);
@ -350,6 +360,7 @@ TEST_CASE("[Triggers] sw_vel, consider the previous velocity for triggers")
SECTION("Should not trigger") { SECTION("Should not trigger") {
synth.noteOn(0, 60, 10); synth.noteOn(0, 60, 10);
synth.noteOn(10, 62, 127); synth.noteOn(10, 62, 127);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr);
synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr);
synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr);

File diff suppressed because it is too large Load diff

View file

@ -81,6 +81,55 @@ unsigned numPlayingVoices(const sfz::Synth& synth)
}); });
} }
const std::vector<std::string> playingSamples(const sfz::Synth& synth)
{
std::vector<std::string> samples;
for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i);
if (!voice->releasedOrFree()) {
if (auto region = voice->getRegion())
samples.push_back(region->sampleId->filename());
}
}
return samples;
}
const std::vector<float> playingVelocities(const sfz::Synth& synth)
{
std::vector<float> velocities;
for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i);
if (!voice->releasedOrFree())
velocities.push_back(voice->getTriggerEvent().value);
}
return velocities;
}
const std::vector<std::string> activeSamples(const sfz::Synth& synth)
{
std::vector<std::string> samples;
for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i);
if (!voice->isFree()) {
const sfz::Region* region = voice->getRegion();
if (region)
samples.push_back(region->sampleId->filename());
}
}
return samples;
}
const std::vector<float> activeVelocities(const sfz::Synth& synth)
{
std::vector<float> velocities;
for (int i = 0; i < synth.getNumVoices(); ++i) {
const auto* voice = synth.getVoiceView(i);
if (!voice->isFree())
velocities.push_back(voice->getTriggerEvent().value);
}
return velocities;
}
std::string createDefaultGraph(std::vector<std::string> lines, int numRegions) std::string createDefaultGraph(std::vector<std::string> lines, int numRegions)
{ {
for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) { for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) {

View file

@ -78,6 +78,38 @@ const std::vector<const sfz::Voice*> getPlayingVoices(const sfz::Synth& synth);
*/ */
unsigned numPlayingVoices(const sfz::Synth& synth); unsigned numPlayingVoices(const sfz::Synth& synth);
/**
* @brief Get the playing samples
*
* @param synth
* @return unsigned
*/
const std::vector<std::string> playingSamples(const sfz::Synth& synth);
/**
* @brief Get the playing notes velocities
*
* @param synth
* @return unsigned
*/
const std::vector<float> playingVelocities(const sfz::Synth& synth);
/**
* @brief Get the active samples
*
* @param synth
* @return unsigned
*/
const std::vector<std::string> activeSamples(const sfz::Synth& synth);
/**
* @brief Get the active notes velocities
*
* @param synth
* @return unsigned
*/
const std::vector<float> activeVelocities(const sfz::Synth& synth);
/** /**
* @brief Create the default dot graph representation for standard regions * @brief Create the default dot graph representation for standard regions
* *