Wavetable Synth with TFT ILI9341 Display, interpolating issues

Status
Not open for further replies.

topicbird

Active member
Hello!

Currently I am trying to build a step sequencer where I can draw four individual waves with the TFT ILI9341 Display and put them on any of the 16 steps. I was trying to interpolate between the points where the screen didn't record any values but for some reason, the screen behaves in an odd way.

WhatsApp Image 2021-01-19 at 12.42.53.jpg

main.cpp:
Code:
#include <Arduino.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Encoder.h>
#include "Display.h"
#include "Sequencer.h"

Display display;
Sequencer sequencer;

int b1 = 0;
int b2 = 0;
int b3 = 0;
int b4 = 0;

void ADSR(float att, float dec, float sus, float rel)
{
  // b1 = digitalRead(20);
  b2 = digitalRead(2);
  b3 = digitalRead(3);
  b4 = digitalRead(4);
}

void setup()
{
  display.init();
}

void loop()
{
  display.update();
  sequencer.update();
}

Display.h:
Code:
#ifndef Display_h
#define Display_h
#include <array>
#include <SPI.h>
#include <ILI9341_t3.h>
#include <font_Arial.h>
#include <XPT2046_Touchscreen.h>

class Display
{
public:
  Display();
  // int getX();
  // int getY();
  void fillWaveArray();
  void clear();
  void init();
  void drawButtons();
  void start();
  void touchButton();
  int16_t *getArray();
  float loPass(float xInput, float xOutput);
  boolean pointInRect(int x, float y, float rectX, float rectY, float rectW, float rectH);
  boolean isTouched();
  TS_Point getPoint();
  void update();
  void linearInterpolation(TS_Point pTS);
  boolean previousIsTouched;
 // float mapper(float x, float in_min, float in_max, float out_min, float out_max);
  int X_old;
  int X;
  int maximumX = 256;
  int minimumX = 0;
  int maximumY = 32767;
  int minimumY = -32767;
  void draw(TS_Point pTS);
  int _heightSquare = 20;
  int _widthSquare = 75;
  int _gap = 4;
  int _beyondDisplay = 400; // Value to use for the else statement of the getX / getY methods
  int _radiusOfRoundCorner = 3;
  int16_t initValue;
  int16_t wave1Values[257] = {initValue};
  int _x;
  int _y;
  boolean _touched;

private:
  XPT2046_Touchscreen ts;
  ILI9341_t3 tft;
  int pointRadius = 2;
  char buttonNames[4][10] = { //First bracket indicates the amount of words
      "Clear",                // second bracket indicates the amount of maximal letters for one string
      "<----",
      "---->",
      "Waves"};
};

#endif

Display.cpp:
Code:
#include "Display.h"

// XPT2046_Touchscreen ts(CS_PIN); // Instanziierung eines ts Objekts der Klasse XPT2046_Touchscreen
// ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);
#define TIRQ_PIN 2
#define CS_PIN 8
#define TFT_DC 9
#define TFT_CS 10

Display::Display() : ts(CS_PIN), tft(TFT_CS, TFT_DC)
{
}

// Original raw minimum and maximum values:
// X --> 340 bis 3900
// Y --> 200 bis 3850

boolean Display::isTouched()
{
    return ts.touched();
}

TS_Point Display::getPoint()
{
    int16_t xScaled{0};
    int16_t yScaled{0};
    TS_Point pUnscaled = ts.getPoint(); // point getter function
    if (map(pUnscaled.x, 340, 3900, maximumX, minimumX) < minimumX)
    {
        xScaled = minimumX;
    }
    else if (map(pUnscaled.x, 340, 3900, maximumX, minimumX) > maximumX)
    {
        xScaled = maximumX;
    }
    else
    {
        xScaled = map(pUnscaled.x, 340, 3900, maximumX, minimumX);
    }

    if (map(pUnscaled.y, 200, 3850, maximumY, minimumY) < minimumY)
    {
        yScaled = minimumY;
    }

    else if (map(pUnscaled.y, 200, 3850, maximumY, minimumY) > maximumY)
    {
        yScaled = maximumY;
    }
    else
    {
        yScaled = map(pUnscaled.y, 200, 3850, maximumY, minimumY);
    }
    TS_Point pScaled{xScaled, yScaled, 0};
    return pScaled;
}

int16_t *Display::getArray()
{
    return wave1Values;
}

void Display::drawButtons()
{
    for (int i{0}; i < 4; ++i)
    {
        tft.fillRoundRect(_gap + (_gap + _widthSquare) * i, 220, _widthSquare, _heightSquare, _radiusOfRoundCorner, CL(250, 0, 0));
        tft.setCursor(26 + ((_gap + _widthSquare) * i), 227);
        tft.print(buttonNames[i]);
    }
    tft.drawLine(0, 120, 320, 120, CL(255, 255, 255));
    tft.drawLine(0, 121, 320, 121, CL(255, 255, 255));
    tft.drawLine(0, 119, 320, 119, CL(255, 255, 255));
}

void Display::clear()
{
    tft.fillScreen(ILI9341_BLACK);
    drawButtons();
}

void Display::start()
{
    Serial.begin(38600);
    tft.begin();
    tft.setRotation(1);
    tft.fillScreen(ILI9341_BLACK);
    ts.begin();
    ts.setRotation(1);
    while (!Serial && (millis() <= 1000))
        ; // weiß nicht was das ist
}

boolean Display::pointInRect(int x, float y, float rectX, float rectY, float rectW, float rectH)
{
    boolean ret = false;
    if ((x >= rectX) && (x <= (rectX + rectW)) && (y >= rectY) && (y <= (rectY + rectH)))
    {
        ret = true;
    }
    return ret;
}

void Display::touchButton()
{
    if (pointInRect(_x, _y, _gap, (240 - _heightSquare), (_widthSquare), (_heightSquare)))
    {
        tft.fillRoundRect(_gap, (240 - _heightSquare), _widthSquare, _heightSquare, _radiusOfRoundCorner, CL(250, 100, 0));
        clear();
        // clearArray();
    }
}

void Display::draw(TS_Point pTS)
{
    wave1Values[pTS.x] = pTS.y;
 //   Serial.println(*getArray());

    for (int i{0}; i < 257; ++i)
    {
        tft.fillCircle(map(i, 0, 256, 0, 320), map(wave1Values[i], -32767, 32767, 0, 240), pointRadius, CL(255, 0, 0));
    }
}

/* float Display::mapper(float x, float in_min, float in_max, float out_min, float out_max)
{
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
} */

void Display::linearInterpolation(TS_Point pTS)
{
    if (X_old <= pTS.x)
    {
        for (int i = X_old; i < pTS.x; ++i)
        {
            wave1Values[i] = map(i, X_old, pTS.x, wave1Values[X_old], wave1Values[pTS.x]);
        }
    }
    else
    {
        for (int i = pTS.x; i < X_old; ++i)
        {
            wave1Values[i] = map(i, X_old, pTS.x, wave1Values[X_old], wave1Values[pTS.x]);
        }
    }
    X_old = pTS.x;
}

void Display::init()
{
    start();
    clear();
    drawButtons();
}

void Display::update()
{
    if (isTouched())
    {
        TS_Point pTS = getPoint();
        if (previousIsTouched)
        {
            linearInterpolation(pTS);
        }
        touchButton();
        clear();
        draw(pTS);
    }
    previousIsTouched = isTouched();
}
 
The logic doesn't look good in linearInterpolation... First you are not dealing with any edge cases, such as when X_old == pTS.x.
Second you are overwriting wave1Values[Xold] the first time round the loop - you should only be updating the entries between
the endpoints I think.

Thirdly where does wave1Values[pTS.x] get initialized before linearInterpolation() is called? Can't figure this out.

Normally Bressenham's would be used for this, and you'd pass in the two x,y pairs for the line segment endpoints and it would
draw all the points as it goes.
 
Hello Mark! Thank you for your reply! The problem was actually solved by initializing the wave1Values array at the beginning of the linearInterpolation function. If I understood you right, thats what you meant too.. :)
Before that, the wave1Values array was initialized in the Display::draw function.

the new linearInterpolation function:
Code:
void Display::linearInterpolation(TS_Point pTS)
{
    wave1Values[pTS.x] = pTS.y;

    if (X_old <= pTS.x)
    {
        for (int i = X_old; i < pTS.x; ++i)
        {
            wave1Values[i] = map(i, X_old, pTS.x, wave1Values[X_old], wave1Values[pTS.x]);
        }
    }

    else
    {
        for (int i = pTS.x; i < X_old; ++i)
        {
            wave1Values[i] = map(i, X_old, pTS.x, wave1Values[X_old], wave1Values[pTS.x]);
        }
    }
    X_old = pTS.x;
}
 
I guess I was saying either use a standard Bressenham's or write something cleaner without the arbitrary mix of global variables and
passed arguments. For me a nice clean interface would be a function you pass two points to and it draws a line on the screen.
 
Status
Not open for further replies.
Back
Top