Portamento Routine

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Portamento Routine
by on (#32992)
How do you guys do musical calculations, such as portamento, for low-level music routines?

My thought would be to create tables containing values, as you would for physics data (parabolic gravity, etc). But that is alot of data.

Edit: Removed incorrect portamento formula

by on (#33005)
So, first, to make sure we're talking about the same thing -- in tracked music, portamento is the process of sliding to a note from whereever we are.

In MODs, there are two kinds of portamento, depending on hardware capabilities -- impulse tracker calls them "amiga" and "linear" slides. Despite the name, the linear slides aren't linear (they're exponential).

So-called "amiga slides", used by Screamtracker 3, Fasttracker 2, and all the original trackers for the Amiga, adjust the period (1/frequency) by X per time interval, until it's arrived at the desired note. It happens to be the case that, in the middle of the range in tracked music (C2 in a MOD, C4 in ST3, C5 in impulse tracker), a speed of 0x10 corresponds to one half-step per time interval. (However, an octave above, it's one whole step per time, and an octave below, it's one quarter step per time)

The math for the Amiga slides is easy -- just something like
Code:
if (target==current) return;

if (target<current)
 if (target<current-speed)
  current-=speed;
 else
  current=target;
else
 if (target>current+speed)
  current+=speed;
 else
  current=target;
and best off, it doesn't require multiply or divide which require tricks or external hardware on the NES.

"Linear slides", used by Impulse Tracker, guarantee this correlation over the entire range, so in an appropriately configured IT song, 0x10 is always one half step per time. They are also supported by MIDI and the NES's pitch slide hardware (albeit with a different conversion)

The NES supports pitch slides on the square wave channels, http://nesdevwiki.org/index.php/APU_Sweep.

You could do something similar to what the NES does, like
Code:
if (!--cycle) {
  current = current - (current>>amount) + (target>>amount);
  cycle=reloadvalue;
}
although this specific code would have problems with integer math, and the 6502 doesn't provide a particularly good way to do a variable shifts.

Or you could instead store all your current state of pitch (as i briefly entertained but decided was overkill in the end) as 8.8 MIDI-style fixed point numbers (where 60.0=C4, 60.5 is halfway between C4 and C#4, &c). In this case, you'd do your portamento directly in the MIDI-note (expontential) domain, and just convert to the NES's period hardware as necessary.

by on (#33006)
lidnariq wrote:
the 6502 doesn't provide a particularly good way to do a variable shifts.

You could use a variant on Duff's device. Load part of ROM with 'lsr a; lsr a; lsr a; lsr a; lsr a; lsr a; lsr a; lsr a; rts', compute a vector into that part in RAM, and then JMP indirect through the vector. Sure, it's not as fast as an ARM barrel shifter, but it's only 2 cycles per bit.

lidnariq wrote:
Or you could instead store all your current state of pitch (as i briefly entertained but decided was overkill in the end) as 8.8 MIDI-style fixed point numbers (where 60.0=C4, 60.5 is halfway between C4 and C#4, &c). In this case, you'd do your portamento directly in the MIDI-note (expontential) domain, and just convert to the NES's period hardware as necessary.

I've considered doing this. But for each channel, how many cycles would it take to convert "MIDI note 60 + 128/256" to a period?

by on (#33007)
I could easily do this in my newest sound engine, which allows me to distort each pitch value by x/32 of a note. I would have to pre-define the note envelope that distorts the note to sweep up to the desired note, though. I suppose I could someday add a routine that will calculate this in real-time.

by on (#33009)
Quote:
and best off, it doesn't require multiply or divide which require tricks or external hardware on the NES.

This is completley wrong, and I've decided to fight against that stupid idea that is arround the boards for some time. Mulitply and divide is VERY EASY to do in assembly, all it requires it writing a VERY SIMPLE loop that shifts and conditionally add/substract numbers. But this isn't the point here.

Since note frequencies are exponential, if you just modify the pitch registers linearly, as shown on the code the other guy posted, it will sound fine, but the slide won't sound linear, it will sound exponential (growing slow down and fast up) because of the exponential nature of the period you control. However, if you write the registers logarithmically, it will compensate the exponential frequency, and sound linear. Some other hardware, like the SNES, gives control over pitch instead of period, so it's the other way arround. To clear it up, if you increase the pitch linearly on the following system :
- Period control (NES, etc...) : First going slow then going fast
- Note control (MIDI syntetisers, ...) : Going up at a constant speed
- Frequency control (SNES, ....) : First going fast then going slow

Computing exponential values isn't exactly trival in assembly (at least not without floating point or tables), but it's easy to pass trough a recursive procedure that will have an exponential result : Take a number and add/substract one faction of itself regularly, then it will grow/decrease exponentially. For exemple if you take the pitch, shift it right 4 times and add it to itself, the result is a multiply by 17/16, and a recursive multiply by 17/16 is expoential base 17/16. This is basically how hardware sweep works on the NES.

Another solution (that I've used) is to simply make your sound routine with note control (and fractional note controls) and then convert pitch from note to period using an logarithmic period table for each note fraction (you only need to do an octave, and can do the others with shifts).

by on (#33010)
tepples wrote:
lidnariq wrote:
Or you could instead store all your current state of pitch (as i briefly entertained but decided was overkill in the end) as 8.8 MIDI-style fixed point numbers (where 60.0=C4, 60.5 is halfway between C4 and C#4, &c). In this case, you'd do your portamento directly in the MIDI-note (expontential) domain, and just convert to the NES's period hardware as necessary.

I've considered doing this. But for each channel, how many cycles would it take to convert "MIDI note 60 + 128/256" to a period?
Here's one (straightforward) attempt. I'm assuming that we've got tables for the note periods (period_lo, period_hi) as well as the absolute difference in pitch between two consecutive notes (delta).
Code:
;; x = note
;; a = 8-bit fraction
;; 43-45 cycles

lda delta,x
sta square_pos+0
eor #$ff
sta square_neg+1

lda period_lo,x
clc
adc (square_neg),y
sec
sbc (square_pos),y
sta $4002

lda period_hi,x
sbc #$00
sta $4003

;; square_pos[$000..$1ff] = i^2/1024
;; square_neg[$000..$1ff] = (i-$ff)^2/1024

This might be off by one since we don't take the fractions of the squares into account. This method extends naturally to more reasonable table sizes too if you cut a bit or two off of the fraction, just change the EOR constant and negative table base from $ff to your new mask.

by on (#33011)
Bregalad wrote:
Quote:
and best off, it doesn't require multiply or divide which require tricks or external hardware on the NES.

This is completley wrong, and I've decided to fight against that stupid idea that is around the boards for some time. Multiply and divide is VERY EASY to do in assembly, all it requires it writing a VERY SIMPLE loop that shifts and conditionally add/subtract numbers. But this isn't the point here.


Sorry. I'm still much more used to other instructions sets (some with hardware multiply, some without, all with less diverse addressing modes), and one of the mantras i learned was "use bitshifts if you can instead", so it's hard to get over that.

doynax wrote:
This might be off by one since we don't take the fractions of the squares into account. This method extends naturally to more reasonable table sizes too if you cut a bit or two off of the fraction, just change the EOR constant and negative table base from $ff to your new mask.


In fact, IT modules only support granularity to 64/halfstep (and 10 octaves), so a 7.6 fixed point number would be sufficient -- and the precision of the NES's sound hardware would make the 64/halfstep only relevant for the bottom 3 or 4 octaves (of the 8 the hardware supports).

The physical hardware of the NES can't support more than 115/halfstep. (one half step above lowest possible pitch, period 2047, is 1932, or 115 per halfstep)

(I actually stopped pursuing the fixed point MIDI method because MODs and S3Ms only use amiga slides anyway)

MIDI, on the other hand, specifies support for up to 8192/halfstep ('master fine tune', or 819200/halfstep in the really defective case where the pitch wheel range is set to 1 cent)), but I don't know what proportion of hardware follows it that precisely. My (decade old) Korg X5 truncates it to cents.

by on (#33012)
I've implemented the 8.8 approach, works rather nicely, though it certainly isn't fast. I think the cycle count came out to something around 200-300ish per channel you're doing it to, on top of any other work you might be doing.