Last active
August 29, 2015 14:08
-
-
Save loveemu/98fa3bb0daa8fcce4fb6 to your computer and use it in GitHub Desktop.
Super Mario World (Earlier N-SPC): Volume Calculation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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