Monophonic Guitar Synthesizer with subtractive synthesis

wolke

Active member
Hi,
first.. many thanx for all the nice teensy stuff!

The project i was working the last weeks results in an nice monophonic Guitar Synthesizer witch can really used. So I decide to share the project. I create an github repository. At the moment the repository is not complete. The schematic and maybe an pcb layout will follow.

It based on an teensy3.6, an SGTL5000 Audio Shield and an small guitar buffer and mixer circuit which only need an 3dpd switch and only "ONE" transistor some resistors and caps.
If the teensy run with 96Mhz the noise is at an acceptable level. At 180Mhz noise from the teensy onboard 3.3V regulator is audible in the guitar buffer circuit.

The Synth need a little bit modified Audio Library which I also hosted on github.

*Note, i am not an software or electronic engineer. the code is not so clean and structured as software developer will write code!

Synth:
https://github.com/wolkstein/GitSynth127

Modified Audio Library:
https://github.com/wolkstein/Audio

German language Video on youtube(25 minutes)


Features:
  • substractive synthesis
  • 4 OSC 's
  • 1 Noise Generator
  • 3 LFO 's
  • 1 Envelope AHDSR
  • 1 Filter DAHDSR
  • 2 parallel Filter
  • 32 Step pitch arp.
  • lfo's and 32 step arp. can sync to internal tempo or midi clock, envelope_attack also sync the lfo's or the arp.
  • USB Midi
  • USB Audio
  • Expression Pedal
  • Tap tempo
  • 127 user presets
  • Midi Gui based on PyQt and rtmidi

The Gui first Tab (Synth Tab)
pyqt-gui.jpg


edit:
Add audio system design tool image
Audio System
audioSystemdesignTool.jpg


Hopefully you like it.

/Thx
Wolke
 
Last edited:
hi urbanspaceman ,
1. i add an new analyse effect called "analyse_attack". this object detect an envelope attack in audio data. you can init the object with some arguments.
myAnalyse_attack.set(int32_t treshold, int32_t falldown, uint16_t bouncetime,void myCallbackFunction(bool attack)). the callback function will called if an attack is detected.
simple! in my project i use it to sync the lfo's and the sequencer to attacks. firstly i also use it to create my guitar envelope but later i found an faster method which run very well in main loop.
so in further develop on my gitsynth this attack callback is deprecated and will be removed.

2. i modify analyse_notefreq. i only change #define AUDIO_GUITARTUNER_BLOCKS 25 to #define AUDIO_GUITARTUNER_BLOCKS 9. this speed up detecting notefreg but upper the detection corner freq to ~80hz. this mean you can not detect note pitch lower than 80hz.

3. i add an extra function to analyse_peak. i call it readFull(). this return the the negative cycle values as they are. the original call return only positive (abs) values. i use with lfo output to modify modulation and pitch values.

4. i modify in audio synth waveform the arbitraryWaveform function. now it is possible to run it continuous as default (like it come from the original code) and to define a amount of cycles before it stop. now it is possible to start the lfo with a given frequency but only run for example "one cycle" and hold the last value. i use it for some lfo function for example "ramp down". for example if you sync the lfo to envelope attack it is possible to control an osc pitch sweep over 12 notes over an given time synced and multiplied to the internal clock tempo.

5. i modify the filter state variable. i increase the resonance from max 5 to max 25. this is experimental and can produce noise and clipping. but i like it to overdrive the filter. mostly i connect the gitsynth to an valve amplifier which also overdrive the signal. that mean i have no need for hifi sound quality. digital clippings can not be identified on my over driven guitar amplifier with some 12" guitar speakers :).

/g
wolke
 
Last edited:
improve envelope and pitch tracking

hi,
i improve the guitar envelope and pitch tracking a little bit, now the latency between plugging a string on guitar and first synth-output is between 3-5ms.
the pitch tracking from colin duffy's Guitar and Bass Tuner library V3 is much faster than his older version currently available in original teensy audio library.
Audiotuner V3 from colin duffy: https://github.com/duff2013/AudioTuner

Images: compare guitar envelope vs. synth envelope.
i record the guitar and the synth output using the usb audio interface function on teensy(gitsynth127). the left side record the guitar signal(upper track) while the right side(lower track) record the synth output. The synth use one OSC with an triangle waveform one octave higher than the guitar fundamental tone. the waveform from synth output is nearly sinus because the filter is active.
envelope-tracking1-klein.jpgenvelope-tracking2-klein.jpgenvelope-tracking3-klein.jpg

Image: Envelope - Acceleration comparison guitar(upper track) vs. synth(lower track)
envelope-tracking4-klein.jpg


edit:
add an link to an small sound sample playing 4 OSC Pulses all ~8 cents out of tune. i use an renoise demo song called DBlue and remove the original bass. now the renoise bass track play midi notes connected to my Gitsynth127 usb midi input. The Gitsynth127 also acts as usb soundcard. The audio playback from renoise will recorded to ardour while the Synth playing midi notes(bass track) and record them also to Ardour at the same time.
https://raw.githubusercontent.com/w...ddemo-based-on-renoise-demo-dblue-tension.mp3

/g
wolke
 
Last edited:
Hi :),
there is a new Gitsynth127 Video on Youtube. It shows how to create a preset with the help of the Qt based Midi-Gui.


/g
wolke
 
Hello,

My name is Emmanuel. I made a few years a go this little box, using the AudioAnalyzeNoteFrequency object from the audio library :
https://www.pjrc.com/wooblizer-acoustic-note-to-wave-synthesizer/

I had good results, but I need to get lower latency. In a post, Wolke mentioned :
"colin duffy's Guitar and Bass Tuner library V3 is much faster than his older version currently available in original teensy audio library.
Audiotuner V3 from colin duffy: https://github.com/duff2013/AudioTuner"

I try to test Guitar and Bass Tuner library V3, but it doesn't work any more... When I initialise the object, it creates a lot of distortion, then crashes.
Would you have any ideas of what goes wrong ?

Here is my test code. I use a T4.0. I don't use the audio shield. I use PCM5102 an PCM1802 as i2S input and output. They work well.
Thank you for any help !
Emmanuel

main sketch :
Code:
int16_t fir_44117_HZ_coefficients[22] =
{
    0, 3, 6, -11, -71, 21,
    352, -15, -1202, -6, 5011, 8209,
    5011, -6, -1202, -15, 352, 21,
    -71, -11, 6, 3
};

int16_t fir_22059_HZ_coefficients[20] =
{
    0, 1, -6, -54, 18, 326,
    -14, -1178, -6, 5001, 8209, 5001,
    -6, -1178, -14, 326, 18, -54,
    -6, 1
};


#include "AudioTuner.h"
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioTuner                tuner;
AudioSynthWaveformSine    sine;
AudioOutputI2S            audio_out;
AudioInputI2S             audio_in;

AudioConnection patchCord2(sine, 0, tuner, 0);
AudioConnection patchCord3(sine, 0, audio_out, 0);
AudioConnection patchCord4(sine, 0, audio_out, 1);

void setup() {
  AudioMemory(30);
  tuner.begin(.15, fir_44117_HZ_coefficients, sizeof(fir_44117_HZ_coefficients), 2);
  sine.frequency(200);
  sine.amplitude(0.1);
}

void loop() {
  // read back fundamental frequency
  if (tuner.available()) {
    float note = tuner.read();
    float prob = tuner.probability();
    Serial.printf("Note: %3.2f | Probability: %.2f\n", note, prob);
  }
}


audiotuner.h
Code:
/* Audio Library Note Frequency Detection & Guitar/Bass Tuner
 * Copyright (c) 2015, Colin Duffy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef AudioTuner_h_
#define AudioTuner_h_

#include "AudioStream.h"
#include "arm_math.h"
/***********************************************************************
 *              Safe to adjust these values below                      *
 *                                                                     *
 *  This parameter defines the size of the buffer.                     *
 *                                                                     *
 *  1.  AUDIO_GUITARTUNER_BLOCKS -  Buffer size is 128 * AUDIO_BLOCKS. *
 *                      The more AUDIO_GUITARTUNER_BLOCKS the lower    *
 *                      the frequency you can detect. The default      *
 *                      (24) is set to measure down to 29.14 Hz        *
 *                      or B(flat)0.                                   *
 *                                                                     *
 *  2.  MAX_COEFF - Maxium number of coefficeints for the FIR filter.  *
 *                                                                     *
 ***********************************************************************/
#define AUDIO_GUITARTUNER_BLOCKS  12
#define MAX_COEFF                 200
/***********************************************************************/

class AudioTuner : public AudioStream {
public:
    /**
     *  constructor to setup Audio Library and initialize
     *
     *  @return none
     */
    AudioTuner( void ) : AudioStream( 1, inputQueueArray ),
    data( 0.0 ),
    coeff_p( NULL ),
    enabled( false ),
    new_output( false ),
    coeff_size( 0 )
    
    {
        
    }
    
    /**
     *  initialize variables and start
     *
     *  @param threshold Allowed uncertainty
     *  @param coeff     coefficients for fir filter
     *  @param taps      number of coefficients, even
     *  @param factor    must be power of 2
     */
    void begin( float threshold, int16_t *coeff, uint8_t taps, uint8_t factor );
    
    /**
     *  sets threshold value
     *
     *  @param thresh
     *  @return none
     */
    void threshold( float p );
    
    /**
     *  triggers true when valid frequency is found
     *
     *  @return flag to indicate valid frequency is found
     */
    bool available( void );
    /**
     *  get frequency
     *
     *  @return frequency in hertz
     */
    float read( void );
    
    /**
     *  get predicitity
     *
     *  @return probability of frequency found
     */
    float probability( void );
    
    /**
     *  fir decimation coefficents
     *
     *  @return none
     */
    void coeff( int16_t *p, int n );
    
    /**
     *  disable yin
     *
     *  @return none
     */
    void disable( void );
    
    /**
     *  Audio Library calls this update function ~2.9ms
     *
     *  @return none
     */
    virtual void update( void );
    
private:
    /**
     *  check the sampled data for fundamental frequency
     *
     *  @param yin  buffer to hold sum*tau value
     *  @param rs   buffer to hold running sum for sampled window
     *  @param head buffer index
     *  @param tau  lag we are currently working on this gets incremented
     *
     *  @return tau
     */
    uint16_t estimate( uint64_t *yin, uint64_t *rs, uint16_t head, uint16_t tau );
    
    /**
     *  process audio data
     *
     *  @return none
     */
    void process( int16_t *p );
    
    /**
     *  Variables
     */
    float    periodicity, yin_threshold, data;
    uint64_t running_sum, yin_buffer[5], rs_buffer[5];
    uint16_t tau_global;
    int16_t  AudioBuffer[AUDIO_GUITARTUNER_BLOCKS*AUDIO_BLOCK_SAMPLES] __attribute__ ( ( aligned ( 4 ) ) );
    int16_t  coeff_state[AUDIO_BLOCK_SAMPLES + MAX_COEFF];
    int16_t  *coeff_p;
    uint8_t  yin_idx, state, coeff_size, decimation_factor, decimation_shift;
    volatile bool new_output, process_buffer, enabled;
    audio_block_t *inputQueueArray[1];
    arm_fir_decimate_instance_q15 firDecimateInst;
};
#endif


audiotuner.ccp
Code:
/* Audio Library Note Frequency Detection & Guitar/Bass Tuner
 * Copyright (c) 2015, Colin Duffy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "utility/dspinst.h"
#include "arm_math.h"
#include "Arduino.h"
#include "AudioTuner.h"

#define NUM_SAMPLES ( AUDIO_GUITARTUNER_BLOCKS << 7 )

void AudioTuner::update( void ) {
    
    audio_block_t *block;
    
    block = receiveReadOnly();
    if (!block) return;
    
    if ( !enabled ) {
        release( block );
        return;
    }
    
    /**
     *  "factor" is the new block size calculated by
     *  the decimated shift to incremnt the buffer
     *  address.
     */
    const uint8_t factor = AUDIO_BLOCK_SAMPLES >> decimation_shift;
    
    // filter and decimate block by block the incoming signal and store in a buffer.
    arm_fir_decimate_fast_q15( &firDecimateInst, block->data, AudioBuffer + ( state * factor ), AUDIO_BLOCK_SAMPLES );
    
    /**
     *  when half the blocks + 1 of the total
     *  blocks have been stored in the buffer
     *  start processing the data.
     */
    if ( state++ >= AUDIO_GUITARTUNER_BLOCKS >> 1 ) {
        
        if ( process_buffer ) process( AudioBuffer );
        
        if ( state == 0 ) process_buffer = true;
    }
    
    release( block );
}

/**
 *  Start the Yin algorithm
 *
 *  TODO: Significant speed up would be to use spectral domain to find fundamental frequency.
 *  This paper explains: https://aubio.org/phd/thesis/brossier06thesis.pdf -> Section 3.2.4
 *  page 79. Might have to downsample for low fundmental frequencies because of fft buffer
 *  size limit.
 */
void AudioTuner::process( int16_t *p ) {
    
    const uint16_t inner_cycles = ( NUM_SAMPLES >> decimation_shift ) >> 1;
    uint16_t outer_cycles = inner_cycles / AUDIO_GUITARTUNER_BLOCKS;
    uint16_t tau = tau_global;
    do {
        uint64_t sum = 0;
        int32_t  a1, a2, b1, b2, c1, c2, d1, d2;
        int32_t  out1, out2, out3, out4;
        uint16_t blkCnt;
        int16_t * cur = p;
        int16_t * lag = p + tau;
        // unrolling the inner loop by 8
        blkCnt = inner_cycles >> 3;
        do {
            // a(n), b(n), c(n), d(n) each hold two samples
            a1 = *__SIMD32( cur )++;
            a2 = *__SIMD32( cur )++;
            b1 = *__SIMD32( lag )++;
            b2 = *__SIMD32( lag )++;
            c1 = *__SIMD32( cur )++;
            c2 = *__SIMD32( cur )++;
            d1 = *__SIMD32( lag )++;
            d2 = *__SIMD32( lag )++;
            // subract two samples at a time
            out1 = __QSUB16( a1, b1 );
            out2 = __QSUB16( a2, b2 );
            out3 = __QSUB16( c1, d1 );
            out4 = __QSUB16( c2, d2 );
            // square the difference
            sum = multiply_accumulate_16tx16t_add_16bx16b( sum, out1, out1 );
            sum = multiply_accumulate_16tx16t_add_16bx16b( sum, out2, out2 );
            sum = multiply_accumulate_16tx16t_add_16bx16b( sum, out3, out3 );
            sum = multiply_accumulate_16tx16t_add_16bx16b( sum, out4, out4 );
            
        } while( --blkCnt );
        
        uint64_t rs = running_sum;
        rs += sum;
        yin_buffer[yin_idx] = sum*tau;
        rs_buffer[yin_idx] = rs;
        running_sum = rs;
        yin_idx = ( ++yin_idx >= 5 ) ? 0 : yin_idx;
        tau = estimate( yin_buffer, rs_buffer, yin_idx, tau );
        
        if ( tau == 0 ) {
            process_buffer  = false;
            new_output      = true;
            yin_idx         = 1;
            running_sum     = 0;
            tau_global      = 1;
            state           = 0;
            return;
        }
        
    } while ( --outer_cycles );
    
    if ( tau >= inner_cycles ) {
        process_buffer  = true;
        new_output      = false;
        yin_idx         = 1;
        running_sum     = 0;
        tau_global      = 1;
        state           = 0;
        return;
    }
    tau_global = tau;
}

/**
 *  check the sampled data for fundamental frequency
 *
 *  @param yin  buffer to hold sum*tau value
 *  @param rs   buffer to hold running sum for sampled window
 *  @param head buffer index
 *  @param tau  lag we are curly working on gets incremented
 *
 *  @return tau
 */
uint16_t AudioTuner::estimate( uint64_t *yin, uint64_t *rs, uint16_t head, uint16_t tau ) {
    const uint64_t *y = ( uint64_t * )yin;
    const uint64_t *r = ( uint64_t * )rs;
    uint16_t _tau, _head;
    const float thresh = yin_threshold;
    _tau = tau;
    _head = head;
    
    if ( _tau > 4 ) {
        
        uint16_t idx0, idx1, idx2;
        idx0 = _head;
        idx1 = _head + 1;
        idx1 = ( idx1 >= 5 ) ? 0 : idx1;
        idx2 = _head + 2;
        idx2 = ( idx2 >= 5 ) ? idx2 - 5 : idx2;
        
        // maybe fixed point would be better here? But how?
        float s0, s1, s2;
        s0 = ( ( float )*( y+idx0 ) / ( float )*( r+idx0 ) );
        s1 = ( ( float )*( y+idx1 ) / ( float )*( r+idx1 ) );
        s2 = ( ( float )*( y+idx2 ) / ( float )*( r+idx2 ) );
        
        if ( s1 < thresh && s1 < s2 ) {
            uint16_t period = _tau - 3;
            periodicity = 1 - s1;
            data = period + 0.5f * ( s0 - s2 ) / ( s0 - 2.0f * s1 + s2 );
            return 0;
        }
    }
    return _tau + 1;
}

/**
 *  Initialise
 *
 *  @param threshold Allowed uncertainty
 */
void AudioTuner::begin( float threshold, int16_t *coeff, uint8_t taps, uint8_t factor ) {
    __disable_irq( );
    process_buffer      = true;
    yin_threshold       = threshold;
    periodicity         = 0.0f;
    running_sum         = 0;
    tau_global          = 1;
    yin_idx             = 1;
    enabled             = true;
    state               = 0;
    data                = 0.0f;
    decimation_factor   = factor;
    decimation_shift    = log( factor ) / log( 2 );
    coeff_size          = taps;
    coeff_p             = coeff;
    arm_fir_decimate_init_q15( &firDecimateInst, coeff_size, decimation_factor, coeff_p, &coeff_state[0], AUDIO_BLOCK_SAMPLES );
    __enable_irq( );
}

/**
 *  available
 *
 *  @return true if data is ready else false
 */
bool AudioTuner::available( void ) {
    __disable_irq( );
    bool flag = new_output;
    if ( flag ) new_output = false;
    __enable_irq( );
    return flag;
}

/**
 *  read processes the data samples for the Yin algorithm.
 *
 *  @return frequency in hertz
 */
float AudioTuner::read( void ) {
    __disable_irq( );
    float d = data;
    __enable_irq( );
    return ( AUDIO_SAMPLE_RATE_EXACT / decimation_factor ) / d;
}

/**
 *  Periodicity of the sampled signal.
 *
 *  @return periodicity
 */
float AudioTuner::probability( void ) {
    __disable_irq( );
    float p = periodicity;
    __enable_irq( );
    return p;
}

/**
 *  New LP coeffients for decimation.
 *
 *  @param p    array pointer of coeffients.
 *  @param n    array size.
 */
void AudioTuner::coeff( int16_t *p, int n ) {
    //coeff_size = n;
    //coeff_p = p;
    //arm_fir_decimate_init_q15(&firDecimateInst, coeff_size, 4, coeff_p, coeff_state, 128);
}

/**
 *  Initialise parameters.
 *
 *  @param thresh    Allowed uncertainty
 */
void AudioTuner::threshold( float p ) {
    __disable_irq( );
    yin_threshold = p;
    __enable_irq( );
}

/**
 *  disable yin from processing data, use begin to start back up
 *
 *  @return none
 */
void AudioTuner::disable( void ) {
    __disable_irq( );
    enabled = false;
    __enable_irq( );
}
 
Hello,

My name is Emmanuel. I made a few years a go this little box, using the AudioAnalyzeNoteFrequency object from the audio library :
https://www.pjrc.com/wooblizer-acoustic-note-to-wave-synthesizer/

I had good results, but I need to get lower latency. In a post, Wolke mentioned :
"colin duffy's Guitar and Bass Tuner library V3 is much faster than his older version currently available in original teensy audio library.
Audiotuner V3 from colin duffy: https://github.com/duff2013/AudioTuner"

I try to test Guitar and Bass Tuner library V3, but it doesn't work any more... When I initialise the object, it creates a lot of distortion, then crashes.
Would you have any ideas of what goes wrong ?

Here is my test code. I use a T4.0. I don't use the audio shield. I use PCM5102 an PCM1802 as i2S input and output. They work well.
Thank you for any help !
Emmanuel

Just to confirm that I have the same result with my own code and V3 of the library whilst using AudioShield. It seems V3 isn't compatible with Teensy 4.x range.
 
Back
Top