Music engine limitations - tempos

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Music engine limitations - tempos
by on (#50097)
Well, I had been noticing that the music with the music engine I have has been doing things like having notes be delayed at random intervals. I found that this was due to the tempo not being a power of 2...

The best example I can give is a 150 tempo song, which ended up translating into this with the engine:
Tempo of $55 and 32 beats = one measure...

These songs would skip fairly often and it was annoying, so I went ahead and made these changes:
Tempo of $80 and 48 beats = one measure. This has no skipping, and that's great. These settings also have the desired tempo of 150.

My problem lies with having both tempo control and skipping avoidance... I can use weird numbers for the tempo control and hope no one notices TOO MUCH, but I'm wondering if there's a better way...

by on (#50113)
I use fixed point for the note delays. So if I wanted a tempo of 140 bpm on a PAL machine, that would make each quarter note 21.42857 frames, or 5485 in 16.8 fixed point.

by on (#50115)
Basically, the way the engine works:
There's a counter. Each song has a constant, TEMPO.
Every time the music code runs, add TEMPO to this counter. If this counter overflows, have the music run one step (clock the note timers, advance to next note if necessary, run commands).

For tempos that are a power of 2, this is no problem, because the counter will always overflow after an equal amount of frames. However, when you have an arbitrary tempo, like $55, the counter will sometimes overflow one frame sooner or later than the rest of the time. So for example, if you play a bunch of 32nd notes with an "uneven" tempo, some of those notes will sound shorter or longer than the rest.

This was the only way I could think of to grant some "fine tuning" control to get the exact tempo desired, but I can't get around the fact that a lot of the tempos are uneven. :P

Though, Sivak, I see what's happening with the $55 tempo, it's the equivalent of saying "3.3333333". In my engine, $55 will clock the song every 3 frames, but what ends up happening is this:
55 -> AA -> FF -> 54 -> A9 -> FE -> 53 -> ...

Until this happens:
... -> 02 -> 57 -> AC -> 01 -> 56 -> AB -> 00 -> 55 -> AA -> FF -> 54

You see it takes 3 additions to overflow the counter, until it rolls over to $00, in which it takes 4 to overflow the counter, then it goes back to 3.

If anyone could suggest a better way to handle tempos, Sivak and I would greatly appreciate it. :P

by on (#50117)
Unless you want to use scanline IRQs to trigger new notes mid-frame to be able to actually have notes that are (almost) 3.33333... frames long I don't see how you would get around this. Some notes will have to be one frame longer than the others, depending on the tempo used. And besides, even on a PAL machine 1 frame is only 20ms, so it's not a big issue IMO, even for 32nd notes.

by on (#50118)
Drag wrote:
If anyone could suggest a better way to handle tempos, Sivak and I would greatly appreciate it. :P


That seems acceptable to me, but I'd be interested in hearing any other ideas also.

Seems like the only way to get a "clean" tempo is to make it purely frame-based, but then it goes straight to hell anyways once you do a PAL/NTSC conversion. I know with NT2 for example, it's native to PAL mode (and doesn't have these fancy fixed-point tempo controls), so when playing in NTSC it just skips every 6th frame! At least one NT2 user, Chibi-Tech, hacked the replay engine to be NTSC-native (no frame skipping). So his tunes actually play slower in the tracker. So I just don't see a way around it, either limiting the tempos to certain speeds, or just living with timing oddities. All my songs have this timing discrepancy throughout the whole thing, but did anyone ever notice?

BTW, the $55 overflow trick is what I use for scanline timing to get the coveted .666 cycles (combined with a "branch to nowhere").

by on (#50124)
Drag wrote:
Basically, the way the engine works:
There's a counter. Each song has a constant, TEMPO.
Every time the music code runs, add TEMPO to this counter. If this counter overflows, have the music run one step (clock the note timers, advance to next note if necessary, run commands).

For tempos that are a power of 2, this is no problem, because the counter will always overflow after an equal amount of frames. However, when you have an arbitrary tempo, like $55, the counter will sometimes overflow one frame sooner or later than the rest of the time. So for example, if you play a bunch of 32nd notes with an "uneven" tempo, some of those notes will sound shorter or longer than the rest.

My music engine does something similar. Note durations are measured in rows (analogous to your "steps"), which can be an 8th note, 8th note triplet, 16th note, 32nd note, etc. depending on the song, and tempos are measured in rows per minute. On each frame, the engine adds the 16-bit tempo to a 16-bit counter mod 3000 (PAL) or mod 3606 (NTSC), and if there is an overflow, plays the next row.

Notes are supposed to be delayed at random intervals. This will inevitably happen with the 3606 frames per minute of NTSC, but human musicians delay notes even worse sometimes, so it isn't too hard to just live with it.

Memblers wrote:
At least one NT2 user, Chibi-Tech, hacked the replay engine to be NTSC-native (no frame skipping).

I did something in between for my NT2-based NSFs: hack the NT2 engine to skip a frame every x frames, allowing me to specify any tempo as 150 * x/(x + 1).

by on (#50145)
I'm working on something similar on my AVR chip: each quarter note is divided by 96 ( so it's 96 Pulses Per Quarter, or 96 PPQ ) so the tempo is just the number of PPQ per frame, and that number is in the format 8.8 (16 bit fixed point). Of course, you get uneven note lenghts, but that's fine because it will be unnoticeable. (Hell, use strange tempos in FamiTracker and play 'even' notes and they will sound even, even though it isn't!)

by on (#50151)
~J-@D!~ wrote:
Of course, you get uneven note lenghts, but that's fine because it will be unnoticeable. (Hell, use strange tempos in FamiTracker and play 'even' notes and they will sound even, even though it isn't!)


Yeah, but I'm wondering why it's so noticable in my engine, where it isn't in others. o_O