some minor tweaks and adds a couple convenience functions to file playback

This commit is contained in:
Spencer Russell 2014-01-10 15:56:42 -05:00
parent 7d7fd71341
commit 207fa26fd9
2 changed files with 144 additions and 18 deletions

101
file_api.md Normal file
View file

@ -0,0 +1,101 @@
Some possible API concepts for dealing with files
=================================================
Notes
-----
* requires libflac for flac decoding
Use Cases
---------
* Play a file through the speakers
* Use a file as input to an AudioNode for processing
* Read a file into an array
* Write an array into a file
* Write the output of an AudioNode to a file
IOStream API
------------
* users use standard julia "open" function to create an IOStream object
* FilePlayer <: AudioNode takes an IOStream and uses `sf_open_fd` to open and
play
* play(io::IOStream) creates a FilePlayer and plays it (just like ArrayPlayer)
* FileStream
### Play a file through the speakers
sndfile = open("myfile.wav")
play(sndfile)
close(sndfile)
### Use a file as input to an AudioNode for processing
sndfile = open("myfile.wav")
# maybe FilePlayer also takes a string input for convenience
node = FilePlayer(sndfile)
mixer = AudioMixer([node])
# etc.
### Read a file into an array
# TODO
### Write an array into a file
# TODO
### Write the output of an AudioNode to a file
node = SinOsc(440)
# ???
Separate Open Function API
--------------------------
* users use an explicit `af_open` function to open sound files
* `af_open` takes mode arguments just like the regular julia `open` function
* `af_open` returns a AudioFile instance.
### Play a file through the speakers
sndfile = af_open("myfile.wav")
play(sndfile)
close(sndfile)
or
play("myfile.wav")
### Use a file as input to an AudioNode for processing
sndfile = af_open("myfile.wav")
# FilePlayer also can take a string filename for convenience
node = FilePlayer(sndfile)
mixer = AudioMixer([node])
# etc.
### Read a file into an array
sndfile = af_open("myfile.wav")
vec = read(sndfile) # takes an optional arg for number of frames to read
close(sndfile)
### Write an array into a file
sndfile = af_open("myfile.wav", "w") #TODO: need to specify format
vec = rand(Float32, 441000) # 10 seconds of noise
write(sndfile, vec)
close(sndfile)
### Write the output of an AudioNode to a file
sndfile = af_open("myfile.wav", "w") #TODO: need to specify format
node = SinOsc(440)
write(sndfile, node, 44100) # record 1 second, optional block_size
# note that write() can handle sample depth conversions, and render() is
# called with the sampling rate of the file
close(sndfile)

View file

@ -1,8 +1,7 @@
export openAudio, closeAudio, readFrames, FileInput
export af_open, FilePlayer
const sndfile = "libsndfile"
const SFM_READ = int32(0x10)
const SFM_WRITE = int32(0x20)
@ -13,14 +12,21 @@ type SF_INFO
format::Int32
sections::Int32
seekable::Int32
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
end
type Sndfile
type AudioFile
filePtr::Ptr{Void}
sfinfo::SF_INFO
end
function openAudio(path::String)
function af_open(path::String, mode::String="r")
# TODO: handle write/append modes
sfinfo = SF_INFO(0, 0, 0, 0, 0, 0)
filePtr = ccall((:sf_open, sndfile), Ptr{Void},
(Ptr{Uint8}, Int32, Ptr{SF_INFO}),
@ -30,24 +36,28 @@ function openAudio(path::String)
error(bytestring(errmsg))
end
return Sndfile(filePtr, sfinfo)
return AudioFile(filePtr, sfinfo)
end
function closeAudio(file::Sndfile)
function Base.close(file::AudioFile)
err = ccall((:sf_close, sndfile), Int32, (Ptr{Void},), file.filePtr)
if err != 0
error("Failed to close file")
end
end
function openAudio(f::Function, path::String)
file = openAudio(path)
function af_open(f::Function, path::String)
file = af_open(path)
f(file)
closeAudio(file)
close(file)
end
function readFrames(file::Sndfile, nframes::Integer, dtype::Type = Int16)
# 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)
arr = []
@assert file.sfinfo.channels <= 2
if file.sfinfo.channels == 2
arr = zeros(dtype, 2, nframes)
else
@ -79,24 +89,29 @@ function readFrames(file::Sndfile, nframes::Integer, dtype::Type = Int16)
return arr
end
type FileInput <: AudioNode
type FilePlayer <: AudioNode
active::Bool
file::Sndfile
file::AudioFile
function FileInput(path::String)
node = new(false, openAudio(path))
finalizer(node, node -> closeAudio(node.file))
function FilePlayer(file::AudioFile)
node = new(false, file)
finalizer(node, node -> close(node.file))
return node
end
function FilePlayer(path::String)
return FilePlayer(af_open(path))
end
end
function render(node::FileInput, device_input::AudioBuf, info::DeviceInfo)
function render(node::FilePlayer, device_input::AudioBuf, info::DeviceInfo)
@assert node.file.sfinfo.samplerate == info.sample_rate
audio = readFrames(node.file, info.buf_size, AudioSample)
audio = read(node.file, info.buf_size, AudioSample)
if audio == Nothing
return zeros(AudioSample, info.buf_size), false
node.active = false
return zeros(AudioSample, info.buf_size), node.active
end
# if the file is stereo, mix the two channels together
@ -106,3 +121,13 @@ function render(node::FileInput, device_input::AudioBuf, info::DeviceInfo)
return audio, node.active
end
function play(filename::String, args...)
player = FilePlayer(filename)
play(player, args...)
end
function play(file::AudioFile, args...)
player = FilePlayer(file)
play(player, args...)
end