phase modulation broken

MarkT

Well-known member
The signed data stream into AudioSynthWaveformModulated is cast to unsigned before multiplying
by the modulation_factor, so that discontinuities in phase appear, if the modulation index isn't a multiple of 180
degrees.

Pull request here: https://github.com/PaulStoffregen/Audio/pull/361

I'll add some 'scope shots soon to show the issue, but here's code to recreate it, tested on Teensy 4.0 and Audio shield rev D
Code:
#include <Audio.h>

// Implement an FM voice, 3 stages in the algorithm in sequence, plus the envelope
class FMVoice
{
public:
  FMVoice () 
  {
    new AudioConnection (modulator, 0, middle, 0) ; // on heap so destructors not called when this 
    new AudioConnection (middle, 0, finalosc, 0) ; // constructor exits!
    new AudioConnection (finalosc, 0, env, 0) ;
  }

  AudioEffectEnvelope & output (void)  // need this to connect up and to set envelope parameters
  {
    return env ;
  }

 
  // depth1 is modulation depth applied to middle,
  // depth2 is modulation depth applied to final
  // f2 and f3 are multipliers for fundamental used in middle and modulator respectively, small integers
  void noteOn (float amp, float freq, float depth1, float depth2, int f2, int f3)
  {
    AudioNoInterrupts() ;
    modulator.frequency (f3 * freq) ;  // modulator is lowly sine wave generator, no begin method...
    modulator.amplitude (depth1) ;
    middle.phaseModulation (320) ; // mid range so no discontinuities
    middle.begin (depth2, f2 * freq, WAVEFORM_SINE) ;
    finalosc.phaseModulation (320) ; // mid range so no discontinuities
    finalosc.begin (amp, freq, WAVEFORM_SINE) ;
    env.noteOn () ;
    AudioInterrupts () ;
  }
  
  void noteOff (void)
  {
    env.noteOff() ;
  }

  bool isActive (void)
  {
    return env.isActive() ;
  }

  void silence ()  // save cycles when the envelope has finished releasing
  {
    finalosc.amplitude (0) ;
    middle.amplitude (0) ;
    modulator.amplitude (0) ;
  }
  
protected:
  AudioSynthWaveformSine modulator ;
  AudioSynthWaveformModulated middle ;
  AudioSynthWaveformModulated finalosc ;
  AudioEffectEnvelope env ;
};


// Create 16 waveforms, one for each MIDI channel, and the mixers
FMVoice fm ;
AudioOutputI2S  audioOut;
AudioConnection patch (fm.output(), 0, audioOut, 0) ;
AudioConnection patch2 (fm.output(), 0, audioOut, 1) ;

// The Audio Shield chip
AudioControlSGTL5000 codec;

void setup_envelope (AudioEffectEnvelope & env)
{
  env.attack(20);
  env.hold(2);
  env.decay(35);
  env.sustain(0.8);  // high sustain for more organ like sound.
  env.release(100);
}


void setup()
{
  Serial.begin(115200);
  
  setup_envelope (fm.output()) ;
 
  AudioMemory(20);

  codec.enable();
  codec.volume(0.4);
  codec.lineOutLevel (13) ;  // turn up line out to max

  fm.noteOn (0.5, 100, 0.6, 0.4, 3, 2) ;
}


void loop()
{
}

Behaviour with stock Audio library:
pm_broken.png

and fixed:
pm_working.png
 
Last edited:
On further reflection this is not the correct fix, the multiply needs to avoid overflow, so should be done as unsigned,
so the signed phase data ought to be cast to unsigned, have 0x8000 added, then multiplied.

Ah, perhaps not, I think its as simple as casting direct to 32 bit unsigned, branch ammended...
 
Last edited:
Back
Top