Jteensy8000 - Clone of a clone with extra filters

I wonder if anyone can help with an issue when overclock the teensy. My usb host stops connecting above the 720 overclock? Is this normal or something I can work around?
 
Ok no help on the over clocking. But overclocked to 720 now have the 8 voices, sync and crossover active.

Moved away from the html editor and aimed to enable the juce editor to work. Finally managed to but need to change it to be an audio effect.

Patch loading of jp8000 patches has started and is close. My filter cc mapping is different so might need to add in a conversion factor for patches but prefer my control.

Couple of demos. I will say I have not recorded in several years and I am way out of practice but I really loved the sounds today and after doing this project for probably a year now I just wanted to play.

Filters are distorting a little but never breaking so looking good.

Project should now run on any teensy and be able to use usb audio. Cc definition in one file and commented.



Please be careful of volume recording was normalised but I was switching filter types at time
So low pass to high pass etc caused drops and resonance can squeal

Now software is in a good place I’ve been looking at the hardware so will be trying to plan the circuit in the coming weeks
 
been working on the patch conversion and loading. Small side quest means my envelopes now have curves and can be updated whilst playing.

All this is supposed to be done on my own project once I get through this training project 🙈 but loving the option.

What has been changed since last time so when I look back I remember.

Can now load all versions of the jp8000 performances and single patches
Split and layer modes added to match exactly from a structural point. But need to refine value mapping. Have names displayed on juce plugin but not hardware.

Decided to leave syx import to juce side only.

Broke away from cc as I was out of numbers. That’s caused a juce app and ableton clash as I’m struggling to solve the dual layers and feedback from the hardware.

Replaced teensy so can now overclock but broke an encoder (nuts!). No cpu gain from 720 to 800 so will need to check what’s going on there?

Flipped reverb to a global effect. Shouldn’t have existed anyway but I like it. Has a nice hold function but shimmer is a cpu killer.

But the code is very close now to the clone it’s cloning.

Performance changes exposed the ability to use 2 supersaws which I killed for cpu savings.

also finally sorted glide added unison amongst loads more.
 

Attachments

  • IMG_4880.jpeg
    IMG_4880.jpeg
    254.2 KB · Views: 33
Been a hard week. Last weekend I ended up messing up my latest teensy board so it no longer gets the encoder pulses from my microdexed.

So after a few nights soldering and re soldering I took the decision to split the synth engine out. So now the project will hopefully be able to run on a bare bone teensy. I will keep the juce app idea as that was and still is easiest path for patch importing.

Current design will be 1 teensy with psram will be the jt8000, there will be a usb midi and audio interface.

For myself I will add in a connected waveshare screen with m5 stack encoders and pots. Along with a waveshare screen.

My aim is the screen and m5 will become a usb midi controller but directly linked to the jt8000.

Just did not realise how small the M5 stack stuff was 😂 so no doubt this will change again.

Going to put the effort in to smoothing pot noise, acceleration of encoder reads. But also plan on adding in the inbuilt sensors and connectivity of screen to make life easier.

Distractions what distractions 🙊. Thankful that this actually plays and I can switch off all the design questions and actually play it.
 

Attachments

  • IMG_4900.jpeg
    IMG_4900.jpeg
    101.9 KB · Views: 28
  • IMG_4899.jpeg
    IMG_4899.jpeg
    139.9 KB · Views: 34

I kept hitting issues with hardware so decided to split out a midi controller using the waveshare esp32 s3.

Note to self original display and microdexed support was not being caught in CPU measure and can remain.

So now hopefully jt8000 can be standalone and suitable for anyone to demo and they just choose how they control.

The screen has a split out so it can also be used to control any other microcontroller like the amysynth that I’ve just bought.

The m5 stack stuff works really well and easy to integrate. Wish it was a bit bigger but certainly will not complain at functionality.

Switches LEDS POTS.

I went from 2 encoders with touch to 8 encoders, 8 pots 8 buttons. Then noticed the angle etc had a selection switch so many more.

I can now store to flash. So will get my patches made.

Will leave the jp8000 patch loads via the juce app as it’s working there

VA filter bank has had a massive update as I let Claude go wild and it cost 20% cpu per voice. Safe to say I backed off and concentrated on the initial filters. Turns out I’d messed up the drive parameter at some point so corrected and exposed that as it’s changes the filter character.

Anyway va filter bank can safely run the 8 voices and I stopped calculating when not in use so my idle cpu is way down.
 
Which Teensy filter model are you using?
Can I develop a my custom GUI using a Waveshare ESP32-S3 display (this is my GUI running on an ST7796S display)? Can I drive the display via SPI from a Teensy, and how fast is it (real-time scope and filter curves)?


 
Last edited:
I started with the ladder filter but took the advice to look in to other options.

So I ported over the obxa filter that is open source and made it compliant with the teensy. I prefer it for my sounds so kept it in.

However that filter’s character didn’t let me replicate the sound of the jp8000 hence the second library in my project audiofiltervabank. Within there I have various filters based on the referenced white paper. Within there I have state variable, moog style, korg and diode. The diode is the goal for the jp8000 and until the other week I had issues with the gain stages or compensation. But that is resolved.

The way the filters code runs just now I am about 4% cpu. They never explode and do sound good. The overdrive being pretty key to the character.

For my displays I run them for editing. I’ve had the scope running and all that jazz but to be honest most of the time I just wanted to see what was happening with the synth.

Happy with what I have now with touch enabled.

The Bluetooth midi is next. But my cardboard box audio broke and I need a break for the weekend.

Need to wire in to an audio out properly so I stop breaking it
 
Which Teensy filter model are you using?
Can I develop a my custom GUI using a Waveshare ESP32-S3 display (this is my GUI running on an ST7796S display)? Can I drive the display via SPI from a Teensy, and how fast is it (real-time scope and filter curves)?


The waveshare esp32 still usses the st7796s. I bought it based on your screen but had a couple of beers and didn’t notice the inbuilt esp32.

I have it standalone now as I’ll split it to have loadable templates for different synths or setups.

So your code will likely just port over easily. There is plenty of head room for taking the io in to it as I have the 3 m5 items and it’s all responsive and not noisy.

Wasn’t too expensive either
 
I am having a problem with read MIDI data and a FT7796s touch controller. The FT7796S interrupt is interfering with my DIN MIDI reception. I poll the FT7796S touch controller using interrupts. I am using the Teensy `Wire.h` library, and the FT7796S is connected to `Wire0`.

C:
// Touchscreen interrupt ---------------------------------------
void Touchscreen_Interrupt()
{
    touch_flag = true;
}

//--------------------------------------------------------------
// Main loop
//--------------------------------------------------------------
void loop()
{
    // render Midi data
    MIDI.read(); // MIDI 5 Pin DIN

    if (touch_flag == true && touch_Timer > 15)
    {
        touch_Timer = 0;
        read_touchscreen();
    }

    // render encoder data
    read_encoder();

//--------------------------------------------------------------
// read Touchscreen
//--------------------------------------------------------------
void read_touchscreen(void)
{
    if (touch_flag)
    {
        touch_flag = false;
        byte ts_status, ts_ID_1, new_ts_event_1, page_ID, parm_ID;
        ts_status = ft6336u.read_td_status();

        if (ts_status >= 0)
        {
            ts_ID_1 = ft6336u.read_touch1_id(); // 1 = 1st touch, 2 = 2nd touch
            if (ts_ID_1 == 0)
            {
                new_ts_event_1 = ft6336u.read_touch1_event(); // 0 = No touching, 1 = touch
                if (old_ts_event_1 == 0 && new_ts_event_1 == 2)
                {
                    uint16_t ts_ypos_1 = 319 - ft6336u.read_touch1_x(); // Display rotated 180 degrees
                    uint16_t ts_xpos_1 = ft6336u.read_touch1_y();       // and x/y exchange
                    // press Top menu buttons 1 - 4
                    if (ts_xpos_1 >= 0 && ts_ypos_1 <= 50)
                    {
                        page_ID = 0;
                        parm_ID = 1;
                        //TS_ctrl_handle_0(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
                    }
                    // press Bottom menu buttons on page 1 - 4
                    else if (ts_xpos_1 >= 0 && ts_ypos_1 >= 270)
                    {
                        page_ID = Menu_page;
                        parm_ID = 1;
                        switch (page_ID)
                        {
                        case 1:
                           // TS_ctrl_handle_1(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
                            break;
                        case 2:
                            //TS_ctrl_handle_2(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
                            break;
                        case 3:
                            //TS_ctrl_handle_3(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
                            break;
                        case 4:
                            //TS_ctrl_handle_4(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
                            break;
                        default:
                            break;
                        }
                    }
                    old_ts_event_1 = new_ts_event_1;
                }
                else if (old_ts_event_1 == 2 && new_ts_event_1 == 1)
                {
                    old_ts_event_1 = 0; // no touch
                }
            }
        }
    }
}
}
 
You don’t say which FT6336U library you’re using, but I’m pretty sure I found it. It’s no wonder you’re having problems, as it’s appallingly badly written. I suspect this line is the cause of most of your woes - I’d suggest you comment it out to see what happens.
 
Yes. I am using the same FT6336U library. If I comment out line 258 (`delay(10)`), the touch controller stops responding or responds only very sporadically.

I'm going to try out the Adafruit FT6x36 library. Or is there a better library :unsure:
 
I had various issues with mid timing in the main loop. I have kept usb host, midi and din within the project but my main loop is kept clean by calling the other functions and each of those is gate kept to keep timing down

The microdexed display was using the Adafruit_FT6206.h library.

But when I done the touch input wrapper it was noted not to allow any blocking calls as this is ran every scan anyway. As always I will flag that I’m using various AI during coding this but that does not take away from the many hours debugging and causing faults and finding a good way to fix them.

One thing I’ve added to all the projects is extensive comments around the reasoning behind logic

Now using the waveshare esp32 though all its loops are separate and simply sending serial midi to the teensy so in theory no blockers anywhere on my main loop.


Code:
// ============================================================================
// update() — poll hardware, maintain state, fire gesture detection.
//
// PERFORMANCE RULES FOR THIS FUNCTION:
//   - Called every loop() at ~1 kHz or faster.
//   - NO Serial.print() here — USB TX flood will crash USB enumeration.
//   - NO blocking calls (delay, Wire timeout loops).
//   - Keep total execution under ~50 us.
// ============================================================================

void TouchInput::update() {
    bool    nowTouched = false;
    int16_t rawX = 0, rawY = 0;

    if (_touchController.touched()) {
        TS_Point p = _touchController.getPoint();
        rawX       = p.x;
        rawY       = p.y;
        nowTouched = true;
    }

    if (nowTouched) {
        _currentPoint = mapCoordinates(rawX, rawY);

        if (!_isTouched) {
            // ---- Finger just landed ----
            _isTouched       = true;
            _gestureStart    = _currentPoint;
            _touchStartTime  = millis();
            _detectedGesture = GESTURE_NONE;

            // DEBUG: log ONCE per touch-down only.
            // Uncomment only during calibration - leave commented for production.
            // Serial.printf("Touch DOWN  raw(%d,%d) -> screen(%d,%d)\n",
            //               rawX, rawY, _currentPoint.x, _currentPoint.y);
        }
        _lastPoint = _currentPoint;

    } else {
        if (_isTouched) {
            // ---- Finger just lifted ----
            _isTouched    = false;
            _touchEndTime = millis();
            detectGesture();
        }
    }
 
Something is wrong here. I tried a different FT6336u library and encountered the same problems. Every time the touchscreen is polled, it interrupts MIDI reception (both USB-MIDI and DIN-MIDI). Could it be the wiring? I’m not using any external pull-ups.
 
I tried a different FT6336u library
That’s not really a different library - the ReadMe says it’s based on the terrible one you had trouble with before…
Could it be the wiring? I’m not using any external pull-ups.
There certainly need to be pull-ups! They aren’t optional. And if you want to get to 400kHz they really need to be about 2k2 with short wires.

For this sort of issue it’s a good idea to use an oscilloscope, and maybe a spare digital output or two, to look at the signal timings and get a feel for how long the touch library is holding up the MIDI.
 
I have just added touch from my waveshare and had some issue with the libraries so I fell back to the demo’s.

They used the sensorlib since I could get the demo to work then I stuck with that. Now my caveat I output midi from esp32 to teensy which obviously doesn’t interrupt the synth engine.

But when wrapping I worked on if there was an interupt pin?

Maybe use this as a test point. Note that it is gated polling as I am sharing a bus with the M5’s

Here Feel free to have a look
 
I poll my seven encoders via interrupt on wire1. They are connected to an MCP23017. I can receive MIDI data and operate the encoders without any issues at the same time.
 
I am no expert but I fear what is polled and responses is the difference. Encoders go to a multiplexer? The library I use was only because of a demo and made it easier.

At one point just logged the time difference of execution times of the loop and reported them if above a threshold. That then let me focus on the pauses is scans and where they came from?

You can and should be able touch the screen whilst receiving midi and not drop notes
 
Soldering in pull-up resistors 2.2K didn't solve the problem. I'm going to test the Adafruit FT6x36 library. It doesn't have a delay loop in the code.

C:
/**************************************************************************/
/*!
    @brief  Reads all touch data registers in one transaction
    @returns True if read was successful, false otherwise
*/
/**************************************************************************/
bool Adafruit_FT6x36::readData(void) {
  uint8_t data[16];
 
  Adafruit_BusIO_Register touch_array =
      Adafruit_BusIO_Register(i2c_dev, 0x00, 16);
 
  if (!touch_array.read(data, 16)) {
    return false;
  }

  // Process touch points
  for (uint8_t i = 0; i < 2; i++) {
    const uint8_t offset = 6 * i;
    _touchX[i] = ((uint16_t)(data[FT6X36_REG_P1_XH + offset] & 0x0F) << 8) |
                   data[FT6X36_REG_P1_XL + offset];
    _touchY[i] = ((uint16_t)(data[FT6X36_REG_P1_YH + offset] & 0x0F) << 8) |
                   data[FT6X36_REG_P1_YL + offset];
    _touchEvent[i] = data[FT6X36_REG_P1_XH + offset] >> 6;
  }
 
  return true;
}
 
Last edited:
I’m waiting on hardware so not using my subscription today so I put you’re code in to my projects agent and Claude suggest using the adafruit ft6206 library v1.1.1 as it’s more mature and covers the chip you need. Both should work though)

My code is for esp32 so was not easy to use or prove in teensy and being honest is part of a larger project than needed so I’ll update.

Couple points brought up on original code so you can see it’s been commented.

Not sure on the whole project but if you take from this what you need. You’ll see stubs where you need to implement you’re existing code


Code:
// =============================================================
// test.cpp — Touch + DIN MIDI integration test
//
// Hardware:
//   Teensy 4.1
//   FT6336U touch controller on Wire (I2C0), INT pin → TOUCH_INT_PIN
//   DIN MIDI on Serial1 (or whichever HW serial is wired)
//
// Problem solved:
//   The original code made 5 separate I2C reads per touch poll
//   (~1.2 ms blocking).  DIN MIDI at 31250 baud delivers a byte
//   every ~320 µs.  The Teensy 4.1 UART FIFO is only 4 bytes
//   deep, so >1.3 ms of blocking causes byte drops.
//
//   The Adafruit_FT6206 library does a single burst read inside
//   touched(), reducing I2C blocking to ~0.3 ms.  Combined with
//   the volatile fix and early-exit on no-touch, MIDI reception
//   is no longer starved.
//
// Library:
//   Adafruit_FT6206_Library v1.1.1 (supports FT6206/FT6236/FT6336U)
//   Install via Library Manager → "Adafruit FT6206"
//   Requires: Adafruit_BusIO (installed automatically)
// =============================================================

#include <Arduino.h>
#include <Wire.h>
#include <MIDI.h>
#include <Adafruit_FT6206.h>

// ----- Pin assignments (adjust to match your wiring) ---------
static constexpr uint8_t TOUCH_INT_PIN = 2;   // FT6336 INT output

// ----- MIDI on hardware serial (DIN 5-pin) -------------------
MIDI_CREATE_DEFAULT_INSTANCE();  // Serial1 at 31250 baud

// ----- Touch controller --------------------------------------
Adafruit_FT6206 touchscreen;

// ----- Interrupt flag ----------------------------------------
// CRITICAL: must be volatile — written by ISR, read by loop().
// Without volatile the compiler may cache it in a register and
// loop() never sees the ISR's write.
volatile bool touch_flag = false;

// ----- Debounce timer ----------------------------------------
// elapsedMillis auto-increments every millisecond (Teensy built-in)
elapsedMillis touch_Timer;

// ----- State for press / release edge detection --------------
static bool was_touched = false;

// ----- Forward declarations ----------------------------------
void touchscreen_ISR();
void handle_touch();

// Stub — replace with your encoder reading code
extern void read_encoder();

// Stub — replace with your menu page variable
extern uint8_t Menu_page;

// =============================================================
// setup
// =============================================================
void setup()
{
    Serial.begin(115200);

    // I2C for touch controller
    Wire.begin();
    Wire.setClock(400000);  // 400 kHz — halves I2C transaction time

    // Touch controller init
    if (!touchscreen.begin(40, &Wire)) {
        Serial.println("FT6336 not found — check wiring");
        while (1) { delay(10); }
    }
    Serial.println("FT6336 OK");

    // INT pin: active-low, falling edge = new touch data ready
    pinMode(TOUCH_INT_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(TOUCH_INT_PIN),
                    touchscreen_ISR, FALLING);

    // DIN MIDI
    MIDI.begin(MIDI_CHANNEL_OMNI);

    Serial.println("Ready");
}

// =============================================================
// ISR — set flag only, no I2C here
// =============================================================
void touchscreen_ISR()
{
    touch_flag = true;
}

// =============================================================
// Main loop
// =============================================================
void loop()
{
    // ---------------------------------------------------------
    // 1. MIDI first, every single iteration.
    //    This is the highest-priority task.  Never gate it
    //    behind anything that might block.
    // ---------------------------------------------------------
    MIDI.read();

    // ---------------------------------------------------------
    // 2. Touch: poll at most every 15 ms (~67 Hz).
    //    Human fingers move at ~100 mm/s max; 67 Hz gives
    //    ~1.5 mm per sample — more than enough resolution.
    //
    //    Total I2C blocking per poll ≈ 0.15 ms at 400 kHz
    //    (one burst read inside touched()).  MIDI is safe.
    // ---------------------------------------------------------
    if (touch_flag && touch_Timer > 15) {
        touch_Timer = 0;
        touch_flag  = false;  // clear before I2C, not after —
                              // if a new touch arrives during
                              // the read, the flag gets re-set
                              // and we catch it next iteration.

        handle_touch();
    }

    // ---------------------------------------------------------
    // 3. Encoders — assumed fast, no I2C contention
    // ---------------------------------------------------------
    read_encoder();
}

// =============================================================
// handle_touch — all I2C happens inside touched() / getPoint()
//
// touched() does a single burst read of registers 0x00–0x0E
// internally.  getPoint() returns the cached result.
// Total: ONE I2C transaction, not five.
// =============================================================
void handle_touch()
{
    uint8_t n_touches = touchscreen.touched();

    // --- No finger down → detect release edge, then exit -----
    if (n_touches == 0) {
        if (was_touched) {
            was_touched = false;
            // Optional: handle release event here
        }
        return;  // nothing else to do — skip getPoint() entirely
    }

    // --- Press edge: only act on the transition 0 → 1 --------
    if (!was_touched) {
        was_touched = true;

        TS_Point p = touchscreen.getPoint(0);  // no I2C — cached data

        // Apply display rotation (180°, axes swapped)
        uint16_t ts_xpos = p.y;
        uint16_t ts_ypos = 319 - p.x;

        // --- Top menu (y 0..50) ---
        if (ts_ypos <= 50) {
            uint8_t page_ID = 0;
            uint8_t parm_ID = 1;
            (void)page_ID; (void)parm_ID;  // suppress unused warnings
            // TS_ctrl_handle_0(page_ID, parm_ID, ts_xpos, ts_ypos);
        }
        // --- Bottom menu (y 270..319) ---
        else if (ts_ypos >= 270) {
            uint8_t parm_ID = 1;
            (void)parm_ID;
            switch (Menu_page) {
                case 1: /* TS_ctrl_handle_1(Menu_page, parm_ID, ts_xpos, ts_ypos); */ break;
                case 2: /* TS_ctrl_handle_2(Menu_page, parm_ID, ts_xpos, ts_ypos); */ break;
                case 3: /* TS_ctrl_handle_3(Menu_page, parm_ID, ts_xpos, ts_ypos); */ break;
                case 4: /* TS_ctrl_handle_4(Menu_page, parm_ID, ts_xpos, ts_ypos); */ break;
                default: break;
            }
        }
    }
}
 
Thanks for your excellent help (y) I'll test it this evening and report back afterwards..

...one hour later :love: MIDI reception is now working flawlessly. Thanks a lot.
 
Last edited:
Thanks for your excellent help (y) I'll test it this evening and report back afterwards..

...one hour later :love: MIDI reception is now working flawlessly. Thanks a lot.
Brilliant news. Glad that I could help in some way.

Look forward to hearing the results
 
// =============================================================
// setup
// =============================================================
void setup()
{
Serial.begin(115200);

// I2C for touch controller
Wire.begin();
Wire.setClock(400000); // 400 kHz — halves I2C transaction time

// Touch controller init
if (!touchscreen.begin(40, &Wire)) {
Serial.println("FT6336 not found — check wiring");
while (1) { delay(10); }
}
Serial.println("FT6336 OK");

// INT pin: active-low, falling edge = new touch data ready
pinMode(TOUCH_INT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(TOUCH_INT_PIN),
touchscreen_ISR, FALLING);

// DIN MIDI
MIDI.begin(MIDI_CHANNEL_OMNI);

Serial.println("Ready");
}
I checked the wire speed of your code with my oscilloscope and found it was only 100 kHz. I modified the initialization slightly, and now it's 400 kHz. A touch query now takes 550 µs.

C:
//----------------------------------------------------------
    // init Touchscreen on Wire0
    //----------------------------------------------------------
    if (!touch.begin(40, &Wire))
    { // pass in 'sensitivity' coefficient and I2C bus
        Serial.println("Couldn't start FT6206 touchscreen controller");
        while (1)
            delay(10);
    }
    Serial.println("Capacitive touchscreen started");
    Wire.setClock(400000UL); // I2C speed 400KHz
 
I'm currently implementing the filter graphics (LP, BP, HP) and functions on my TFT Display. It's a bit of a complex task, but my AI Fred is friendly and helps me a lot :cool:

20260704_175305.jpg

20260704_175431.jpg

20260704_175545.jpg
 
Last edited:
Back
Top