Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 2 of 2

Thread: Supersaw Oscillator

  1. #1
    Senior Member Rolfdegen's Avatar
    Join Date
    Sep 2020
    Location
    Germany
    Posts
    313

    Supersaw Oscillator

    Hello Teensy friends


    I'm trying to build a Supersaw Oscillator. I use the Teensy Audio Library. Here is my test with an oscillator. With SawMix I change the volume of the fundamental and side waves. With Spread I change the speed.
    It's already working quite well. But the sound is still too static. A random value is missing.

    Code:
    // Supersaw
            case WAVEFORM_SAWTOOTH:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
               
                saw_inc32 += 2039 * SupersawSpreadA;
                uint32_t spread_2 = saw_inc32;
                uint32_t spread_3 = saw_inc32 << 1;
                uint32_t spread_4 = saw_inc32 << 2;
                uint32_t spread_5 = saw_inc32 << 3;
                uint32_t ph_1 = phasedata[i];
                uint32_t ph_2 = (ph_1 + spread_2);
                uint32_t ph_3 = (ph_1 + spread_3) >> 1;
                uint32_t ph_4 = (ph_1 + spread_4) >> 2;
                uint32_t ph_5 = (ph_1 + spread_5) >> 3;
                int16_t val_1 = signed_multiply_32x16t(magnitude * Supersaw_gain1A, ph_1);
                int16_t val_2 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_2);
                int16_t val_3 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_3);
                int16_t val_4 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_4);
                int16_t val_5 = signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_5);
                *bp++ = val_1 + val_2 + val_3 + val_4 + val_5;
            }
            break;


    Youtube: https://youtu.be/LLFAaJ7f5rg

  2. #2
    Senior Member Rolfdegen's Avatar
    Join Date
    Sep 2020
    Location
    Germany
    Posts
    313
    The Supersaw in the Jeannie now works well. Its synth engien from Mutable instruments Shruthi and Audio Lib from Teensy. I fixed a few more bugs in my code.

    synth_waveform.h

    Code:
    * Audio Library for Teensy 3.X
     * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
     *
     * Development of this audio library was funded by PJRC.COM, LLC by sales of
     * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
     * open source software by purchasing Teensy or other PJRC products.
     *
     * 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.
     
     ElectroTechnique 2020
     Added WAVEFORM_SILENT, sync()                           
     */
    
    
    #ifndef synth_waveform_h_
    #define synth_waveform_h_
    
    
    #include <Arduino.h>
    #include "AudioStream.h"
    #include "arm_math.h"
    
    
    
    
    // waveforms.c
    extern "C" {
    extern const int16_t AudioWaveformSine[257];
    }
    
    
    extern uint8_t LFO1mode;
    extern uint8_t LFO2mode;
    extern uint8_t LFO3mode;
    extern boolean lfo1oneShoot; 
    extern boolean lfo2oneShoot;
    extern boolean lfo3oneShoot;  
    extern uint8_t LFO1phase;
    extern uint8_t LFO2phase;
    extern uint8_t LFO3phase;
    extern uint8_t lfo1ph;
    extern uint8_t lfo2ph;
    extern uint8_t lfo3ph;
    extern int8_t Lfo3Modoutput;
    extern boolean LFO1randomFlag;
    extern boolean LFO2randomFlag;
    extern boolean LFO3randomFlag;
    extern uint32_t LFO1delayTime;
    extern uint32_t LFO2delayTime;
    extern uint32_t LFO3delayTime;
    extern uint8_t SupersawSpreadA;
    extern uint8_t SupersawSpreadB;
    extern float Supersaw_gain1A;
    extern float Supersaw_gain2A;
    extern float Supersaw_gain1B;
    extern float Supersaw_gain2B;
    
    
    // Oscillator
    #define WAVEFORM_SINE                            0
    #define WAVEFORM_TRIANGLE                        1
    #define WAVEFORM_SAWTOOTH                        3
    #define WAVEFORM_SQUARE                            2
    #define WAVEFORM_PULSE                            4
    #define WAVEFORM_SAWTOOTH_REVERSE                5
    #define WAVEFORM_SAMPLE_HOLD                    6
    #define WAVEFORM_TRIANGLE_VARIABLE                7
    #define WAVEFORM_BANDLIMIT_SAWTOOTH_REVERSE        8
    #define WAVEFORM_BANDLIMIT_SAWTOOTH                9
    #define WAVEFORM_BANDLIMIT_SQUARE                10
    #define WAVEFORM_BANDLIMIT_PULSE                11
    #define WAVEFORM_ARBITRARY                        12
    #define WAVEFORM_SUPERSAW                       30
    #define WAVEFORM_SILENT                            19
    
    
    // LFO1 (OSC)
    #define WAVEFORM_ARBITRARY1                        20
    #define WAVEFORM_SAMPLE_HOLD1                    21
    
    
    // LFO2 (FILTER)
    #define WAVEFORM_ARBITRARY2                        22
    #define WAVEFORM_SAMPLE_HOLD2                    23
    
    
    // LFO3 (AMP)
    #define WAVEFORM_ARBITRARY3                        24
    #define WAVEFORM_SAMPLE_HOLD3                    25
    
    
    #define PWM_WAVEFORM_SINE                        26
    #define PWM_WAVEFORM_TRIANGLE                    27
    #define PWM_WAVEFORM_SAWTOOTH                    28
    #define PWM_WAVEFORM_SQUARE                        29
    
    
    
    
    
    
    typedef struct step_state
    {
      int offset ;
      bool positive ;
    } step_state ;
    
    
    
    
    class BandLimitedWaveform
    {
    public:
      BandLimitedWaveform (void) ;
      int16_t generate_sawtooth (uint32_t new_phase, int i) ;
      int16_t generate_square (uint32_t new_phase, int i) ;
      int16_t generate_pulse (uint32_t new_phase, uint32_t pulse_width, int i) ;
      void init_sawtooth (uint32_t freq_word) ;
      void init_square (uint32_t freq_word) ;
      void init_pulse (uint32_t freq_word, uint32_t pulse_width) ;
      
    
    
    private:
      int32_t lookup (int offset) ;
      void insert_step (int offset, bool rising, int i) ;
      int32_t process_step (int i) ;
      int32_t process_active_steps (uint32_t new_phase) ;
      int32_t process_active_steps_saw (uint32_t new_phase) ;
      int32_t process_active_steps_pulse (uint32_t new_phase, uint32_t pulse_width) ;
      void new_step_check_square (uint32_t new_phase, int i) ;
      void new_step_check_pulse (uint32_t new_phase, uint32_t pulse_width, int i) ;
      void new_step_check_saw (uint32_t new_phase, int i) ;
      
      
      uint32_t phase_word ;
      int32_t dc_offset ;
      step_state states [32] ; // circular buffer of active steps
      int newptr ;         // buffer pointers into states, AND'd with PTRMASK to keep in buffer range.
      int delptr ;
      int32_t  cyclic[16] ;    // circular buffer of output samples
      bool pulse_state ;
      uint32_t sampled_width ; // pulse width is sampled once per waveform
    };
    
    
    
    
    class AudioSynthWaveformTS : public AudioStream
    {
    public:
      AudioSynthWaveformTS(void) : AudioStream(0,NULL),
        phase_accumulator(0), phase_increment(0), phase_offset(0),
        magnitude(0), pulse_width(0x40000000),
        arbdata(NULL), sample(0), tone_type(WAVEFORM_SINE),
        tone_offset(0),syncFlag(0) {
      }
    
    
      void frequency(float freq) {
        if (freq < 0.0) {
          freq = 0.0;
        } else if (freq > AUDIO_SAMPLE_RATE_EXACT / 2) {
          freq = AUDIO_SAMPLE_RATE_EXACT / 2;
        }
        phase_increment = freq * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT);
        if (phase_increment > 0x7FFE0000u) phase_increment = 0x7FFE0000;
      }
      void phase(float angle) {
        if (angle < 0.0) {
          angle = 0.0;
        } else if (angle > 360.0) {
          angle = angle - 360.0;
          if (angle >= 360.0) return;
        }
        phase_offset = angle * (4294967296.0 / 360.0);
      }
      void sync() {
        syncFlag = 1;
      }       
      void amplitude(float n) { // 0 to 1.0
        if (n < 0) {
          n = 0;
        } else if (n > 1.0) {
          n = 1.0;
        }
        magnitude = n * 65536.0;
      }
      void offset(float n) {
        if (n < -1.0) {
          n = -1.0;
        } else if (n > 1.0) {
          n = 1.0;
        }
        tone_offset = n * 32767.0;
      }
      void pulseWidth(float n) {  // 0.0 to 1.0
        if (n < 0) {
          n = 0;
        } else if (n > 1.0) {
          n = 1.0;
        }
        pulse_width = n * 4294967296.0;
      }
      void begin(short t_type) {
        phase_offset = 0;
        tone_type = t_type;
        if (t_type == WAVEFORM_BANDLIMIT_SQUARE)
          band_limit_waveform.init_square (phase_increment) ;
        else if (t_type == WAVEFORM_BANDLIMIT_PULSE)
          band_limit_waveform.init_pulse (phase_increment, pulse_width) ;
        else if (t_type == WAVEFORM_BANDLIMIT_SAWTOOTH || t_type == WAVEFORM_BANDLIMIT_SAWTOOTH_REVERSE)
          band_limit_waveform.init_sawtooth (phase_increment) ;
      }
      void begin(float t_amp, float t_freq, short t_type) {
        amplitude(t_amp);
        frequency(t_freq);
        phase_offset = 0;
        begin (t_type);
      }
      void arbitraryWaveform(const int16_t *data, float maxFreq) {
        arbdata = data;
      }
      virtual void update(void);
    
    
    private:
      uint32_t phase_accumulator;
      uint32_t phase_increment;
      uint32_t phase_offset;
      int32_t  magnitude;
      uint32_t pulse_width;
      const int16_t *arbdata;
      int16_t  sample; // for WAVEFORM_SAMPLE_HOLD
      int16_t  sample2; // for WAVEFORM_SAMPLE_HOLD and oneShot
      short  tone_type;
      int16_t tone_offset;
      int16_t syncFlag;
      BandLimitedWaveform band_limit_waveform;
    };
    
    
    
    
    class AudioSynthWaveformModulatedTS : public AudioStream
    {
    public:
      AudioSynthWaveformModulatedTS(void) : AudioStream(2, inputQueueArray),
        phase_accumulator(0), phase_increment(0), modulation_factor(32768),
        magnitude(0), arbdata(NULL), sample(0), tone_offset(0),
        tone_type(WAVEFORM_SINE), modulation_type(0), syncFlag(0) {
      }
    
    
      void frequency(float freq) {
        if (freq < 0.0) {
          freq = 0.0;
        } else if (freq > AUDIO_SAMPLE_RATE_EXACT / 2) {
          freq = AUDIO_SAMPLE_RATE_EXACT / 2;
        }
        phase_increment = freq * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT);
        if (phase_increment > 0x7FFE0000u) phase_increment = 0x7FFE0000;
      }
      void amplitude(float n) { // 0 to 1.0
        if (n < 0) {
          n = 0;
        } else if (n > 1.0) {
          n = 1.0;
        }
        magnitude = n * 65536.0;
      }
       void sync() {
        syncFlag = 1;
      }              
      void offset(float n) {
        if (n < -1.0) {
          n = -1.0;
        } else if (n > 1.0) {
          n = 1.0;
        }
        tone_offset = n * 32767.0;
      }
      void begin(short t_type) {
        tone_type = t_type;
        if (t_type == WAVEFORM_BANDLIMIT_SQUARE)
          band_limit_waveform.init_square (phase_increment) ;
        else if (t_type == WAVEFORM_BANDLIMIT_PULSE)
          band_limit_waveform.init_pulse (phase_increment, 0x80000000u) ;
        else if (t_type == WAVEFORM_BANDLIMIT_SAWTOOTH || t_type == WAVEFORM_BANDLIMIT_SAWTOOTH_REVERSE)
          band_limit_waveform.init_sawtooth (phase_increment) ;
      }
      void begin(float t_amp, float t_freq, short t_type) {
        amplitude(t_amp);
        frequency(t_freq);
        begin (t_type) ;
      }
      void arbitraryWaveform(const int16_t *data, float maxFreq) {
        arbdata = data;
      }
      void frequencyModulation(float octaves) {
        if (octaves > 12.0) {
          octaves = 12.0;
        } else if (octaves < 0.1) {
          octaves = 0.1;
        }
        modulation_factor = octaves * 4096.0;
        modulation_type = 0;
      }
      void phaseModulation(float degrees) {
        if (degrees > 9000.0) {
          degrees = 9000.0;
        } else if (degrees < 30.0) {
          degrees = 30.0;
        }
        modulation_factor = degrees * (65536.0 / 180.0);
        modulation_type = 1;
      }
      virtual void update(void);
      
     
      
    private:
      audio_block_t *inputQueueArray[2];
      uint32_t phase_accumulator;
      uint32_t phase_increment;
      uint32_t modulation_factor;
      int32_t  magnitude;
      const int16_t *arbdata;
      uint32_t phasedata[AUDIO_BLOCK_SAMPLES];
      int16_t  sample; // for WAVEFORM_SAMPLE_HOLD
      int16_t  tone_offset;
      uint8_t  tone_type;
      uint8_t  modulation_type;
      int16_t  syncFlag;
      uint32_t data_qs_phase[3];
      BandLimitedWaveform band_limit_waveform;
    };
    
    #endif



    synth_waveform.cpp
    Code:
     *Audio Library for Teensy 3.X
    * Copyright (c) 2018, Paul Stoffregen, paul@pjrc.com
    *
    * Development of this audio library was funded by PJRC.COM, LLC by sales of
    * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
    * open source software by purchasing Teensy or other PJRC products.
    *
    * 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.
    ElectroTechnique 2020
    Added WAVEFORM_SILENT, syncFlag
    */
    
    
    #include <Arduino.h>
    #include "synth_waveform.h"
    #include "arm_math.h"
    #include "utility/dspinst.h"
    
    
    
    
    // uncomment for more accurate but more computationally expensive frequency modulation
    //#define IMPROVE_EXPONENTIAL_ACCURACY
    #define BASE_AMPLITUDE 0x6000  // 0x7fff won't work due to Gibb's phenomenon, so use 3/4 of full range.
    
    
    
    
    
    
    void AudioSynthWaveformTS::update(void)
    {
        audio_block_t *block;
        int16_t *bp, *end;
        int32_t val1, val2;
        int16_t val3;
        int16_t magnitude15;
        uint32_t i, ph, index, index2, scale;
        const uint32_t inc = phase_increment;
        uint32_t phaseX;
        
        if(syncFlag == 1) {
            phase_accumulator = 0;
            phaseX = 0;
            syncFlag = 0;
            LFO1randomFlag = false;
            LFO2randomFlag = false;
        }
        
        ph = phase_accumulator + phase_offset;
        phaseX = phase_accumulator;
        
        if (magnitude == 0) {
            phase_accumulator += inc * AUDIO_BLOCK_SAMPLES;
            return;
        }
        block = allocate();
        if (!block) {
            phase_accumulator += inc * AUDIO_BLOCK_SAMPLES;
            return;
        }
        bp = block->data;
    
    
        switch(tone_type) {
            
            // PWM SINE
            case PWM_WAVEFORM_SINE:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                index = ph >> 24;
                val1 = AudioWaveformSine[index];
                val2 = AudioWaveformSine[index+1];
                scale = (ph >> 8) & 0xFFFF;
                val2 *= scale;
                val1 *= 0x10000 - scale;
                *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
                ph += inc;
            }
            break;
            
            case PWM_WAVEFORM_TRIANGLE:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                uint32_t phtop = ph >> 30;
                if (phtop == 1 || phtop == 2) {
                    *bp++ = ((0xFFFF - (ph >> 15)) * magnitude) >> 16;
                    } else {
                    *bp++ = (((int32_t)ph >> 15) * magnitude) >> 16;
                }
                ph += inc;
            }
            break;
            
            // PWM SAWTOOTH (LFO offset 0.0f)
            case PWM_WAVEFORM_SAWTOOTH:        // normal sawtooth and inv sawtooth
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                *bp++ = signed_multiply_32x16t(magnitude, ph >> 1);
                ph += inc;
            }
            break;
    
    
            
            // PWM SQUARE (LFO offset 1.0)
            case PWM_WAVEFORM_SQUARE:
            magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                //if (ph & 0x80000000) {
                if (ph & 0x80000000) {
                    *bp++ = -magnitude15;
                    } else {
                    *bp++ = magnitude15;
                }
                ph += inc;
            }
            break;
            
            case WAVEFORM_ARBITRARY1:
            if (!arbdata) {
                release(block);
                phase_accumulator += inc * AUDIO_BLOCK_SAMPLES;
                return;
            }
            // len = 256
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                index = ph >> 24;
                index2 = index + 1;
                if (LFO1mode == 0) {
                    if (index2 >= 256) index2 = 0;  // loop
                }
                else {
                    if (index2 >= 256) index2 = 255; // one shot
                }
                val1 = *(arbdata + index);
                val2 = *(arbdata + index2);
                scale = (ph >> 8) & 0xFFFF;
                val2 *= scale;
                val1 *= 0x10000 - scale;
                *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
                uint32_t ph_old = ph;
                uint32_t phaseX_old = phaseX;
                if (LFO1phase == 0) {    // Shape normal
                    ph += inc;
                    phaseX += inc;
                }
                else {
                    ph -= inc;
                    phaseX -= inc;        // Shape inverse
                }
                
                if (lfo1oneShoot == true && phaseX < inc) {
                    ph = ph_old;
                    phaseX = phaseX_old;
                }
            }
            break;
            
            case WAVEFORM_ARBITRARY2:
            if (!arbdata) {
                release(block);
                phase_accumulator += inc * AUDIO_BLOCK_SAMPLES;
                return;
            }
            // len = 256
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                index = ph >> 24;
                index2 = index + 1;
                if (LFO2mode == 0) {
                    if (index2 >= 256) index2 = 0;  // loop
                }
                else {
                    if (index2 >= 255) index2 = 255; // one shot
                }
                val1 = *(arbdata + index);
                val2 = *(arbdata + index2);
                scale = (ph >> 8) & 0xFFFF;
                val2 *= scale;
                val1 *= 0x10000 - scale;
                *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
                uint32_t ph_old = ph;
                uint32_t phaseX_old = phaseX;
                if (LFO2phase == 0) {    // Shape normal
                    ph += inc;
                    phaseX += inc;
                }
                else {
                    ph -= inc;
                    phaseX -= inc;        // Shape inverse
                }
                
                if (lfo2oneShoot == true && phaseX < inc) {
                    ph = ph_old;
                    phaseX = phaseX_old;
                }
            }
            break;
            
            case WAVEFORM_ARBITRARY3:
            if (!arbdata) {
                release(block);
                phase_accumulator += inc * AUDIO_BLOCK_SAMPLES;
                return;
            }
            // len = 256
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                index = ph >> 24;
                index2 = index + 1;
                if (LFO3mode == 0) {
                    if (index2 >= 256) index2 = 0;  // loop
                }
                else {
                    if (index2 >= 255) index2 = 255; // one shot
                }
                val1 = *(arbdata + index);
                val2 = *(arbdata + index2);
                scale = (ph >> 8) & 0xFFFF;
                val2 *= scale;
                val1 *= 0x10000 - scale;
                
                //*bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
                val3 = multiply_32x32_rshift32(val1 + val2, magnitude);
                *bp++ = val3;
                Lfo3Modoutput = val3 >> 8;
                uint32_t ph_old = ph;
                uint32_t phaseX_old = phaseX;
                if (LFO3phase == 0) {    // Shape normal
                    ph += inc;
                    phaseX += inc;
                }
                else {
                    ph -= inc;
                    phaseX -= inc;        // Shape inverse
                }
                
                if (lfo3oneShoot == true && phaseX < inc) {
                    ph = ph_old;
                    phaseX = phaseX_old;
                }
            }
            break;
    
    
            case WAVEFORM_SAMPLE_HOLD1:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                uint32_t newph = ph + inc;
                if (lfo1oneShoot == true && LFO1randomFlag == false) {    // one shot
                    sample = random(magnitude) - (magnitude >> 1);
                    sample2 = sample;
                    LFO1randomFlag = true;
                }
                else if (newph < ph && lfo1oneShoot == false) {
                    sample = random(magnitude) - (magnitude >> 1);
                }
                else if (lfo1oneShoot == true && LFO1randomFlag == true) {
                    sample = sample2;
                }
                
                *bp++ = sample;
                ph = newph;
            }
            break;
            
            case WAVEFORM_SAMPLE_HOLD2:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                uint32_t newph = ph + inc;
                if (lfo2oneShoot == true && LFO2randomFlag == false) {    // one shot
                    sample = random(magnitude) - (magnitude >> 1);
                    sample2 = sample;
                    LFO2randomFlag = true;
                }
                else if (newph < ph && lfo2oneShoot == false) {
                    sample = random(magnitude) - (magnitude >> 1);
                }
                else if (lfo2oneShoot == true && LFO2randomFlag == true) {
                    sample = sample2;
                }
                
                *bp++ = sample;
                ph = newph;
            }
            break;
            
            case WAVEFORM_SAMPLE_HOLD3:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                uint32_t newph = ph + inc;
                if (lfo3oneShoot == true && LFO3randomFlag == false) {    // one shot
                    sample = random(magnitude) - (magnitude >> 1);
                    sample2 = sample;
                    LFO3randomFlag = true;
                }
                else if (newph < ph && lfo3oneShoot == false) {
                    sample = random(magnitude) - (magnitude >> 1);
                }
                else if (lfo3oneShoot == true && LFO3randomFlag == true) {
                    sample = sample2;
                }
                
                *bp++ = sample;
                ph = newph;
            }
            break;
        }
        
        phase_accumulator = ph - phase_offset;
    
    
        if (tone_offset) {
            bp = block->data;
            end = bp + AUDIO_BLOCK_SAMPLES;
            do {
                val1 = *bp;
                *bp++ = signed_saturate_rshift(val1 + tone_offset, 16, 0);
            } while (bp < end);
        }
        transmit(block, 0);
        release(block);
    }
    
    
    //--------------------------------------------------------------------------------
    
    
    void AudioSynthWaveformModulatedTS::update(void)
    {
        audio_block_t *block, *moddata, *shapedata;
        int16_t *bp, *end;
        int32_t val1, val2;
        int16_t magnitude15;
        uint32_t i, ph, index, index2, scale, priorphase;
        const uint32_t inc = phase_increment;
        
        
        uint32_t phase_spread;
        uint32_t saw_phase_increment;
        uint32_t increments[3];
        uint32_t ph_1;
        uint32_t ph_2;
        uint32_t ph_3;
        uint32_t ph_4;
        int16_t Ssaw_value;
        
        moddata = receiveReadOnly(0);
        shapedata = receiveReadOnly(1);
    
    
        if(syncFlag==1){
            phase_accumulator = 0;
            syncFlag = 0;
        }
        
        // Pre-compute the phase angle for every output sample of this update
        ph = phase_accumulator;
        priorphase = phasedata[AUDIO_BLOCK_SAMPLES-1];
        if (moddata && modulation_type == 0) {
            // Frequency Modulation
            bp = moddata->data;
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                int32_t n = (*bp++) * modulation_factor; // n is # of octaves to mod
                int32_t ipart = n >> 27; // 4 integer bits
                n &= 0x7FFFFFF;          // 27 fractional bits
                #ifdef IMPROVE_EXPONENTIAL_ACCURACY
                // exp2 polynomial suggested by Stefan Stenzel on "music-dsp"
                // mail list, Wed, 3 Sep 2014 10:08:55 +0200
                int32_t x = n << 3;
                n = multiply_accumulate_32x32_rshift32_rounded(536870912, x, 1494202713);
                int32_t sq = multiply_32x32_rshift32_rounded(x, x);
                n = multiply_accumulate_32x32_rshift32_rounded(n, sq, 1934101615);
                n = n + (multiply_32x32_rshift32_rounded(sq,
                multiply_32x32_rshift32_rounded(x, 1358044250)) << 1);
                n = n << 1;
                #else
                // exp2 algorithm by Laurent de Soras
                // https://www.musicdsp.org/en/latest/Other/106-fast-exp2-approximation.html
                n = (n + 134217728) << 3;
    
    
                n = multiply_32x32_rshift32_rounded(n, n);
                n = multiply_32x32_rshift32_rounded(n, 715827883) << 3;
                n = n + 715827882;
                #endif
                uint32_t scale = n >> (14 - ipart);
                uint64_t phstep = (uint64_t)inc * scale;
                uint32_t phstep_msw = phstep >> 32;
                if (phstep_msw < 0x7FFE) {
                    ph += phstep >> 16;
                    } else {
                    ph += 0x7FFE0000;
                }
                phasedata[i] = ph;
            }
            release(moddata);
            } else if (moddata) {
            // Phase Modulation
            bp = moddata->data;
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                // more than +/- 180 deg shift by 32 bit overflow of "n"
                uint32_t n = (uint16_t)(*bp++) * modulation_factor;
                phasedata[i] = ph + n;
                ph += inc;
            }
            release(moddata);
            } else {
            // No Modulation Input
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                phasedata[i] = ph;
                ph += inc;
            }
        }
        phase_accumulator = ph;
    
    
        //Amplitude is always 1 on TSynth when oscillator is sounding
        //magnitude must be set to zero, otherwise digital noise comes through
        if(tone_type == WAVEFORM_SILENT){
            magnitude  = 0;
            }else{
            magnitude = 65536.0;
        }
        // If the amplitude is zero, no output, but phase still increments properly
        if (magnitude == 0) {
            if (shapedata) release(shapedata);
            return;
        }
        block = allocate();
        if (!block) {
            if (shapedata) release(shapedata);
            return;
        }
        bp = block->data;
    
    
        // Now generate the output samples using the pre-computed phase angles
        switch(tone_type) {
            case WAVEFORM_SINE:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                ph = phasedata[i];
                index = ph >> 24;
                val1 = AudioWaveformSine[index];
                val2 = AudioWaveformSine[index+1];
                scale = (ph >> 8) & 0xFFFF;
                val2 *= scale;
                val1 *= 0x10000 - scale;
                *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
            }
            break;
    
    
            case WAVEFORM_ARBITRARY:
            if (!arbdata) {
                release(block);
                if (shapedata) release(shapedata);
                return;
            }
            // len = 256
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                ph = phasedata[i];
                index = ph >> 24;
                index2 = index + 1;
                if (index2 >= 256) index2 = 0;
                val1 = *(arbdata + index);
                val2 = *(arbdata + index2);
                scale = (ph >> 8) & 0xFFFF;
                val2 *= scale;
                val1 *= 0x10000 - scale;
                *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
            }
            break;
    
    
            case WAVEFORM_PULSE:
            if (shapedata) {
                magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
                for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                    uint32_t width = ((shapedata->data[i] + 0x8000) & 0xFFFF) << 16;
                    if (phasedata[i] < width) {
                        *bp++ = magnitude15;
                        } else {
                        *bp++ = -magnitude15;
                    }
                }
                break;
            } // else fall through to orginary square without shape modulation
    
    
            case WAVEFORM_SQUARE:
            magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                if (phasedata[i] & 0x80000000) {
                    *bp++ = -magnitude15;
                    } else {
                    *bp++ = magnitude15;
                }
            }
            break;
    
    
            case WAVEFORM_BANDLIMIT_PULSE:
            if (shapedata)
            {
                for (i=0; i < AUDIO_BLOCK_SAMPLES; i++)
                {
                    uint32_t width = ((shapedata->data[i] + 0x8000) & 0xFFFF) << 16;
                    int32_t val = band_limit_waveform.generate_pulse (phasedata[i], width, i) ;
                    *bp++ = (int16_t) ((val * magnitude) >> 16) ;
                }
                break;
            } // else fall through to orginary square without shape modulation
    
    
            case WAVEFORM_BANDLIMIT_SQUARE:
            for (i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
            {
                int32_t val = band_limit_waveform.generate_square (phasedata[i], i) ;
                *bp++ = (int16_t) ((val * magnitude) >> 16);
            }
            break;
            
            // Quadsaw
            case WAVEFORM_SAWTOOTH:
            phase_spread = (phase_increment >> 14) * SupersawSpreadA;
            ++phase_spread;
            saw_phase_increment = (phase_increment & 0x0000FFFF);
            
            for (uint8_t i = 0; i < 3; ++i) {
                saw_phase_increment += phase_spread;
                increments[i] = saw_phase_increment;
            }
            
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                
                data_qs_phase[0] += increments[0];
                data_qs_phase[1] += increments[1];
                data_qs_phase[2] += increments[2];
                
                ph_1 = phasedata[i];
                ph_2 = (ph_1 + data_qs_phase[0]);
                ph_3 = (ph_2 + data_qs_phase[1]);
                ph_4 = (ph_3 + data_qs_phase[2]);
                
                Ssaw_value = signed_multiply_32x16t(magnitude * Supersaw_gain1A, ph_1);
                Ssaw_value += signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_2);
                Ssaw_value += signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_3);
                Ssaw_value += signed_multiply_32x16t(magnitude * Supersaw_gain2A, ph_4);
                *bp++ = Ssaw_value;
            }
            break;
    
    
            case WAVEFORM_SAWTOOTH_REVERSE:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                *bp++ = signed_multiply_32x16t(0xFFFFFFFFu - magnitude, phasedata[i]);
            }
            break;
    
    
            case WAVEFORM_BANDLIMIT_SAWTOOTH:
            case WAVEFORM_BANDLIMIT_SAWTOOTH_REVERSE:
            for (i = 0 ; i < AUDIO_BLOCK_SAMPLES ; i++)
            {
                int16_t val = band_limit_waveform.generate_sawtooth (phasedata[i], i) ;
                val = (int16_t) ((val * magnitude) >> 16) ;
                *bp++ = tone_type == WAVEFORM_BANDLIMIT_SAWTOOTH_REVERSE ? (int16_t) -val : (int16_t) +val ;
            }
            break;
    
    
            case WAVEFORM_TRIANGLE_VARIABLE:
            if (shapedata) {
                for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                    uint32_t width = (shapedata->data[i] + 0x8000) & 0xFFFF;
                    uint32_t rise = 0xFFFFFFFF / width;
                    uint32_t fall = 0xFFFFFFFF / (0xFFFF - width);
                    uint32_t halfwidth = width << 15;
                    uint32_t n;
                    ph = phasedata[i];
                    if (ph < halfwidth) {
                        n = (ph >> 16) * rise;
                        *bp++ = ((n >> 16) * magnitude) >> 16;
                        } else if (ph < 0xFFFFFFFF - halfwidth) {
                        n = 0x7FFFFFFF - (((ph - halfwidth) >> 16) * fall);
                        *bp++ = (((int32_t)n >> 16) * magnitude) >> 16;
                        } else {
                        n = ((ph + halfwidth) >> 16) * rise + 0x80000000;
                        *bp++ = (((int32_t)n >> 16) * magnitude) >> 16;
                    }
                    ph += inc;
                }
                break;
            } // else fall through to orginary triangle without shape modulation
    
    
            case WAVEFORM_TRIANGLE:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                ph = phasedata[i];
                uint32_t phtop = ph >> 30;
                if (phtop == 1 || phtop == 2) {
                    *bp++ = ((0xFFFF - (ph >> 15)) * magnitude) >> 16;
                    } else {
                    *bp++ = (((int32_t)ph >> 15) * magnitude) >> 16;
                }
            }
            break;
            
            case WAVEFORM_SAMPLE_HOLD:
            for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
                ph = phasedata[i];
                if (ph < priorphase) { // does not work for phase modulation
                    sample = random(magnitude) - (magnitude >> 1);
                }
                priorphase = ph;
                *bp++ = sample;
            }
            break;
        }
    
    
        if (tone_offset) {
            bp = block->data;
            end = bp + AUDIO_BLOCK_SAMPLES;
            do {
                val1 = *bp;
                *bp++ = signed_saturate_rshift(val1 + tone_offset, 16, 0);
            } while (bp < end);
        }
        if (shapedata) release(shapedata);
        transmit(block, 0);
        release(block);
    }
    
    
    
    
    // BandLimitedWaveform
    
    
    
    
    #define SUPPORT_SHIFT 4
    #define SUPPORT (1 << SUPPORT_SHIFT)
    #define PTRMASK ((2 << SUPPORT_SHIFT) - 1)
    
    
    #define SCALE 16
    #define SCALE_MASK (SCALE-1)
    #define N (SCALE * SUPPORT * 2)
    
    
    #define GUARD_BITS 8
    #define GUARD      (1 << GUARD_BITS)
    #define HALF_GUARD (1 << (GUARD_BITS-1))
    
    
    
    
    #define DEG180 0x80000000u
    
    
    #define PHASE_SCALE (0x100000000L / (2 * BASE_AMPLITUDE))
    
    
    
    
    extern "C"
    {
        extern const int16_t step_table [258] ;
    }
    
    
    int32_t BandLimitedWaveform::lookup (int offset)
    {
        int off = offset >> GUARD_BITS ;
        int frac = offset & (GUARD-1) ;
    
    
        int32_t a, b ;
        if (off < N/2)   // handle odd symmetry by reflecting table
        {
            a = step_table [off+1] ;
            b = step_table [off+2] ;
        }
        else
        {
            a = - step_table [N-off] ;
            b = - step_table [N-off-1] ;
        }
        return  BASE_AMPLITUDE + ((frac * b + (GUARD - frac) * a + HALF_GUARD) >> GUARD_BITS) ; // interpolated
    }
    
    
    // create a new step, apply its past waveform into the cyclic sample buffer
    // and add a step_state object into active list so it can be added for the future samples
    void BandLimitedWaveform::insert_step (int offset, bool rising, int i)
    {
        while (offset <= (N/2-SCALE)<<GUARD_BITS)
        {
            if (offset >= 0)
            cyclic [i & 15] += rising ? lookup (offset) : -lookup (offset) ;
            offset += SCALE<<GUARD_BITS ;
            i ++ ;
        }
    
    
        states[newptr].offset = offset ;
        states[newptr].positive = rising ;
        newptr = (newptr+1) & PTRMASK ;
    }
    
    
    // generate value for current sample from one active step, checking for the
    // dc_offset adjustment at the end of the table.
    int32_t BandLimitedWaveform::process_step (int i)
    {
        int off = states[i].offset ;
        bool positive = states[i].positive ;
    
    
        int32_t entry = lookup (off) ;
        off += SCALE<<GUARD_BITS ;
        states[i].offset = off ;  // update offset in table for next sample
        if (off >= N<<GUARD_BITS)             // at end of step table we alter dc_offset to extend the step into future
        dc_offset += positive ? 2*BASE_AMPLITUDE : -2*BASE_AMPLITUDE ;
    
    
        return positive ? entry : -entry ;
    }
    
    
    // process all active steps for current sample, basically generating the waveform portion
    // due only to steps
    // square waves use this directly.
    int32_t BandLimitedWaveform::process_active_steps (uint32_t new_phase)
    {
        int32_t sample = dc_offset ;
        
        int step_count = (newptr - delptr) & PTRMASK ;
        if (step_count > 0)        // for any steps in-flight we sum in table entry and update its state
        {
            int i = newptr ;
            do
            {
                i = (i-1) & PTRMASK ;
                sample += process_step (i) ;
            } while (i != delptr) ;
            if (states[delptr].offset >= N<<GUARD_BITS)  // remove any finished entries from the buffer.
            {
                delptr = (delptr+1) & PTRMASK ;
                // can be upto two steps per sample now for pulses
                if (newptr != delptr && states[delptr].offset >= N<<GUARD_BITS)
                delptr = (delptr+1) & PTRMASK ;
            }
        }
        return sample ;
    }
    
    
    // for sawtooth need to add in the slope and compensate for all the steps being one way
    int32_t BandLimitedWaveform::process_active_steps_saw (uint32_t new_phase)
    {
        int32_t sample = process_active_steps (new_phase) ;
    
    
        sample += (int16_t) ((((uint64_t)phase_word * (2*BASE_AMPLITUDE)) >> 32) - BASE_AMPLITUDE) ;  // generate the sloped part of the wave
    
    
        if (new_phase < DEG180 && phase_word >= DEG180) // detect wrap around, correct dc offset
        dc_offset += 2*BASE_AMPLITUDE ;
    
    
        return sample ;
    }
    
    
    // for pulse need to adjust the baseline according to the pulse width to cancel the DC component.
    int32_t BandLimitedWaveform::process_active_steps_pulse (uint32_t new_phase, uint32_t pulse_width)
    {
        int32_t sample = process_active_steps (new_phase) ;
    
    
        return sample + BASE_AMPLITUDE/2 - pulse_width / (0x80000000u / BASE_AMPLITUDE) ; // correct DC offset for duty cycle
    }
    
    
    // Check for new steps using the phase update for the current sample for a square wave
    void BandLimitedWaveform::new_step_check_square (uint32_t new_phase, int i)
    {
        if (new_phase >= DEG180 && phase_word < DEG180) // detect falling step
        {
            int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (sampled_width - phase_word) / (new_phase - phase_word)) ;
            if (offset == SCALE<<GUARD_BITS)
            offset -- ;
            if (pulse_state) // guard against two falling steps in a row (if pulse width changing for instance)
            {
                insert_step (- offset, false, i) ;
                pulse_state = false ;
            }
        }
        else if (new_phase < DEG180 && phase_word >= DEG180) // detect wrap around, rising step
        {
            int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (- phase_word) / (new_phase - phase_word)) ;
            if (offset == SCALE<<GUARD_BITS)
            offset -- ;
            if (!pulse_state) // guard against two rising steps in a row (if pulse width changing for instance)
            {
                insert_step (- offset, true, i) ;
                pulse_state = true ;
            }
        }
    }
    
    
    // Checking for new steps for pulse waveform has to deal with changing frequency and pulse width and
    // not letting a pulse glitch out of existence as these change across a single period of the waveform
    // now we detect the rising edge just like for a square wave and use that to sample the pulse width
    // parameter, which then has to be checked against the instantaneous frequency every sample.
    void BandLimitedWaveform::new_step_check_pulse (uint32_t new_phase, uint32_t pulse_width, int i)
    {
        if (pulse_state && phase_word < sampled_width && (new_phase >= sampled_width || new_phase < phase_word))  // falling edge
        {
            int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (sampled_width - phase_word) / (new_phase - phase_word)) ;
            if (offset == SCALE<<GUARD_BITS)
            offset -- ;
            insert_step (- offset, false, i) ;
            pulse_state = false ;
        }
        if ((!pulse_state) && phase_word >= DEG180 && new_phase < DEG180) // detect wrap around, rising step
        {
            // sample the pulse width value so its not changing under our feet later in cycle due to modulation
            sampled_width = pulse_width ;
    
    
            int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (- phase_word) / (new_phase - phase_word)) ;
            if (offset == SCALE<<GUARD_BITS)
            offset -- ;
            insert_step (- offset, true, i) ;
            pulse_state = true ;
            
            if (pulse_state && new_phase >= sampled_width) // detect falling step directly after a rising edge
            //if (new_phase - sampled_width < DEG180) // detect falling step directly after a rising edge
            {
                int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (sampled_width - phase_word) / (new_phase - phase_word)) ;
                if (offset == SCALE<<GUARD_BITS)
                offset -- ;
                insert_step (- offset, false, i) ;
                pulse_state = false ;
            }
        }
    }
    
    
    // new steps for sawtooth are at 180 degree point, always falling.
    void BandLimitedWaveform::new_step_check_saw (uint32_t new_phase, int i)
    {
        if (new_phase >= DEG180 && phase_word < DEG180) // detect falling step
        {
            int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (DEG180 - phase_word) / (new_phase - phase_word)) ;
            if (offset == SCALE<<GUARD_BITS)
            offset -- ;
            insert_step (- offset, false, i) ;
        }
    }
    
    
    // the generation function pushd new sample into cyclic buffer, having taken out the oldest entry
    // to return.  The output is thus 16 samples behind, which allows the non-casual step function to
    // work in real time.
    int16_t BandLimitedWaveform::generate_sawtooth (uint32_t new_phase, int i)
    {
        new_step_check_saw (new_phase, i) ;
        int32_t val = process_active_steps_saw (new_phase) ;
        int16_t sample = (int16_t) cyclic [i&15] ;
        cyclic [i&15] = val ;
        phase_word = new_phase ;
        return sample ;
    }
    
    
    int16_t BandLimitedWaveform::generate_square (uint32_t new_phase, int i)
    {
        new_step_check_square (new_phase, i) ;
        int32_t val = process_active_steps (new_phase) ;
        int16_t sample = (int16_t) cyclic [i&15] ;
        cyclic [i&15] = val ;
        phase_word = new_phase ;
        return sample ;
    }
    
    
    int16_t BandLimitedWaveform::generate_pulse (uint32_t new_phase, uint32_t pulse_width, int i)
    {
        new_step_check_pulse (new_phase, pulse_width, i) ;
        int32_t val = process_active_steps_pulse (new_phase, pulse_width) ;
        int32_t sample = cyclic [i&15] ;
        cyclic [i&15] = val ;
        phase_word = new_phase ;
        return (int16_t) ((sample >> 1) - (sample >> 5)) ; // scale down to avoid overflow on narrow pulses, where the DC shift is big
    }
    
    
    void BandLimitedWaveform::init_sawtooth (uint32_t freq_word)
    {
        phase_word = 0 ;
        newptr = 0 ;
        delptr = 0 ;
        for (int i = 0 ; i < 2*SUPPORT ; i++)
        phase_word -= freq_word ;
        dc_offset = phase_word < DEG180 ? BASE_AMPLITUDE : -BASE_AMPLITUDE ;
        for (int i = 0 ; i < 2*SUPPORT ; i++)
        {
            uint32_t new_phase = phase_word + freq_word ;
            new_step_check_saw (new_phase, i) ;
            cyclic [i & 15] = (int16_t) process_active_steps_saw (new_phase) ;
            phase_word = new_phase ;
        }
    }
    
    
    
    
    void BandLimitedWaveform::init_square (uint32_t freq_word)
    {
        init_pulse (freq_word, DEG180) ;
    }
    
    
    void BandLimitedWaveform::init_pulse (uint32_t freq_word, uint32_t pulse_width)
    {
        phase_word = 0 ;
        sampled_width = pulse_width ;
        newptr = 0 ;
        delptr = 0 ;
        for (int i = 0 ; i < 2*SUPPORT ; i++)
        phase_word -= freq_word ;
    
    
        if (phase_word < pulse_width)
        {
            dc_offset = BASE_AMPLITUDE ;
            pulse_state = true ;
        }
        else
        {
            dc_offset = -BASE_AMPLITUDE ;
            pulse_state = false ;
        }
        
        for (int i = 0 ; i < 2*SUPPORT ; i++)
        {
            uint32_t new_phase = phase_word + freq_word ;
            new_step_check_pulse (new_phase, pulse_width, i) ;
            cyclic [i & 15] = (int16_t) process_active_steps_pulse (new_phase, pulse_width) ;
            phase_word = new_phase ;
        }
    }
    
    
    BandLimitedWaveform::BandLimitedWaveform()
    {
        newptr = 0 ;
        delptr = 0 ;
        dc_offset = BASE_AMPLITUDE ;
        phase_word = 0 ;
    }


    Sound demo


    Youtube: https://youtu.be/3O7SyWCHRpU



    Youtube: https://youtu.be/HZyj_MlfSJk

    PS: Sounds only come from Jeannie

    Greetings from germany. Rolf


    Last edited by Rolfdegen; 12-14-2022 at 03:52 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •