Files

1503 lines
49 KiB
Lua

-----------------------
-- Name: Piano Roll (NES)
-- Author: zeta0134
-- Based on: https://github.com/zeta0134/mesen-piano-roll
-----------------------
local consoleType = emu.getState()["consoleType"]
if consoleType ~= "Nes" then
emu.displayMessage("Script", "This script only works on the NES.")
return
end
local NTSC_CPU_FREQUENCY = 1.789773 * 1024.0 * 1024.0
local LOWEST_NOTE_FREQ = 55.0 -- ~A0
local HIGHEST_NOTE_FREQ = 4434.922 -- ~C#8
local PIANO_ROLL_WIDTH = 240
local PIANO_ROLL_KEYS = 76
local PIANO_ROLL_KEY_HEIGHT = 2
local PIANO_ROLL_HEIGHT = PIANO_ROLL_KEYS * PIANO_ROLL_KEY_HEIGHT
local NOISE_KEY_HEIGHT = 2
local NOISE_ROLL_HEIGHT = NOISE_KEY_HEIGHT * 16
local NOISE_ROLL_OFFSET = 200
local SQUARE1_COLORS = {0xFFA0A0, 0xFF40FF, 0xFF4040}
SQUARE1_COLORS[4] = SQUARE1_COLORS[2]
local SQUARE2_COLORS = {0xFFE0A0, 0xFFC040, 0xFFFF40}
SQUARE2_COLORS[4] = SQUARE2_COLORS[2]
local TRIANGLE_COLORS = {0x40FF40}
local NOISE_COLORS = {0x4040FF, 0x40FFFF}
local DMC_BASE_COLOR = 0x806040A0
local DMC_PLAYING_COLOR = 0x8040C0
local DMC_OFFSET = 178
local DMC_HEIGHT = 19
local BACKGROUND_COLOR = 0x80000000
local PIANO_STRING_BLACK_COLOR = 0x80101010
local PIANO_STRING_WHITE_COLOR = 0x80060606
local NOISE_STRING_BLACK_COLOR = 0x80060606
local NOISE_STRING_WHITE_COLOR = 0x800A0A0A
local settings = {
background="solid"
}
local shadow_apu = {}
for address = 0x4000, 0x4017 do
shadow_apu[address] = 0
end
local shadow_triangle = {}
shadow_triangle.lc_reload = false
shadow_triangle.lc_value = 0
function clock_linear_counter()
if shadow_triangle.lc_reload then
shadow_triangle.lc_value = shadow_apu[0x4008] & 0x7F
else
if shadow_triangle.lc_value > 0 then
shadow_triangle.lc_value = shadow_triangle.lc_value - 1
end
end
if shadow_apu[0x4008] & 0x80 == 0 then
shadow_triangle.lc_reload = false
end
end
function clock_shadow_frame_sequencer()
clock_linear_counter()
clock_linear_counter()
clock_linear_counter()
clock_linear_counter()
end
function apu_register_write(address, value)
shadow_apu[address] = value
if address == 0x400B then
shadow_triangle.lc_reload = true
end
end
emu.addEventCallback(clock_shadow_frame_sequencer, emu.eventType.endFrame)
emu.addMemoryCallback(apu_register_write, emu.callbackType.write, 0x4000 , 0x4017)
function set_background()
if settings.background == "solid" then
BACKGROUND_COLOR = 0x000000
PIANO_STRING_BLACK_COLOR = 0x101010
PIANO_STRING_WHITE_COLOR = 0x060606
NOISE_STRING_BLACK_COLOR = 0x060606
NOISE_STRING_WHITE_COLOR = 0x0A0A0A
DMC_BASE_COLOR = 0x006040A0
end
if settings.background == "transparent" then
BACKGROUND_COLOR = 0x80000000
PIANO_STRING_BLACK_COLOR = 0x80101010
PIANO_STRING_WHITE_COLOR = 0x80060606
NOISE_STRING_BLACK_COLOR = 0x80060606
NOISE_STRING_WHITE_COLOR = 0x800A0A0A
DMC_BASE_COLOR = 0x806040A0
end
if settings.background == "clear" then
BACKGROUND_COLOR = 0xFF000000
PIANO_STRING_BLACK_COLOR = 0xFF000000
PIANO_STRING_WHITE_COLOR = 0xFF000000
NOISE_STRING_BLACK_COLOR = 0xFF060606
NOISE_STRING_WHITE_COLOR = 0xFF0A0A0A
DMC_BASE_COLOR = 0x806040A0
end
end
-- call this once to initialize
set_background()
function toggle_background()
if settings.background == "clear" then
settings.background = "transparent"
elseif settings.background == "transparent" then
settings.background = "solid"
elseif settings.background == "solid" then
settings.background = "clear"
end
set_background()
end
function tiny_a(x, y, color)
emu.drawLine(x,y+1,x, y+4, color)
emu.drawLine(x+2,y+1,x+2, y+4, color)
emu.drawPixel(x+1,y,color)
emu.drawPixel(x+1,y+2,color)
end
function tiny_b(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+1, y+2, color)
emu.drawPixel(x+1, y+4, color)
emu.drawPixel(x+2, y+1, color)
emu.drawPixel(x+2, y+3, color)
end
function tiny_c(x, y, color)
emu.drawLine(x, y+1, x, y+3, color)
emu.drawLine(x+1, y, x+2, y, color)
emu.drawLine(x+1, y+4, x+2, y+4, color)
end
function tiny_d(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+1, y+4, color)
emu.drawLine(x+2, y+1, x+2, y+3,color)
end
function tiny_e(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+1, y, x+2, y, color)
emu.drawPixel(x+1, y+2, color)
emu.drawLine(x+1, y+4, x+2, y+4, color)
end
function tiny_f(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+1, y, x+2, y, color)
emu.drawPixel(x+1, y+2, color)
end
function tiny_g(x, y, color)
emu.drawLine(x, y+1, x, y+3, color)
emu.drawLine(x+1, y, x+2, y, color)
emu.drawLine(x+1, y+4, x+2, y+4, color)
emu.drawLine(x+2, y+2, x+2, y+3, color)
end
function tiny_h(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+2, y, x+2, y+4, color)
emu.drawPixel(x+1, y+2, color)
end
function tiny_i(x, y, color)
emu.drawLine(x, y, x+2, y, color)
emu.drawLine(x, y+4, x+2, y+4, color)
emu.drawLine(x+1, y+1, x+1, y+3, color)
end
function tiny_j(x, y, color)
emu.drawLine(x, y, x+2, y, color)
emu.drawLine(x+2, y+1, x+2, y+3, color)
emu.drawPixel(x+1, y+4, color)
emu.drawPixel(x, y+3, color)
end
function tiny_k(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawPixel(x+1, y+2, color)
emu.drawLine(x+2, y, x+2, y+1, color)
emu.drawLine(x+2, y+3, x+2, y+4, color)
end
function tiny_l(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+1, y+4, x+2, y+4, color)
end
function tiny_m(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+1, y+1, x+1, y+2, color)
emu.drawLine(x+2, y, x+2, y+4, color)
end
function tiny_n(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawPixel(x+1, y, color)
emu.drawLine(x+2, y+1, x+2, y+4, color)
end
function tiny_o(x, y, color)
emu.drawLine(x, y+1, x, y+3, color)
emu.drawLine(x+2, y+1, x+2, y+3, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+1, y+4, color)
end
function tiny_p(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+2, y+1, color)
emu.drawPixel(x+1, y+2, color)
end
function tiny_q(x, y, color)
emu.drawLine(x, y+1, x, y+3, color)
emu.drawLine(x+2, y+1, x+2, y+3, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+1, y+3, color)
emu.drawLine(x+1, y+4, x+2, y+4, color)
end
function tiny_r(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+2, y+1, color)
emu.drawPixel(x+1, y+2, color)
emu.drawLine(x+2, y+3, x+2, y+4, color)
end
function tiny_s(x, y, color)
emu.drawLine(x+1, y, x+2, y, color)
emu.drawPixel(x, y+1, color)
emu.drawPixel(x+1, y+2, color)
emu.drawPixel(x+2, y+3, color)
emu.drawLine(x, y+4, x+1, y+4, color)
end
function tiny_t(x, y, color)
emu.drawLine(x, y, x+2, y, color)
emu.drawLine(x+1, y+1, x+1, y+4, color)
end
function tiny_u(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+2, y, x+2, y+4, color)
emu.drawPixel(x+1, y+4, color)
end
function tiny_v(x, y, color)
emu.drawLine(x, y, x, y+3, color)
emu.drawLine(x+2, y, x+2, y+3, color)
emu.drawPixel(x+1, y+4, color)
end
function tiny_w(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+1, y+2, x+1, y+3, color)
emu.drawLine(x+2, y, x+2, y+4, color)
end
function tiny_x(x, y, color)
emu.drawLine(x, y, x, y+1, color)
emu.drawLine(x+2, y, x+2, y+1, color)
emu.drawPixel(x+1, y+2, color)
emu.drawLine(x, y+3, x, y+4, color)
emu.drawLine(x+2, y+3, x+2, y+4, color)
end
function tiny_y(x, y, color)
emu.drawLine(x, y, x, y+2, color)
emu.drawLine(x+2, y, x+2, y+2, color)
emu.drawLine(x+1, y+2, x+1, y+4, color)
end
function tiny_z(x, y, color)
emu.drawLine(x, y, x+2, y, color)
emu.drawLine(x+2,y+1,x,y+3, color)
emu.drawLine(x,y+4,x+2,y+4, color)
end
function tiny_0(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+2, y, x+2, y+4, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+1, y+4, color)
end
function tiny_1(x, y, color)
emu.drawLine(x+1, y, x+1, y+4, color)
emu.drawLine(x, y+4, x+2, y+4, color)
emu.drawPixel(x, y+1,color)
end
function tiny_2(x, y, color)
emu.drawLine(x, y, x+1, y, color)
emu.drawLine(x+2,y+1,x,y+3, color)
emu.drawLine(x,y+4,x+2,y+4, color)
end
function tiny_3(x, y, color)
emu.drawLine(x+2, y, x+2, y+4, color)
emu.drawLine(x, y, x+1, y, color)
emu.drawPixel(x+1, y+2, color)
emu.drawLine(x, y+4, x+1, y+4, color)
end
function tiny_4(x, y, color)
emu.drawLine(x+2, y, x+2, y+4, color)
emu.drawLine(x, y, x, y+2, color)
emu.drawPixel(x+1, y+2, color)
end
function tiny_5(x, y, color)
emu.drawLine(x, y, x+2, y, color)
emu.drawLine(x, y+1, x, y+2, color)
emu.drawPixel(x+1, y+2, color)
emu.drawPixel(x+2, y+3, color)
emu.drawLine(x,y+4,x+1,y+4, color)
end
function tiny_6(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+1, y, x+2, y, color)
emu.drawLine(x+1, y+2, x+2, y+2, color)
emu.drawLine(x+1, y+4, x+2, y+4, color)
emu.drawPixel(x+2, y+3, color)
end
function tiny_7(x, y, color)
emu.drawLine(x+2, y, x+2, y+4, color)
emu.drawLine(x, y, x+1, y, color)
end
function tiny_8(x, y, color)
emu.drawLine(x, y, x, y+4, color)
emu.drawLine(x+2, y, x+2, y+4, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+1, y+2, color)
emu.drawPixel(x+1, y+4, color)
end
function tiny_9(x, y, color)
emu.drawLine(x+2, y, x+2, y+4, color)
emu.drawLine(x, y, x+1, y, color)
emu.drawLine(x, y+2, x+1, y+2, color)
emu.drawLine(x, y+4, x+1, y+4, color)
emu.drawPixel(x, y+1, color)
end
function tiny_hex_char(x, y, value, color)
local hex_functions = {
tiny_0, tiny_1, tiny_2, tiny_3, tiny_4, tiny_5, tiny_6, tiny_7,
tiny_8, tiny_9, tiny_a, tiny_b, tiny_c, tiny_d, tiny_e, tiny_f}
if hex_functions[value+1] then
hex_functions[value+1](x,y,color)
end
end
function tiny_hex(x, y, value, color, width)
while width > 0 do
width = width - 1
tiny_hex_char(x + width * 4, y, value & 0xF, color)
value = value >> 4
end
end
local char_functions = {
tiny_a, tiny_b, tiny_c, tiny_d, tiny_e, tiny_f,
tiny_g, tiny_h, tiny_i, tiny_j, tiny_k, tiny_l,
tiny_m, tiny_n, tiny_o, tiny_p, tiny_q, tiny_r,
tiny_s, tiny_t, tiny_u, tiny_v, tiny_w, tiny_x,
tiny_y, tiny_z
}
local num_functions = {
tiny_0, tiny_1, tiny_2, tiny_3, tiny_4,
tiny_5, tiny_6, tiny_7, tiny_8, tiny_9
}
function tiny_char(x, y, character_code, color)
if character_code >= string.byte("a", 1) and character_code <= string.byte("z") then
char_functions[character_code - string.byte("a") + 1](x, y, color)
end
if character_code >= string.byte("A", 1) and character_code <= string.byte("Z") then
char_functions[character_code - string.byte("A") + 1](x, y, color)
end
if character_code >= string.byte("0", 1) and character_code <= string.byte("9") then
num_functions[character_code - string.byte("0") + 1](x, y, color)
end
end
function tiny_string(x, y, str, color)
for i = 1, #str do
tiny_char(x + ((i-1) * 4), y, string.byte(str, i), color)
end
end
function frequency_to_coordinate(note_frequency, lowest_freq, highest_freq, viewport_height)
local highest_log = math.log(highest_freq)
local lowest_log = math.log(lowest_freq)
local range = highest_log - lowest_log
local note_log = math.log(note_frequency)
local coordinate = (note_log - lowest_log) * viewport_height / range
if coordinate >= 0 and coordinate < viewport_height then
return coordinate
else
return nil
end
end
function pulse_frequency(pulse_period)
return NTSC_CPU_FREQUENCY / (16.0 * (pulse_period + 1.0))
end
function triangle_frequency(triangle_period)
return NTSC_CPU_FREQUENCY / (32.0 * (triangle_period + 1.0))
end
function unpack_argb(color)
return ((color & 0xFF000000) >> 24), ((color & 0xFF0000) >> 16), ((color & 0xFF00) >> 8), (color & 0xFF)
end
function pack_argb(a, r, g, b)
return (a << 24) | (r << 16) | (g << 8) | (b)
end
function apply_brightness(color, brightness)
local a, r, g, b = unpack_argb(color)
r = math.floor(r * brightness)
g = math.floor(g * brightness)
b = math.floor(b * brightness)
return pack_argb(a, r, g, b)
end
function apply_transparency(color, alpha)
local a, r, g, b = unpack_argb(color)
a = math.floor(0xFF * (1.0 - alpha))
return pack_argb(a, r, g, b)
end
local square1_roll = {}
local square2_roll = {}
local triangle_roll = {}
local noise_roll = {}
local dmc_roll = {}
function initialize_piano_roll()
for i = 1, PIANO_ROLL_WIDTH - 1 do
local channel_state = {}
-- this is enough for pulse, triangle, and noise
channel_state.enabled = false
-- dmc calculates a delta, so it needs full dummy samples
channel_state.playing = false
channel_state.delta = 0
table.insert(square1_roll, channel_state)
table.insert(square2_roll, channel_state)
table.insert(triangle_roll, channel_state)
table.insert(noise_roll, channel_state)
table.insert(dmc_roll, channel_state)
end
end
initialize_piano_roll()
function update_piano_roll(channel, state_table)
local channel_state = {}
local coordinate = frequency_to_coordinate(channel.frequency, LOWEST_NOTE_FREQ, HIGHEST_NOTE_FREQ, PIANO_ROLL_HEIGHT)
if coordinate then
channel_state.y = PIANO_ROLL_HEIGHT - coordinate
end
if channel.envelope then
-- pulse channels
channel_state.volume = channel.envelope.volume
channel_state.enabled = channel.envelope.volume ~= 0 and channel.lengthCounter.counter > 0
channel_state.duty = channel.duty
else
-- triangle channels
channel_state.volume = 6
channel_state.enabled =
channel.lengthCounter.counter > 0 and
shadow_triangle.lc_value > 0 and
channel.period > 2 and
channel.enabled
channel_state.duty = 0
end
table.insert(state_table, channel_state)
if #state_table > PIANO_ROLL_WIDTH then
table.remove(state_table, 1)
end
end
local noise_period_table = {}
noise_period_table[4] = 0
noise_period_table[8] = 1
noise_period_table[16] = 2
noise_period_table[32] = 3
noise_period_table[64] = 4
noise_period_table[96] = 5
noise_period_table[128] = 6
noise_period_table[160] = 7
noise_period_table[202] = 8
noise_period_table[254] = 9
noise_period_table[380] = 10
noise_period_table[508] = 11
noise_period_table[762] = 12
noise_period_table[1016] = 13
noise_period_table[2034] = 14
noise_period_table[4068] = 15
function debug_table_keys(name, t)
local keys = {}
for k,v in pairs(t) do
keys[#keys+1] = k
end
emu.log(name..": "..table.concat(keys, ","))
end
function update_noise_roll(channel, state_table)
local channel_state = {}
if noise_period_table[channel.period + 1] then
channel_state.y = noise_period_table[channel.period + 1] * NOISE_KEY_HEIGHT
channel_state.period = noise_period_table[channel.period + 1]
channel_state.mode = channel.modeFlag
end
if channel.envelope.constantVolume then
channel_state.volume = channel.envelope.volume
else
channel_state.volume = channel.envelope.counter
end
channel_state.enabled = channel_state.volume ~= 0 and channel.lengthCounter.counter > 0
table.insert(state_table, channel_state)
if #state_table > PIANO_ROLL_WIDTH then
table.remove(state_table, 1)
end
end
local old_dmc_level = 0
local dmc_period_table = {}
dmc_period_table[428] = 0
dmc_period_table[380] = 1
dmc_period_table[340] = 2
dmc_period_table[320] = 3
dmc_period_table[286] = 4
dmc_period_table[254] = 5
dmc_period_table[226] = 6
dmc_period_table[214] = 7
dmc_period_table[190] = 8
dmc_period_table[160] = 9
dmc_period_table[142] = 10
dmc_period_table[128] = 11
dmc_period_table[106] = 12
dmc_period_table[84] = 13
dmc_period_table[72] = 14
dmc_period_table[54] = 15
function update_dmc_roll(channel, state_table)
local channel_state = {}
local dmc_playing = channel.bytesRemaining > 0
local delta = math.abs(channel.outputVolume - old_dmc_level)
channel_state.playing = dmc_playing
channel_state.delta = delta
channel_state.address = channel.sampleAddr
if dmc_period_table[channel.period + 1] then
channel_state.rate = dmc_period_table[channel.period + 1]
end
table.insert(state_table, channel_state)
if #state_table > PIANO_ROLL_WIDTH then
table.remove(state_table, 1)
end
old_dmc_level = channel.outputVolume
end
function draw_piano_roll(emu, state_table, duty_colors)
for x = 1, #state_table do
if state_table[x].enabled and state_table[x].y then
local volume = state_table[x].volume
local y = state_table[x].y - 2
local foreground = duty_colors[state_table[x].duty + 1]
-- outer lines, drawn at brightness dependant on volume
local outermost_volume = math.max(0, volume - 8) / 8
local outermost_brightness = math.min(1.0, outermost_volume)
emu.drawLine(x - 1, y-2, x - 1, y+2, apply_transparency(foreground, outermost_brightness))
local innermost_volume = volume / 8
local innermost_brightness = math.min(1.0, innermost_volume)
emu.drawLine(x - 1, y-1, x - 1, y+1, apply_transparency(foreground, innermost_brightness))
-- center line, always drawn at full brightness when playing
emu.drawPixel(x - 1, y, foreground)
end
end
end
function draw_noise_roll(emu, state_table, duty_colors)
for x = 1, #state_table do
if state_table[x].enabled and state_table[x].y then
local volume = state_table[x].volume
local y = state_table[x].y
local foreground = duty_colors[1]
if state_table[x].mode then
foreground = duty_colors[2]
end
-- outer lines, drawn at brightness dependant on volume
local outermost_volume = math.max(0, volume - 8) / 8
local outermost_brightness = math.min(1.0, outermost_volume)
emu.drawLine(x - 1, NOISE_ROLL_OFFSET + y-2, x - 1, NOISE_ROLL_OFFSET + y+2, apply_transparency(foreground, outermost_brightness))
local innermost_volume = volume / 8
local innermost_brightness = math.min(1.0, innermost_volume)
emu.drawLine(x - 1, NOISE_ROLL_OFFSET + y-1, x - 1, NOISE_ROLL_OFFSET + y+1, apply_transparency(foreground, innermost_brightness))
-- center line, always drawn at full brightness when playing
emu.drawPixel(x - 1, NOISE_ROLL_OFFSET + y, foreground)
end
end
end
function draw_piano_background()
emu.drawRectangle(0, 0, 256, 240, BACKGROUND_COLOR, true)
end
function draw_piano_strings()
local black_string = PIANO_STRING_BLACK_COLOR
local white_string = PIANO_STRING_WHITE_COLOR
local string_colors = {
white_string, --C
white_string, --B
black_string, --Bb
white_string, --A
black_string, --Ab
white_string, --G
black_string, --Gb
white_string, --F
white_string, --E
black_string, --Eb
white_string, --D
black_string, --Db
}
for i = 0, PIANO_ROLL_KEYS do
local string_color = string_colors[i%12 + 1]
local y = i*PIANO_ROLL_KEY_HEIGHT
emu.drawLine(0, y, PIANO_ROLL_WIDTH, y, string_color)
end
end
function draw_noise_strings()
local noise_string_colors = {
NOISE_STRING_BLACK_COLOR,
NOISE_STRING_WHITE_COLOR
}
for i = 0, 15 do
local y = i * NOISE_KEY_HEIGHT
emu.drawLine(0, NOISE_ROLL_OFFSET + y, PIANO_ROLL_WIDTH, NOISE_ROLL_OFFSET + y, noise_string_colors[(i%2)+1])
end
end
local white_key_border = 0x404040
local white_key = 0x505050
local black_key = 0x000000
local black_key_border = 0x181818
local upper_key_pixels = {
white_key, -- C
white_key_border,
white_key, -- B
black_key, black_key, black_key, -- Bb
white_key, -- A
black_key, black_key, black_key, -- Ab
white_key, -- G
black_key, black_key, black_key, -- Gb
white_key, -- F
white_key_border,
white_key, -- E
black_key, black_key, black_key, -- Eb
white_key, -- D
black_key, black_key, black_key, -- Db
}
local lower_key_pixels = {
white_key, -- C (bottom half)
white_key_border,
white_key, white_key, -- B
white_key_border,
white_key, white_key, white_key, -- A
white_key_border,
white_key, white_key, white_key, -- G
white_key_border,
white_key, white_key, -- F
white_key_border,
white_key, white_key, -- E
white_key_border,
white_key, white_key, white_key, -- D
white_key_border,
white_key, -- C (top half)
}
function draw_piano_keys()
-- first, draw the keys themselves
for y = 0, PIANO_ROLL_KEYS * PIANO_ROLL_KEY_HEIGHT do
local upper_key_color = upper_key_pixels[y % #upper_key_pixels + 1]
local lower_key_color = lower_key_pixels[y % #lower_key_pixels + 1]
emu.drawLine(240, y, 248, y, upper_key_color)
emu.drawLine(248, y, 256, y, lower_key_color)
end
-- now clean up the border between the key and the piano roll
emu.drawLine(240, 0, 240, PIANO_ROLL_HEIGHT, black_key_border)
end
function draw_right_white_key(y, color)
emu.drawLine(248, y + 1, 256, y + 1, color)
emu.drawLine(240, y, 256, y, color)
end
function draw_center_white_key(y, color)
emu.drawLine(248, y - 1, 256, y - 1, color)
emu.drawLine(240, y, 256, y, color)
emu.drawLine(248, y + 1, 256, y + 1, color)
end
function draw_left_white_key(y, color)
emu.drawLine(240, y, 256, y, color)
emu.drawLine(248, y - 1, 256, y - 1, color)
end
function draw_black_key(y, color)
emu.drawLine(241, y - 1, 247, y - 1, color)
emu.drawLine(241, y, 247, y, color)
emu.drawLine(241, y + 1, 247, y + 1, color)
end
function draw_key_spot(note, duty_colors)
if (note.y == nil or note.enabled == false) then
return
end
local key_drawing_functions = {
draw_left_white_key, --C
draw_right_white_key, --B
draw_black_key, --Bb
draw_center_white_key,--A
draw_black_key, --Ab
draw_center_white_key,--G
draw_black_key, --Gb
draw_left_white_key, --F
draw_right_white_key, --E
draw_black_key, --Eb
draw_center_white_key,--D
draw_black_key, --Db
}
local base_color = duty_colors[note.duty + 1]
-- Calculate two key spots, based on tuning. This helps to
-- highlight intentionally out of tune notes, and lights up the closest
-- piano keys relative to standard tuning. The brightness of each pad is
-- relative to the tuning offset, so the brighter key is (usually) the
-- real note
local note_key = (note.y - 2) / PIANO_ROLL_KEY_HEIGHT
local base_key = math.floor(note_key)
local base_percent = 1.0 - (note_key % 1)
local adjacent_key = math.ceil(note_key)
local adjacent_percent = note_key % 1
-- Now take both of these colors and apply some transparency based on volume
local volume_percent = (note.volume / 15) * 0.75 + 0.25
base_percent = base_percent * volume_percent
adjacent_percent = adjacent_percent * volume_percent
local base_y = base_key * PIANO_ROLL_KEY_HEIGHT
local base_key_color = apply_transparency(base_color, base_percent)
local adjacent_y = adjacent_key * PIANO_ROLL_KEY_HEIGHT
local adjacent_key_color = apply_transparency(base_color, adjacent_percent)
key_drawing_functions[base_key % 12 + 1](base_y, base_key_color)
key_drawing_functions[adjacent_key % 12 + 1](adjacent_y, adjacent_key_color)
end
function draw_pad(x, y, value, foreground, background)
emu.drawRectangle(x, y-1, 4, 9, background, true)
emu.drawLine(x+4, y, x+4, y+6, background)
tiny_hex_char(x+1,y+1,value,foreground)
end
function draw_noise_pads(active_note, duty_colors)
local pad_colors = {
0x101010,
0x121212,
0x141414,
0x161616,
0x181818,
0x1A1A1A,
0x1C1C1C,
0x1E1E1E,
0x202020,
0x222222,
0x242424,
0x262626,
0x282828,
0x2A2A2A,
0x2C2C2C,
0x2E2E2E,
}
for i = 0, 15 do
local x = 240 + ((i % 4) * 4)
local y = i * 2 + NOISE_ROLL_OFFSET - 3
local color = pad_colors[(0xF - i) + 1]
local hex_value = 0xF - i;
if (active_note.y and active_note.enabled and active_note.period == i) then
color = duty_colors[1]
if active_note.mode then
color = duty_colors[2]
end
end
draw_pad(x, y, hex_value, apply_brightness(color, 1.8), color)
end
emu.drawLine(240, NOISE_ROLL_OFFSET - 4, 240, NOISE_ROLL_OFFSET + 30, 0x101010)
emu.drawLine(241, NOISE_ROLL_OFFSET + 30, 243, NOISE_ROLL_OFFSET + 30, NOISE_STRING_BLACK_COLOR)
end
function draw_dmc_roll(emu, state_table)
for x = 1, #state_table do
local y = DMC_OFFSET
local color = DMC_PLAYING_COLOR
if not state_table[x].playing then
color = DMC_BASE_COLOR
end
-- width of "sample" based on delta, maximum is 127
local sample_offset = math.floor((state_table[x].delta / 127) * DMC_HEIGHT)
emu.drawLine(x - 1, y - sample_offset, x - 1, y + sample_offset, color)
end
end
function draw_dmc_head(note)
local foreground = 0x404040
local background = 0x202020
if note.playing then
foreground = DMC_PLAYING_COLOR
background = apply_brightness(DMC_PLAYING_COLOR, 0.5)
end
emu.drawRectangle(242, DMC_OFFSET - 6, 13, 13, background, true)
emu.drawRectangle(241, DMC_OFFSET - 5, 15, 11, background, true)
emu.drawLine(240, DMC_OFFSET - 3, 240, DMC_OFFSET + 3, 0x101010)
tiny_hex(241, DMC_OFFSET - 2, note.address, foreground, 4)
end
local old_mouse_state = {}
function is_region_clicked(x ,y, width, height, mouse_state)
if mouse_state.left and not old_mouse_state.left then
if mouse_state.x >= x and mouse_state.x < x + width and mouse_state.y >= y and mouse_state.y < y + height then
return true
end
end
return false
end
function handle_input()
local mouse_state = emu.getMouseState()
if is_region_clicked(0, 0, 256, 240, mouse_state) then
toggle_background()
end
old_mouse_state = mouse_state
end
-- drawing functions for various icons in the register display
function draw_raw_registers(x, y, box_color, shadow_color, numeral_color, background_color, values)
emu.drawRectangle(x+1, y+1, 36, 7, shadow_color)
emu.drawRectangle(x, y, 36, 7, box_color, true)
for i = 1, 4 do
emu.drawRectangle(x + ((i - 1) * 9) + 1, y + 1, 7, 5, background_color, true)
if values[i] ~= nil then
tiny_hex(x + ((i - 1) * 9) + 1, y + 1, values[i], numeral_color, 2)
end
end
end
function draw_duty_12(x, y, color)
emu.drawLine(x, y+3, x+4, y+3, color)
emu.drawLine(x+1, y+1, x+1, y+2, color)
end
function draw_duty_25(x, y, color)
emu.drawLine(x, y+3, x+1, y+3, color)
emu.drawLine(x+3, y+3, x+4, y+3, color)
emu.drawLine(x+1, y+1, x+3, y+1, color)
emu.drawPixel(x+1, y+2, color)
emu.drawPixel(x+3, y+2, color)
end
function draw_duty_50(x, y, color)
emu.drawLine(x, y+3, x+2, y+3, color)
emu.drawLine(x+2, y+1, x+4, y+1, color)
emu.drawPixel(x+2, y+2, color)
end
function draw_duty_75(x, y, color)
emu.drawLine(x, y+1, x+1, y+1, color)
emu.drawLine(x+3, y+1, x+4, y+1, color)
emu.drawLine(x+1, y+3, x+3, y+3, color)
emu.drawPixel(x+1, y+2, color)
emu.drawPixel(x+3, y+2, color)
end
local duty_icon_functions = {draw_duty_12, draw_duty_25, draw_duty_50, draw_duty_75}
function draw_duty_indicator(x, y, icon_color, box_color, shadow_color, light_color, dark_color, selected_light_color, selected_dark_color, duty_cycle, playing)
local icon_color = light_color
if playing then
icon_color = selected_light_color
end
emu.drawLine(x, y+6, x+2, y+6, icon_color)
emu.drawLine(x+2, y, x+2, y+5, icon_color)
emu.drawLine(x+3, y, x+6, y, icon_color)
emu.drawLine(x+7, y, x+7, y+5, icon_color)
emu.drawLine(x+7, y+6, x+9, y+6, icon_color)
emu.drawRectangle(x+12, y+1, 25, 7, shadow_color)
emu.drawRectangle(x+11, y, 25, 7, box_color, true)
for i = 1, 4 do
if playing and duty_cycle == (i - 1) then
emu.drawRectangle(x + ((i - 1) * 6) + 12, y + 1, 5, 5, selected_dark_color, true)
duty_icon_functions[i](x + ((i - 1) * 6) + 12, y + 1, selected_light_color)
else
emu.drawRectangle(x + ((i - 1) * 6) + 12, y + 1, 5, 5, dark_color, true)
duty_icon_functions[i](x + ((i - 1) * 6) + 12, y + 1, light_color)
end
end
end
function draw_sweep_indicator(x, y, icon_color, box_color, shadow_color, light_color, dark_color, selected_light_color, selected_dark_color, sweep_byte)
local negate = ((sweep_byte & 0x40) ~= 0)
local period = ((sweep_byte & 0x70) >> 4)
local shift = ((sweep_byte & 0x07))
local enabled = ((sweep_byte & 0x80) ~= 0) and (shift ~= 0)
local icon_color = light_color
if enabled then
icon_color = selected_light_color
end
-- sweep icon
emu.drawLine(x, y, x+6, y+6, icon_color)
emu.drawLine(x+3, y+4, x+5, y+6, icon_color)
emu.drawLine(x+3, y+5, x+4, y+6, icon_color)
emu.drawLine(x+4, y+3, x+6, y+5, icon_color)
emu.drawLine(x+7, y+4, x+7, y+5, icon_color)
emu.drawPixel(x+9, y+4, icon_color)
emu.drawPixel(x+8, y+2, icon_color)
-- bar graph indicators
emu.drawPixel(x+21, y, icon_color)
emu.drawPixel(x+22, y+1, icon_color)
emu.drawPixel(x+21, y+2, icon_color)
emu.drawPixel(x+24, y, icon_color)
emu.drawPixel(x+25, y+1, icon_color)
emu.drawPixel(x+24, y+2, icon_color)
emu.drawPixel(x+21, y+5, icon_color)
emu.drawLine(x+22, y+4, x+22, y+6, icon_color)
emu.drawLine(x+24, y+4, x+24, y+6, icon_color)
emu.drawPixel(x+25, y+5, icon_color)
-- indicator outlines
emu.drawRectangle(x+12, y+1, 7, 7, shadow_color)
emu.drawRectangle(x+11, y, 7, 7, box_color, true)
emu.drawRectangle(x+28, y+1, 9, 7, shadow_color)
emu.drawRectangle(x+27, y, 9, 7, box_color, true)
if enabled then
emu.drawRectangle(x+12, y+1, 5, 5, selected_dark_color, true)
emu.drawLine(x+13, y+3, x+15, y+3, selected_light_color)
if negate then
emu.drawPixel(x+14, y+2, selected_light_color)
emu.drawLine(x+12, y+4, x+16, y+4, selected_light_color)
else
emu.drawPixel(x+14, y+4, selected_light_color)
emu.drawLine(x+12, y+2, x+16, y+2, selected_light_color)
end
emu.drawRectangle(x+28, y+1, 7, 2, selected_dark_color)
emu.drawRectangle(x+28, y+4, 7, 2, selected_dark_color)
emu.drawRectangle(x+28, y+1, shift, 2, selected_light_color)
emu.drawRectangle(x+28, y+4, period, 2, selected_light_color)
else
-- simply blank out the indicators and draw nothing
emu.drawRectangle(x+12, y+1, 5, 5, dark_color, true)
emu.drawRectangle(x+28, y+1, 7, 2, dark_color)
emu.drawRectangle(x+28, y+4, 7, 2, dark_color)
end
end
function draw_volume_envelope_indicator(x, y, icon_color, box_color, shadow_color, light_color, dark_color, selected_light_color, selected_dark_color, volume_byte)
local constant_volume = ((volume_byte & 0x10) ~= 0)
local icon_color = light_color
if not constant_volume then
icon_color = selected_light_color
end
-- volume icon
emu.drawLine(x, y+2, x, y+4, icon_color)
emu.drawPixel(x+1, y+2, icon_color)
emu.drawPixel(x+1, y+4, icon_color)
emu.drawLine(x+2, y+1, x+2, y+5, icon_color)
emu.drawLine(x+3, y, x+3, y+1, icon_color)
emu.drawLine(x+3, y+5, x+3, y+6, icon_color)
emu.drawPixel(x+4, y, icon_color)
emu.drawPixel(x+4, y+6, icon_color)
emu.drawLine(x+5, y, x+5, y+6, icon_color)
emu.drawLine(x+7, y+2, x+7, y+4, icon_color)
emu.drawLine(x+7, y, x+8, y, icon_color)
emu.drawLine(x+7, y+6, x+8, y+6, icon_color)
emu.drawLine(x+9, y+1, x+9, y+5, icon_color)
-- box backgrounds and scaffolding
emu.drawRectangle(x+12, y+1, 13, 7, shadow_color)
emu.drawRectangle(x+11, y, 13, 7, box_color, true)
emu.drawLine(x+25, y+4, x+32, y+4, shadow_color)
emu.drawLine(x+24, y+3, x+31, y+3, box_color)
emu.drawRectangle(x+32, y+1, 5, 7, shadow_color)
emu.drawRectangle(x+31, y, 5, 7, box_color, true)
if constant_volume then
-- draw un-highlighted icons
emu.drawRectangle(x+12, y+1, 5, 5, dark_color, true)
emu.drawRectangle(x+12, y+1, 2, 5, light_color, true)
emu.drawRectangle(x+14, y+2, 2, 3, light_color, true)
emu.drawPixel(x+16, y+3, light_color)
emu.drawRectangle(x+18, y+1, 5, 5, dark_color, true)
emu.drawPixel(x+19, y+2, light_color)
emu.drawPixel(x+19, y+4, light_color)
emu.drawLine(x+21, y+1, x+21, y+5, light_color)
-- do not draw envelope period, but blank the cell
emu.drawRectangle(x+32, y+1, 3, 5, dark_color, true)
else
-- draw uhighlighted icons
emu.drawRectangle(x+12, y+1, 5, 5, selected_dark_color, true)
emu.drawRectangle(x+12, y+1, 2, 5, selected_light_color, true)
emu.drawRectangle(x+14, y+2, 2, 3, selected_light_color, true)
emu.drawPixel(x+16, y+3, selected_light_color)
local loop = (volume_byte & 0x30) ~= 0
if loop then
emu.drawRectangle(x+18, y+1, 5, 5, selected_dark_color, true)
emu.drawPixel(x+19, y+2, selected_light_color)
emu.drawPixel(x+19, y+4, selected_light_color)
emu.drawLine(x+21, y+1, x+21, y+5, selected_light_color)
else
emu.drawRectangle(x+18, y+1, 5, 5, dark_color, true)
emu.drawPixel(x+19, y+2, light_color)
emu.drawPixel(x+19, y+4, light_color)
emu.drawLine(x+21, y+1, x+21, y+5, light_color)
end
-- populate the envelope period
local period = (volume_byte & 0xF)
emu.drawRectangle(x+32, y+1, 3, 5, selected_dark_color, true)
tiny_hex(x+32, y+1, period, selected_light_color, 1)
end
end
function draw_volume_bar(x, y, icon_color, box_color, shadow_color, light_color, dark_color, selected_light_color, selected_dark_color, volume_byte, current_volume)
emu.drawRectangle(x+1, y+1, 36, 7, shadow_color)
emu.drawRectangle(x, y, 36, 7, box_color, true)
-- first, handle the constant volume indicator
local constant_volume = ((volume_byte & 0x10) ~= 0)
if constant_volume then
emu.drawRectangle(x+1, y+1, 3, 5, selected_dark_color, true)
tiny_hex(x+1, y+1, current_volume, selected_light_color, 1)
else
emu.drawRectangle(x+1, y+1, 3, 5, dark_color, true)
end
-- then, based on the current playing volume, update the bar. Note this is the output volume, NOT the
-- value written to the register. It should follow the active envelope.
for i = 1, 15 do
local dx = x+5 + ((i-1) * 2)
local dy = y+1
if current_volume >= i then
emu.drawLine(dx, dy, dx, dy+4, selected_dark_color)
emu.drawLine(dx+1, dy, dx+1, dy+4, selected_light_color)
else
emu.drawLine(dx, dy, dx, dy+4, dark_color)
emu.drawLine(dx+1, dy, dx+1, dy+4, light_color)
end
end
end
function draw_triangle_indicator(x, y, icon_color, box_color, shadow_color, light_color, dark_color, selected_light_color, selected_dark_color, triangle_playing)
local icon_color = light_color
if triangle_playing then
icon_color = selected_light_color
end
emu.drawLine(x, y+5, x+4, y+1, icon_color)
emu.drawLine(x+5, y+1, x+9, y+5, icon_color)
emu.drawRectangle(x+12, y+1, 25, 7, shadow_color)
emu.drawRectangle(x+11, y, 25, 7, box_color, true)
if triangle_playing then
emu.drawRectangle(x+12, y+1, 23, 5, selected_dark_color, true)
emu.drawRectangle(x+21, y+1, 2, 5, selected_light_color)
emu.drawRectangle(x+23, y+2, 2, 3, selected_light_color)
emu.drawLine(x+25, y+3, x+26, y+3, selected_light_color)
else
emu.drawRectangle(x+12, y+1, 23, 5, dark_color, true)
emu.drawRectangle(x+21, y+1, 2, 5, light_color)
emu.drawRectangle(x+23, y+2, 2, 3, light_color)
emu.drawLine(x+25, y+3, x+26, y+3, light_color)
end
end
function draw_mode_0(x, y, color)
emu.drawPixel(x, y+2, color)
emu.drawPixel(x+1, y, color)
emu.drawPixel(x+2, y+3, color)
emu.drawPixel(x+3, y+4, color)
emu.drawPixel(x+4, y+2, color)
emu.drawPixel(x+5, y, color)
emu.drawPixel(x+6, y+2, color)
emu.drawPixel(x+7, y+3, color)
emu.drawPixel(x+8, y+1, color)
emu.drawPixel(x+9, y+4, color)
emu.drawPixel(x+10, y, color)
end
function draw_mode_1(x, y, color)
emu.drawPixel(x, y+2, color)
emu.drawLine(x+1, y+1, x+1, y+2, color)
emu.drawPixel(x+2, y, color)
emu.drawPixel(x+3, y+4, color)
emu.drawPixel(x+4, y+3, color)
emu.drawLine(x+5, y+2, x+5, y+3, color)
emu.drawPixel(x+6, y+1, color)
emu.drawPixel(x+7, y, color)
emu.drawLine(x+8, y+3, x+8, y+4, color)
emu.drawPixel(x+9, y+2, color)
emu.drawPixel(x+10, y+1, color)
end
local mode_functions = {}
mode_functions[0] = draw_mode_0
mode_functions[1] = draw_mode_1
function draw_noise_mode(x, y, icon_color, box_color, shadow_color, light_color, dark_color, selected_light_color, selected_dark_color, noise_byte, playing)
local icon_color = light_color
if playing then
icon_color = selected_light_color
end
emu.drawPixel(x, y+3, icon_color)
emu.drawPixel(x+1, y+2, icon_color)
emu.drawLine(x+2, y+3, x+2, y+5, icon_color)
emu.drawLine(x+3, y, x+3, y+2, icon_color)
emu.drawLine(x+4, y+3, x+4, y+4, icon_color)
emu.drawPixel(x+5, y+2, icon_color)
emu.drawLine(x+6, y+3, x+6, y+6, icon_color)
emu.drawLine(x+7, y+1, x+7, y+2, icon_color)
emu.drawLine(x+8, y+3, x+9, y+3, icon_color)
emu.drawRectangle(x+12, y+1, 25, 7, shadow_color)
emu.drawRectangle(x+11, y, 25, 7, box_color, true)
local noise_mode = ((noise_byte & 0x80) >> 7)
for i = 0, 1 do
if i == noise_mode then
emu.drawRectangle(x+12 + (i*12), y+1, 11, 5, selected_dark_color, true)
mode_functions[i](x+12 + (i*12), y+1, selected_light_color)
else
emu.drawRectangle(x+12 + (i*12), y+1, 11, 5, dark_color, true)
mode_functions[i](x+12 + (i*12), y+1, light_color)
end
end
end
function draw_dpcm_sample_indicator(x, y, icon_color, box_color, shadow_color, light_color, dark_color, selected_light_color, selected_dark_color, frequency_byte, sample_address_byte, dpcm_active)
local icon_color = light_color
if dpcm_active then
icon_color = selected_light_color
end
emu.drawLine(x, y+3, x+9, y+3, icon_color)
emu.drawLine(x+1, y+2, x+1, y+4, icon_color)
emu.drawLine(x+2, y+1, x+2, y+5, icon_color)
emu.drawLine(x+4, y+2, x+4, y+4, icon_color)
emu.drawLine(x+6, y+1, x+6, y+5, icon_color)
emu.drawLine(x+7, y, x+7, y+6, icon_color)
emu.drawLine(x+8, y+2, x+8, y+4, icon_color)
emu.drawRectangle(x+13, y+1, 5, 7, shadow_color)
emu.drawRectangle(x+12, y, 5, 7, box_color, true)
emu.drawLine(x+18, y+4, x+19, y+4, shadow_color)
emu.drawLine(x+17, y+3, x+18, y+3, box_color)
emu.drawRectangle(x+20, y+1, 17, 7, shadow_color)
emu.drawRectangle(x+19, y, 17, 7, box_color, true)
local frequency = frequency_byte & 0xF
local address = 0xC000 + (sample_address_byte << 6)
if dpcm_active then
emu.drawRectangle(x+13, y+1, 3, 5, selected_dark_color, true)
emu.drawRectangle(x+20, y+1, 15, 5, selected_dark_color, true)
tiny_hex(x+13, y+1, frequency, selected_light_color, 1)
tiny_hex(x+20, y+1, address, selected_light_color, 4)
else
emu.drawRectangle(x+13, y+1, 3, 5, dark_color, true)
emu.drawRectangle(x+20, y+1, 15, 5, dark_color, true)
tiny_hex(x+13, y+1, frequency, light_color, 1)
tiny_hex(x+20, y+1, address, light_color, 4)
end
end
function draw_dpcm_length_indicator(x, y, icon_color, box_color, shadow_color, light_color, dark_color, selected_light_color, selected_dark_color, control_byte, length_byte, dpcm_active)
local icon_color = light_color
if dpcm_active then
icon_color = selected_light_color
end
emu.drawLine(x+3, y, x+5, y, icon_color)
emu.drawLine(x+7, y+2, x+7, y+4, icon_color)
emu.drawLine(x+3, y+6, x+5, y+6, icon_color)
emu.drawLine(x+1, y+2, x+1, y+4, icon_color)
emu.drawPixel(x+2,y+1, icon_color)
emu.drawPixel(x+6,y+1, icon_color)
emu.drawPixel(x+2,y+5, icon_color)
emu.drawPixel(x+6,y+5, icon_color)
emu.drawLine(x+4, y+1, x+4, y+3, icon_color)
emu.drawLine(x+5, y+3, x+6, y+3, icon_color)
emu.drawRectangle(x+18, y+1, 19, 7, shadow_color)
emu.drawRectangle(x+17, y, 19, 7, box_color, true)
local looping = ((control_byte & 0x40) ~= 0)
local sample_length = (length_byte * 16) + 1
if dpcm_active then
emu.drawRectangle(x+18,y+1,11, 5, selected_dark_color, true)
emu.drawRectangle(x+30,y+1,5, 5, selected_dark_color, true)
tiny_hex(x+18,y+1,sample_length, selected_light_color, 3)
emu.drawPixel(x+31, y+2, selected_light_color)
emu.drawPixel(x+31, y+4, selected_light_color)
emu.drawLine(x+33, y+1, x+33, y+5, selected_light_color)
else
emu.drawRectangle(x+18,y+1,11, 5, dark_color, true)
emu.drawRectangle(x+30,y+1,5, 5, dark_color, true)
tiny_hex(x+18,y+1,sample_length, light_color, 3)
emu.drawPixel(x+31, y+2, light_color)
emu.drawPixel(x+31, y+4, light_color)
emu.drawLine(x+33, y+1, x+33, y+5, light_color)
end
end
local ICON_COLOR = 0x808080
local SHADOW_COLOR = 0x80000000
local BOX_OUTLINE_COLOR = 0x000000
local UNSELECTED_DARK_COLOR = 0x404040
local UNSELECTED_LIGHT_COLOR = 0x808080
function draw_apu_registers()
emu.drawRectangle(0, 0, 39, 240, 0x40202020, true)
local square1_duty = (shadow_apu[0x4000] & 0xC0) >> 6
local square1_selected_light = SQUARE1_COLORS[square1_duty + 1]
local square1_selected_dark = square1_selected_light + 0xB0000000
tiny_string(1, 1, "Pulse 1", 0xFFFFFF)
draw_raw_registers(1, 7, BOX_OUTLINE_COLOR, SHADOW_COLOR, UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR,
{shadow_apu[0x4000],shadow_apu[0x4001],shadow_apu[0x4002],shadow_apu[0x4003]})
draw_duty_indicator(1, 16,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
square1_selected_light, square1_selected_dark, -- icon color when highlighted
(shadow_apu[0x4000] & 0xC0) >> 6, square1_roll[#square1_roll].enabled)
draw_sweep_indicator(1, 25,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
square1_selected_light, square1_selected_dark, -- icon color when highlighted
shadow_apu[0x4001])
draw_volume_envelope_indicator(1, 34,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
square1_selected_light, square1_selected_dark, -- icon color when highlighted
shadow_apu[0x4000])
draw_volume_bar(1, 43,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
square1_selected_light, square1_selected_dark, -- icon color when highlighted
shadow_apu[0x4000],
square1_roll[#square1_roll].volume)
local square2_duty = (shadow_apu[0x4004] & 0xC0) >> 6
local square2_selected_light = SQUARE2_COLORS[square2_duty + 1]
local square2_selected_dark = square2_selected_light + 0xB0000000
tiny_string(1, 60, "Pulse 2", 0xFFFFFF)
draw_raw_registers(1, 66, BOX_OUTLINE_COLOR, SHADOW_COLOR, UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR,
{shadow_apu[0x4004],shadow_apu[0x4005],shadow_apu[0x4006],shadow_apu[0x4007]})
draw_duty_indicator(1, 75,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
square2_selected_light, square2_selected_dark, -- icon color when highlighted
(shadow_apu[0x4004] & 0xC0) >> 6, square2_roll[#square2_roll].enabled)
draw_sweep_indicator(1, 84,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
square2_selected_light, square2_selected_dark, -- icon color when highlighted
shadow_apu[0x4005])
draw_volume_envelope_indicator(1, 93,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
square2_selected_light, square2_selected_dark, -- icon color when highlighted
shadow_apu[0x4004])
draw_volume_bar(1, 102,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
square2_selected_light, square2_selected_dark, -- icon color when highlighted
shadow_apu[0x4004],
square2_roll[#square2_roll].volume)
local triangle_selected_light = TRIANGLE_COLORS[1]
local triangle_selected_dark = triangle_selected_light + 0xB0000000
tiny_string(1, 119, "Triangle", 0xFFFFFF)
draw_raw_registers(1, 126, BOX_OUTLINE_COLOR, SHADOW_COLOR, UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR,
{shadow_apu[0x4008],nil,shadow_apu[0x400A],shadow_apu[0x400B]})
draw_triangle_indicator(1, 135,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
triangle_selected_light, triangle_selected_dark, -- icon color when highlighted
triangle_roll[#triangle_roll].enabled)
local noise_mode = ((shadow_apu[0x400E] & 0x80) >> 7)
local noise_selected_light = NOISE_COLORS[noise_mode+1]
local noise_selected_dark = noise_selected_light + 0xB0000000
tiny_string(1, 195, "Noise", 0xFFFFFF)
draw_raw_registers(1, 202, BOX_OUTLINE_COLOR, SHADOW_COLOR, UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR,
{shadow_apu[0x400C],nil,shadow_apu[0x400E],shadow_apu[0x400F]})
draw_noise_mode(1, 211,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
noise_selected_light, noise_selected_dark, -- icon color when highlighted
shadow_apu[0x400E], noise_roll[#noise_roll].enabled)
draw_volume_envelope_indicator(1, 220,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
noise_selected_light, noise_selected_dark, -- icon color when highlighted
shadow_apu[0x400C])
draw_volume_bar(1, 229,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
noise_selected_light, noise_selected_dark, -- icon color when highlighted
shadow_apu[0x400C],
noise_roll[#noise_roll].volume)
local dpcm_selected_light = DMC_PLAYING_COLOR
local dpcm_selected_dark = dpcm_selected_light + 0xB0000000
tiny_string(1, 159, "DPCM", 0xFFFFFF)
draw_raw_registers(1, 166, BOX_OUTLINE_COLOR, SHADOW_COLOR, UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR,
{shadow_apu[0x4010],shadow_apu[0x4011],shadow_apu[0x4012],shadow_apu[0x4013]})
draw_dpcm_sample_indicator(1, 175,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
dpcm_selected_light, dpcm_selected_dark, -- icon color when highlighted
shadow_apu[0x4010], shadow_apu[0x4012], dmc_roll[#dmc_roll].playing)
draw_dpcm_length_indicator(1, 184,
ICON_COLOR, --line color
BOX_OUTLINE_COLOR, SHADOW_COLOR, -- box outline and shadow
UNSELECTED_LIGHT_COLOR, UNSELECTED_DARK_COLOR, -- icon color when darkened
dpcm_selected_light, dpcm_selected_dark, -- icon color when highlighted
shadow_apu[0x4010], shadow_apu[0x4013], dmc_roll[#dmc_roll].playing)
end
function mesen_draw()
local state = emu.getState()
local clockRate = state["clockRate"]
-- x y str fore: argb back: argb
--emu.drawString(10, 10, "Hello Mesen", 0x80ff0000, 0x80FFFFFF)
--emu.drawString(10, 20, "P1: "..apu.square1.period, 0xff0000, 0xFFFFFF)
local apu = {
square1 = {
frequency = clockRate / 16 / (state["apu.square1.realPeriod"] + 1),
envelope = { volume = state["apu.square1.envelope.volume"] },
lengthCounter = { counter = state["apu.square1.envelope.lengthCounter.counter"] },
duty = state["apu.square1.duty"]
},
square2 = {
frequency = clockRate / 16 / (state["apu.square2.realPeriod"] + 1),
envelope = { volume = state["apu.square2.envelope.volume"] },
lengthCounter = { counter = state["apu.square2.envelope.lengthCounter.counter"] },
duty = state["apu.square2.duty"]
},
triangle = {
frequency = clockRate / 32 / (state["apu.triangle.timer.period"] + 1),
lengthCounter = { counter = state["apu.triangle.lengthCounter.counter"] },
period = state["apu.triangle.timer.period"],
enabled = state["apu.triangle.lengthCounter.enabled"]
},
noise = {
period = state["apu.noise.timer.period"],
mode = state["apu.noise.modeFlag"],
envelope = {
constantVolume = state["apu.noise.envelope.constantVolume"],
volume = state["apu.noise.envelope.volume"],
counter = state["apu.noise.envelope.counter"]
},
lengthCounter = { counter = state["apu.noise.envelope.lengthCounter.counter"] }
},
dmc = {
bytesRemaining = state["apu.dmc.bytesRemaining"],
outputVolume = state["apu.dmc.timer.lastOutput"],
period = state["apu.dmc.timer.period"],
sampleAddr = state["apu.dmc.sampleAddr"],
}
}
update_piano_roll(apu.square1, square1_roll)
update_piano_roll(apu.square2, square2_roll)
update_piano_roll(apu.triangle, triangle_roll)
update_noise_roll(apu.noise, noise_roll)
update_dmc_roll(apu.dmc, dmc_roll)
draw_piano_background()
draw_piano_strings()
draw_piano_keys()
draw_noise_strings()
draw_noise_pads(noise_roll[#noise_roll], NOISE_COLORS)
draw_piano_roll(emu, square1_roll, SQUARE1_COLORS)
draw_piano_roll(emu, square2_roll, SQUARE2_COLORS)
draw_piano_roll(emu, triangle_roll, TRIANGLE_COLORS)
draw_noise_roll(emu, noise_roll, NOISE_COLORS)
draw_dmc_roll(emu, dmc_roll)
draw_key_spot(square1_roll[#square1_roll], SQUARE1_COLORS)
draw_key_spot(square2_roll[#square2_roll], SQUARE2_COLORS)
draw_key_spot(triangle_roll[#square1_roll], TRIANGLE_COLORS)
draw_dmc_head(dmc_roll[#dmc_roll])
draw_apu_registers()
handle_input()
end
emu.addEventCallback(mesen_draw, emu.eventType.endFrame);