diff --git a/src/sfizz.h b/src/sfizz.h index 056a2793..b36989e6 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -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. - * - * 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 * + * 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 delay The delay of the event in the block, in samples. * @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. + * @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 * the note-on velocity. - * @since 0.2.0 * * @param synth The synth. * @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. - * - * 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 * + * 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 delay The delay of the event in the block, in samples. * @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. - * - * 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 * + * 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 delay The delay of the event in the block, in samples. * @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. + * @since 0.6.0 * * This updates the CC value known to the synth, but without performing * additional MIDI-specific interpretations. (eg. the CC 120 and up) * - * 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.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 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. - * - * 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 * + * 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 delay The delay. * @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. * @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 delay The delay at which the event occurs; this should be lower * 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. * @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 delay The delay at which the event occurs; this should be lower * 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. + * + * 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 * * @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. + * + * 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 * * @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. + * + * 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 * * @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. + * + * 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 * * @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. * The synth will memorize the inputs and render sample accurates envelopes * depending on the input events passed to it. + * * @since 0.2.0 * * @param synth The synth. diff --git a/src/sfizz.hpp b/src/sfizz.hpp index 382be9ac..b9f2063f 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -334,9 +334,12 @@ public: /** * @brief Send a note on 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. + * * @param delay the delay at which the event occurs; this should be lower * than the size of the block in the next call to renderBlock(). * @param noteNumber the midi note number. @@ -349,9 +352,12 @@ public: /** * @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. + * * @param delay the delay at which the event occurs; this should be lower * than the size of the block in the next call to renderBlock(). * @param noteNumber the midi note number. @@ -364,9 +370,12 @@ public: /** * @brief Send a CC 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. + * * @param delay the delay at which the event occurs; this should be lower * than the size of the block in the next call to renderBlock(). * @param ccNumber the cc number. @@ -379,9 +388,12 @@ public: /** * @brief Send a high precision CC event to the synth - * * @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 * than the size of the block in the next call to renderBlock(). * @param ccNumber the cc number. @@ -398,6 +410,10 @@ public: * This updates the CC value known to the synth, but without performing * 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 * * @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 * + * 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 * * @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. * + * 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 * * @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 * 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 * * @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. * + * 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 * * @param delay the delay at which the event occurs; this should be lower @@ -471,6 +503,10 @@ public: /** * @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 * * @param delay The delay. @@ -485,6 +521,10 @@ public: /** * @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 * * @param delay The delay. @@ -499,6 +539,10 @@ public: /** * @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 * * @param delay The delay. diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 5e8a95fa..6b806898 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -417,7 +417,8 @@ public: * @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 - * 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 */ void tempo(int delay, float secondsPerQuarter) noexcept; @@ -438,28 +439,35 @@ public: */ void hdPolyAftertouch(int delay, int noteNumber, float normAftertouch) noexcept; /** - * @brief Send the time signature. + * @brief Send the time signature. * - * @param delay The delay. - * @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. + * @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(), and ordered with respect to + * 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); /** - * @brief Send the time position. + * @brief Send the time position. * - * @param delay The delay. - * @param bar The current bar. - * @param bar_beat The fractional position of the current beat within the bar. + * @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(), and ordered with respect to + * 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); /** - * @brief Send the playback state. + * @brief Send the playback state. * - * @param delay The delay. - * @param playback_state The playback state, 1 if playing, 0 if stopped. + * @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(), 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); + /** * @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 @@ -534,16 +542,16 @@ public: 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::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::counter().getTotalBytes(); } @@ -610,15 +618,15 @@ public: 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; /** - * @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; diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 8c09292a..c1d568b2 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -500,10 +500,14 @@ TEST_CASE("[Files] Note and octave offsets") TEST_CASE("[Files] Off modes") { Synth synth; + AudioBuffer buffer { 2, 256 }; synth.setSamplesPerBlock(256); + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/off_mode.sfz"); REQUIRE( synth.getNumRegions() == 3 ); + synth.noteOn(0, 64, 63); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); const auto* fastVoice = synth.getVoiceView(0)->getRegion()->offMode == OffMode::fast ? @@ -514,9 +518,10 @@ TEST_CASE("[Files] Off modes") synth.getVoiceView(1) : synth.getVoiceView(0) ; synth.noteOn(100, 63, 63); + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 1 ); - AudioBuffer buffer { 2, 256 }; for (unsigned i = 0; i < 20; ++i) // Not enough for the "normal" voice to die synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); diff --git a/tests/PolyphonyT.cpp b/tests/PolyphonyT.cpp index c2c75aa3..f2c56bf7 100644 --- a/tests/PolyphonyT.cpp +++ b/tests/PolyphonyT.cpp @@ -17,8 +17,6 @@ using namespace Catch::literals; using namespace sfz::literals; -constexpr int blockSize { 256 }; - TEST_CASE("[Polyphony] Polyphony in hierarchy") { sfz::Synth synth; @@ -76,72 +74,72 @@ TEST_CASE("[Polyphony] Polyphony groups") TEST_CASE("[Polyphony] group polyphony limits") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( group=1 polyphony=2 sample=*sine key=65 )"); synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); + synth.noteOn(1, 65, 64); + synth.noteOn(2, 65, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing } TEST_CASE("[Polyphony] Hierarchy polyphony limits") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( polyphony=2 sample=*sine key=65 )"); synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); + synth.noteOn(1, 65, 64); + synth.noteOn(2, 65, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing } TEST_CASE("[Polyphony] Hierarchy polyphony limits (group)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( polyphony=2 sample=*sine key=65 )"); synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); + synth.noteOn(1, 65, 64); + synth.noteOn(2, 65, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing } TEST_CASE("[Polyphony] Hierarchy polyphony limits (master)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( polyphony=2 polyphony=5 sample=*sine key=65 )"); synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); + synth.noteOn(1, 65, 64); + synth.noteOn(2, 65, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing } TEST_CASE("[Polyphony] Hierarchy polyphony limits (limit in another master)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( polyphony=2 sample=*saw key=65 @@ -150,38 +148,37 @@ TEST_CASE("[Polyphony] Hierarchy polyphony limits (limit in another master)") sample=*sine key=66 )"); synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - synth.noteOn(0, 66, 64); - synth.noteOn(0, 66, 64); - synth.noteOn(0, 66, 64); - REQUIRE( synth.getNumActiveVoices() == 6); + synth.noteOn(1, 65, 64); + synth.noteOn(2, 65, 64); + synth.noteOn(3, 66, 64); + synth.noteOn(4, 66, 64); + synth.noteOn(5, 66, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 6); REQUIRE( numPlayingVoices(synth) == 5); // One is releasing } TEST_CASE("[Polyphony] Hierarchy polyphony limits (global)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( polyphony=2 polyphony=5 sample=*sine key=65 )"); synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); + synth.noteOn(1, 65, 64); + synth.noteOn(2, 65, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing } TEST_CASE("[Polyphony] Polyphony in master") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; - synth.setSamplesPerBlock(blockSize); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( polyphony=2 group=2 @@ -192,28 +189,28 @@ TEST_CASE("[Polyphony] Polyphony in master") sample=*sine key=61 )"); synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - synth.noteOn(0, 65, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); + synth.noteOn(1, 65, 64); + synth.noteOn(2, 65, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing synth.allSoundOff(); synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0); synth.noteOn(0, 63, 64); - synth.noteOn(0, 63, 64); - synth.noteOn(0, 63, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); + synth.noteOn(1, 63, 64); + synth.noteOn(2, 63, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 2 ); // One is releasing synth.allSoundOff(); synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0); synth.noteOn(0, 61, 64); - synth.noteOn(0, 61, 64); - synth.noteOn(0, 61, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); + synth.noteOn(1, 61, 64); + synth.noteOn(2, 61, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( numPlayingVoices(synth) == 3 ); } @@ -221,15 +218,15 @@ TEST_CASE("[Polyphony] Polyphony in master") TEST_CASE("[Polyphony] Self-masking") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( sample=*sine key=64 note_polyphony=2 )"); synth.noteOn(0, 64, 63 ); - synth.noteOn(0, 64, 62 ); - synth.noteOn(0, 64, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing + synth.noteOn(1, 64, 62 ); + synth.noteOn(2, 64, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); @@ -242,15 +239,15 @@ TEST_CASE("[Polyphony] Self-masking") TEST_CASE("[Polyphony] Not self-masking") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( sample=*sine key=66 note_polyphony=2 note_selfmask=off )"); synth.noteOn(0, 66, 63 ); - synth.noteOn(0, 66, 62 ); - synth.noteOn(0, 66, 64); - REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing + synth.noteOn(1, 66, 62 ); + synth.noteOn(2, 66, 64); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); 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") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( sample=*sine key=64 note_polyphony=2 )"); synth.noteOn(0, 64, 64); - synth.noteOn(0, 64, 63 ); - synth.noteOn(0, 64, 63 ); - REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing + synth.noteOn(1, 64, 63 ); + synth.noteOn(2, 64, 63 ); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 64_norm); 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") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( sample=*sine key=64 note_polyphony=1 )"); 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( numPlayingVoices(synth) == 2 ); // id 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)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( sample=*saw key=64 note_polyphony=1 sample=*sine key=64 note_polyphony=1 )"); synth.noteOn(0, 64, 62 ); - synth.noteOn(0, 64, 63 ); - REQUIRE( synth.getNumActiveVoices() == 4); + synth.noteOn(1, 64, 63 ); synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); 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)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( sw_lokey=36 sw_hikey=37 sw_default=36 sw_last=36 key=48 note_polyphony=1 sample=*saw sw_last=37 key=48 transpose=12 note_polyphony=1 sample=*tri )"); 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); + 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( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); 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") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( group=1 sample=*saw key=64 note_polyphony=1 group=2 sample=*sine key=64 note_polyphony=1 )"); synth.noteOn(0, 64, 62 ); - synth.noteOn(0, 64, 63 ); - REQUIRE( synth.getNumActiveVoices() == 4); // Both notes are playing + synth.noteOn(1, 64, 63 ); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 4); // Both notes are playing REQUIRE(numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm); 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)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( sw_lokey=36 sw_hikey=37 sw_default=36 group=1 sw_last=36 key=48 note_polyphony=1 sample=*saw group=2 sw_last=37 key=48 transpose=12 note_polyphony=1 sample=*tri )"); 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); + 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( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); 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") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( key=48 note_polyphony=1 sample=*saw trigger=release_key ampeg_attack=1 ampeg_decay=1 )"); synth.noteOn(0, 48, 63 ); synth.noteOff(10, 48, 0 ); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1); synth.noteOn(20, 48, 65 ); synth.noteOff(30, 48, 10 ); - REQUIRE( synth.getNumActiveVoices() == 2 ); synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE(numPlayingVoices(synth) == 1 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); 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)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( key=48 note_polyphony=1 sample=*saw trigger=release_key ampeg_attack=1 ampeg_decay=1 )"); synth.noteOn(0, 48, 63 ); synth.noteOff(10, 48, 0 ); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1); REQUIRE( numPlayingVoices(synth) == 1 ); synth.noteOn(20, 48, 61 ); synth.noteOff(30, 48, 10 ); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( numPlayingVoices(synth) == 2 ); 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") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( key=48 sample=*sine 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.noteOn(4, 48, 63 ); 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); - REQUIRE( numPlayingVoices(synth) == 3 ); + std::vector expectedSamples { "*sine", "*sine", "*sine" }; + REQUIRE( playingSamples(synth) == expectedSamples ); 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); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 61_norm); - REQUIRE( synth.getVoiceView(0)->releasedOrFree()); - REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); - 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()); + std::vector expectedSamples2 { "*saw" }; + std::vector expectedVelocities { 63_norm }; + REQUIRE( playingSamples(synth) == expectedSamples2 ); + REQUIRE( playingVelocities(synth) == expectedVelocities ); } TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal (masking)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( key=48 sample=*sine 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.noteOn(4, 48, 61 ); 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); - REQUIRE( numPlayingVoices(synth) == 3 ); + std::vector expectedSamples { "*sine", "*sine", "*sine" }; + REQUIRE( playingSamples(synth) == expectedSamples ); 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); - REQUIRE( numPlayingVoices(synth) == 3 ); - REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); - REQUIRE( synth.getVoiceView(0)->releasedOrFree()); - REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); - 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()); + std::vector expectedSamples2 { "*saw", "*saw", "*saw" }; + std::vector expectedVelocities { 63_norm, 62_norm, 61_norm }; + REQUIRE( playingSamples(synth) == expectedSamples2 ); + REQUIRE( playingVelocities(synth) == expectedVelocities ); } TEST_CASE("[Polyphony] Bi-directional choking (with polyphony)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( key=60 polyphony=1 sample=kick.wav loop_mode=one_shot sample=snare.wav trigger=release )"); synth.noteOn(0, 60, 63 ); - REQUIRE( synth.getNumActiveVoices() == 1); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "kick.wav" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "kick.wav" } ); synth.noteOff(10, 60, 63 ); - REQUIRE( synth.getNumActiveVoices() == 2); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "snare.wav" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "snare.wav" } ); synth.noteOn(20, 60, 63 ); - REQUIRE( synth.getNumActiveVoices() == 3); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "kick.wav" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "kick.wav" } ); } TEST_CASE("[Polyphony] Bi-directional choking (with note_polyphony)") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( key=60 note_polyphony=1 sample=kick.wav loop_mode=one_shot sample=snare.wav trigger=release )"); synth.noteOn(0, 60, 63 ); - REQUIRE( synth.getNumActiveVoices() == 1); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "kick.wav" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "kick.wav" } ); synth.noteOff(10, 60, 63 ); - REQUIRE( synth.getNumActiveVoices() == 2); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "snare.wav" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "snare.wav" } ); synth.noteOn(20, 60, 63 ); - REQUIRE( synth.getNumActiveVoices() == 3); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "kick.wav" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "kick.wav" } ); } diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index d62e1ef1..fb69c984 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -190,159 +190,200 @@ TEST_CASE("Region activation", "Region tests") TEST_CASE("[Keyswitches] Normal lastKeyswitch range") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( sw_lokey=40 sw_hikey=42 sw_default=40 sw_last=40 key=60 sample=*sine sw_last=41 key=62 sample=*saw )"); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 41, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); } TEST_CASE("[Keyswitches] No lastKeyswitch range") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( sw_last=40 key=60 sample=*sine sw_last=41 key=62 sample=*saw )"); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); synth.noteOn(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 41, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); } TEST_CASE("[Keyswitches] Out of lastKeyswitch range") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( sw_lokey=40 sw_hikey=42 sw_default=40 sw_last=40 key=60 sample=*sine sw_last=43 key=62 sample=*saw )"); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 43, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); } TEST_CASE("[Keyswitches] Overlapping key and lastKeyswitch range") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( sw_lokey=1 sw_hikey=127 sw_default=40 sw_last=40 key=60 sample=*sine sw_last=41 key=62 sample=*saw )"); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 41, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); synth.noteOn(0, 43, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 3); } TEST_CASE("[Keyswitches] sw_down, in range") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( sw_lokey=1 sw_hikey=127 sw_default=40 sw_down=40 key=60 sample=*sine )"); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); synth.noteOn(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOff(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); } TEST_CASE("[Keyswitches] sw_down, out of range") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( sw_lokey=1 sw_hikey=10 sw_default=40 sw_down=40 key=60 sample=*sine )"); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); synth.noteOn(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOff(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); } TEST_CASE("[Keyswitches] sw_up, in range") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( sw_lokey=1 sw_hikey=127 sw_default=40 sw_up=40 key=60 sample=*sine )"); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOff(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); } TEST_CASE("[Keyswitches] sw_up, out of range") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"( sw_lokey=1 sw_hikey=127 sw_default=40 sw_up=40 key=60 sample=*sine )"); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOn(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.noteOff(0, 40, 64); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); } TEST_CASE("[Keyswitches] sw_default") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"( sw_lokey=30 sw_hikey=50 sw_default=40 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") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"( sw_lokey=30 sw_hikey=50 sw_default=40 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() ); synth.noteOn(0, 41, 64); synth.noteOff(0, 41, 0); + synth.renderBlock(buffer); REQUIRE( synth.getLayerView(0)->isSwitchedOn() ); REQUIRE( !synth.getLayerView(1)->isSwitchedOn() ); REQUIRE( synth.getLayerView(2)->isSwitchedOn() ); REQUIRE( !synth.getLayerView(3)->isSwitchedOn() ); synth.noteOn(0, 40, 64); synth.noteOff(0, 40, 64); + synth.renderBlock(buffer); REQUIRE( !synth.getLayerView(0)->isSwitchedOn() ); REQUIRE( synth.getLayerView(1)->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") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( 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 REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 51, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 51, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); 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 sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( sample=*saw sw_previous=60 lokey=50 hikey=55 )"); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 51, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 51, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); synth.noteOn(0, 61, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); } TEST_CASE("[Keyswitches] sw_lolast and sw_hilast") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( sw_lolast=57 sw_hilast=59 key=70 sample=*saw 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(1)->isSwitchedOn()); synth.noteOn(0, 51, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 57, 64); + synth.renderBlock(buffer); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 58, 64); + synth.renderBlock(buffer); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 61, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 59, 64); + synth.renderBlock(buffer); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 62, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->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") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( sw_last=40 sw_lolast=57 sw_hilast=59 key=70 sample=*saw 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(1)->isSwitchedOn()); synth.noteOn(0, 40, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 41, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 57, 64); + synth.renderBlock(buffer); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 41, 64); + synth.renderBlock(buffer); REQUIRE(synth.getLayerView(0)->isSwitchedOn()); REQUIRE(!synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 60, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->isSwitchedOn()); REQUIRE(synth.getLayerView(1)->isSwitchedOn()); synth.noteOn(0, 40, 64); + synth.renderBlock(buffer); REQUIRE(!synth.getLayerView(0)->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") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( sw_default=58 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") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( sw_default=60 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") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"( sw_default=60 sw_last=58 key=70 sample=*saw diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index c19dbbe1..5e5931a3 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -227,6 +227,7 @@ TEST_CASE("Legato triggers", "Region triggers") TEST_CASE("[Triggers] sw_vel, basic") { Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; std::vector messageList; Client client(&messageList); client.setReceiveCallback(&simpleMessageReceiver); @@ -236,6 +237,7 @@ TEST_CASE("[Triggers] sw_vel, basic") )"); synth.noteOn(0, 60, 127); synth.noteOn(10, 62, 10); + synth.renderBlock(buffer); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/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") { Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; std::vector messageList; Client client(&messageList); client.setReceiveCallback(&simpleMessageReceiver); @@ -259,6 +262,7 @@ TEST_CASE("[Triggers] sw_vel, without sw_previous") )"); synth.noteOn(0, 60, 127); synth.noteOn(10, 62, 10); + synth.renderBlock(buffer); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/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") { Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; std::vector messageList; Client client(&messageList); client.setReceiveCallback(&simpleMessageReceiver); @@ -284,6 +289,7 @@ TEST_CASE("[Triggers] sw_vel, with a note in between") synth.noteOn(0, 60, 127); synth.noteOn(5, 64, 63); synth.noteOn(10, 62, 10); + synth.renderBlock(buffer); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/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") { Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; std::vector messageList; Client client(&messageList); 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(5, 64, 63); synth.noteOn(10, 62, 10); + synth.renderBlock(buffer); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/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") { Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; std::vector messageList; Client client(&messageList); client.setReceiveCallback(&simpleMessageReceiver); @@ -336,6 +345,7 @@ TEST_CASE("[Triggers] sw_vel, consider the previous velocity for triggers") SECTION("Should trigger") { synth.noteOn(0, 60, 127); synth.noteOn(10, 62, 10); + synth.renderBlock(buffer); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/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") { synth.noteOn(0, 60, 10); synth.noteOn(10, 62, 127); + synth.renderBlock(buffer); synth.dispatchMessage(client, 0, "/num_active_voices", "", nullptr); synth.dispatchMessage(client, 0, "/voice0/trigger_value", "", nullptr); synth.dispatchMessage(client, 0, "/voice1/trigger_value", "", nullptr); diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index cb1a2c9e..bea0fcf5 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -20,17 +20,15 @@ using namespace sfz::literals; // Need these for the introspection of Synth #include "sfizz/Effects.h" -constexpr int blockSize { 256 }; - TEST_CASE("[Synth] Play and check active voices") { sfz::Synth synth; - synth.setSamplesPerBlock(blockSize); - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.noteOn(0, 36, 24); synth.noteOn(0, 36, 89); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); // Render for a while for (int i = 0; i < 300; ++i) @@ -41,19 +39,21 @@ TEST_CASE("[Synth] Play and check active voices") TEST_CASE("[Synth] All sound off") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.noteOn(0, 36, 24); synth.noteOn(0, 36, 89); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); synth.allSoundOff(); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); } TEST_CASE("[Synth] Change the number of voice while playing") { sfz::Synth synth; - synth.setSamplesPerBlock(blockSize); - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.noteOn(0, 36, 24); @@ -100,8 +100,7 @@ TEST_CASE("[Synth] Check that we can change the size of the preload before and a { sfz::Synth synth; synth.setPreloadSize(512); - synth.setSamplesPerBlock(blockSize); - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.setPreloadSize(1024); @@ -116,8 +115,7 @@ TEST_CASE("[Synth] Check that we can change the oversampling factor before and a { sfz::Synth synth; synth.setOversamplingFactor(sfz::Oversampling::x2); - synth.setSamplesPerBlock(blockSize); - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.setOversamplingFactor(sfz::Oversampling::x4); @@ -132,6 +130,7 @@ TEST_CASE("[Synth] Check that we can change the oversampling factor before and a TEST_CASE("[Synth] All notes offs/all sounds off") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.setNumVoices(8); synth.loadSfzString(fs::current_path() / "tests/TestFiles/sound_off.sfz", R"( key=60 sample=*noise @@ -139,43 +138,47 @@ TEST_CASE("[Synth] All notes offs/all sounds off") )"); synth.noteOn(0, 60, 63); synth.noteOn(0, 62, 63); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); synth.cc(0, 120, 63); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 62, 63); synth.noteOn(0, 60, 63); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); synth.cc(0, 123, 63); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); } TEST_CASE("[Synth] Reset all controllers") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; const auto& midiState = synth.getResources().midiState; synth.cc(0, 12, 64); + synth.renderBlock(buffer); REQUIRE(midiState.getCCValue(12) == 64_norm); synth.cc(0, 121, 64); + synth.renderBlock(buffer); REQUIRE(midiState.getCCValue(12) == 0_norm); } TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice") { sfz::Synth synth; - synth.setSamplesPerBlock(1024); - sfz::AudioBuffer buffer { 2, 1024 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.setNumVoices(1); synth.loadSfzString(fs::current_path() / "tests/TestFiles/delay_release.sfz", R"( ampeg_delay=0.005 ampeg_release=1 sample=*noise )"); synth.noteOn(0, 60, 63); - REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.noteOff(100, 60, 63); synth.renderBlock(buffer); REQUIRE( synth.getVoiceView(0)->isFree() ); synth.noteOn(200, 60, 63); - REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.noteOff(1000, 60, 63); synth.renderBlock(buffer); REQUIRE( !synth.getVoiceView(0)->isFree() ); @@ -184,14 +187,12 @@ TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kil TEST_CASE("[Synth] Releasing after the initial and normal mode does not trigger a fast release") { sfz::Synth synth; - synth.setSamplesPerBlock(1024); - sfz::AudioBuffer buffer(2, 1024); + sfz::AudioBuffer buffer{ 2, static_cast(synth.getSamplesPerBlock()) }; synth.setNumVoices(1); synth.loadSfzString(fs::current_path() / "tests/TestFiles/delay_release.sfz", R"( ampeg_delay=0.005 ampeg_release=1 sample=*noise )"); synth.noteOn(200, 60, 63); - REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.renderBlock(buffer); REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.noteOff(0, 60, 63); @@ -213,9 +214,9 @@ TEST_CASE("[Synth] Trigger=release and an envelope properly kills the voice at t ampeg_attack=0.02 ampeg_decay=0.02 ampeg_release=0.1 ampeg_sustain=0 )"); synth.noteOn(0, 60, 63); - synth.noteOff(0, 60, 63); - REQUIRE( !synth.getVoiceView(0)->isFree() ); + synth.noteOff(10, 60, 63); synth.renderBlock(buffer); // Attack (0.02) + REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.renderBlock(buffer); synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); @@ -240,9 +241,9 @@ TEST_CASE("[Synth] Trigger=release_key and an envelope properly kills the voice ampeg_attack=0.02 ampeg_decay=0.02 ampeg_release=0.1 ampeg_sustain=0 )"); synth.noteOn(0, 60, 63); - synth.noteOff(0, 60, 63); - REQUIRE( !synth.getVoiceView(0)->isFree() ); + synth.noteOff(10, 60, 63); synth.renderBlock(buffer); // Attack (0.02) + REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.renderBlock(buffer); synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); @@ -268,8 +269,8 @@ TEST_CASE("[Synth] loopmode=one_shot and an envelope properly kills the voice at )"); synth.noteOn(0, 60, 63); synth.noteOff(0, 60, 63); - REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.renderBlock(buffer); // Attack (0.02) + REQUIRE( !synth.getVoiceView(0)->isFree() ); synth.renderBlock(buffer); synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); @@ -284,8 +285,7 @@ TEST_CASE("[Synth] loopmode=one_shot and an envelope properly kills the voice at TEST_CASE("[Synth] Number of effect buses and resetting behavior") { sfz::Synth synth; - synth.setSamplesPerBlock(blockSize); - sfz::AudioBuffer buffer { 2, blockSize }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; REQUIRE( synth.getEffectBusView(0) == nullptr); // No effects at first synth.loadSfzString(fs::current_path() / "tests/TestFiles/Effects/base.sfz", R"( @@ -565,6 +565,7 @@ TEST_CASE("[Synth] Region by identifier") TEST_CASE("[Synth] sample quality") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString("tests/TestFiles/sampleQuality.sfz", R"( sample=kick.wav key=60 @@ -573,6 +574,7 @@ TEST_CASE("[Synth] sample quality") // default sample quality synth.noteOn(0, 60, 100); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::sampleQuality); synth.allSoundOff(); @@ -580,6 +582,7 @@ TEST_CASE("[Synth] sample quality") // default sample quality, freewheeling synth.enableFreeWheeling(); synth.noteOn(0, 60, 100); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == sfz::Default::freewheelingQuality); synth.allSoundOff(); @@ -588,6 +591,7 @@ TEST_CASE("[Synth] sample quality") // user-defined sample quality synth.setSampleQuality(sfz::Synth::ProcessLive, 3); synth.noteOn(0, 60, 100); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == 3); synth.allSoundOff(); @@ -596,6 +600,7 @@ TEST_CASE("[Synth] sample quality") synth.enableFreeWheeling(); synth.setSampleQuality(sfz::Synth::ProcessFreewheeling, 8); synth.noteOn(0, 60, 100); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == 8); synth.allSoundOff(); @@ -603,6 +608,7 @@ TEST_CASE("[Synth] sample quality") // region sample quality synth.noteOn(0, 61, 100); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == 5); synth.allSoundOff(); @@ -610,6 +616,7 @@ TEST_CASE("[Synth] sample quality") // region sample quality, freewheeling synth.enableFreeWheeling(); synth.noteOn(0, 61, 100); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); REQUIRE(synth.getVoiceView(0)->getCurrentSampleQuality() == 5); synth.allSoundOff(); @@ -620,6 +627,7 @@ TEST_CASE("[Synth] sample quality") TEST_CASE("[Synth] Sister voices") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sister_voices.sfz", R"( key=61 sample=*sine key=62 sample=*sine @@ -629,10 +637,12 @@ TEST_CASE("[Synth] Sister voices") key=63 sample=*saw )"); synth.noteOn(0, 61, 85); + synth.renderBlock(buffer); REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(0)) == 1 ); REQUIRE( synth.getVoiceView(0)->getNextSisterVoice() == synth.getVoiceView(0) ); REQUIRE( synth.getVoiceView(0)->getPreviousSisterVoice() == synth.getVoiceView(0) ); synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 3 ); REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(1)) == 2 ); REQUIRE( synth.getVoiceView(1)->getNextSisterVoice() == synth.getVoiceView(2) ); @@ -641,6 +651,7 @@ TEST_CASE("[Synth] Sister voices") REQUIRE( synth.getVoiceView(2)->getNextSisterVoice() == synth.getVoiceView(1) ); REQUIRE( synth.getVoiceView(2)->getPreviousSisterVoice() == synth.getVoiceView(1) ); synth.noteOn(0, 63, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 6 ); REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(3)) == 3 ); REQUIRE( synth.getVoiceView(3)->getNextSisterVoice() == synth.getVoiceView(4) ); @@ -656,13 +667,14 @@ TEST_CASE("[Synth] Sister voices") TEST_CASE("[Synth] Apply function on sisters") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sister_voices.sfz", R"( key=63 sample=*saw key=63 sample=*saw key=63 sample=*saw )"); synth.noteOn(0, 63, 85); + synth.renderBlock(buffer); REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(0)) == 3 ); float start = 1.0f; sfz::SisterVoiceRing::applyToRing(synth.getVoiceView(0), [&](const sfz::Voice* v) { @@ -674,18 +686,18 @@ TEST_CASE("[Synth] Apply function on sisters") TEST_CASE("[Synth] Sisters and off-by") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sister_voices.sfz", R"( key=62 sample=*sine group=1 off_by=2 key=62 sample=*sine group=2 key=63 sample=*saw )"); synth.noteOn(0, 62, 85); - REQUIRE( synth.getNumActiveVoices() == 2 ); - REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(0)) == 2 ); synth.renderBlock(buffer); + REQUIRE( sfz::SisterVoiceRing::countSisterVoices(synth.getVoiceView(0)) == 2 ); REQUIRE( synth.getNumActiveVoices() == 2 ); synth.noteOn(0, 63, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 3 ); for (unsigned i = 0; i < 100; ++i) synth.renderBlock(buffer); @@ -703,49 +715,44 @@ TEST_CASE("[Synth] Release (basic behavior with sample)") key=62 sample=closedhat.wav trigger=release_key )"); synth.noteOn(0, 62, 85); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "*sine" ); - synth.noteOff(0, 62, 85); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "closedhat.wav" ); synth.renderBlock(buffer); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "closedhat.wav" ); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); + synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "closedhat.wav" } ); } TEST_CASE("[Synth] Release key (basic behavior with sample)") { sfz::Synth synth; - synth.setSamplesPerBlock(4096); - sfz::AudioBuffer buffer { 2, 4096 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release_key_sample.sfz", R"( key=62 sample=closedhat.wav trigger=release_key )"); synth.noteOn(0, 62, 85); synth.noteOff(0, 62, 85); - REQUIRE( numPlayingVoices(synth) == 1 ); synth.renderBlock(buffer); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "closedhat.wav" ); + REQUIRE( playingSamples(synth) == std::vector { "closedhat.wav" } ); } TEST_CASE("[Synth] Release key (pedal)") { sfz::Synth synth; - synth.setSamplesPerBlock(4096); - sfz::AudioBuffer buffer { 2, 4096 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release_key_pedal.sfz", R"( key=62 sample=*sine trigger=release_key )"); synth.noteOn(0, 62, 85); synth.cc(0, 64, 127); synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); } TEST_CASE("[Synth] Release") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( key=62 sample=*silence key=62 sample=*sine trigger=release @@ -753,14 +760,17 @@ TEST_CASE("[Synth] Release") synth.noteOn(0, 62, 85); synth.cc(0, 64, 127); synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); synth.cc(0, 64, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); } TEST_CASE("[Synth] Release (pedal was already down)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( key=62 sample=*silence key=62 sample=*sine trigger=release @@ -768,30 +778,36 @@ TEST_CASE("[Synth] Release (pedal was already down)") synth.cc(0, 64, 127); synth.noteOn(0, 62, 85); synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); synth.cc(0, 64, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); } TEST_CASE("[Synth] Release samples don't play unless there is another playing region that matches") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( key=62 sample=*sine trigger=release )"); synth.noteOn(0, 62, 85); synth.noteOff(0, 62, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.cc(0, 64, 127); synth.noteOn(0, 62, 85); synth.noteOff(0, 62, 0); synth.cc(0, 64, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); } TEST_CASE("[Synth] Release key (Different sustain CC)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( sustain_cc=54 key=62 sample=*sine trigger=release_key @@ -799,12 +815,14 @@ TEST_CASE("[Synth] Release key (Different sustain CC)") synth.noteOn(0, 62, 85); synth.cc(0, 54, 127); synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); } TEST_CASE("[Synth] Release (Different sustain CC)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( sustain_cc=54 key=62 sample=*silence @@ -813,14 +831,17 @@ TEST_CASE("[Synth] Release (Different sustain CC)") synth.noteOn(0, 62, 85); synth.cc(0, 54, 127); synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); synth.cc(0, 54, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); } TEST_CASE("[Synth] Release (don't check sustain)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( sustain_cc=54 sustain_sw=off key=62 sample=*silence @@ -829,12 +850,14 @@ TEST_CASE("[Synth] Release (don't check sustain)") synth.noteOn(0, 62, 85); synth.cc(0, 54, 127); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices() == 2 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); } TEST_CASE("[Synth] Release key (Different sostenuto CC)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( sostenuto_cc=54 key=62 sample=*sine trigger=release_key @@ -842,12 +865,14 @@ TEST_CASE("[Synth] Release key (Different sostenuto CC)") synth.noteOn(0, 62, 85); synth.cc(0, 54, 127); synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); } TEST_CASE("[Synth] Release (don't check sostenuto)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( sostenuto_cc=54 sostenuto_sw=off key=62 sample=*silence @@ -856,12 +881,14 @@ TEST_CASE("[Synth] Release (don't check sostenuto)") synth.noteOn(0, 62, 85); synth.cc(0, 54, 127); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices() == 2 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); } TEST_CASE("[Synth] Release (Different sostenuto CC)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( sostenuto_cc=54 key=62 sample=*silence @@ -874,8 +901,10 @@ TEST_CASE("[Synth] Release (Different sostenuto CC)") synth.noteOn(0, 62, 85); synth.cc(1, 54, 127); synth.noteOff(2, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); synth.cc(3, 54, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); } SECTION("Two notes, only one with sostenuto") @@ -885,8 +914,10 @@ TEST_CASE("[Synth] Release (Different sostenuto CC)") synth.noteOn(2, 64, 85); synth.noteOff(3, 62, 85); synth.noteOff(3, 64, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 3 ); synth.cc(4, 54, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 4 ); } } @@ -894,6 +925,7 @@ TEST_CASE("[Synth] Release (Different sostenuto CC)") TEST_CASE("[Synth] Release (sustain + sostenuto)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( key=62 sample=*silence key=62 sample=*sine trigger=release @@ -906,10 +938,13 @@ TEST_CASE("[Synth] Release (sustain + sostenuto)") synth.cc(1, 66, 127); synth.cc(1, 64, 127); synth.noteOff(2, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); synth.cc(3, 64, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); synth.cc(4, 66, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); } SECTION("Sostenuto up first") @@ -918,10 +953,13 @@ TEST_CASE("[Synth] Release (sustain + sostenuto)") synth.cc(1, 66, 127); synth.cc(1, 64, 127); synth.noteOff(2, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); synth.cc(3, 66, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); synth.cc(4, 64, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); } } @@ -975,18 +1013,21 @@ TEST_CASE("[Synth] One shot regions with sustain + sostenuto") TEST_CASE("[Synth] Sustain threshold default") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( key=62 sample=*sine trigger=release )"); synth.noteOn(0, 62, 85); synth.cc(0, 64, 1); synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); } TEST_CASE("[Synth] Sustain threshold") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( sustain_lo=63 key=62 sample=*silence @@ -995,20 +1036,25 @@ TEST_CASE("[Synth] Sustain threshold") synth.noteOn(0, 62, 85); synth.cc(0, 64, 1); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices() == 2 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); synth.noteOn(0, 62, 85); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices() == 4 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine", "*sine" } ); synth.noteOn(0, 62, 85); - REQUIRE( synth.getNumActiveVoices() == 5 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*silence", "*sine", "*sine" } ); synth.cc(0, 64, 64); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices() == 5 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*silence", "*sine", "*sine" } ); } TEST_CASE("[Synth] Sustain") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sostenuto.sfz", R"( sample=*sine )"); @@ -1016,11 +1062,14 @@ TEST_CASE("[Synth] Sustain") SECTION("1 note") { synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); synth.cc(1, 64, 1); synth.noteOff(2, 62, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); synth.cc(3, 64, 0); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 0 ); } @@ -1031,8 +1080,10 @@ TEST_CASE("[Synth] Sustain") synth.cc(1, 64, 1); synth.noteOff(2, 62, 85); synth.noteOff(2, 64, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 2 ); synth.cc(3, 64, 0); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 0 ); } @@ -1041,8 +1092,10 @@ TEST_CASE("[Synth] Sustain") synth.cc(0, 64, 1); synth.noteOn(1, 62, 85); synth.noteOff(2, 62, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); synth.cc(3, 64, 0); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 0 ); } @@ -1053,8 +1106,10 @@ TEST_CASE("[Synth] Sustain") synth.noteOn(2, 64, 85); synth.noteOff(3, 62, 85); synth.noteOff(3, 64, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 2 ); synth.cc(4, 64, 0); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 0 ); } } @@ -1062,6 +1117,7 @@ TEST_CASE("[Synth] Sustain") TEST_CASE("[Synth] Sostenuto") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/sostenuto.sfz", R"( sample=*sine )"); @@ -1071,8 +1127,10 @@ TEST_CASE("[Synth] Sostenuto") synth.noteOn(0, 62, 85); synth.cc(1, 66, 1); synth.noteOff(2, 62, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); synth.cc(3, 66, 0); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 0 ); } @@ -1083,8 +1141,10 @@ TEST_CASE("[Synth] Sostenuto") synth.cc(1, 66, 1); synth.noteOff(2, 62, 85); synth.noteOff(2, 64, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 2 ); synth.cc(3, 66, 0); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 0 ); } @@ -1093,6 +1153,7 @@ TEST_CASE("[Synth] Sostenuto") synth.cc(0, 66, 1); synth.noteOn(1, 62, 85); synth.noteOff(2, 62, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 0 ); } @@ -1103,6 +1164,7 @@ TEST_CASE("[Synth] Sostenuto") synth.noteOn(2, 64, 85); synth.noteOff(3, 62, 85); synth.noteOff(3, 64, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( getActiveVoices(synth)[0]->getTriggerEvent().number == 62 ); } @@ -1111,6 +1173,7 @@ TEST_CASE("[Synth] Sostenuto") TEST_CASE("[Synth] Release (Multiple notes, release_key ignores the pedal)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( lokey=62 hikey=64 sample=*sine trigger=release_key )"); @@ -1121,20 +1184,14 @@ TEST_CASE("[Synth] Release (Multiple notes, release_key ignores the pedal)") synth.noteOff(0, 64, 0); synth.noteOff(0, 63, 2); synth.noteOff(0, 62, 85); - REQUIRE( synth.getNumActiveVoices() == 3 ); - - std::vector requiredVelocities { 34_norm, 78_norm, 85_norm}; - std::vector actualVelocities; - for (auto* v: getActiveVoices(synth)) { - actualVelocities.push_back(v->getTriggerEvent().value); - } - sortAll(requiredVelocities, actualVelocities); - REQUIRE( requiredVelocities == actualVelocities ); + synth.renderBlock(buffer); + REQUIRE( activeVelocities(synth) == std::vector { 34_norm, 78_norm, 85_norm} ); } TEST_CASE("[Synth] Release (Multiple notes, release, cleared the delayed voices after)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( lokey=62 hikey=64 sample=*silence lokey=62 hikey=64 sample=*sine trigger=release @@ -1147,24 +1204,20 @@ TEST_CASE("[Synth] Release (Multiple notes, release, cleared the delayed voices synth.noteOff(0, 64, 0); synth.noteOff(0, 63, 2); synth.noteOff(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 3 ); synth.cc(0, 64, 0); - REQUIRE( synth.getNumActiveVoices() == 6 ); - - std::vector requiredVelocities { 34_norm, 78_norm, 85_norm, 34_norm, 78_norm, 85_norm }; - std::vector actualVelocities; - for (auto* v: getActiveVoices(synth)) { - actualVelocities.push_back(v->getTriggerEvent().value); - } - sortAll(requiredVelocities, actualVelocities); - REQUIRE( requiredVelocities == actualVelocities ); - + synth.renderBlock(buffer); + auto velocities = activeVelocities(synth); + absl::c_sort(velocities); + REQUIRE( velocities == std::vector { 34_norm, 34_norm, 78_norm, 78_norm, 85_norm, 85_norm } ); REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); } TEST_CASE("[Synth] Release (Multiple notes after pedal is down, release, cleared the delayed voices after)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( lokey=62 hikey=64 sample=*silence lokey=62 hikey=64 sample=*sine trigger=release @@ -1177,24 +1230,20 @@ TEST_CASE("[Synth] Release (Multiple notes after pedal is down, release, cleared synth.noteOff(2, 64, 0); synth.noteOff(2, 63, 2); synth.noteOff(2, 62, 3); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 3 ); synth.cc(3, 64, 0); - REQUIRE( synth.getNumActiveVoices() == 6 ); - - std::vector requiredVelocities { 34_norm, 78_norm, 85_norm, 34_norm, 78_norm, 85_norm }; - std::vector actualVelocities; - for (auto* v: getActiveVoices(synth)) { - actualVelocities.push_back(v->getTriggerEvent().value); - } - sortAll(requiredVelocities, actualVelocities); - REQUIRE( requiredVelocities == actualVelocities ); - + synth.renderBlock(buffer); + auto velocities = activeVelocities(synth); + absl::c_sort(velocities); + REQUIRE( velocities == std::vector { 34_norm, 34_norm, 78_norm, 78_norm, 85_norm, 85_norm } ); REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); } TEST_CASE("[Synth] Release (Multiple note ons during pedal down)") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/release.sfz", R"( lokey=62 hikey=64 sample=*silence lokey=62 hikey=64 sample=*sine trigger=release @@ -1205,17 +1254,13 @@ TEST_CASE("[Synth] Release (Multiple note ons during pedal down)") synth.noteOff(0, 62, 0); synth.noteOn(0, 62, 78); synth.noteOff(0, 62, 2); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 2 ); synth.cc(0, 64, 0); - REQUIRE( synth.getNumActiveVoices() == 4 ); - - std::vector requiredVelocities { 78_norm, 85_norm, 78_norm, 85_norm }; - std::vector actualVelocities; - for (auto* v: getActiveVoices(synth)) { - actualVelocities.push_back(v->getTriggerEvent().value); - } - sortAll(requiredVelocities, actualVelocities); - REQUIRE( requiredVelocities == actualVelocities ); + synth.renderBlock(buffer); + auto velocities = activeVelocities(synth); + absl::c_sort(velocities); + REQUIRE( velocities == std::vector { 78_norm, 78_norm, 85_norm, 85_norm } ); REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); } @@ -1231,24 +1276,29 @@ TEST_CASE("[Synth] No release sample after the main sample stopped sounding by d loopmode=one_shot ampeg_attack=0.02 ampeg_release=0.1 )"); synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); for (unsigned i = 0; i < 100; ++i) { synth.renderBlock(buffer); } REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOff(0, 62, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 62, 85); synth.cc(0, 64, 127); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); for (unsigned i = 0; i < 100; ++i) { synth.renderBlock(buffer); } REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOff(0, 62, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.cc(0, 64, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); @@ -1266,6 +1316,7 @@ TEST_CASE("[Synth] If rt_dead is active the release sample can sound after the a loopmode=one_shot ampeg_attack=0.02 ampeg_release=0.1 )"); synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); for (unsigned i = 0; i < 100; ++i) { synth.renderBlock(buffer); @@ -1276,14 +1327,17 @@ TEST_CASE("[Synth] If rt_dead is active the release sample can sound after the a synth.noteOn(0, 62, 85); synth.cc(0, 64, 127); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); for (unsigned i = 0; i < 100; ++i) { synth.renderBlock(buffer); } REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOff(0, 62, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.cc(0, 64, 0); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); REQUIRE( synth.getLayerView(1)->delayedSustainReleases_.empty() ); @@ -1292,42 +1346,51 @@ TEST_CASE("[Synth] If rt_dead is active the release sample can sound after the a TEST_CASE("[Synth] sw_default works at a global level") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( sw_default=36 sw_lokey=36 sw_hikey=39 sw_last=36 key=62 sample=*sine sw_last=37 key=63 sample=*sine )"); synth.noteOn(0, 63, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); } TEST_CASE("[Synth] sw_default works at a master level") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( sw_default=36 sw_lokey=36 sw_hikey=39 sw_last=36 key=62 sample=*sine sw_last=37 key=63 sample=*sine )"); synth.noteOn(0, 63, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); } TEST_CASE("[Synth] sw_default works at a group level") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( sw_default=36 sw_lokey=36 sw_hikey=39 sw_last=36 key=62 sample=*sine sw_last=37 key=63 sample=*sine )"); synth.noteOn(0, 63, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); } @@ -1422,30 +1485,35 @@ TEST_CASE("[Synth] Used CCs EGs") TEST_CASE("[Synth] Activate also on the sustain CC") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( locc64=64 key=53 sample=*sine )"); synth.noteOn(0, 53, 127); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.cc(1, 64, 127); synth.noteOn(2, 53, 127); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); } TEST_CASE("[Synth] Trigger also on the sustain CC") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( on_locc64=64 sample=*sine )"); synth.cc(0, 64, 127); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); } TEST_CASE("[Synth] end=-1 voices are immediately killed after triggering but they kill other voices") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( key=60 end=-1 sample=*sine @@ -1454,10 +1522,13 @@ TEST_CASE("[Synth] end=-1 voices are immediately killed after triggering but the key=63 end=-1 sample=*saw group=2 )"); synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 61, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); REQUIRE( numPlayingVoices(synth) == 1 ); synth.noteOn(1, 63, 85); @@ -1468,7 +1539,7 @@ TEST_CASE("[Synth] end=-1 voices are immediately killed after triggering but the TEST_CASE("[Synth] end=0 voices are immediately killed after triggering but they kill other voices") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( key=60 end=0 sample=*sine @@ -1477,10 +1548,13 @@ TEST_CASE("[Synth] end=0 voices are immediately killed after triggering but they key=63 end=0 sample=*saw group=2 )"); synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 61, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); REQUIRE( numPlayingVoices(synth) == 1 ); synth.noteOn(1, 63, 85); @@ -1491,7 +1565,7 @@ TEST_CASE("[Synth] end=0 voices are immediately killed after triggering but they TEST_CASE("[Synth] ampeg_sustain = 0 puts the ampeg envelope in free-running mode, which kills the voice almost instantly in most cases") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( key=60 sample=*sine ampeg_sustain=0 @@ -1499,10 +1573,10 @@ TEST_CASE("[Synth] ampeg_sustain = 0 puts the ampeg envelope in free-running mod )"); synth.noteOn(0, 60, 85); - REQUIRE( synth.getNumActiveVoices() == 1 ); synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); synth.noteOn(0, 61, 85); + synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 1 ); // Render a bit; this does not kill the voice for (unsigned i = 0; i < 5; ++i) @@ -1517,19 +1591,22 @@ TEST_CASE("[Synth] ampeg_sustain = 0 puts the ampeg envelope in free-running mod TEST_CASE("[Synth] Off by standard") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( group=1 off_by=2 sample=*saw transpose=12 key=60 group=2 off_by=1 sample=*triangle key=62 )"); synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); synth.noteOn(10, 62, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); auto playingVoices = getPlayingVoices(synth); REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(62) ); synth.noteOn(10, 60, 85); + synth.renderBlock(buffer); playingVoices = getPlayingVoices(synth); REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(60) ); } @@ -1537,19 +1614,22 @@ TEST_CASE("[Synth] Off by standard") TEST_CASE("[Synth] Off by same group") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( group=1 off_by=1 sample=*saw transpose=12 key=60 group=1 off_by=1 sample=*triangle key=62 )"); synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); synth.noteOn(10, 62, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); auto playingVoices = getPlayingVoices(synth); REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(62) ); synth.noteOn(10, 60, 85); + synth.renderBlock(buffer); playingVoices = getPlayingVoices(synth); REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(60) ); } @@ -1557,16 +1637,19 @@ TEST_CASE("[Synth] Off by same group") TEST_CASE("[Synth] Off by alone and repeated") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( group=1 off_by=1 sample=*sine key=60 )"); synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 2 ); synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 3 ); } @@ -1574,22 +1657,22 @@ TEST_CASE("[Synth] Off by alone and repeated") TEST_CASE("[Synth] Off by same note and group") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( group=1 off_by=1 sample=*saw transpose=12 key=60 group=1 off_by=1 sample=*triangle key=60 )"); synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 2 ); - synth.noteOn(0, 60, 85); } TEST_CASE("[Synth] Off by with CC switches") { sfz::Synth synth; - sfz::AudioBuffer buffer { 2, 256 }; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path(), R"( ampeg_decay=5 ampeg_sustain=0 ampeg_release=5 key=60 @@ -1597,16 +1680,16 @@ TEST_CASE("[Synth] Off by with CC switches") sample=*triangle group=2 off_by=1 locc4=64 )"); synth.noteOn(0, 60, 85); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "*saw" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" }); synth.cc(0, 4, 127); synth.noteOn(0, 60, 85); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "*triangle" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*triangle" }); synth.cc(0, 4, 0); synth.noteOn(0, 60, 85); - REQUIRE( numPlayingVoices(synth) == 1 ); - REQUIRE( getPlayingVoices(synth).front()->getRegion()->sampleId->filename() == "*saw" ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" }); } TEST_CASE("[Synth] Off by a CC event") @@ -1620,10 +1703,10 @@ TEST_CASE("[Synth] Off by a CC event") )"); synth.noteOn(0, 60, 85); synth.renderBlock(buffer); - REQUIRE( numPlayingVoices(synth) == 1 ); + REQUIRE( playingSamples(synth) == std::vector { "*saw" }); synth.cc(10, 67, 127); synth.renderBlock(buffer); - REQUIRE( numPlayingVoices(synth) == 1 ); + REQUIRE( playingSamples(synth) == std::vector { "*sine" }); } TEST_CASE("[Synth] Off by a note-off event") @@ -1633,24 +1716,24 @@ TEST_CASE("[Synth] Off by a note-off event") synth.loadSfzString(fs::current_path(), R"( key=60 group=1 off_by=2 sample=*saw - key=62 sample=*silence + key=62 sample=*sine key=62 trigger=release group=2 sample=*silence )"); synth.noteOn(0, 60, 85); synth.renderBlock(buffer); - REQUIRE( numPlayingVoices(synth) == 1 ); + REQUIRE( playingSamples(synth) == std::vector { "*saw" }); synth.noteOn(0, 62, 85); synth.renderBlock(buffer); - REQUIRE( numPlayingVoices(synth) == 2 ); + REQUIRE( playingSamples(synth) == std::vector { "*saw", "*sine" }); synth.noteOff(10, 62, 85); synth.renderBlock(buffer); - REQUIRE( numPlayingVoices(synth) == 1 ); - // TODO: check the samples; the last one should be *silence + REQUIRE( playingSamples(synth) == std::vector { "*silence" }); } TEST_CASE("[Synth] Initial values of CC") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "init_cc.sfz", R"( sample=*sine @@ -1666,6 +1749,7 @@ TEST_CASE("[Synth] Initial values of CC") REQUIRE(synth.getDefaultHdcc(11) == 1.0f); synth.hdcc(0, 10, 0.7f); + synth.renderBlock(buffer); REQUIRE(synth.getHdcc(10) == 0.7f); REQUIRE(synth.getDefaultHdcc(10) == 0.5f); @@ -1695,6 +1779,7 @@ TEST_CASE("[Synth] Send CC vs. Automate CC") { { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "send_cc.sfz", R"( sample=*sine @@ -1703,11 +1788,13 @@ TEST_CASE("[Synth] Send CC vs. Automate CC") synth.noteOn(0, 60, 100); synth.hdcc(1, 120, 0.0f); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); } { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "automate_cc.sfz", R"( sample=*sine @@ -1716,6 +1803,7 @@ TEST_CASE("[Synth] Send CC vs. Automate CC") synth.noteOn(0, 60, 100); synth.automateHdcc(1, 120, 0.0f); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); } } @@ -1723,14 +1811,19 @@ TEST_CASE("[Synth] Send CC vs. Automate CC") TEST_CASE("[Keyswitches] Trigger from aftertouch extended CC") { sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; synth.loadSfzString(fs::current_path() / "tests/TestFiles/aftertouch_trigger.sfz", R"( start_locc129=100 start_hicc129=127 sample=*saw )"); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); synth.aftertouch(0, 90); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 0); synth.aftertouch(0, 110); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 1); synth.aftertouch(0, 120); + synth.renderBlock(buffer); REQUIRE(synth.getNumActiveVoices() == 2); } diff --git a/tests/TestHelpers.cpp b/tests/TestHelpers.cpp index 6c572e7e..3e1f7715 100644 --- a/tests/TestHelpers.cpp +++ b/tests/TestHelpers.cpp @@ -81,6 +81,55 @@ unsigned numPlayingVoices(const sfz::Synth& synth) }); } +const std::vector playingSamples(const sfz::Synth& synth) +{ + std::vector 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 playingVelocities(const sfz::Synth& synth) +{ + std::vector 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 activeSamples(const sfz::Synth& synth) +{ + std::vector 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 activeVelocities(const sfz::Synth& synth) +{ + std::vector 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 lines, int numRegions) { for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) { diff --git a/tests/TestHelpers.h b/tests/TestHelpers.h index 23e83a6a..454f221d 100644 --- a/tests/TestHelpers.h +++ b/tests/TestHelpers.h @@ -78,6 +78,38 @@ const std::vector getPlayingVoices(const sfz::Synth& synth); */ unsigned numPlayingVoices(const sfz::Synth& synth); +/** + * @brief Get the playing samples + * + * @param synth + * @return unsigned + */ +const std::vector playingSamples(const sfz::Synth& synth); + +/** + * @brief Get the playing notes velocities + * + * @param synth + * @return unsigned + */ +const std::vector playingVelocities(const sfz::Synth& synth); + +/** + * @brief Get the active samples + * + * @param synth + * @return unsigned + */ +const std::vector activeSamples(const sfz::Synth& synth); + +/** + * @brief Get the active notes velocities + * + * @param synth + * @return unsigned + */ +const std::vector activeVelocities(const sfz::Synth& synth); + /** * @brief Create the default dot graph representation for standard regions *