-
Notifications
You must be signed in to change notification settings - Fork 27
Hi!
Recently tried matching that BPM clock up (in a recording, without connecting USB MIDI), and noticed it seems to drift (think it was a little fast).
Any recommended solution? Is the third party MIDI library causing that, or is there some hw_timer or other config calculation that should be adjusted?
Thanks in advance for recommending a proper solution! (or confirming it's because of a 3rd party library)
All reactions
Replies: 14 comments 18 replies
Wondering if maybe millis() is incorrectly faster than it should be. Maybe some issue with the ESP32 Arduino library?
Saying that because of this:
'/* If MIDI is playing, then check for tick /
if (midi_playing && (now - last_midi_tick) >= midi_tick_ms) {
OMNI_do_midi_tick();
last_midi_tick += midi_tick_ms;
if (last_midi_tick < now) {
/ we are at least one tick late, give up */
last_midi_tick = now;
}
}
}'
All reactions
BPM seems generally accurate at 50 BPM, 100 BPM, and 150 BPM (although didn't run it very long), and 110 BPM seems to actually be about 110.3 BPM, and 120 BPM seems to be about 121 BPM. 130 BPM seems to be about 131.6 BPM. 140 BPM seems to be about 141.5 BPM.
Surprisingly 150 BPM seems to be 150 BPM.
160 BPM seems to be about 163 BPM. 170 BPM seems to be about 170.45.
All reactions
By the way, I did change this, so maybe that caused it?:
#define MIDI_TICKS_PER_16TH 2
All reactions
Seems to maybe be a rounding error. Would using microseconds instead of milliseconds for midi_tick_ms solve that?
All reactions
Yes this should do better. Also some averaging may improve the issue
All reactions
Thanks! Planning to try that. Hopefully midi_tick_ms isn't used too many places.
All reactions
Tried it. Milliseconds edition makes BPM slower instead of faster...surprising.
What averaging is recommendable?
Had considered making a LUT (with average vals between each estimated actual BPM), but that seems a horrible workaround.
All reactions
101 BPM seems to about 100.595 BPM now. 121 BPM seems to be 120.79 BPM. Maybe I should make sure the floats are being converted to ints also, although haven't examined the logic enough to see if it's making the bpm from float decimals somehow.
All reactions
As i remember, now it is waiting for timer value to overcome 1 ms and then calls some onTick() method and resets the 1 ms counter. The right action instead would be adding 1000 us for the next triggering value, this would compensate introduced errors when it triggers not at 1000us exactly, but at 1005 or any other "greater or equal than 1000 value", so correct version would set next at 2000, not at 2005.
All reactions
Ah, OK! I can try that then. Thanks for explaining!
All reactions
Currently it's:
const float tick_coef = 1000000ul * 15 / MIDI_TICKS_PER_16TH;
//-----------
static unsigned long midi_tick_ms = tick_coef / bpm;
inline void set_bpm(float newBpm) {
bpm = newBpm;
midi_tick_ms = tick_coef / newBpm;
}'
'now = micros();
/* If MIDI is playing, then check for tick */
if (midi_playing && (now - last_midi_tick) >= midi_tick_ms) {
do_midi_tick();
last_midi_tick += midi_tick_ms;
if (last_midi_tick < now) {
/* we are at least one tick late, give up */
last_midi_tick = now;
}
}
}
Talking about that code for that 1 millsecond adjustment? '/* we are at least one tick late, give up */ last_midi_tick = now; '
All reactions
The code seems to be correct, except for this guard
if (last_midi_tick < now) {
/ we are at least one tick late, give up */
last_midi_tick = now;
}
But i am not sure that it is executed ever. Maybe the micros itself are faulty.. dunno
All reactions
Try this version of the guard
while (last_midi_tick < now) {
// we are at least one tick late, rush now
last_midi_tick += midi_tick_ms;
}
All reactions
Oh, thanks! I can try that. Actually had spent hours doing a manual adjustment trying different vals of tick_coef.
All reactions
Tick coef is nothing misterious, but [microseconds in 1 second] ×ばつ [60 seconds in a minute] / [4 quarters notes in a bar] / [ticks in a 16th note]
Where 60/4 gives 15
Then later we use coef/bpm, we get micros/tick value
All reactions
Ah, OK. I was adjusting it manually to compensate for whatever offset was happening.
Tried that while loop and it simply produced noise. (I guess because it was blocking the CPU during that while loop)
All reactions
It is strange, plz check the code from the post on web, not from email. Or try this one.
if (midi_playing && (now - last_midi_tick) >= midi_tick_ms) {
do_midi_tick();
while (last_midi_tick < now) {
last_midi_tick += midi_tick_ms;
}
}
All reactions
Thanks! I can try that now. 'const float tick_coef = 7477800;' is generally "close enough" (2 MIDI_TICKS_PER_16TH) with the original code modded to microseconds.
All reactions
Tried that code and it also "blew up" basically. So using the 'const float tick_coef = 7477800;' for now.
Thanks for attempting though!
All reactions
After about 5 hours or more, I found that 'const float tick_coef = 7477800;' is generally "close enough" (2 MIDI_TICKS_PER_16TH).
I can try 'if (midi_playing && (now - last_midi_tick) >= midi_tick_ms) {
do_midi_tick();
while (last_midi_tick < now) {
last_midi_tick += midi_tick_ms;
}
}' next.
All reactions
By the way, tried adding single "flam" (adding a second drum sound by using that 2 MIDI_TICKS_PER_16TH), but it seems that was too much for the CPU to handle. I may make a more basic sample player to see if that makes it better.
Recommend any open source simple sample players (with MIDI) for playing an array of those uint_8t hexadecimal Mono 16 bit PCM samples? Guess it wouldn't be difficult to make one, but adding the MIDI part looks excessive to do if there is already a good one that is efficient.
Think there is a sample player in AudioKit, so may look at that to see if it is C or C++ under the Swift code.
All reactions
Sample player used is quite simple, I don't think you 're gonna gain a lot changing it.
Actually, I don't think it's a CPU problem, it's more like simplified sequencer logics crashes on modification attempts, cause there are a lot of unwanted couplings, hard-coded things and weak assumptions..
All reactions
Ah, it wasn't crashing, it plays the drum sounds between, although the timing (or maybe something else) sounded a little off.
May look at that again later now that the BPM timing issues were generally (close enough for most anyway) solved.
All reactions
For me this version works with no glitches, but I don't know how accurate it is.
void run_tick() {
now = micros();
button_divider++;
if (button_divider >= 4) {
for (int i = 0; i < ButLast; i++) {
read_button(&buttons[i]);
}
run_ui();
button_divider = 0;
}
// If MIDI is playing, then check for tick
if (midi_playing && (now - last_midi_tick) >= midi_tick_ms) {
do_midi_tick();
// sync in case we are late
while (last_midi_tick < now) {
last_midi_tick += midi_tick_ms;
}
}
}
All reactions
Thanks! Interesting code, using that while. Why would it (how could it have?) have missed a midi_tick? Obviously eliminating that possibility of a missed midi_tick would solve any timing issues seemingly.
All reactions
Because before this mod, in case when we are late, it forced last_midi_tick = now;, which is not mathematically correct, cause it zeroes the phase instead of wrapping it.
Note that the clause (now - last_midi_tick) >= midi_tick_ms doesn't check for the number of triggered ticks, just fires if >=, so if the CPU 's been busy, it could have missed some ticks theoretically. So this is the guard to stay in sync.
All the other code seems to be correct, so maybe internal micros counter is slightly faulty - I've never checked how accurate it is.
All reactions
Saw now. I tried it and it did kind of a double note noisy thing (I use 2 MIDI_TICKS_PER_16TH, so maybe that is why).
All reactions
They say, the internal quartz is quite reliable to expect +/-3.6 seconds per hour.
All reactions
Oh, that's pretty good. I may look at the code again later to see what I modified that caused that to maybe not be compatible code.
All reactions
By the way, tried the drum roll code again today after the BPM was adjusted to base on milliseconds and using that adjusted coef I posted before, and it thankfully sounds good now.
Sadly the bass drum sound still doesn't play occasionally, seemingly because it's a longer array.
Anyway, here is that simple flam code for inside of do_midi_tick():
//EXPERIMENTAL DOUBLE C_HAT:
if (midi_tick == 1){
instr_noteon(3,45,0,0);
}
All reactions
Obviously that only makes a double sound when there are already 16 c_hats programmed.
Now I may add a snare drum also.
All reactions
I want to add synth shuffle also, but eliminating that random missing bass drum sound (and adding proper rolling) is higher priority.