Created
March 17, 2023 07:08
-
-
Save MCJack123/4e746df3c81232410e1eacbbca78b1d6 to your computer and use it in GitHub Desktop.
Atari ST SAP player for CraftOS-PC sound plugin (requires https://gist.github.com/MCJack123/cf500b62fdbcb135d2db6cf97a771c6e)
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
local M6502 = require "M6502" | |
local nsongs = 1 | |
local defaultsong = 0 | |
local stereo = false | |
local ntsc = false | |
local type = 0 -- 0 = B, 1 = C, 2 = D, 3 = S, 4 = R | |
local fastplay = 312 | |
local initaddr = 0x0000 | |
local musicaddr = 0x0000 | |
local playeraddr = 0x0000 | |
local duration = 0 | |
local stwave = {} | |
do | |
--local m = 0 | |
local sz = 256 | |
for i = 1, sz/4 do stwave[i] = -16*((i-1)/sz - 0.25)^2 end | |
for i = sz/4+1, sz do stwave[i] = 0.05/((i-1)/sz - 0.2028) - 0.0625 end | |
--for i = 1, #stwave do stwave[i] = math.min(math.max(stwave[i], -1), 1) end | |
end | |
term.clear() | |
term.setCursorPos(1, 1) | |
local win = window.create(term.current(), 41, 1, 10, 10) | |
win.clear() | |
local status = window.create(term.current(), 1, 1, 40, 19) | |
local oldterm = term.redirect(status) | |
pcall(function(...) | |
local mem = setmetatable({}, {__index = function() return 0 end}) | |
local file = assert(fs.open(shell.resolve(...), "rb")) | |
if file.read(5) ~= "SAP\r\n" then file.close() error("Not a SAP file") end | |
while true do | |
local s = file.read(2) | |
if s == "\xFF\xFF" then break end | |
repeat s = s .. file.read(1) | |
until s:sub(-2, -1) == "\r\n" | |
local cmd, par = s:match "^(%u+) ?(.*)\r\n" | |
print(cmd, par) | |
if cmd == "SONG" then nsongs = tonumber(par) | |
elseif cmd == "DEFSONG" then defaultsong = tonumber(par) | |
elseif cmd == "STEREO" then stereo = true | |
elseif cmd == "NTSC" then ntsc, fastplay = true, 262 | |
elseif cmd == "TYPE" then | |
if par == "B" then type = 0 | |
elseif par == "C" then type = 1 | |
elseif par == "D" then type = 2 | |
elseif par == "S" then type = 3 | |
elseif par == "R" then type = 4 | |
else file.close() error("Unknown TYPE") end | |
elseif cmd == "FASTPLAY" then fastplay = tonumber(par) | |
elseif cmd == "INIT" then initaddr = tonumber(par, 16) | |
elseif cmd == "MUSIC" then musicaddr = tonumber(par, 16) | |
elseif cmd == "PLAYER" then playeraddr = tonumber(par, 16) | |
elseif cmd == "NAME" then --print("Title:", par:sub(2, -2)) | |
elseif cmd == "AUTHOR" then --print("Author:", par:sub(2, -2)) | |
elseif cmd == "DATE" then --print("Date:", par:sub(2, -2)) | |
end | |
end | |
while true do | |
local s = file.read(2) | |
if not s then break end | |
local addr = ("<H"):unpack(s) | |
if addr == 0xFFFF then addr = ("<H"):unpack(file.read(2)) end | |
local e = ("<H"):unpack(file.read(2)) | |
for a = addr, e do mem[a] = file.read() end | |
end | |
file.close() | |
local cpu = M6502.new() | |
cpu:power(true) | |
local cpufreq = ntsc and 1789772.5 or 1773447 | |
local pokey = { | |
{lowfreq = false, hp24 = false, hp13 = false, div43 = false, div21 = false, c3hifreq = false, c1hifreq = false}, | |
{lowfreq = false, hp24 = false, hp13 = false, div43 = false, div21 = false, c3hifreq = false, c1hifreq = false} | |
} | |
for i = 1, stereo and 8 or 4 do | |
sound.setVolume(i, 0) | |
sound.setFrequency(i, 0) | |
sound.setWaveType(i, "square", 0.5) | |
-- sound.setPan(i, stereo and (i > 4 and -1 or 1) or 0) | |
if sound.version then sound.setInterpolation(i, "linear") end | |
end | |
local start = os.epoch "utc" | |
function cpu:read(addr) | |
if bit32.band(addr, 0xFF0F) == 0xD40B then print("v") return math.floor((((os.epoch "utc" - start) / (ntsc and 16.666666 or 20)) % 1) * (ntsc and 130 or 155)) end | |
if bit32.band(addr, 0xFF00) == 0xD200 then return mem[bit32.band(addr, stereo and 0xFF1F or 0xFF0F)] end | |
if bit32.band(addr, 0xF800) == 0xD000 then return mem[bit32.band(addr, 0xFF0F)] end | |
return mem[addr] | |
end | |
function cpu:write(addr, val) | |
if bit32.band(addr, 0xFF00) == 0xD200 then | |
local reg = bit32.band(addr, 0x000F) | |
local chipsel = (stereo and bit32.btest(addr, 0x0010)) and 2 or 1 | |
mem[0xD200 + reg + ((chipsel-1) * 0x10)] = val -- technically not supposed to happen, but for safety in the emulator | |
--print(reg, val) | |
if reg == 8 then | |
win.setCursorPos(1, 1) | |
win.clearLine() | |
win.write(("AUDCTL %02X"):format(val)) | |
pokey[chipsel].lowfreq = bit32.btest(val, 0x01) | |
pokey[chipsel].hp24 = bit32.btest(val, 0x02) if pokey[chipsel].hp24 then print("Unimplemented high-pass") end | |
pokey[chipsel].hp13 = bit32.btest(val, 0x04) if pokey[chipsel].hp13 then print("Unimplemented high-pass") end | |
pokey[chipsel].div43 = bit32.btest(val, 0x08) | |
pokey[chipsel].div21 = bit32.btest(val, 0x10) | |
pokey[chipsel].c3hifreq = bit32.btest(val, 0x20) | |
pokey[chipsel].c1hifreq = bit32.btest(val, 0x40) | |
elseif reg == 0xE then | |
print("IRQ!") | |
elseif reg < 8 then | |
local channel = bit32.rshift(reg, 1) + ((stereo and bit32.btest(addr, 0x0010)) and 4 or 0) + 1 | |
if bit32.btest(addr, 1) then | |
-- control | |
win.setCursorPos(1, channel + 1) | |
--win.clearLine() | |
win.write(("%d %02X"):format(channel, val)) | |
local vol = bit32.band(val, 0x0F) / 15 / 2 | |
--print(channel, val) | |
--sound.setVolume(channel + 8, bit32.band(val, 0x0F) / 150) | |
local mode = bit32.band(val, 0xE0) | |
local wt, arg = "none" | |
if bit32.btest(val, 0x10) then wt, arg = "square", 1 | |
elseif mode == 0xA0 or mode == 0xE0 then wt, arg = "square", 0.5 | |
elseif mode == 0x20 then wt, arg = "triangle" | |
elseif mode == 0xC0 then wt, arg, vol = sound.version and "custom" or "rsawtooth", stwave, vol * 2 | |
elseif mode == 0x80 then wt = "pnoise" end | |
-- TODO: maybe implement other types (weird square-ish waves) | |
if sound.getWaveType(channel) ~= wt then sound.setWaveType(channel, wt, arg) end | |
--if channel == 1 then | |
sound.setVolume(channel, vol) | |
--end | |
else | |
-- frequency | |
local basefreq = (((channel % 4 == 1 and pokey[chipsel].c1hifreq) or (channel % 4 == 3 and pokey[chipsel].c3hifreq)) and cpufreq) or (pokey[chipsel].lowfreq and 15700 or 63921) | |
if (pokey[chipsel].div43 and bit32.rshift(reg, 1) == 2) or (pokey[chipsel].div21 and bit32.rshift(reg, 1) == 0) then basefreq = sound.getFrequency(channel + 1) sound.setVolume(channel + 1, 0) end | |
local freq | |
if basefreq == cpufreq then freq = basefreq / (2 * (val + 4)) -- TODO: hifreq | |
else freq = basefreq / (2 * (val + 1)) end | |
--print(channel, freq) | |
if freq >= 24000 then freq = 0 end | |
win.setCursorPos(6, channel + 1) | |
win.write(("% 5d"):format(freq)) | |
if sound.getWaveType(channel) == "custom" then freq = freq / 2^(35/12) end | |
if sound.getFrequency(channel) ~= freq then sound.setFrequency(channel, freq) end | |
--sound.setFrequency(channel + 8, freq) | |
end | |
end | |
elseif addr == 0xD40A then print("punt!") | |
else | |
mem[addr] = val | |
end | |
end | |
local song = tonumber(select(2, ...) or nil) or defaultsong | |
if type == 0 then | |
print(("Initializing at %04X"):format(initaddr)) | |
cpu.state.a = song | |
cpu:call(initaddr) | |
print(("Playing at %04X"):format(playeraddr)) | |
local n = 0 | |
while true do | |
local start = os.epoch "utc" | |
cpu.state.pc = 0 | |
cpu:call(playeraddr, 114 * 20 * fastplay) | |
if n % 100 == 0 then sleep(fastplay / (ntsc and 60*262 or 50*312)) | |
else while (os.epoch "utc" - start) / 1000 < fastplay / (ntsc and 60*262 or 50*312) do end end | |
n = n + 1 | |
end | |
elseif type == 1 then | |
print(("Initializing at %04X"):format(playeraddr + 3)) | |
cpu.state.a = 0x70 | |
cpu.state.x = bit32.rshift(musicaddr, 8) | |
cpu.state.y = bit32.band(musicaddr, 0xFF) | |
cpu:call(playeraddr + 3) | |
cpu.state.a = 0 | |
cpu.state.x = song | |
cpu:call(playeraddr + 3) | |
print(("Playing at %04X"):format(playeraddr + 6)) | |
local n = 0 | |
while true do | |
local start = os.epoch "utc" | |
cpu.state.pc = 0 | |
cpu:call(playeraddr + 6, 114 * 20 * fastplay) | |
if n % 100 == 0 then sleep(fastplay / (ntsc and 60*262 or 50*312)) | |
else while (os.epoch "utc" - start) / 1000 < fastplay / (ntsc and 60*262 or 50*312) do end end | |
n = n + 1 | |
end | |
else error("Unimplemented TYPE " .. type) end | |
end, ...) | |
term.redirect(oldterm) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment