USB MIDI clock jitter with ILI9341 display calls -- use two Teensys?

Status
Not open for further replies.

graydetroit

Well-known member
Background:

I'm using the Teensy 4.1 to build a MIDI program change sequencer/orchestrator. I wanted a device that acts as the main clock source for various MIDI instruments, and I wanted a device that can sequence program change messages. Most existing sequencers do way more than that, so I thought this would be a good project for a Teensy.

All it really does is send MIDI clock and program change messages over USB MIDI to a max of 4 instruments connected to the Teensy via the USB Host port.

I'm also using a ILI9341 driven display module for the main UI display, specifically this one for now for prototyping: https://www.adafruit.com/product/1480

The rest of the build is just some switches/buttons.

Here's a link to my unfinished code.

Here's a photo of the prototype so far. All the wiring is underneath the perfboard, but I don't think the wiring is the problem.

Here's a video of the MIDI clock working as expected, with no jitter, with no calls to update the display during playback.

Problem 1:

I have some code in XRSequencer.cpp, which gets executed during the main loop if the sequencer is running:

Code:
[COLOR=#2838B0][I]void[/I][/COLOR] Sequencer[COLOR=#666666]:[/COLOR][COLOR=#666666]:[/COLOR]doStep[COLOR=#888888]([/COLOR][COLOR=#888888])[/COLOR]
[COLOR=#888888]{[/COLOR]
    [COLOR=#2838B0]for[/COLOR] [COLOR=#888888]([/COLOR][COLOR=#2838B0]auto[/COLOR] [COLOR=#666666]&[/COLOR][COLOR=#289870]track[/COLOR] [COLOR=#888888]:[/COLOR] [COLOR=#2838B0]this[/COLOR][COLOR=#666666]-[/COLOR][COLOR=#666666]>[/COLOR]instrumentTracks[COLOR=#888888])[/COLOR] [COLOR=#888888]{[/COLOR]
        [COLOR=#2838B0]if[/COLOR] [COLOR=#888888]([/COLOR][COLOR=#666666]![/COLOR]track[COLOR=#666666]-[/COLOR][COLOR=#666666]>[/COLOR]deviceActive [COLOR=#666666]|[/COLOR][COLOR=#666666]|[/COLOR] [COLOR=#666666]![/COLOR]track[COLOR=#666666]-[/COLOR][COLOR=#666666]>[/COLOR]trackActive[COLOR=#888888])[/COLOR] [COLOR=#888888]{[/COLOR]
            [COLOR=#2838B0]return[/COLOR][COLOR=#888888];[/COLOR]
        [COLOR=#888888]}[/COLOR]

        [COLOR=#2838B0][I]bool[/I][/COLOR] isProgChanging [COLOR=#666666]=[/COLOR] track[COLOR=#666666]-[/COLOR][COLOR=#666666]>[/COLOR]doStep[COLOR=#888888]([/COLOR][COLOR=#2838B0]this[/COLOR][COLOR=#666666]-[/COLOR][COLOR=#666666]>[/COLOR]usb[COLOR=#888888])[/COLOR][COLOR=#888888];[/COLOR]

        [COLOR=#2838B0]if[/COLOR] [COLOR=#888888]([/COLOR][COLOR=#2838B0]this[/COLOR][COLOR=#666666]-[/COLOR][COLOR=#666666]>[/COLOR]hardStart [COLOR=#666666]|[/COLOR][COLOR=#666666]|[/COLOR] isProgChanging[COLOR=#888888])[/COLOR] [COLOR=#888888]{[/COLOR]

            [COLOR=#888888][I]// EXPERIMENTAL, highlight program index indicator on step
[/I][/COLOR][COLOR=#888888][I]//            this->display->drawMainScreen(
[/I][/COLOR][COLOR=#888888][I]//                this->currInstrument,
[/I][/COLOR][COLOR=#888888][I]//                this->instrumentTracks,
[/I][/COLOR][COLOR=#888888][I]//                true,
[/I][/COLOR][COLOR=#888888][I]//                this->tempo,
[/I][/COLOR][COLOR=#888888][I]//                this->currCursoredProgramIdx,
[/I][/COLOR][COLOR=#888888][I]//                this->cursoringPrograms,
[/I][/COLOR][COLOR=#888888][I]//                true
[/I][/COLOR][COLOR=#888888][I]//            );
[/I][/COLOR]        [COLOR=#888888]}[/COLOR]
    [COLOR=#888888]}[/COLOR]
[COLOR=#888888]}[/COLOR]

when the commented out part is uncommented, that portion of the code makes calls to update the display while the internal sequencer is running / sending MIDI messages. In this example, the calls to update the display occur on every completed bar (isProgChanging), since this code updates the display to indicate which program is currently playing.

I notice some jitter in the clock. The instruments begin to sound disjointed, like every display update is introducing some delay time to the main sequencer update loop.

See this video for an example of this problem.

Problem 2:

There's another area of the code that I modified to try and allow setting the tempo while sequencer is running, but which causes the clock to get all messed up, more extreme than the previous example. I think the reason it's so messed up is because every time I push the up/down buttons to set the tempo, the display is being updated, causing the clock timing to get messed up somehow.

This is that portion of the code in XRSequencer.cpp, but is currently limited to being executed if the sequencer is not running, so as to not cause this issue:
Code:
if (upBtnPressed) {
    this->tempo++;
    this->tempo = min((int)this->tempo, 240);


    this->display->drawTempoScreen(this->tempo);


    this->doSetTempo();
} else if (downBtnPressed) {
    this->tempo--;
    this->tempo = max((int)this->tempo, 60);


    this->display->drawTempoScreen(this->tempo);


    this->doSetTempo();
}


See this video for an example of this problem.

Solution?

This could be a code issue, but my thought is, what I just offloaded the display processing to a separate Teensy, so that the MIDI processing isn't hindered by the display processing? I'd love to not have to do that, but I'm not sure what else in the code could be causing the MIDI jitter like this.

Thanks in advance!
 
@KurtE has developed a DMA based update for the ILI9341 - it might be this with "Asynchronous Update support (Frame buffer)" :: github.com/KurtE/ILI9341_t3n

That takes a memory buffer for the screen - but the DMA update may run without delay or other issues
 
Thanks -- I'm not familiar with DMA, but on reading about it, looks like it may work for what I need. Just not really sure how to exactly implement it. The examples in KurtE's ILI9341 repository seem a bit complex, it'll take me a bit to wrap my head around it I think.
 
Perhaps run an example to see it work.
Then following an example to swap the library and instantiation, the change would come with updates to the buffer - then kicking off the DMA driven update - that will return while the screen is updated.
 
I think Problem #2 is actually not display-related. It has to do with changing the step length while the sequencer is running, not sure why it freaks out though. I may just not allow the tempo to be adjusted while the sequencer is running.
 
I eked out a little bit more flexibility by using a coroutine library. I didn't want to try and implement a fully fledged RTOS or multi-threading just yet, but now I'm at least able to push relatively infrequent updates to the display while the sequencer is running without the internal clock stuttering. The DMA approach didn't seem to reduce the load / blocking at all, and it introduced nasty looking tearing in the screen on draw calls.

I'd love to swap the current MIDI/internal clock code out with this eventually: https://github.com/midilab/uClock

But that library uses some core AVR stuff for the timing that the Teensy doesn't have, and I'm not well versed in the lower level timing stuff enough to try and port it over to the Teensy.
 
Status
Not open for further replies.
Back
Top