some minor tweaks and adds a couple convenience functions to file playback
This commit is contained in:
parent
7d7fd71341
commit
207fa26fd9
2 changed files with 144 additions and 18 deletions
101
file_api.md
Normal file
101
file_api.md
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue