Test and API doc update w.r.t. ordering
Updated tests mostly to call on `renderBlock()` before testing things like number of active voices and such, in case we buffer/reorder events in the library later on.
This commit is contained in:
parent
d0cd21acf9
commit
cfb9eae68f
10 changed files with 611 additions and 294 deletions
72
src/sfizz.h
72
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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<float>::counter().getNumBuffers(); }
|
||||
|
||||
/**
|
||||
* @brief Gets the number of bytes allocated through the buffers
|
||||
* @brief Gets the number of bytes allocated through the buffers
|
||||
*
|
||||
* @return The allocated bytes.
|
||||
* @return The allocated bytes.
|
||||
*/
|
||||
int getAllocatedBytes() const noexcept { return Buffer<float>::counter().getTotalBytes(); }
|
||||
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -500,10 +500,14 @@ TEST_CASE("[Files] Note and octave offsets")
|
|||
TEST_CASE("[Files] Off modes")
|
||||
{
|
||||
Synth synth;
|
||||
AudioBuffer<float> 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<float> 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 );
|
||||
|
|
|
|||
|
|
@ -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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<group> group=1 polyphony=2
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<group> polyphony=2
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<group> polyphony=2
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<master> polyphony=2
|
||||
<group> polyphony=5
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<master> polyphony=2
|
||||
<region> sample=*saw key=65
|
||||
|
|
@ -150,38 +148,37 @@ TEST_CASE("[Polyphony] Hierarchy polyphony limits (limit in another master)")
|
|||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<global> polyphony=2
|
||||
<group> polyphony=5
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
synth.setSamplesPerBlock(blockSize);
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<master> polyphony=2
|
||||
<group> group=2
|
||||
|
|
@ -192,28 +189,28 @@ TEST_CASE("[Polyphony] Polyphony in master")
|
|||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path(), R"(
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> sample=*saw key=64 note_polyphony=1
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<global> sw_lokey=36 sw_hikey=37 sw_default=36
|
||||
<region> sw_last=36 key=48 note_polyphony=1 sample=*saw
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> group=1 sample=*saw key=64 note_polyphony=1
|
||||
<region> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<global> sw_lokey=36 sw_hikey=37 sw_default=36
|
||||
<region> group=1 sw_last=36 key=48 note_polyphony=1 sample=*saw
|
||||
<region> group=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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> key=48 note_polyphony=1 sample=*saw trigger=release_key ampeg_attack=1 ampeg_decay=1
|
||||
)");
|
||||
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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> key=48 note_polyphony=1 sample=*saw trigger=release_key ampeg_attack=1 ampeg_decay=1
|
||||
)");
|
||||
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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> key=48 sample=*sine
|
||||
<region> key=48 note_polyphony=1 sample=*saw trigger=release ampeg_attack=1 ampeg_decay=1
|
||||
|
|
@ -449,37 +452,21 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain ped
|
|||
synth.noteOff(3, 48, 0 );
|
||||
synth.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<std::string> 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<std::string> expectedSamples2 { "*saw" };
|
||||
std::vector<float> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<region> key=48 sample=*sine
|
||||
<region> key=48 note_polyphony=1 sample=*saw trigger=release ampeg_attack=1 ampeg_decay=1
|
||||
|
|
@ -491,75 +478,53 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain ped
|
|||
synth.noteOff(3, 48, 0 );
|
||||
synth.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<std::string> 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<std::string> expectedSamples2 { "*saw", "*saw", "*saw" };
|
||||
std::vector<float> 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<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<group> key=60 polyphony=1
|
||||
<region> sample=kick.wav loop_mode=one_shot
|
||||
<region> 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<std::string> { "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<std::string> { "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<std::string> { "kick.wav" } );
|
||||
}
|
||||
|
||||
TEST_CASE("[Polyphony] Bi-directional choking (with note_polyphony)")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, blockSize };
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||
<group> key=60 note_polyphony=1
|
||||
<region> sample=kick.wav loop_mode=one_shot
|
||||
<region> 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<std::string> { "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<std::string> { "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<std::string> { "kick.wav" } );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,159 +190,200 @@ TEST_CASE("Region activation", "Region tests")
|
|||
TEST_CASE("[Keyswitches] Normal lastKeyswitch range")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
|
||||
<global> sw_lokey=40 sw_hikey=42 sw_default=40
|
||||
<region> sw_last=40 key=60 sample=*sine
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
|
||||
<region> sw_last=40 key=60 sample=*sine
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
|
||||
<global> sw_lokey=40 sw_hikey=42 sw_default=40
|
||||
<region> sw_last=40 key=60 sample=*sine
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
|
||||
<global> sw_lokey=1 sw_hikey=127 sw_default=40
|
||||
<region> sw_last=40 key=60 sample=*sine
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
|
||||
<global> sw_lokey=1 sw_hikey=127 sw_default=40
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
|
||||
<global> sw_lokey=1 sw_hikey=10 sw_default=40
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
|
||||
<global> sw_lokey=1 sw_hikey=127 sw_default=40
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/keyswitches.sfz", R"(
|
||||
<global> sw_lokey=1 sw_hikey=127 sw_default=40
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"(
|
||||
<global> sw_lokey=30 sw_hikey=50 sw_default=40
|
||||
<region> sw_last=41 key=51 sample=*sine
|
||||
|
|
@ -360,6 +401,7 @@ TEST_CASE("[Keyswitches] sw_default")
|
|||
TEST_CASE("[Keyswitches] sw_default and playing with switches")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_default.sfz", R"(
|
||||
<global> sw_lokey=30 sw_hikey=50 sw_default=40
|
||||
<region> sw_last=41 key=51 sample=*sine
|
||||
|
|
@ -374,12 +416,14 @@ TEST_CASE("[Keyswitches] sw_default and playing with switches")
|
|||
REQUIRE( synth.getLayerView(3)->isSwitchedOn() );
|
||||
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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||
<region> sample=*saw sw_previous=60 lokey=50 hikey=70
|
||||
)");
|
||||
|
|
@ -397,17 +442,22 @@ TEST_CASE("[Keyswitches] sw_previous in range")
|
|||
// the test assumes that sw_previous regions are disabled by default
|
||||
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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||
<region> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||
<region> sw_lolast=57 sw_hilast=59 key=70 sample=*saw
|
||||
<region> sw_lolast=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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||
<region> sw_last=40 sw_lolast=57 sw_hilast=59 key=70 sample=*saw
|
||||
<region> sw_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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||
<global> sw_default=58
|
||||
<region> sw_lolast=57 sw_hilast=59 key=70 sample=*saw
|
||||
|
|
@ -511,6 +583,7 @@ TEST_CASE("[Keyswitches] sw_lolast and sw_hilast with sw_default")
|
|||
TEST_CASE("[Keyswitches] Multiple sw_default")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||
<global> sw_default=60
|
||||
<region> sw_last=60 key=70 sample=*saw
|
||||
|
|
@ -528,6 +601,7 @@ TEST_CASE("[Keyswitches] Multiple sw_default")
|
|||
TEST_CASE("[Keyswitches] Multiple sw_default, in region")
|
||||
{
|
||||
sfz::Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_previous.sfz", R"(
|
||||
<global> sw_default=60
|
||||
<region> sw_last=58 key=70 sample=*saw
|
||||
|
|
|
|||
|
|
@ -227,6 +227,7 @@ TEST_CASE("Legato triggers", "Region triggers")
|
|||
TEST_CASE("[Triggers] sw_vel, basic")
|
||||
{
|
||||
Synth synth;
|
||||
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
std::vector<std::string> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
std::vector<std::string> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
std::vector<std::string> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
std::vector<std::string> 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<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||
std::vector<std::string> 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);
|
||||
|
|
|
|||
311
tests/SynthT.cpp
311
tests/SynthT.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -81,6 +81,55 @@ unsigned numPlayingVoices(const sfz::Synth& synth)
|
|||
});
|
||||
}
|
||||
|
||||
const std::vector<std::string> playingSamples(const sfz::Synth& synth)
|
||||
{
|
||||
std::vector<std::string> samples;
|
||||
for (int i = 0; i < synth.getNumVoices(); ++i) {
|
||||
const auto* voice = synth.getVoiceView(i);
|
||||
if (!voice->releasedOrFree()) {
|
||||
if (auto region = voice->getRegion())
|
||||
samples.push_back(region->sampleId->filename());
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
const std::vector<float> playingVelocities(const sfz::Synth& synth)
|
||||
{
|
||||
std::vector<float> velocities;
|
||||
for (int i = 0; i < synth.getNumVoices(); ++i) {
|
||||
const auto* voice = synth.getVoiceView(i);
|
||||
if (!voice->releasedOrFree())
|
||||
velocities.push_back(voice->getTriggerEvent().value);
|
||||
}
|
||||
return velocities;
|
||||
}
|
||||
|
||||
const std::vector<std::string> activeSamples(const sfz::Synth& synth)
|
||||
{
|
||||
std::vector<std::string> samples;
|
||||
for (int i = 0; i < synth.getNumVoices(); ++i) {
|
||||
const auto* voice = synth.getVoiceView(i);
|
||||
if (!voice->isFree()) {
|
||||
const sfz::Region* region = voice->getRegion();
|
||||
if (region)
|
||||
samples.push_back(region->sampleId->filename());
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
const std::vector<float> activeVelocities(const sfz::Synth& synth)
|
||||
{
|
||||
std::vector<float> velocities;
|
||||
for (int i = 0; i < synth.getNumVoices(); ++i) {
|
||||
const auto* voice = synth.getVoiceView(i);
|
||||
if (!voice->isFree())
|
||||
velocities.push_back(voice->getTriggerEvent().value);
|
||||
}
|
||||
return velocities;
|
||||
}
|
||||
|
||||
std::string createDefaultGraph(std::vector<std::string> lines, int numRegions)
|
||||
{
|
||||
for (int regionIdx = 0; regionIdx < numRegions; ++regionIdx) {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,38 @@ const std::vector<const sfz::Voice*> getPlayingVoices(const sfz::Synth& synth);
|
|||
*/
|
||||
unsigned numPlayingVoices(const sfz::Synth& synth);
|
||||
|
||||
/**
|
||||
* @brief Get the playing samples
|
||||
*
|
||||
* @param synth
|
||||
* @return unsigned
|
||||
*/
|
||||
const std::vector<std::string> playingSamples(const sfz::Synth& synth);
|
||||
|
||||
/**
|
||||
* @brief Get the playing notes velocities
|
||||
*
|
||||
* @param synth
|
||||
* @return unsigned
|
||||
*/
|
||||
const std::vector<float> playingVelocities(const sfz::Synth& synth);
|
||||
|
||||
/**
|
||||
* @brief Get the active samples
|
||||
*
|
||||
* @param synth
|
||||
* @return unsigned
|
||||
*/
|
||||
const std::vector<std::string> activeSamples(const sfz::Synth& synth);
|
||||
|
||||
/**
|
||||
* @brief Get the active notes velocities
|
||||
*
|
||||
* @param synth
|
||||
* @return unsigned
|
||||
*/
|
||||
const std::vector<float> activeVelocities(const sfz::Synth& synth);
|
||||
|
||||
/**
|
||||
* @brief Create the default dot graph representation for standard regions
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue