From db2cc16fe93a237d3b9cebeb9c7f37cbfd842817 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 13 Jul 2019 20:07:16 -0400 Subject: [PATCH] Absolute Pitch Shift (#957) Addressing #865, we want to be able to shift pitch by either a relative (note space) frequency or an absolute (frequency space) frequency amount. The implementation works as follows: 1. Make the pitch parameter allow absolute for pitch_semi_7b 2. Make sure the absolute property saves and restores 3. In SurgeVoice plumb through a note-setter function which will allow us to find the note equivalent to the absolute shift from a base 4. An implementation of constant pitch shift to note which uses the current tuning table to interpolate in absolute mode and just adds in normal mode --- src/common/Parameter.cpp | 6 ++- src/common/SurgeStorage.h | 5 +-- src/common/dsp/SurgeVoice.cpp | 43 ++++++++++----------- src/common/dsp/SurgeVoice.h | 71 +++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 29 deletions(-) diff --git a/src/common/Parameter.cpp b/src/common/Parameter.cpp index e6e55131165..cee9a415349 100644 --- a/src/common/Parameter.cpp +++ b/src/common/Parameter.cpp @@ -175,6 +175,7 @@ bool Parameter::can_be_absolute() switch (ctrltype) { case ct_oscspread: + case ct_pitch_semi7bp: return true; } return false; @@ -798,7 +799,10 @@ void Parameter::get_display(char* txt, bool external, float ef) sprintf(txt, "%.2f semitones", f); break; case ct_pitch_semi7bp: - sprintf(txt, "%.2f", get_extended(f)); + if(absolute) + sprintf(txt, "%.1f Hz", 10 * get_extended(f)); + else + sprintf(txt, "%.2f", get_extended(f)); break; default: sprintf(txt, "%.2f", f); diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index 52013dde8cf..60a83b4e4dc 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -56,10 +56,7 @@ const int FIRoffsetI16 = FIRipolI16_N >> 1; extern float sinctable alignas(16)[(FIRipol_M + 1) * FIRipol_N * 2]; extern float sinctable1X alignas(16)[(FIRipol_M + 1) * FIRipol_N]; extern short sinctableI16 alignas(16)[(FIRipol_M + 1) * FIRipolI16_N]; -extern float table_dB alignas(16)[512], - table_pitch alignas(16)[512], - table_pitch_inv alignas(16)[512], - table_envrate_lpf alignas(16)[512], +extern float table_envrate_lpf alignas(16)[512], table_envrate_linear alignas(16)[512]; extern float table_note_omega alignas(16)[2][512]; extern float waveshapers alignas(16)[8][1024]; diff --git a/src/common/dsp/SurgeVoice.cpp b/src/common/dsp/SurgeVoice.cpp index 84868888981..0e0d1d74a84 100644 --- a/src/common/dsp/SurgeVoice.cpp +++ b/src/common/dsp/SurgeVoice.cpp @@ -490,10 +490,9 @@ bool SurgeVoice::process_block(QuadFilterChainState& Q, int Qe) if (osc3 || ring23 || ((osc1 || osc2) && (FMmode == fm_3to2to1)) || (osc1 && (FMmode == fm_2and3to1))) { - osc[2]->process_block((scene->osc[2].keytrack.val.b ? state.pitch : ktrkroot) + - localcopy[scene->osc[2].pitch.param_id_in_scene].f * - (scene->osc[2].pitch.extend_range ? 12.f : 1.f) + - 12 * scene->osc[2].octave.val.i, + osc[2]->process_block(noteShiftFromPitchParam( (scene->osc[2].keytrack.val.b ? state.pitch : ktrkroot) + + 12 * scene->osc[2].octave.val.i, + 2 ), drift, is_wide); if (osc3) @@ -518,19 +517,18 @@ bool SurgeVoice::process_block(QuadFilterChainState& Q, int Qe) { if (FMmode == fm_3to2to1) { - osc[1]->process_block((scene->osc[1].keytrack.val.b ? state.pitch : ktrkroot) + - localcopy[scene->osc[1].pitch.param_id_in_scene].f * - (scene->osc[1].pitch.extend_range ? 12.f : 1.f) + - 12 * scene->osc[1].octave.val.i, + osc[1]->process_block(noteShiftFromPitchParam((scene->osc[1].keytrack.val.b ? state.pitch : ktrkroot) + + 12 * scene->osc[1].octave.val.i, + 1 ), + drift, is_wide, true, db_to_linear(localcopy[scene->fm_depth.param_id_in_scene].f)); } else { - osc[1]->process_block((scene->osc[1].keytrack.val.b ? state.pitch : ktrkroot) + - localcopy[scene->osc[1].pitch.param_id_in_scene].f * - (scene->osc[1].pitch.extend_range ? 12.f : 1.f) + - 12 * scene->osc[1].octave.val.i, + osc[1]->process_block(noteShiftFromPitchParam((scene->osc[1].keytrack.val.b ? state.pitch : ktrkroot) + + 12 * scene->osc[1].octave.val.i, + 1), drift, is_wide); } if (osc2) @@ -557,28 +555,25 @@ bool SurgeVoice::process_block(QuadFilterChainState& Q, int Qe) if (FMmode == fm_2and3to1) { add_block(osc[1]->output, osc[2]->output, fmbuffer, BLOCK_SIZE_OS_QUAD); - osc[0]->process_block((scene->osc[0].keytrack.val.b ? state.pitch : ktrkroot) + - localcopy[scene->osc[0].pitch.param_id_in_scene].f * - (scene->osc[0].pitch.extend_range ? 12.f : 1.f) + - 12 * scene->osc[0].octave.val.i, + osc[0]->process_block(noteShiftFromPitchParam((scene->osc[0].keytrack.val.b ? state.pitch : ktrkroot) + + 12 * scene->osc[0].octave.val.i, 0 ), drift, is_wide, true, db_to_linear(localcopy[scene->fm_depth.param_id_in_scene].f)); } else if (FMmode) { - osc[0]->process_block((scene->osc[0].keytrack.val.b ? state.pitch : 60) + - localcopy[scene->osc[0].pitch.param_id_in_scene].f * - (scene->osc[0].pitch.extend_range ? 12.f : 1.f) + - 12 * scene->osc[0].octave.val.i, + osc[0]->process_block(noteShiftFromPitchParam((scene->osc[0].keytrack.val.b ? state.pitch : 60) + + 12 * scene->osc[0].octave.val.i, + 0), + drift, is_wide, true, db_to_linear(localcopy[scene->fm_depth.param_id_in_scene].f)); } else { - osc[0]->process_block((scene->osc[0].keytrack.val.b ? state.pitch : ktrkroot) + - localcopy[scene->osc[0].pitch.param_id_in_scene].f * - (scene->osc[0].pitch.extend_range ? 12.f : 1.f) + - 12 * scene->osc[0].octave.val.i, + osc[0]->process_block(noteShiftFromPitchParam((scene->osc[0].keytrack.val.b ? state.pitch : ktrkroot) + + 12 * scene->osc[0].octave.val.i, + 0), drift, is_wide); } if (osc1) diff --git a/src/common/dsp/SurgeVoice.h b/src/common/dsp/SurgeVoice.h index 863552d1924..d81999e1483 100644 --- a/src/common/dsp/SurgeVoice.h +++ b/src/common/dsp/SurgeVoice.h @@ -50,6 +50,77 @@ class alignas(16) SurgeVoice SurgeVoiceState state; int age, age_release; + /* + ** Given a note0 and an oscilator this returns the appropriate note. + ** This is a pretty easy calculation in non-absolute mode. Just add. + ** But in absolute mode you need to find the virtual note which would + ** map to that frequency shift. + */ + inline float noteShiftFromPitchParam( float note0 /* the key + octave */, int oscNum /* The osc for pitch diffs */) + { + if( scene->osc[oscNum].pitch.absolute ) + { + // remember note_to_pitch is linear interpolation on storage->table_pitch from + // position note + 256 % 512 + bool debug = false; + // OK so now what we are searching for is the pair which surrounds us plus the pitch drift... so + float fqShift = 10 * localcopy[scene->osc[oscNum].pitch.param_id_in_scene].f * + (scene->osc[oscNum].pitch.extend_range ? 12.f : 1.f); + float tableNote0 = note0 + 256; + + int tableIdx = (int)tableNote0; + if( tableIdx > 0x1fe ) + tableIdx = 0x1fe; + + // so just iterate up. Deal with negative also of course. Since we will always be close just + // do it brute force for now but later we can do a binary or some such. + float pitch0 = storage->table_pitch[tableIdx]; + float targetPitch = pitch0 + fqShift * 32.0 / 261.626; + + if( fqShift > 0 ) + { + while( tableIdx < 0x1fe ) + { + float pitch1 = storage->table_pitch[tableIdx + 1]; + if( pitch0 <= targetPitch && pitch1 > targetPitch ) + { + break; + } + pitch0 = pitch1; + tableIdx ++; + } + } + else + { + while( tableIdx > 0 ) + { + float pitch1 = storage->table_pitch[tableIdx - 1]; + if( pitch0 >= targetPitch && pitch1 < targetPitch ) + { + tableIdx --; + break; + } + pitch0 = pitch1; + tableIdx --; + } + } + + // So what's the frac + // (1-x) * [tableIdx] + x * [tableIdx+1] = targetPitch + // Or: x = ( target - table) / ( [ table+1 ] - [table] ); + float frac = (targetPitch - storage->table_pitch[tableIdx]) / + ( storage->table_pitch[tableIdx + 1] - storage->table_pitch[tableIdx] ); + // frac = 1 -> targetpitch = +1; frac = 0 -> targetPitch + + return tableIdx + frac - 256; + } + else + { + return note0 + localcopy[scene->osc[oscNum].pitch.param_id_in_scene].f * + (scene->osc[oscNum].pitch.extend_range ? 12.f : 1.f); + } + } + private: template void calc_ctrldata(QuadFilterChainState*, int); void update_portamento();