zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

commit f80dc09be8404d246b8b47862e01fdbbece7c4ee
parent 435e1244b4825f5673eb37d1f1e62924417c0b6d
Author: Ricard Wanderlof <polluxsynth@butoba.net>
Date:   Fri, 22 Oct 2021 01:10:34 +0200

Add portamento tests to KitTest

Test that the portamento behavior is correct with respect to
different notes played.

- Notes played without portamento in poly mode
- Notes played with portamento in poly mode
- Notes played with small delta with trigger set to >3 (=> no portamento)
- Notes played with small delta with trigger set to <3 (=> portamento)
- Notes played with large delta with trigger set to >3 (=> portamento)
- Notes played with large delta with trigger set to <3 (=> no portamento)
- Notes played legato in legato mode (=> portamento)
- Notes played staccato in legato mode (=> no portamento)
- Notes played legato in mono mode (=> portamento)
- Notes played staccato in mono mode (=> no portamento)

Diffstat:
Msrc/Tests/KitTest.cpp | 438+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 438 insertions(+), 0 deletions(-)

diff --git a/src/Tests/KitTest.cpp b/src/Tests/KitTest.cpp @@ -21,6 +21,7 @@ #define private public #define protected public #include "../Synth/SynthNote.h" +#include "../Synth/Portamento.h" #include "../Misc/Part.h" #include "../globals.h" @@ -1058,6 +1059,432 @@ class KitTest TS_ASSERT_EQUAL_INT(pool.ndesc[3].note, 65); } + void testPortamentoOff(void) + { + auto &pool = part->notePool; + + // Play four notes, one octave apart (> default threshold of 3) + part->NoteOn(64, 127, 0); + part->NoteOn(76, 127, 0); + part->NoteOn(88, 127, 0); + part->NoteOn(100, 127, 0); + + // Verify note pitches and that there are no portamento objects + // for any note + pool.cleanup(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 76); + TS_ASSERT(pool.ndesc[1].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[2].note, 88); + TS_ASSERT(pool.ndesc[2].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[3].note, 100); + TS_ASSERT(pool.ndesc[3].portamentoRealtime == NULL); + } + + void testPortamentoOnPlayingLegato(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + + // Play four notes + // This implicitly tests that note deltas larger than the + // default threshold will trigger portamento + part->NoteOn(64, 127, 0); + part->NoteOn(76, 127, 0); + part->NoteOn(88, 127, 0); + part->NoteOn(100, 127, 0); + + // Verify note pitches and that there are separate portamento + // objects for all but the first note (first note should not + // exhibit portamento on account of being first), and + // that the initial portamento offsets are -1, -2 and -3 octaves, + // respectively. + pool.cleanup(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 76); + TS_NON_NULL(pool.ndesc[1].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[1].portamentoRealtime->portamento.freqdelta_log2, -1.0f, 0.001f); + + TS_ASSERT_EQUAL_INT(pool.ndesc[2].note, 88); + TS_NON_NULL(pool.ndesc[2].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[2].portamentoRealtime->portamento.freqdelta_log2, -2.0f, 0.001f); + TS_ASSERT(pool.ndesc[2].portamentoRealtime != + pool.ndesc[1].portamentoRealtime); + + TS_ASSERT_EQUAL_INT(pool.ndesc[3].note, 100); + TS_NON_NULL(pool.ndesc[3].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[3].portamentoRealtime->portamento.freqdelta_log2, -3.0f, 0.001f); + TS_ASSERT(pool.ndesc[3].portamentoRealtime != + pool.ndesc[2].portamentoRealtime); + TS_ASSERT(pool.ndesc[3].portamentoRealtime != + pool.ndesc[1].portamentoRealtime); + } + + void testPortamentoOnPlayingStaccato(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + + // The point here is to verify that even when a note is released, + // its portamento offset is retained for subsequent notes to + // start at, rather than subsequent notes starting at the + // target pitch for the previous note. + // This tests that the cleanup lambda that is issued when + // allocating the Portamento object in Part.cpp + // is actually called and does its job. + + // Play an initial note, then release it + part->NoteOn(64, 127, 0); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + + part->NoteOff(64); + + // We simulate the note being killed after release phase + // completed, in order to trigger deallocation of portamento + pool.kill(pool.ndesc[0]); + + // Play second note + part->NoteOn(76, 127, 0); + + // The only note remaining should be the second one, + // with pitch offset -1 octave + pool.cleanup(); + pool.dump(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 76); + TS_NON_NULL(pool.ndesc[0].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[0].portamentoRealtime->portamento.freqdelta_log2, -1.0f, 0.001f); + + // Release second note, play third + part->NoteOff(76); + pool.kill(pool.ndesc[0]); + + part->NoteOn(88, 127, 0); + + // The only note remaining should be the third one, + // with pitch offset -2 octave + pool.cleanup(); + pool.dump(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 88); + TS_NON_NULL(pool.ndesc[0].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[0].portamentoRealtime->portamento.freqdelta_log2, -2.0f, 0.001f); + + // Release third note, play fourth + part->NoteOff(88); + pool.kill(pool.ndesc[0]); + + part->NoteOn(100, 127, 0); + + // The only note remaining should be the fourth one, + // with pitch offset -3 octave + pool.cleanup(); + pool.dump(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 100); + TS_NON_NULL(pool.ndesc[0].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[0].portamentoRealtime->portamento.freqdelta_log2, -3.0f, 0.001f); + } + + void testPortamentoSmallerThanThresholdTrigIfLarger(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + + // Test that we don't get any portamento when playing notes + // that are closer than the default threshold of 3, when the + // trigger type is set to (default) larger-than-threshold. + + // Play four notes, two semitones apart (< default threshold of 3) + // By playing several notes, we test that it's each individual + // note step that counts, not the total distance from the + // first note. + part->NoteOn(64, 127, 0); + part->NoteOn(66, 127, 0); + part->NoteOn(68, 127, 0); + part->NoteOn(70, 127, 0); + + // Verify note pitches and that there are no portamento objects + // for any note + pool.cleanup(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 66); + TS_ASSERT(pool.ndesc[1].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[2].note, 68); + TS_ASSERT(pool.ndesc[2].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[3].note, 70); + TS_ASSERT(pool.ndesc[3].portamentoRealtime == NULL); + } + + void testPortamentoLargerThanThresholdTrigIfSmaller(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + part->ctl.portamento.pitchthreshtype = 0; + + // Test that we don't get any portamento when playing notes + // that are further away than the default threshold of 3, when the + // trigger type is set to smaller-than-threshold. + + part->NoteOn(64, 127, 0); + part->NoteOn(68, 127, 0); + part->NoteOn(72, 127, 0); + part->NoteOn(76, 127, 0); + + // Verify note pitches and that there are no portamento objects + // for any note + pool.cleanup(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 68); + TS_ASSERT(pool.ndesc[1].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[2].note, 72); + TS_ASSERT(pool.ndesc[2].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[3].note, 76); + TS_ASSERT(pool.ndesc[3].portamentoRealtime == NULL); + } + + void testPortamentoSmallerThanThresholdTrigIfSmaller(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + part->ctl.portamento.pitchthreshtype = 0; + + // Test that we get portamento when playing notes + // that are closer than the default threshold of 3, when the + // trigger type is set to smaller-than-threshold. + + part->NoteOn(64, 127, 0); + part->NoteOn(65, 127, 0); + part->NoteOn(66, 127, 0); + part->NoteOn(68, 127, 0); + + // Verify note pitches and that there are separate portamento + // objects for all but the first note (first note should not + // exhibit portamento on account of being first), and + // that the initial portamento offsets are -1, -2 and -3 semitones, + // respectively. + pool.cleanup(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 65); + TS_NON_NULL(pool.ndesc[1].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[1].portamentoRealtime->portamento.freqdelta_log2, -1.0f/12.0f, 0.001f); + + TS_ASSERT_EQUAL_INT(pool.ndesc[2].note, 66); + TS_NON_NULL(pool.ndesc[2].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[2].portamentoRealtime->portamento.freqdelta_log2, -2.0f/12.0f, 0.001f); + + TS_ASSERT_EQUAL_INT(pool.ndesc[3].note, 68); + TS_NON_NULL(pool.ndesc[3].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[3].portamentoRealtime->portamento.freqdelta_log2, -4.0f/12.0f, 0.001f); + } + + void testPortamentoSmallerThanThresholdTrigIfSmallerStaccato(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + part->ctl.portamento.pitchthreshtype = 0; + + // Test that we get portamento when playing staccato notes + // that are closer than the default threshold of 3, when the + // trigger type is set to smaller-than-threshold. + + // Play an initial note, then release it + part->NoteOn(64, 127, 0); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + + part->NoteOff(64); + // We simulate the note being killed after release phase + // completed, in order to trigger deallocation of portamento + pool.kill(pool.ndesc[0]); + + // Play second note + part->NoteOn(65, 127, 0); + + // The only note remaining should be the second one, + // with pitch offset -1 semitone + pool.cleanup(); + pool.dump(); + + // Verify note pitch and portamento + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 65); + TS_NON_NULL(pool.ndesc[0].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[0].portamentoRealtime->portamento.freqdelta_log2, -1.0f/12.0f, 0.001f); + + part->NoteOff(65); + // We simulate the note being killed after release phase + // completed, in order to trigger deallocation of portamento + pool.kill(pool.ndesc[0]); + + // Play third note + part->NoteOn(66, 127, 0); + + // The only note remaining should be the third one, + // with pitch offset -2 semitones + pool.cleanup(); + pool.dump(); + + // Verify note pitch and portamento + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 66); + TS_NON_NULL(pool.ndesc[0].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[0].portamentoRealtime->portamento.freqdelta_log2, -2.0f/12.0f, 0.001f); + + part->NoteOff(66); + // We simulate the note being killed after release phase + // completed, in order to trigger deallocation of portamento + pool.kill(pool.ndesc[0]); + + // Play fourth note + part->NoteOn(68, 127, 0); + + // The only note remaining should be the fourth one, + // with pitch offset -4 semitones. + // This tests that the decision to have the note exhibit + // portamento is based on the target pitch of the previous note + // (i.e. the actual note played), rather than the starting pitch + // of the portamento (which, since we have not called update() + // will still be the same as the pitch of the first note played), + // which is 4 semitones away and hence above the trigger threshold. + pool.cleanup(); + pool.dump(); + + // Verify note pitch and portamento + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 68); + TS_NON_NULL(pool.ndesc[0].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[0].portamentoRealtime->portamento.freqdelta_log2, -4.0f/12.0f, 0.001f); + } + + void testPortamentoOnLegatoOnPlayingLegato(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + part->Ppolymode = false; + part->Plegatomode = true; + + // Play two notes + part->NoteOn(64, 127, 0); + part->NoteOn(76, 127, 0); + + // Verify note pitch and that there is a portamento object + // registered for the first note in the legato pair but not + // the second, with the appropriate pitch offset + pool.cleanup(); + // First descriptor in legato pair + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 76); + TS_NON_NULL(pool.ndesc[0].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[0].portamentoRealtime->portamento.freqdelta_log2, -1.0f, 0.001f); + // Second descriptor + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 76); + TS_ASSERT(pool.ndesc[1].portamentoRealtime == NULL); + } + + void testPortamentoOnLegatoOnPlayingStaccato(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + part->Ppolymode = false; + part->Plegatomode = true; + + // Play an initial note, then release it + part->NoteOn(64, 127, 0); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 64); + TS_ASSERT(pool.ndesc[1].portamentoRealtime == NULL); + + part->NoteOff(64); + + // Play second note + part->NoteOn(76, 127, 0); + + pool.cleanup(); + pool.dump(); + + // First, re-verify first note, since it will remain + // in the release phase. + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 64); + TS_ASSERT(pool.ndesc[1].portamentoRealtime == NULL); + + // Verify pitch and lack of portamento for second note, due to + // playing staccato. + // First descriptor in legato pair + TS_ASSERT_EQUAL_INT(pool.ndesc[2].note, 76); + TS_ASSERT(pool.ndesc[2].portamentoRealtime == NULL); + // Second descriptor + TS_ASSERT_EQUAL_INT(pool.ndesc[3].note, 76); + TS_ASSERT(pool.ndesc[3].portamentoRealtime == NULL); + } + + void testPortamentoOnMonoPlayingLegato(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + part->Ppolymode = false; + + // Play two notes + part->NoteOn(64, 127, 0); + part->NoteOn(76, 127, 0); + + // Verify note pitch and that there is a portamento object + // registered for the second note with the appropriate pitch offset + // but not for the first, + pool.cleanup(); + pool.dump(); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 76); + TS_NON_NULL(pool.ndesc[1].portamentoRealtime); + TS_ASSERT_DELTA(pool.ndesc[1].portamentoRealtime->portamento.freqdelta_log2, -1.0f, 0.001f); + } + + void testPortamentoOnMonoPlayingStaccato(void) + { + auto &pool = part->notePool; + + part->ctl.setportamento(127); + part->Ppolymode = false; + + // Play an initial note, then release it + part->NoteOn(64, 127, 0); + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + + part->NoteOff(64); + + // Play second note + part->NoteOn(76, 127, 0); + + pool.cleanup(); + pool.dump(); + + // First, re-verify first note, since it will remain + // in the release phase. + TS_ASSERT_EQUAL_INT(pool.ndesc[0].note, 64); + TS_ASSERT(pool.ndesc[0].portamentoRealtime == NULL); + + // Verify pitch and lack of portamento for second note, due to + // playing staccato. + TS_ASSERT_EQUAL_INT(pool.ndesc[1].note, 76); + TS_ASSERT(pool.ndesc[1].portamentoRealtime == NULL); + } + void tearDown() { delete part; delete[] outL; @@ -1084,5 +1511,16 @@ int main() RUN_TEST(testSingleKitNoLegatoYesMono); RUN_TEST(testKeyLimit); RUN_TEST(testVoiceLimit); + RUN_TEST(testPortamentoOff); + RUN_TEST(testPortamentoOnPlayingLegato); + RUN_TEST(testPortamentoOnPlayingStaccato); + RUN_TEST(testPortamentoSmallerThanThresholdTrigIfLarger); + RUN_TEST(testPortamentoLargerThanThresholdTrigIfSmaller); + RUN_TEST(testPortamentoSmallerThanThresholdTrigIfSmaller); + RUN_TEST(testPortamentoSmallerThanThresholdTrigIfSmallerStaccato); + RUN_TEST(testPortamentoOnLegatoOnPlayingLegato); + RUN_TEST(testPortamentoOnLegatoOnPlayingStaccato); + RUN_TEST(testPortamentoOnMonoPlayingLegato); + RUN_TEST(testPortamentoOnMonoPlayingStaccato); return test_summary(); }