Skip to content

Commit

Permalink
Absolute Pitch Shift (#957)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
baconpaul authored Jul 14, 2019
1 parent 42078ac commit db2cc16
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 29 deletions.
6 changes: 5 additions & 1 deletion src/common/Parameter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ bool Parameter::can_be_absolute()
switch (ctrltype)
{
case ct_oscspread:
case ct_pitch_semi7bp:
return true;
}
return false;
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 1 addition & 4 deletions src/common/SurgeStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
43 changes: 19 additions & 24 deletions src/common/dsp/SurgeVoice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
71 changes: 71 additions & 0 deletions src/common/dsp/SurgeVoice.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <bool first> void calc_ctrldata(QuadFilterChainState*, int);
void update_portamento();
Expand Down

0 comments on commit db2cc16

Please sign in to comment.