mirror of
https://github.com/zenith391/OCEmu.git
synced 2025-09-17 16:56:56 -04:00
Begin sound card emulation
This commit is contained in:
parent
8ea389be37
commit
f909517e89
242
src/component/sound_card.lua
Normal file
242
src/component/sound_card.lua
Normal file
@ -0,0 +1,242 @@
|
||||
local address, _, tier = ...
|
||||
|
||||
local ffi = require("ffi")
|
||||
local desired = ffi.new("SDL_AudioSpec",{freq=8000, format=elsa.SDL.AUDIO_S16, channels=1, samples=4096, callback=ffi.NULL})
|
||||
local obtained = ffi.new("SDL_AudioSpec",{})
|
||||
local dev = elsa.SDL.openAudioDevice(ffi.NULL, 0, desired, obtained, 0)
|
||||
if dev == 0 then
|
||||
print(elsa.getError())
|
||||
else
|
||||
local same=true
|
||||
for k,v in pairs({"freq", "format", "channels"}) do
|
||||
if desired[v] ~= obtained[v] then
|
||||
same = false
|
||||
print(v .. ") " .. desired[v] .. " -> " .. obtained[v])
|
||||
end
|
||||
end
|
||||
if not same then
|
||||
print("Could not obtain requested audio format.")
|
||||
end
|
||||
end
|
||||
|
||||
-- computronics sound card component
|
||||
local mai = {}
|
||||
local obj = {}
|
||||
local delayTime = 0
|
||||
local delayQueue = {}
|
||||
local channels = {}
|
||||
for i=1, 8 do
|
||||
channels[i] = {
|
||||
open = false,
|
||||
frequency = 0
|
||||
}
|
||||
end
|
||||
local di = {
|
||||
class = "multimedia",
|
||||
description = "Audio interface",
|
||||
vendor = "Yanaki Sound Systems",
|
||||
product = "MinoSound 244-X"
|
||||
}
|
||||
|
||||
local function checkChannel(n, index)
|
||||
compCheckArg(n, index, "number")
|
||||
index = math.floor(index)
|
||||
if index < 1 or index > 8 then
|
||||
error("invalid channel: " .. tostring(index))
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
mai.setAM = {direct = true, doc = "function(channel:number, modIndex:number); Instruction; Assigns an amplitude modulator channel to the specified channel."}
|
||||
function obj.setAM(channel, modIndex)
|
||||
--STUB
|
||||
cprint("sound.setAM", channel, modIndex)
|
||||
end
|
||||
|
||||
mai.resetAM = {direct = true, doc = "function(channel:number); Instruction; Removes the specified channel's amplitude modulator."}
|
||||
function obj.resetAM(channel)
|
||||
--STUB
|
||||
cprint("sound.resetAM", channel)
|
||||
end
|
||||
|
||||
mai.setFM = {direct = true, doc = "function(channel:number, modIndex:number, intensity:number); Instruction; Assigns a frequency modulator channel to the specified channel with the specified intensity."}
|
||||
function obj.setFM(channel, modIndex, intensity)
|
||||
--STUB
|
||||
cprint("sound.setFM", channel, modIndex, intensity)
|
||||
end
|
||||
|
||||
mai.resetFM = {direct = true, doc = "function(channel:number); Instruction; Removes the specified channel's frequency modulator."}
|
||||
function obj.resetFM(channel)
|
||||
--STUB
|
||||
cprint("sound.resetFM", channel)
|
||||
end
|
||||
|
||||
mai.setADSR = {direct = true, doc = "function(channel:number, attack:number, decay:number, attenuation:number, release:number); Instruction; Assigns ADSR to the specified channel with the specified phase durations in milliseconds and attenuation between 0 and 1."}
|
||||
function obj.setADSR(channel, attack, decay, attenuation, release)
|
||||
--STUB
|
||||
cprint("sound.setADSR", channel, attack, decay, attenuation, release)
|
||||
end
|
||||
|
||||
mai.setLFSR = {direct = true, doc = "function(channel:number, initial:number, mask:number); Instruction; Makes the specified channel generate LFSR noise. Functions like a wave type."}
|
||||
function obj.setLFSR(channel, initial, mask)
|
||||
--STUB
|
||||
cprint("sound.setLFSR", channel, initial, mask)
|
||||
end
|
||||
|
||||
mai.setTotalVolume = {direct = true, doc = "function(volume:number); Sets the general volume of the entire sound card to a value between 0 and 1. Not an instruction, this affects all channels directly."}
|
||||
function obj.setTotalVolume(volume)
|
||||
--STUB
|
||||
cprint("sound.setTotalVolume", volume)
|
||||
end
|
||||
|
||||
mai.setVolume = {direct = true, doc = "function(channel:number, volume:number); Instruction; Sets the volume of the channel between 0 and 1."}
|
||||
function obj.setVolume(channel, volume)
|
||||
--STUB
|
||||
cprint("sound.setVolume", channel, volume)
|
||||
end
|
||||
|
||||
mai.resetEnvelope = {direct = true, doc = "function(channel:number); Instruction; Removes ADSR from the specified channel."}
|
||||
function obj.resetEnvelope(channel)
|
||||
--STUB
|
||||
cprint("sound.resetEnvelope", channel)
|
||||
end
|
||||
|
||||
mai.close = {direct = true, doc = "function(channel:number); Instruction; Closes the specified channel, stopping sound from being generated."}
|
||||
function obj.close(channel)
|
||||
cprint("sound.close", channel)
|
||||
channels[checkChannel(1, channel)].open = false
|
||||
end
|
||||
|
||||
mai.setWave = {direct = true, doc = "function(channel:number, type:number); Instruction; Sets the wave type on the specified channel."}
|
||||
function obj.setWave(channel, type)
|
||||
--STUB
|
||||
cprint("sound.setWave", channel, type)
|
||||
end
|
||||
|
||||
mai.open = {direct = true, doc = "function(channel:number); Instruction; Opens the specified channel, allowing sound to be generated."}
|
||||
function obj.open(channel)
|
||||
cprint("sound.open", channel)
|
||||
channels[checkChannel(1, channel)].open = true
|
||||
end
|
||||
|
||||
mai.clear = {direct = true, doc = "function(); Clears the instruction queue."}
|
||||
function obj.clear()
|
||||
cprint("sound.clear")
|
||||
delayTime = 0
|
||||
delayQueue = {}
|
||||
end
|
||||
|
||||
mai.modes = {doc = "This is a bidirectional table of all valid modes."}
|
||||
function obj.modes()
|
||||
--STUB
|
||||
cprint("sound.modes")
|
||||
end
|
||||
|
||||
local processEnd = 0
|
||||
local processTime = 0
|
||||
local processQueue = {}
|
||||
mai.process = {direct = true, doc = "function(); Starts processing the queue; Returns true is processing began, false if there is still a queue being processed."}
|
||||
function obj.process()
|
||||
--STUB
|
||||
cprint("sound.process")
|
||||
elsa.SDL.pauseAudioDevice(dev, 0)
|
||||
print(elsa.SDL.getQueuedAudioSize(dev))
|
||||
|
||||
if processEnd == 0 then
|
||||
-- start process
|
||||
processEnd = elsa.timer.getTime() * 1000 + delayTime
|
||||
processTime = delayTime
|
||||
processQueue = delayQueue -- cloned
|
||||
delayQueue = {}
|
||||
delayTime = 0
|
||||
print("start processing!")
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
mai.channel_count = {doc = "This is the number of channels this card provides.", getter = true }
|
||||
function obj.channel_count()
|
||||
cprint("sound.channel_count")
|
||||
return 8
|
||||
end
|
||||
|
||||
mai.delay = {direct = true, doc = "function(duration:number); Instruction; Adds a delay of the specified duration in milliseconds, allowing sound to generate."}
|
||||
function obj.delay(duration)
|
||||
cprint("sound.delay", duration)
|
||||
local delayEntry = {}
|
||||
for _, channel in pairs(channels) do
|
||||
if channel.open and channel.frequency ~= 0 then
|
||||
table.insert(delayEntry, {
|
||||
frequency = channel.frequency,
|
||||
offset = 0
|
||||
})
|
||||
end
|
||||
end
|
||||
table.insert(delayQueue, { tstart = delayTime, tend = delayTime + duration, entry = delayEntry })
|
||||
|
||||
delayTime = delayTime + duration
|
||||
end
|
||||
|
||||
mai.setFrequency = {direct = true, doc = "function(channel:number, frequency:number); Instruction; Sets the frequency on the specified channel."}
|
||||
function obj.setFrequency(channel, frequency)
|
||||
cprint("sound.setFrequency", channel, frequency)
|
||||
channel = checkChannel(1, channel)
|
||||
compCheckArg(2, frequency, "number")
|
||||
|
||||
channels[channel].frequency = frequency
|
||||
end
|
||||
|
||||
local firstProc = false
|
||||
table.insert(machineTickHandlers, function(dt)
|
||||
if processEnd ~= 0 then
|
||||
local timeMs = elsa.timer.getTime() * 1000
|
||||
if timeMs >= processEnd-- and elsa.SDL.getQueuedAudioSize(dev) == 0 then
|
||||
then
|
||||
processEnd = 0
|
||||
processQueue = {}
|
||||
firstProc = true
|
||||
return
|
||||
end
|
||||
if firstProc then
|
||||
local datatype = ffi.typeof("int16_t[?]")
|
||||
local rate = tonumber(obtained.freq)
|
||||
local vol = 32*255
|
||||
local offset = 0
|
||||
local duration = processTime
|
||||
|
||||
local time = 0
|
||||
local sampleCount = math.floor(duration*rate/1000)
|
||||
local data = datatype(sampleCount)
|
||||
for i=1, sampleCount do
|
||||
local value = 0
|
||||
for _, item in pairs(processQueue) do
|
||||
if time*1000 >= item.tstart and time*1000 < item.tend then
|
||||
local entry = item.entry
|
||||
for k, channel in pairs(entry) do
|
||||
local step = channel.frequency / rate
|
||||
|
||||
local remainder = (time*channel.frequency) % 1
|
||||
if remainder > 0.5 then
|
||||
value = value + vol
|
||||
else
|
||||
value = value - vol
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
data[i-1] = value
|
||||
time = time + (1 / rate)
|
||||
end
|
||||
if elsa.SDL.queueAudio(dev, data, sampleCount * 2) ~= 0 then
|
||||
error(elsa.getError())
|
||||
end
|
||||
print(elsa.SDL.getQueuedAudioSize(dev))
|
||||
firstProc = false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
obj.type = "sound"
|
||||
return obj,nil,mai,di
|
12
src/main.lua
12
src/main.lua
@ -73,6 +73,8 @@ function elsa.quit()
|
||||
config.save()
|
||||
end
|
||||
|
||||
machineTickHandlers = {}
|
||||
|
||||
if settings.components == nil then
|
||||
-- Format: string:type, (string or number or nil):address, (number or nil):slot, component parameters
|
||||
-- Read component files for parameter documentation
|
||||
@ -207,7 +209,7 @@ end if not machine.sleep then
|
||||
end
|
||||
|
||||
if settings.emulatorDebug then
|
||||
local filter = ""
|
||||
local filter = "sound"
|
||||
cprint = function(...)
|
||||
local args = {}
|
||||
local filtered = filter == ""
|
||||
@ -522,7 +524,12 @@ end
|
||||
|
||||
kbdcodes = {}
|
||||
|
||||
local lastUpdate = elsa.timer.getTime()
|
||||
function elsa.update(dt)
|
||||
if not dt then
|
||||
dt = elsa.timer.getTime() - lastUpdate
|
||||
end
|
||||
lastUpdate = elsa.timer.getTime()
|
||||
if #kbdcodes > 0 then
|
||||
local kbdcode = kbdcodes[1]
|
||||
table.remove(kbdcodes,1)
|
||||
@ -531,6 +538,9 @@ function elsa.update(dt)
|
||||
if modem_host then
|
||||
modem_host.processPendingMessages()
|
||||
end
|
||||
for _, v in pairs(machineTickHandlers) do
|
||||
v(dt)
|
||||
end
|
||||
machine.callBudget = maxCallBudget
|
||||
if machine.syncfunc then
|
||||
local func = machine.syncfunc
|
||||
|
@ -389,6 +389,7 @@ void SDL_CloseAudioDevice(SDL_AudioDeviceID dev);
|
||||
int SDL_QueueAudio(SDL_AudioDeviceID dev,
|
||||
const void* data,
|
||||
Uint32 len);
|
||||
Uint32 SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev);
|
||||
int SDL_SetClipboardText(const char *text);
|
||||
char * SDL_GetClipboardText(void);
|
||||
SDL_bool SDL_HasClipboardText(void);
|
||||
|
@ -202,6 +202,7 @@ register('unlockAudioDevice', 'SDL_UnlockAudioDevice')
|
||||
register('closeAudio', 'SDL_CloseAudio')
|
||||
register('closeAudioDevice', 'SDL_CloseAudioDevice')
|
||||
register('queueAudio', 'SDL_QueueAudio')
|
||||
register('getQueuedAudioSize', 'SDL_GetQueuedAudioSize')
|
||||
register('setClipboardText', 'SDL_SetClipboardText')
|
||||
register('getClipboardText', 'SDL_GetClipboardText')
|
||||
register('hasClipboardText', 'SDL_HasClipboardText')
|
||||
|
Loading…
x
Reference in New Issue
Block a user