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

Thread: Audio Library Object for Weaver (Third Method) SSB Modulation/Demodulation

  1. #1
    Senior Member
    Join Date
    May 2016
    Posts
    115

    Audio Library Object for Weaver (Third Method) SSB Modulation/Demodulation

    As we have been discussing in another thread, I have been working on Weaver modulation for SDR (Software Defined Radio) using the so-called Weaver Method. I am happy to release the first of what will be a set of Audio Library based objects that can be incorporated into Teensy based SDR projects. The full code can be downloaded from github here (together with a copy of my tutorial on the Weaver method), but for illustration purposes I include it here:
    Code:
    //-------------------------------------------------------------------------------------------
    // Test sketch for the "AudioWeaverModem" Teensy Audio object for Weaver methhod SSB
    // modulation/demodulation.
    //
    // Uses two instances of AudioWeaverModem objects:
    //   1) the first configured as a modulator to take an audio signal and convert to an SSB signal
    //   2) the second configured as a demodulator  to convert the SSB back to audio.
    // Plays audio through the I2S input and outputs on the Teensy Audio shield
    //
    // Author:  Derek Rowell
    // Date:    2/27/2017
    //
    //-------------------------------------------------------------------------------------------
    #include <Audio.h>
    #include "AudioWeaverModem.h"
    #include <Wire.h>
    #include <SPI.h>
    #include <SerialFlash.h>
    #define LPF_cutoff 1500.0
    
    //------------------------------------------------------
    AudioInputI2S     AudioInput;
    AudioMixer4       InGain;
    AudioWeaverModem  Demodulator;         
    AudioWeaverModem  Modulator;         
    AudioMixer4       OutGain;
    AudioOutputI2S    Output;
    //-------------------------------------------------------
    AudioConnection c1(AudioInput,0,  InGain,0);
    AudioConnection c2(InGain,0,      Modulator,0);
    AudioConnection c3(Modulator,0,   Demodulator,0);
    AudioConnection c4(Demodulator,0, OutGain,0);
    AudioConnection c5(OutGain,0,     Output,0);
    AudioControlSGTL5000 audioShield;
    
    //-------------------------------------------------------
    void setup() {
      double TuningFrequency;                         // Weaver tuning frequency 
      double SSBFrequency;
    //----------------     Enter parameters here:
      SSBFrequency = 12000.;                          // SSB signal frequency
      boolean USB = true;                             // Select upper or lower sideband
    //----------------       
      if (USB){
        TuningFrequency = SSBFrequency + LPF_cutoff;  // Set up for USB
        Modulator.USB();
        Demodulator.USB();
      } else { 
        TuningFrequency = SSBFrequency - LPF_cutoff;  // Set up for LSB
        Modulator.LSB();
        Demodulator.LSB();
      }
      audioShield.enable();
      AudioMemory(20);
      audioShield.volume(0.7);
      InGain.gain(0., 2.0);                           // Input gain (adjust to suit audio source)
      OutGain.gain(0., 5.0);                         // Input gain (adjust to suit audio source)
      Modulator.Oscillator1_freq(LPF_cutoff);         // Weaver modulator oscillator frequencies
      Modulator.Oscillator2_freq(TuningFrequency);
      Demodulator.Oscillator1_freq(TuningFrequency);  // Weaver demodulator oscillator frequencies
      Demodulator.Oscillator2_freq(LPF_cutoff);
    }
    //----------------------------------------------------------------------------
    void loop() {
    }
    Code:
    //---------------------------------------------------------------------------------
    // File:    AudioWeaverModem.cpp
    //          A ''Teensy'' Audio Object function for Weaver SSB demodulation
    //
    // Note:   This version uses FIR filtering.  An IIR version is being prepared.
    //
    // Author:  Derek Rowell
    //
    // Date:    Feb. 19, 2017
    //
    // Copyright (c) 2017, Derek Rowell
    //
    // 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:
    // 1) The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    // 2) THIS 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
    // AUTHOR 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 "AudioWeaverModem.h"
    extern "C" {extern const int16_t AudioWaveformSine[257];}
    
    int16_t Oscillator(uint16_t Phase){
      uint16_t  index;                   // Index into sine table
      uint16_t  delta;                   // Remainder for interpolation,
      int16_t   val1, val2;
      index   = Phase>>8;                              // index into lookup table
      delta   = Phase&0xFF;                            // remainder 
      val1    = AudioWaveformSine[index];
      val2    = AudioWaveformSine[index+1];            // table has 257 entries
      return val1 + (((val2 - val1)*delta)>>8);  // linear interpolation
    }
    
    void AudioWeaverModem::update(void) {
      int16_t  oscOutI, oscOutQ;
      int16_t  TempI1[AUDIO_BLOCK_SAMPLES], TempQ1[AUDIO_BLOCK_SAMPLES];
      int16_t  TempI2[AUDIO_BLOCK_SAMPLES], TempQ2[AUDIO_BLOCK_SAMPLES];
      audio_block_t *blockIn, *blockOut, *blockDebug;
      //
      blockIn = receiveReadOnly(0);
      if (!blockIn) return;
      blockOut   = allocate();                           // create new output block
      blockDebug = allocate();                         // create new output block
      if (!blockOut)  return;
      //
      //--- First multiplication stage
      for (uint16_t i=0; i < AUDIO_BLOCK_SAMPLES; i++) {  
        blockDebug->data[i] = blockIn->data[i];
        TempI1[i]  = (blockIn->data[i]*Oscillator(sinPhase1))>>15;
        TempQ1[i]  = (blockIn->data[i]*Oscillator(cosPhase1))>>15;
        sinPhase1 += phaseInc1;
        cosPhase1 += phaseInc1;
      }
    //--- Low-pass filter both blocks
      arm_fir_fast_q15(&filterI, TempI1, TempI2, AUDIO_BLOCK_SAMPLES);
      arm_fir_fast_q15(&filterQ, TempQ1, TempQ2, AUDIO_BLOCK_SAMPLES);
    //   
    //-- Second multiplication stage
      for (uint16_t i=0; i < AUDIO_BLOCK_SAMPLES; i++) { 
     //     blockDebug->data[i] = TempQ2[i];
        oscOutI = Oscillator(sinPhase2);
        oscOutQ = Oscillator(cosPhase2);
    //
    //--- Sum or difference to produce the output.
        if (lowSB) blockOut->data[i] = ((TempI2[i]*oscOutI)>>15) - ((TempQ2[i]*oscOutQ)>>15);
        else       blockOut->data[i] = ((TempI2[i]*oscOutI)>>15) + ((TempQ2[i]*oscOutQ)>>15);
        sinPhase2 += phaseInc2;
        cosPhase2 += phaseInc2;
        }
    //--- End of processing...
      transmit(blockOut,   0);
      transmit(blockDebug, 1);
      release(blockDebug);
      release(blockOut);
      release(blockIn);
      return;
    }
    Code:
    //---------------------------------------------------------------------------------
    // File:    AudioWeaverModem.h
    //          Helper file for ''Teensy'' Audio Object AudioWeaverModem for Weaver SSB
    //          modulation/demodulation.
    //
    // Note:   This version uses FIR filtering.  An IIR version is being prepared.
    //
    // Author:  Derek Rowell
    //
    // Date:    Feb. 19, 2017
    //
    // Copyright (c) 2017, Derek Rowell
    //
    // 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:
    //  1) The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    // 2) THIS 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
    // AUTHOR 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 weaver_modem_h_
    #define weaver_modem_h_
    #include "Arduino.h"
    #include "AudioStream.h"
    #include "arm_math.h"
    #define FIR_MAX_COEFFS 200
    class AudioWeaverModem : public AudioStream {
      public:
        AudioWeaverModem() : AudioStream(1,inputQueueArray) {
          sinPhase1 = 0;            // Initial phase for oscillator 1
          cosPhase1 = 16384;        // Corresponds to phase shift of pi/2 for cosine
          sinPhase2 = 0;            // Initial phase for oscillator 2
          cosPhase2 = 16384;        // Corresponds to phase shift of pi/2 for cosine
          arm_fir_init_q15(&filterI, n_coeffs, coeffs, FIRstateI, AUDIO_BLOCK_SAMPLES);
          arm_fir_init_q15(&filterQ, n_coeffs, coeffs, FIRstateQ, AUDIO_BLOCK_SAMPLES);
       }
    
    //--------
         virtual void update(void);
    
    
    //--- Set internal oscillator 1 frequency
        void Oscillator1_freq(float oscFreq1) {
         if (oscFreq1 < 0.0) oscFreq1 = 0.0;
          else if (oscFreq1 > AUDIO_SAMPLE_RATE_EXACT/2) oscFreq1 = AUDIO_SAMPLE_RATE_EXACT/2;
          phaseInc1 = uint16_t(oscFreq1*(65536.0/AUDIO_SAMPLE_RATE_EXACT));
         }
        
    //--- Set internal oscillator 2 frequency
        void Oscillator2_freq(float oscFreq2) {
          if (oscFreq2 < 0.0) oscFreq2 = 0.0;
          else if (oscFreq2 > AUDIO_SAMPLE_RATE_EXACT/2) oscFreq2 = AUDIO_SAMPLE_RATE_EXACT/2;
          phaseInc2 = uint16_t(oscFreq2*(65536.0/AUDIO_SAMPLE_RATE_EXACT)); 
        }
        
    //--- Set to LSB mode
        void LSB(void){lowSB = true;}
        
    //--- Set USB mode
        void USB(void){lowSB = false;}
    
    //
      private:
        audio_block_t *inputQueueArray[1];
        uint16_t sinPhase1;
        uint16_t cosPhase1;
        uint16_t phaseInc1;
        uint16_t sinPhase2;
        uint16_t cosPhase2;
        uint16_t phaseInc2;
        boolean  lowSB = true;
        arm_fir_instance_q15 filterI;
        arm_fir_instance_q15 filterQ;
        q15_t FIRstateI[AUDIO_BLOCK_SAMPLES + FIR_MAX_COEFFS];
        q15_t FIRstateQ[AUDIO_BLOCK_SAMPLES + FIR_MAX_COEFFS];
    // Lowpass Filter coefficients:
        int16_t n_coeffs   = 70;
        int16_t coeffs[70] = {
             67,     43,     52,     59,     61,     58,     47,     27,     -2,    -40,
            -87,   -141,   -199,   -257,   -311,   -355,   -383,   -391,   -372,   -323,
           -238,   -117,     40,    233,    457,    706,    973,   1249,   1523,   1784,
           2022,   2226,   2388,   2500,   2558,   2558,   2500,   2388,   2226,   2022,   
           1784,   1523,   1249,    973,    706,    457,    233,     40,   -117,   -238,
           -323,   -372,   -391,   -383,   -355,   -311,   -257,   -199,   -141,    -87,
            -40,     -2,     27,     47,     58,     61,     59,     52,     43,     67
        }; 
    };
    #endif
    The AudioWeaverModem object can act either as a SSB modulator or demodulator, simply by reversing the order of two oscillators. In this example we do both: we take an audio stream and create a SSB signal (upper or lower sideband) at a frequency within the Nyquist rate (22.05 kHz), then demodulate it back to audio again. See my tutorial for a description of how it works.

    The details of this implementation are:
    Filter - FIR, F_c = 1500 Hz, Parks-McClellan, length 70, designed using Iowa Hills FIR package. You can simply change this if you wish.
    Filtering is done using the arm_math function arm_fir_fast_q15().

    I have been streaming music and speech continuously for the past few days, and I'm surprised at the quality. It is, of course, limited to about 3kHz bandwidth by the filter - but that's the whole idea !

    FIR filters are slow. The audio block processing time on a Teensy 3.2 is just on 500 usecs, and most is taken up by the two length 70 filters.

    I also have an fully-operational 16-bit IIR based system with 8th order bi-quad Chebyshev II filters. It runs considerably faster, but my implementation using arm_math is suffering from the classic IIR limit cycle oscillation, which causes a -48dB 1500Hz tone in the output. It's very faint but also annoying. I'm looking at 32-bits for when I get my Teensy 3.6 up and running.

    I also have the quadrature input demodulator version AudioWeaverDemodQSD for QSD (Quadrature Sampling Detector) SDR receivers. I'll be loading it on github probably later today, BUT the situation is not quite as rosy as I paint in the tutorial. It is very sensitive to dc offsets and gain imbalances on the inputs. I'd be delighted to have somebody try it, but let's discuss it...
    Attached Files Attached Files

Posting Permissions

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