diff --git a/file_api.md b/file_api.md new file mode 100644 index 0000000..f484f93 --- /dev/null +++ b/file_api.md @@ -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) + diff --git a/src/sndfile.jl b/src/sndfile.jl index 70183aa..bf557be 100644 --- a/src/sndfile.jl +++ b/src/sndfile.jl @@ -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