Page 1 of 1

Pitch Table Ideas

Posted: Wed Dec 30, 2009 3:41 pm
by neilbaldwin
First off, hope everyone had a good Christmas. I was away for the most of it and had very little internet access. On the upside, the time spent away from the internet made me more productive and NTRQ (my NES tracker) now has pretty much full audio. I'll put up a new video soon.

Anyway, aware that I've already posted about this in the past but I'm looking at it again to try to get a clever, low-impact solution for NTRQ.

It's to do with pitch sliding and vibrato and getting a uniform effect throughout the note range.

In Nijuu I had a massive table with 32 fractional steps in between each semi-tone for one complete octave and then multiplied/divided that value based on the octave. It worked really well but doing maths took it's toll on the CPU usage and I want to try to avoid that this time.

I was recently looking at some code for a C64 tracker and it had quite a neat little solution. In order to obtain the offset to add to the note frequency each frame, it used the current note number as an index to the period table but actually used the high-byte of the 16-bit note frequency setting as the low-byte of the per-frame pitch modification, setting the high-byte of the offset to 0 (mostly and simplified).

What this then gave you was a 16-bit offset value that was scaled depending on the note pitch. Luckily, on the C64, the frequency uses 16-bits as opposed to 11 on the NES so you have much better resolution.

I tried to copy the trick but I had to do so much bodging and massaging of the results (as you know the top 3 or 4 octaves of the NES has 0 as the high-byte so the resolution is very poor) that it became a bit silly (plus I had to have a third fraction byte as the offsets in the upper octaves were too coarse).

Another idea I had was to construct a second pitch table that was a table of 16-bit offsets to use for slide/vibrato. Currently I can't think of a clever way of calculating the values though.

Any thoughts?

Posted: Wed Dec 30, 2009 8:26 pm
by tepples
If you're not worried about CPU usage (and outside of games you probably aren't), you could try linear interpolation at runtime. Subtract two consecutive semitones' periods (whose difference will always fit in 8 bits on an NES) and then multiply the difference by the fractional pitch.

Posted: Wed Dec 30, 2009 9:13 pm
by tokumaru
Isn't anyone who makes sound engines thinking about making them usable in games? Most of the ones I've seen use too much CPU time!

I haven't done much music work on the NES, but maybe this is one of those things that have to be customized/optimized on a per-project basis (much like scrolling engines, map compression, things like that)?

Posted: Wed Dec 30, 2009 9:55 pm
by Celius
Well, for my NROM projects I cut out vibrato, because too much space is taken up for me to include it. I suppose I -could- have included it, but it was just a sacrifice I was willing to make. However, for other projects, I usually have a table of 11-bit entries for each pitch in the 12-tone scale that you store directly into the sound registers. Then for arpeggio, it's a little more complicated. I have a table that has the difference between each two notes in the pitch table divided by 32. By this, I mean like the value for NTSC to store into the sound registers is $7F1 for low A, and the value for the Bb right above that is $780. I take the difference of those two values ($71) and divide that by 32. That's one entry in the arpeggio table, and I do the same for the difference between Bb and B, B and C, C and Db, etc. all the way up to the highest necessary note. As you can see, $71 is 113 in decimal, and that divided by 32 is roughly 3.52. So I take that value, and multiply it by some value between 0 and 31 depending on how much I want to bend the pitch, and I subtract it from the 11-bit value of the next note down.

If this sounds confusing, it's probably because I'm bad at explaining things. But say we were talking about bending the low A to be halfway between the low A and the Bb above it. I would take the value in the arpeggio table that is the difference between those notes divided by 32 (3.52) and I would multiply it by 16, since I want to play an A and 1/2, or A and 16/32. So you take 3.52, multiply it by 16, and you get 56.32. Then you subtract that from 2033 ($7F1), and you get 1976.68, which in hex is about $7B8. That would be the value you store in the sound registers to play the pitch between a low A and the Bb above it.

For the arpeggio table, I keep track of the difference between each pitch divided by 32 with 8 bits. The high 2 most significant bits represent the value before the decimal point. Since each value never exceeds 4.00, I only need to use 2 bits to represent values 0-3 for the numbers left of the decimal point. Then the other bits are just precision bits. All together, the arpeggio table takes up about $5D or 93 bytes. The code to deal with it takes a bit more space, and it takes up more time to process. Sorry if this doesn't make any sense :). I'd be happy to try and clarify what I'm talking about if there's any confusion.

Posted: Wed Dec 30, 2009 10:23 pm
by tokumaru
Celius, it seems you do something similar to what tepples suggested, but you are limited to 32 units between notes. I don't know if having this table is worth it, because you need a multiplication anyway, just like in tepples' method which isn't limited to 32 units.

Posted: Wed Dec 30, 2009 10:58 pm
by Celius
Well you could probably do the same but have 64 units between each pitch. But to be honest, the difference between 1/32 and 2/32 pitchbend is quite inaudible. I don't know why you'd need more precision than that. But I guess you're right about the multiplication being too slow.

I believe I might have a possible solution. You take the arpeggio table idea, and you pre-calculate the desired entry times 1, 2, 3, 4, 5, up to however much you care to alter the pitch by. By pre-calculate, I mean you calculate them with multiplication routines before doing anything with pitch bending. You keep these results stored in RAM, and you add or subtract one of these results from the current pitch to alter it one fragment at a time. Kind of like how you were saying with scrolling and map decompression. Sometimes it's not optimal to calculate the resulting value from scratch. Rather just add a little at a time. This solution only has you doing additions and subtractions rather than multiplications every frame (well, you'll have to do them at least once, which is the down side. Plus it requires more RAM).

Re: Pitch Table Ideas

Posted: Thu Dec 31, 2009 12:56 am
by Drag
neilbaldwin wrote:[...] In order to obtain the offset to add to the note frequency each frame, it used the current note number as an index to the period table but actually used the high-byte of the 16-bit note frequency setting as the low-byte of the per-frame pitch modification, setting the high-byte of the offset to 0 (mostly and simplified).

[...] I tried to copy the trick but I had to do so much bodging and massaging of the results (as you know the top 3 or 4 octaves of the NES has 0 as the high-byte so the resolution is very poor) that it became a bit silly (plus I had to have a third fraction byte as the offsets in the upper octaves were too coarse).
The first thing that comes to my mind is to grab the 3 high bits of the period, then ROL some of the lower 8 bits into them. It would cost 7 CPU cycles per bit (if you use zeropage), but spending 14 cycles to achieve a 5-bit resolution instead of working with a 3-bit resolution doesn't sound so bad to me. :P

Better yet, it'd cost 21 cycles to get the highest 8 bits of the period, if you take the lowest byte and ROR the 3 high bits into them. I'm not entirely sure if that's what you need though.

Posted: Thu Dec 31, 2009 3:17 am
by neilbaldwin
Thanks everyone.

Celius: your difference-between-semitones-divided-by-32-table is probably close to what I was thinking. I might try a similar idea and tweak it a bit to suit. Just a general question: how does your method work if, say, you want to slide the note over several semitones or, say, an octave?

Drag: i actually tried this but I found that ROLing the 11 bits exaggerated the effect on lower pitches because of the non-linearity (obviously of course). It worked OK higher up the pitch range though. I then tried a convoluted method of ROLing just the low byte and then doing a ADC #$00 to the high byte (actually that's what I've got in the code right now). It kind of works but you don't really get a uniform(ish) effect in each octave.

Actually my current method is taking the current note's index into the pitch table, subtracting the slide setting (just an arbitrary number from $01 to $1f which denotes the speed, slow to fast, of the slide) which gives you an index that is $01 to $1f semitones lower than the current note. I then grab the pitch table values, put the low byte into the offset fraction byte and the high byte into the offset low byte.

This then gets added (or subtracted) per frame until the target pitch is reached. Actually it's slightly more complicated in the code but the idea is right.

Posted: Fri Jan 01, 2010 7:25 pm
by Celius
The problem with my current method is I could only bend a tone to be + or - 4 notes. You specify pitch bend with a signed byte, where $80 specifies no pitch change, and $60 would specify bending a note a whole note down. In the case where it bends beyond one semitone, I look at the pitch table for the nearest semitone to what I'm trying to bend to, and then I find the appropriate difference between those two semitones, and multiply that value by whatever and add/subtract it from the value of the nearest semitone.

Posted: Fri Jan 01, 2010 7:36 pm
by tepples
In a logarithmic pitch environment like MIDI, when you bend up or down farther than a couple semitones, you probably want to do it with a legato noteon so that the base note for the bend gets rewritten. General MIDI, for instance, appears to have a range of +/- 2 semitones for the pitch bend command.

Posted: Sat Jan 02, 2010 7:23 am
by Bregalad
Well I think both method of the table containing 32 entries per note (for 12 notes) and the method based on linear interpolation of a whole note table would work. I don't think the error of the linear interpolation could be noticeable by human hear. So, you'd have to figure which of both method is the fastest CPU wise and use it. I have no idea which one it will be, you'll have to try both.

Posted: Sat Jan 02, 2010 11:24 am
by neilbaldwin
I think I've got a pretty acceptable solution now.

I added a couple of fake octaves to the start of the frequency table (i.e. numbers bigger than $7FF) and then the code to get the offset is this;

Code: Select all

;
;In : slide speed, $00 to $1F. Lower numbers = slower slide
;Requires plyrSlideFrom and plyrSlideTo to be set before calling
;
getSlideAdd:
		sta plyrTemp00
		lda plyrSlideFrom		;subtract end note from start
		sec
		sbc plyrSlideTo
		sta plyrTemp01		;temp store for later compare		
		lda plyrSlideFrom		;use start note as index?
		asl plyrTemp01		;shift bit 7 into carry
		bcs @a			;carry set if sliding up
		lda plyrSlideTo		;so use destination note for table index
@a		clc
		adc #$0C			;shift up by 1 octave
		sec
		sbc plyrTemp00		;subtract slide setting value
		bpl @b
		lda #$00			;clamp at 0 if gone negative
@b		tax
		lda NTSC_noteLo,x		;set fraction to low byte of freq
		sta plyrSlideAddFrac
		lda NTSC_noteHi,x		;set low add to high byte of freq
		sta plyrSlideAddLo
		lda #$00			;clear other vars
		sta plyrSlideAddHi
		sta plyrSlideFrac
		sta plyrSlideLo
		sta plyrSlideHi
		rts
I've been playing around making some music in the tracker and it's actually pretty good. I'll put up a new video soon so you can hear.

Posted: Sat Jan 02, 2010 2:44 pm
by lidnariq
tepples wrote:General MIDI, for instance, appears to have a range of +/- 2 semitones for the pitch bend command.
But also specified in GM are so-called RPNs that can control that range -- even if many people don't know it. My commercial synth can be told to increase the range up to +/- 24 semitones.

Posted: Sat Jan 02, 2010 4:47 pm
by Drag
Ah, I think I completely missed what you were trying to do. Were you trying to get it so that the pitch slides themselves were linear, or were you trying to get it such that no matter what the source and destination pitches are, the slide itself always takes the same amount of time?

Posted: Sat Jan 02, 2010 9:34 pm
by neilbaldwin
Drag wrote:Ah, I think I completely missed what you were trying to do. Were you trying to get it so that the pitch slides themselves were linear, or were you trying to get it such that no matter what the source and destination pitches are, the slide itself always takes the same amount of time?
Probably more the latter, within reason.

Actually I was trying achieve a uniform-ish effect (slide/vibrato) despite the octave(s) you were working in.

I know it's always going to be a compromise but I wanted a compromise that was both simple(ish) ans musically nice to work with,