2014-01-10 21:56:42 +01:00
|
|
|
export af_open, FilePlayer
|
2014-01-06 17:24:53 +01:00
|
|
|
|
|
|
|
const sndfile = "libsndfile"
|
|
|
|
|
|
|
|
const SFM_READ = int32(0x10)
|
|
|
|
const SFM_WRITE = int32(0x20)
|
|
|
|
|
|
|
|
type SF_INFO
|
|
|
|
frames::Int64
|
|
|
|
samplerate::Int32
|
|
|
|
channels::Int32
|
|
|
|
format::Int32
|
|
|
|
sections::Int32
|
|
|
|
seekable::Int32
|
2014-01-10 21:56:42 +01:00
|
|
|
|
|
|
|
function SF_INFO(frames::Integer, samplerate::Integer, channels::Integer,
|
|
|
|
format::Integer, sections::Integer, seekable::Integer)
|
|
|
|
new(int64(frames), int32(samplerate), int32(channels), int32(format),
|
|
|
|
int32(sections), int32(seekable))
|
|
|
|
end
|
2014-01-06 17:24:53 +01:00
|
|
|
end
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
type AudioFile
|
2014-01-06 17:24:53 +01:00
|
|
|
filePtr::Ptr{Void}
|
|
|
|
sfinfo::SF_INFO
|
|
|
|
end
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
function af_open(path::String, mode::String="r")
|
|
|
|
# TODO: handle write/append modes
|
2014-01-06 17:24:53 +01:00
|
|
|
sfinfo = SF_INFO(0, 0, 0, 0, 0, 0)
|
|
|
|
filePtr = ccall((:sf_open, sndfile), Ptr{Void},
|
|
|
|
(Ptr{Uint8}, Int32, Ptr{SF_INFO}),
|
|
|
|
path, SFM_READ, &sfinfo)
|
|
|
|
if filePtr == C_NULL
|
|
|
|
errmsg = ccall((:sf_strerror, sndfile), Ptr{Uint8}, (Ptr{Void},), filePtr)
|
|
|
|
error(bytestring(errmsg))
|
|
|
|
end
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
return AudioFile(filePtr, sfinfo)
|
2014-01-06 17:24:53 +01:00
|
|
|
end
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
function Base.close(file::AudioFile)
|
2014-01-06 17:24:53 +01:00
|
|
|
err = ccall((:sf_close, sndfile), Int32, (Ptr{Void},), file.filePtr)
|
|
|
|
if err != 0
|
|
|
|
error("Failed to close file")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
function af_open(f::Function, path::String)
|
|
|
|
file = af_open(path)
|
2014-01-06 17:24:53 +01:00
|
|
|
f(file)
|
2014-01-10 21:56:42 +01:00
|
|
|
close(file)
|
2014-01-06 17:24:53 +01:00
|
|
|
end
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
# TODO: we should implement a general read(node::AudioNode) that pulls data
|
|
|
|
# through an arbitrary render chain and returns the result as a vector
|
|
|
|
function Base.read(file::AudioFile, nframes::Integer = file.sfinfo.frames,
|
|
|
|
dtype::Type = Int16)
|
2014-01-06 17:24:53 +01:00
|
|
|
arr = []
|
2014-01-10 21:56:42 +01:00
|
|
|
@assert file.sfinfo.channels <= 2
|
2014-01-06 17:24:53 +01:00
|
|
|
if file.sfinfo.channels == 2
|
2014-01-09 00:25:55 +01:00
|
|
|
arr = zeros(dtype, 2, nframes)
|
2014-01-06 17:24:53 +01:00
|
|
|
else
|
2014-01-09 00:25:55 +01:00
|
|
|
arr = zeros(dtype, nframes)
|
2014-01-06 17:24:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
if dtype == Int16
|
|
|
|
nread = ccall((:sf_readf_short, sndfile), Int64,
|
|
|
|
(Ptr{Void}, Ptr{Int16}, Int64),
|
|
|
|
file.filePtr, arr, nframes)
|
|
|
|
elseif dtype == Int32
|
|
|
|
nread = ccall((:sf_readf_int, sndfile), Int64,
|
|
|
|
(Ptr{Void}, Ptr{Int32}, Int64),
|
|
|
|
file.filePtr, arr, nframes)
|
|
|
|
elseif dtype == Float32
|
|
|
|
nread = ccall((:sf_readf_float, sndfile), Int64,
|
|
|
|
(Ptr{Void}, Ptr{Float32}, Int64),
|
|
|
|
file.filePtr, arr, nframes)
|
|
|
|
elseif dtype == Float64
|
|
|
|
nread = ccall((:sf_readf_double, sndfile), Int64,
|
|
|
|
(Ptr{Void}, Ptr{Float64}, Int64),
|
|
|
|
file.filePtr, arr, nframes)
|
|
|
|
end
|
|
|
|
|
|
|
|
if nread == 0
|
2014-01-09 00:25:55 +01:00
|
|
|
return Nothing
|
2014-01-06 17:24:53 +01:00
|
|
|
end
|
|
|
|
|
2014-01-09 00:25:55 +01:00
|
|
|
return arr
|
|
|
|
end
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
type FilePlayer <: AudioNode
|
2014-01-09 00:25:55 +01:00
|
|
|
active::Bool
|
2014-01-10 21:56:42 +01:00
|
|
|
file::AudioFile
|
2014-01-09 00:25:55 +01:00
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
function FilePlayer(file::AudioFile)
|
|
|
|
node = new(false, file)
|
|
|
|
finalizer(node, node -> close(node.file))
|
2014-01-09 00:25:55 +01:00
|
|
|
return node
|
|
|
|
end
|
2014-01-10 21:56:42 +01:00
|
|
|
|
|
|
|
function FilePlayer(path::String)
|
|
|
|
return FilePlayer(af_open(path))
|
|
|
|
end
|
2014-01-09 00:25:55 +01:00
|
|
|
end
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
function render(node::FilePlayer, device_input::AudioBuf, info::DeviceInfo)
|
2014-01-09 00:25:55 +01:00
|
|
|
@assert node.file.sfinfo.samplerate == info.sample_rate
|
|
|
|
|
2014-01-10 21:56:42 +01:00
|
|
|
audio = read(node.file, info.buf_size, AudioSample)
|
2014-01-09 00:25:55 +01:00
|
|
|
|
|
|
|
if audio == Nothing
|
2014-01-10 21:56:42 +01:00
|
|
|
node.active = false
|
|
|
|
return zeros(AudioSample, info.buf_size), node.active
|
2014-01-09 00:25:55 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# if the file is stereo, mix the two channels together
|
|
|
|
if node.file.sfinfo.channels == 2
|
|
|
|
return (audio[1, :] / 2) + (audio[2, :] / 2), node.active
|
2014-01-06 17:24:53 +01:00
|
|
|
end
|
|
|
|
|
2014-01-09 00:25:55 +01:00
|
|
|
return audio, node.active
|
2014-01-06 17:24:53 +01:00
|
|
|
end
|
2014-01-10 21:56:42 +01:00
|
|
|
|
|
|
|
function play(filename::String, args...)
|
|
|
|
player = FilePlayer(filename)
|
|
|
|
play(player, args...)
|
|
|
|
end
|
|
|
|
|
|
|
|
function play(file::AudioFile, args...)
|
|
|
|
player = FilePlayer(file)
|
|
|
|
play(player, args...)
|
|
|
|
end
|