How to fine tune pitch of a note played with wavetable

CraigH

Member
I've built a synth playing multiple soundfonts using the wavetable.playNote() function. However I need to "Fine Tune" the pitch of the synth to other non-electronic instruments and I've not been able to find any way to do this. The wavetable object does not seem to have a "fine tune" or even "pitch bend" function. The "Fine Tuning (cents)" information for each sample is defined in the soundfont file, and that pitch information is transferred to Teensy using the Teensy Soundfont Decoder, so it's possible to tune the soundfont at it's source before exporting it to Teensy, but this is not practical each time a tuning is needed. I don't believe that any of the "frequency" functions are useful as we want to maintain the integrity of the musical note pitches the wavetable is playing. The only way I can see at present is to somehow modify the 4th item in the instrument.cpp descriptor, (per hz phase increment):

(1 << (32 - 14)) * WAVETABLE_CENTS_SHIFT(-57) * 44100.0 / WAVETABLE_NOTE_TO_FREQUENCY(40) / AUDIO_SAMPLE_RATE_EXACT + 0.5,

However this does not seem to be a clean solution. Does anyone know of a way to fine tune the pitch of notes playing in the wavetable?
 
I tried moving the sample descriptor to RAM, leaving the sample itself in PROGMEM. I then modified the tuning line to include a "+TuningCents" parameter as follows:
(1 << (32 - 11)) * WAVETABLE_CENTS_SHIFT(-25 + TuningCents) * 44100.0 / WAVETABLE_NOTE_TO_FREQUENCY(86) / AUDIO_SAMPLE_RATE_EXACT + 0.5, // PER_HERTZ_PHASE_INCREMENT

The tuning changed with "TuningCents" as expected, but each note had to be replayed to pick up the new pitch. Whilst this hack worked, each sample needs to have its descriptor moved to RAM and modified, so this is far from ideal. It would be best if we have a "Fine Tune" function associated with wavetable object. Is anyone aware of a better way to fine tune the pitch of notes in wavetable?
 
using setFrequency would be the way to go as it do update while the note is playing

use it like this:
C++:
float freqAdj = 0.1;
//float freqAdj = -0.1; // or negative adj
setFrequency(noteToFreq(note) + freqAdj);

internally of AudioSynthWavetable:
Code:
void AudioSynthWavetable::playNote(int note, int amp) {
    setState(note, amp, noteToFreq(note));
}

void AudioSynthWavetable::setState(int note, int amp, float freq) {
    //.... init code
    setFrequency(freq);
    //.... end code
}
// this function uses the variable that you wanted to change (PER_HERTZ_PHASE_INCREMENT)
void AudioSynthWavetable::setFrequency(float freq) {
    if (nullptr != current_sample)
    {
        float tone_incr_temp = freq * current_sample->PER_HERTZ_PHASE_INCREMENT;
        tone_incr = tone_incr_temp;
        vib_pitch_offset_init = tone_incr_temp * current_sample->VIBRATO_PITCH_COEFFICIENT_INITIAL;
        vib_pitch_offset_scnd = tone_incr_temp * current_sample->VIBRATO_PITCH_COEFFICIENT_SECOND;
        mod_pitch_offset_init = tone_incr_temp * current_sample->MODULATION_PITCH_COEFFICIENT_INITIAL;
        mod_pitch_offset_scnd = tone_incr_temp * current_sample->MODULATION_PITCH_COEFFICIENT_SECOND;
    }
}

to make sure that the note is played directly with the desired adjustment
a new function could be added to AudioSynthWavetable:
Code:
void AudioSynthWavetable::playNote(int note, int amp, float freqAdj) {
    setState(note, amp, noteToFreq(note)+freqAdj);
}

and if you want to go really deep the following new functions could be used:
(but then you will need to have current CENTS_OFFSET and SAMPLE_NOTE for the current sample)
Code:
void AudioSynthWavetable::playNote(int note, int amp, int CENTS_OFFSET, int SAMPLE_NOTE) {
    setState(note, amp, noteToFreq(note), CENTS_OFFSET, SAMPLE_NOTE);
}

void AudioSynthWavetable::setState(int note, int amp, float freq, int CENTS_OFFSET, int SAMPLE_NOTE) {
    cli();
    int i;
    env_state = STATE_IDLE;
    current_sample = nullptr;
    if (nullptr != instrument)
    {
        // note ranges calculated by sound font decoder
        for (i = 0; (note > instrument->sample_note_ranges[i])/* && (i < instrument->sample_count)*/; i++)
            ;
        //if (i == instrument->sample_count) i = instrument->sample_count-1;
        current_sample = &instrument->samples[i];
    }
    if (current_sample == NULL) {
        sei();
        return;
    }
    setFrequency(freq, CENTS_OFFSET, SAMPLE_NOTE);
    vib_count = mod_count = tone_phase = env_incr = env_mult = 0;
    vib_phase = mod_phase = TRIANGLE_INITIAL_PHASE;
    env_count = current_sample->DELAY_COUNT;
    // linear scalar for amp with UINT16_MAX being no attenuation
    tone_amp = amp * (UINT16_MAX / 127);
    // scale relative to initial attenuation defined by soundfont file
    tone_amp = current_sample->INITIAL_ATTENUATION_SCALAR * tone_amp >> 16;
    env_state = STATE_DELAY;
    PRINT_ENV(STATE_DELAY);
    state_change = true;
    sei();
}

void AudioSynthWavetable::setFrequency(float freq, int CENTS_OFFSET, int SAMPLE_NOTE) {
    if (nullptr != current_sample)
    {
        int LENGTH_BITS = current_sample->INDEX_BITS;
        float PER_HERTZ_PHASE_INCREMENT = (1 << (32 - LENGTH_BITS)) * WAVETABLE_CENTS_SHIFT(CENTS_OFFSET) * 44100.0 / WAVETABLE_NOTE_TO_FREQUENCY(SAMPLE_NOTE) / AUDIO_SAMPLE_RATE_EXACT + 0.5
        float tone_incr_temp = freq * PER_HERTZ_PHASE_INCREMENT;
        tone_incr = tone_incr_temp;
        vib_pitch_offset_init = tone_incr_temp * current_sample->VIBRATO_PITCH_COEFFICIENT_INITIAL;
        vib_pitch_offset_scnd = tone_incr_temp * current_sample->VIBRATO_PITCH_COEFFICIENT_SECOND;
        mod_pitch_offset_init = tone_incr_temp * current_sample->MODULATION_PITCH_COEFFICIENT_INITIAL;
        mod_pitch_offset_scnd = tone_incr_temp * current_sample->MODULATION_PITCH_COEFFICIENT_SECOND;
    }
}
 
That's great thanks. I can hopefully work with the first method. I'll need to calculate the freqAdj for each note so we always get the same nr cents correction, but that's just some musical knowledge and maths which seems do-able. I've added a new function "PlayTunedNote" into synth_wavetable.cpp and synth_wavetable.h. using the code you provided. I'm getting a compile error from audio.h, "error: default argument missing for parameter 3 of 'void AudioSynthWavetable::playTunedNote(int, int, float)" but I'll sort that out eventually. I might also give the second method a try and see which is the more elegant.
 
think your compile error is because of

Code:
void PlayTunedNote(int note, int amp = DEFAULT_AMPLITUDE, float freqAdj);

should be

Code:
void PlayTunedNote(int note, float freqAdj, int amp = DEFAULT_AMPLITUDE);

as the amp parameter is optional

note. don't forget to change the function in the cpp-file as well
 
Back
Top