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

Thread: Teensy 4.0 - Audio Library screen oscilloscope

  1. #1
    Junior Member fmenes's Avatar
    Join Date
    Apr 2020
    Location
    London
    Posts
    3

    Teensy 4.0 - Audio Library screen oscilloscope

    Hello,

    I am working on a FM synthesizer that uses the Audio library on a Teensy 4.0 and the audio shield rev D. I am currently working on a basic algorithm with two mixed waveforms that are being modulated by two other basic waveforms.
    I would like to show the different waves that create the complex sound on a SSD13151 OLED screen that has a resolution of 128*128 pixels. The idea is to get something similar to the Korg Minilogue's oscilloscope. I would like to represent separated waves next to one another in real time. I have had a look at different oscilloscopes projects with Teensys but they are all working with external signals. I would like to avoid resampling the audio signal output if possible, as this would be using more resources, more memory and only output the end signal.
    Would there be a way for me to output all the waveforms separately to an array of buffers and then draw it on the screen ? I am not really familiar with the screen library or buffers yet and don't really know what signal sampling rate to use for this application. Also I don't know how much memory this would take. The goal for now is to get this working for one voice (2 carriers + 2 operators) but I'm aiming at having multiple voices at once and have a visualizer that displays the end output of every voice. If anyone has examples I could take a look at to figure the process for one wave that would be amazing.

    Thank you guys and thanks to everyone involved in this forum and Teensy, I'm having a lot of fun with amazing tool.

  2. #2
    Junior Member
    Join Date
    Apr 2020
    Posts
    6
    I have the Adafruit SSD1306 display which is 128x64. I've made a working sketch which is a mashup of the example code for that + the demo code for the Teensy Audio Shield with 3x encoders and 3x buttons. I doubt the SSD1306 library will work out of the box with your display but the part numbers seem similar enough that it might be close.

    This is not quite an oscilloscope display but displaying an FFT analysis of the entire audio output afaik.

    By the way the Minilogue is definitely something I also wanted to emulate so I'm interested in how this goes.

    I think overlaying both waves might be as simple as calling something like display.drawlines (possibly in a loop) for each waveform, and only clearing the display after both lines are drawn.

    Code:
    // Advanced Microcontroller-based Audio Workshop
    //
    // http://www.pjrc.com/store/audio_tutorial_kit.html
    // https://hackaday.io/project/8292-microcontroller-audio-workshop-had-supercon-2015
    // 
    // Part 2-8: Oscillators
    
    #include <Bounce.h>
    
    Bounce button0 = Bounce(0, 15);
    Bounce button1 = Bounce(1, 15);  // 15 = 15 ms debounce time
    Bounce button2 = Bounce(2, 15);
    
    
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    //#include <SD.h>
    #include <SerialFlash.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    
    #define OLED_RESET 5 
    Adafruit_SSD1306 display(OLED_RESET);
    
    #define LOGO16_GLCD_HEIGHT 16 
    #define LOGO16_GLCD_WIDTH  16 
    static const unsigned char PROGMEM logo16_glcd_bmp[] =
    { B00000000, B11000000,
      B00000001, B11000000,
      B00000001, B11000000,
      B00000011, B11100000,
      B11110011, B11100000,
      B11111110, B11111000,
      B01111110, B11111111,
      B00110011, B10011111,
      B00011111, B11111100,
      B00001101, B01110000,
      B00011011, B10100000,
      B00111111, B11100000,
      B00111111, B11110000,
      B01111100, B11110000,
      B01110000, B01110000,
      B00000000, B00110000 };
    
    // GUItool: begin automatically generated code
    #include <Audio.h>
    #include <Wire.h>
    #include <SPI.h>
    //#include <SD.h>
    #include <SerialFlash.h>
    
    // GUItool: begin automatically generated code
    AudioSynthWaveform       waveform1;      //xy=740,490
    AudioSynthWaveformSine   sine1;          //xy=761,618
    AudioSynthNoisePink      pink1;          //xy=764,692
    AudioSynthWaveformSineModulated sine_fm1;       //xy=805,562
    AudioMixer4              mixer1;         //xy=974,580
    AudioEffectEnvelope      envelope1;      //xy=1003.7500190734863,731.7500114440918
    AudioMixer4              mixer2;         //xy=1151.500015258789,653.2500076293945
    AudioAnalyzeFFT1024      LeftFFT;      //xy=1355.0000190734863,682.5000114440918
    AudioAnalyzeFFT1024      RightFFT;      //xy=1358,744
    AudioOutputI2S           i2s1;           //xy=1393,540
    AudioConnection          patchCord1(waveform1, 0, mixer1, 0);
    AudioConnection          patchCord2(waveform1, sine_fm1);
    AudioConnection          patchCord3(sine1, 0, mixer1, 2);
    AudioConnection          patchCord4(pink1, 0, mixer1, 3);
    AudioConnection          patchCord5(sine_fm1, 0, mixer1, 1);
    AudioConnection          patchCord6(mixer1, 0, mixer2, 0);
    AudioConnection          patchCord7(mixer1, envelope1);
    AudioConnection          patchCord8(envelope1, 0, mixer2, 1);
    AudioConnection          patchCord9(mixer2, 0, i2s1, 0);
    AudioConnection          patchCord10(mixer2, 0, i2s1, 1);
    AudioConnection          patchCord11(mixer2, LeftFFT);
    AudioConnection          patchCord12(mixer2, RightFFT);
    AudioControlSGTL5000     sgtl5000_1;     //xy=953,944
    // GUItool: end automatically generated code
    
    
    
    void setup() {
      Serial.begin(9600);
      AudioMemory(20);
      sgtl5000_1.enable();
      sgtl5000_1.volume(0.32);
    
      display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
      display.display();
      delay(2000);
    
      display.clearDisplay();
    
      display.setCursor(0, 0);
      
      pinMode(0, INPUT_PULLUP);
      pinMode(1, INPUT_PULLUP);
      pinMode(2, INPUT_PULLUP);
      mixer1.gain(0, 0.75);
      mixer1.gain(1, 0.0);
      mixer1.gain(2, 0.0);
      mixer1.gain(3, 0.0);
      mixer2.gain(0, 0.15);
      mixer2.gain(1, 0.0);
      mixer2.gain(2, 0.0);
      mixer2.gain(3, 0.0);
      waveform1.begin(WAVEFORM_SAWTOOTH);
      waveform1.amplitude(0.75);
      waveform1.frequency(50);
      waveform1.pulseWidth(0.15);
      sine_fm1.frequency(440);
      sine_fm1.amplitude(0.75);
      sine1.frequency(200);
      sine1.amplitude(0.75);
      pink1.amplitude(0.75);
      envelope1.attack(10);
      envelope1.hold(10);
      envelope1.decay(25);
      envelope1.sustain(0.4);
      envelope1.release(70);
    }
    
    int waveform_type = WAVEFORM_SAWTOOTH;
    int mixer1_setting = 0;
    int mixer2_setting = 0;
    elapsedMillis timeout = 0;
    bool mixer2_envelope = false;
    
    unsigned long last_time = millis();
    uint8_t overlayCounter = 0;
    float lastLoopTime = 0;
    uint16_t lastCPU = 0;
    uint16_t lastMem = 0;
    
    float leftBands[40] = {
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    
    float RightBands[40] = {
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    
    
    void loop() {
      button0.update();
      button1.update();
      button2.update();
    
      display.setCursor(0, 0);
    
      // Left changes the type of control waveform
      if (button0.fallingEdge()) {
        Serial.print("Control waveform: ");
        if (waveform_type == WAVEFORM_SAWTOOTH) {
          waveform_type = WAVEFORM_SINE;
          Serial.println("Sine");
        } else if (waveform_type == WAVEFORM_SINE) {
          waveform_type = WAVEFORM_SQUARE;
          Serial.println("Square");
        } else if (waveform_type == WAVEFORM_SQUARE) {
          waveform_type = WAVEFORM_TRIANGLE;
          Serial.println("Triangle");
        } else if (waveform_type == WAVEFORM_TRIANGLE) {
          waveform_type = WAVEFORM_PULSE;
          Serial.println("Pulse");
        } else if (waveform_type == WAVEFORM_PULSE) {
          waveform_type = WAVEFORM_SAWTOOTH;
          Serial.println("Sawtooth");
        }
        waveform1.begin(waveform_type);
      }
    
      // middle button switch which source we hear from mixer1
      if (button1.fallingEdge()) {
        if (mixer1_setting == 0) {
          mixer1.gain(0, 0.75);
          mixer1.gain(1, 0.0);
          mixer1.gain(2, 0.0);
          mixer1.gain(3, 0.0);
          Serial.println("Mixer1: Control oscillator");
          mixer1_setting = 1;
        } else if (mixer1_setting == 1) {
          mixer1.gain(0, 0.0);
          mixer1.gain(1, 0.75);
          mixer1.gain(2, 0.0);
          mixer1.gain(3, 0.0);
          Serial.println("Mixer1: Frequency Modulated Oscillator");
          mixer1_setting = 2;
        } else if (mixer1_setting == 2) {
          mixer1.gain(0, 0.0);
          mixer1.gain(1, 0.0);
          mixer1.gain(2, 0.75);
          mixer1.gain(3, 0.0);
          Serial.println("Mixer1: Regular Sine Wave Oscillator");
          mixer1_setting = 3;
        } else if (mixer1_setting == 3) {
          mixer1.gain(0, 0.0);
          mixer1.gain(1, 0.0);
          mixer1.gain(2, 0.0);
          mixer1.gain(3, 0.75);
          Serial.println("Mixer1: Pink Noise");
          mixer1_setting = 0;
        }
      }
    
      // Right button activates the envelope
      if (button2.fallingEdge()) {
        mixer2.gain(0, 0.0);
        mixer2.gain(1, 1.0);
        mixer2_envelope = true;
        timeout = 0;
        envelope1.noteOn();
      }
      if (button2.risingEdge()) {
        envelope1.noteOff();
        timeout = 0;
      }
    
      // after 4 seconds of inactivity, go back to
      // steady listening intead of the envelope
      if (mixer2_envelope == true && timeout > 4000) {
        mixer2.gain(0, 0.15);
        mixer2.gain(1, 0.0);
        mixer2_envelope = false;
      }
    
      // use the knobs to adjust parameters
      float knob1 = (float)analogRead(A1) / 1023.0;
      float knob2 = (float)analogRead(A2) / 1023.0;
      float knob3 = (float)analogRead(A3) / 1023.0;
      waveform1.frequency(360 * knob2 + 0.25);
      sine_fm1.frequency(knob3 * 1500 + 50);
      sine1.frequency(knob3 * 1500 + 50);
    //  Serial.println("knob1 = " + knob1);
      sgtl5000_1.volume(knob1); 
    
    
      float loopTime;
      int i;
    
      //calc loopTime
      unsigned long this_time = millis();
      if (this_time > last_time)
      {
        loopTime = (this_time - last_time);
      }
      last_time = this_time;
    
      //Update data every 20 frames for readability
      overlayCounter++;
      if (overlayCounter > 20)
      {
        lastLoopTime = loopTime;
        lastCPU = AudioProcessorUsageMax();
        AudioProcessorUsageMaxReset();
        lastMem = AudioMemoryUsageMax();
        AudioMemoryUsageMaxReset();
    
        overlayCounter = 0;
      }
    
      //Draw a frame
      display.clearDisplay();
    
      //Draw left bands
      for (i = 0; i < 40; i++)
      {
        if (leftBands[i] > 0.5) leftBands[i] = 0.25;
        display.drawLine(62 - i, 31, 62 - i, 31 - (int) (leftBands[i] * 1023), WHITE);
      }
    
      //Draw Right bands
      for (i = 0; i < 40; i++)
      {
        if (RightBands[i] > 0.5) RightBands[i] = 0.25;
        display.drawLine(65 + i, 31, 65 + i, 31 - (int) (RightBands[i] * 1023), WHITE);
      }
    
        // text display tests
      display.setTextSize(1);
      display.setTextColor(WHITE);
      display.setCursor(0,0);
    //  display.println("Hello, world!");
    
      //Overlay info
      //  loop time
      display.setCursor(0, 0);
      display.print("Loop=");
      display.print((uint8_t)lastLoopTime);
      display.print("ms");
      //  Teensy Audio info
      display.setCursor(83, 0);
      display.print("cpu=");
      display.print(lastCPU);
      display.setCursor(91, 8);
      display.print("mem=");
      display.print(lastMem);
      //  L/R letters
      display.setCursor(15, 24);
      display.print("L");
      display.setCursor(108, 24);
      display.print("R");
    
      if (LeftFFT.available()) {
        // each time new FFT data is available
        for (i = 0; i < 40; i++) {
          leftBands[i] = LeftFFT.read(i);
        }
      }
      if (RightFFT.available()) {
        // each time new FFT data is available
        for (i = 0; i < 40; i++) {
          RightBands[i] = RightFFT.read(i);
        }
      }
    
      display.display();
    
      
    }

  3. #3
    Junior Member fmenes's Avatar
    Join Date
    Apr 2020
    Location
    London
    Posts
    3
    Hi Rozling,

    Thanks for your input ! I will dive a bit more into that. These screens are definitely similar and use GFX so porting the code should be easy.
    I'm looking at the possibility of writing a class for an Oscilloscope object with four inputs. I'm still learning about libraries at the moment so I will see how far I manage to push this with these informationss. From what I understand, it's just about finding the right way to map the AudioStream or a array of parallel audio buffers to a cursor on the screen and shift the columns of pixels at a certain rate. I have only tested the screen with the basic test code on the teensy 4.0 but the refresh rate was great and I believe should be comfortable enough for a small oscilloscope.
    I will post any of the advancements and if I eventually manage to write a small library I think you'll be able to do it easily with the SSD1306. Cheers !

  4. #4
    Junior Member fmenes's Avatar
    Join Date
    Apr 2020
    Location
    London
    Posts
    3
    Hey everyone,

    I have had good progress on the rest of my work so I could start working on this yesterday. This is part of a bigger synthesizer project on which I want to work on for a while but I think I'll share more about this on another post if anyone is interested.
    I'm glad to share that I have managed to write a small class that does the trick for me right now with the SSD1351. It is very one of my first "libraries" so I would really appreciate if I could get any advice on how to optimize this. Some aspects are still not perfect but I have some optimisations in mind I would like to share and would like to know if these would be efficient.

    If haven't succeeded in using both the Audio Shield and the display on the same SPI ports. When using the test code from the Adafruit SSD1351 with the screen directly plugged on the audio board it runs perfectly. With the Audio Library on it didn't work I get because it's conflicting with some of the signals of the shield. I also tried with KurtE's adaptation of the alternative SSD1351 library for teensy 4.0 but couldn't manage to get it working with my setup.
    I finally got the display working smoothly using SPI1 (if anyone is looking for that I used the RST pin on 3V3, DC on 24, CS on 25, MOSI on 26 and SCK on 27).

    The class I wrote uses a pointer to an object of the Adafruit_SSD1351 but I think it could be easily adapted for any type of screen that uses the GFX library. This is still an early code but I think it could be useful for a lot of people.
    Right now it displays an audio block on the screen every time a new block arrives and I think that is a bit too much for the screen. I am not using fillscreen otherwise I can't use the rest of my interface because of the disable IRQ but keeping the lit pixels in memory to turn them off at the next block. I can edit sound live and see how it comes through on the oscilloscope. Right now the shapes aren't perfect but I do love their graphical aspect ! I'm going to try adding a drawline for a certain threshold distance between two pixels in the y axis so the lines are perfectly continuous. Also to reduce the refresh rate I think I will align blocks on different zones of the screen eg : new block arrives on the right half of the screen, when new block right half copied to left and new block is written on the right side,etc.

    Here's a preview of the oscilloscope. Sorry for the sound from phone and not so melodic but I'm still setting up the interface. Everything is one voice made out of 2 waveforms modulated by one operator each and filtered. Showing the true power of the teensy 4.0 ! Thanks again everyone involved in making Teensy and helping around it. I never thought I would be able to achieve something even close from this.

    https://drive.google.com/file/d/1t-R...ew?usp=sharing

    Here's the library, I'll be posting more when this gets updated:

    Code:
    #ifndef oscilloscope_h_
    #define oscilloscope_h_
    
    #include "Arduino.h"
    #include "AudioStream.h"
    #include "Adafruit_SSD1351.h"
    
    #define CHANNELS 5
    #define SCREEN_WIDTH 128
    #define SCREEN_HEIGHT 128
    #define SCREEN_MIDDLE 63
    #define SCREEN_WHITE 65535
    #define SCREEN_GREEN 5553
    #define SCREEN_BLACK 0
    
    class Oscilloscope : public AudioStream
    {
    public:
      Oscilloscope(void) : AudioStream(5, inputQueueArray){
      }
      virtual void update(void);
      void ScreenSetup(Adafruit_SSD1351*);
      void AudioToPixel(int16_t*);
      void SetWaveColor(int16_t);
      int16_t color;
      int16_t backgroundcolor;
    
    
    private:
      audio_block_t *inputQueueArray[5];;
      volatile bool new_output;
      Adafruit_SSD1351 *display;
      int16_t old_val[AUDIO_BLOCK_SAMPLES];
    };
    #endif
    
    
    #include <Arduino.h>
    #include "Oscilloscope.h"
    
    
    void Oscilloscope::ScreenSetup(Adafruit_SSD1351 *screen){
      __disable_irq();
    display = screen;
    display->fillScreen(0);
    __enable_irq();
    return;
    }
    
    void Oscilloscope::SetWaveColor(int16_t colour){
    color = colour;
    }
    
    
    void Oscilloscope::AudioToPixel(int16_t *audio){
    
    const int16_t *begin = audio;
    const int16_t *end = audio + AUDIO_BLOCK_SAMPLES;
    __disable_irq();
    do {
      int16_t wave_data = *audio;
      int16_t pixel_y = map(wave_data, -32768, 32767, -63, 63) +64;
      int16_t pixel_x = audio - begin;
      display->drawPixel(pixel_x, old_val[pixel_x], backgroundcolor);
      display->drawPixel(pixel_x, pixel_y, color);
      old_val[pixel_x] = {pixel_y};
      audio++;
    } while (audio < end);
    __enable_irq();
    return;
    }
    
    void Oscilloscope::update(void)
    {
    if (!display){
      return;
    }
    unsigned int channel;
    audio_block_t *block=NULL;
    
    for (channel = 0; channel < 5; channel++){
      if (!block){
          block = receiveReadOnly(channel);
    	     if (block){
              AudioToPixel(block->data);
            }
      }
    }
      if (block) {
        release(block);
      }
    }
    Last edited by fmenes; 05-23-2020 at 07:17 PM. Reason: forgot something

Posting Permissions

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