Skip to content

Instantly share code, notes, and snippets.

@loveemu
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save loveemu/98fa3bb0daa8fcce4fb6 to your computer and use it in GitHub Desktop.
Save loveemu/98fa3bb0daa8fcce4fb6 to your computer and use it in GitHub Desktop.
Super Mario World (Earlier N-SPC): Volume Calculation
-- Lua 5.1: Super Mario World (Earlier N-SPC): Volume Calculation
-- Note that I do not handle a few things like tremolo
-- Also, note that it is somewhat different from modern N-SPC.
-- 1270:
local velocity_table = {
0x08, 0x12, 0x1b, 0x24, 0x2c, 0x35, 0x3e, 0x47,
0x51, 0x5a, 0x62, 0x6b, 0x7d, 0x8f, 0xa1, 0xb3
}
-- 1280:
local pan_table = {
0x00, 0x01, 0x03, 0x07, 0x0d, 0x15, 0x1e, 0x29,
0x34, 0x42, 0x51, 0x5e, 0x67, 0x6e, 0x73, 0x77,
0x7a, 0x7c, 0x7d, 0x7e, 0x7f
}
local channel_volume = 255 -- $0241+x (vcmd e7): range (0..255)
local master_volume = 230 -- $57 (vcmd e0): range (0..255)
local note_velocity = 14 -- low nybble of the note byte: range (0..15)
local pan = 15 -- $0281+x (vcmd db): range (0..20)
-- Parse options if they are given
if #arg > 0 then
if #arg == 4 then
channel_volume = tonumber(arg[1])
master_volume = tonumber(arg[2])
note_velocity = tonumber(arg[3])
pan = tonumber(arg[4])
else
print("Option: [channel_volume] [master_volume] [note_velocity] [pan]")
return 1
end
end
-- See $1036-1074 and $122d-125b of SMW SPC700 ASM
-- if you want to see the "real" implementation.
-- Kill surround bits just in case
pan = pan % 32
-- Note that Lua Array starts from index 1
local note_volume = velocity_table[1 + note_velocity] -- $0211+x
-- In actual driver, pan value is stored into a 16-bit variable,
-- and there is a linear-interpolation procedure at the pan rate calculation.
-- This script does not care about that. All pan values are 8-bit here.
local pan_rate_l = pan_table[1 + pan]
local pan_rate_r = pan_table[1 + (20 - pan)]
local final_volume = 255 -- tremolo will decrease this value
final_volume = math.floor(final_volume * note_volume / 256)
final_volume = math.floor(final_volume * channel_volume / 256)
final_volume = math.floor(final_volume * master_volume / 256)
final_volume = math.floor(final_volume * final_volume / 256)
local reg_vol_l = math.floor(final_volume * pan_rate_l / 256)
local reg_vol_r = math.floor(final_volume * pan_rate_r / 256)
print(string.format("- Channel Volume: %d", channel_volume))
print(string.format("- Master Volume: %d", master_volume))
print(string.format("- Note Volume: %d (velocity %d)", note_volume, note_velocity))
print(string.format("- Pan: %d of 20 -> L %d (%.2f %%), R %d (%.2f %%)", pan, pan_rate_l, pan_rate_l / 128.0, pan_rate_r, pan_rate_r / 128.0))
print()
-- This should be what you can see in the register view of SNES SPC700 Player
print(string.format("- VOL(L)=$%02x", reg_vol_l))
print(string.format("- VOL(R)=$%02x", reg_vol_r))
-- Well, for decibel lovers...
-- The real output will be affected by ADSR settings, of course
local left_volume_decibel = 20 * math.log10(reg_vol_l / 128.0)
local right_volume_decibel = 20 * math.log10(reg_vol_r / 128.0)
print(string.format("- Left: %.2f dB, Right: %.2f dB", left_volume_decibel, right_volume_decibel))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment