Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added single stream read/write, callback capability #55

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
9c23d02
Update portaudio.jl
wherrera10 Nov 1, 2015
3f90e3c
Update portaudio.jl
wherrera10 Nov 2, 2015
57fcabb
Update REQUIRE
wherrera10 Nov 3, 2015
6786c2b
Update AudioIO.jl
wherrera10 Nov 3, 2015
e87ef29
Update nodes.jl
wherrera10 Nov 3, 2015
2c4b219
Update sndfile.jl
wherrera10 Nov 3, 2015
1ad2d5b
Update sndfile.jl
wherrera10 Nov 3, 2015
3c663c6
Update AudioIO.jl
wherrera10 Nov 4, 2015
348e257
Update nodes.jl
wherrera10 Nov 4, 2015
826fdaf
Update portaudio.jl
wherrera10 Nov 4, 2015
7c87bfa
Create test_single_stream_input_and_output.jl
wherrera10 Nov 16, 2015
5f4c990
Update README.md
wherrera10 Nov 16, 2015
8449a47
Update portaudio.jl
wherrera10 Nov 19, 2015
b6099d8
Update portaudio.jl
wherrera10 Nov 19, 2015
46db9e4
Update portaudio.jl
wherrera10 Nov 19, 2015
409597b
Update portaudio.jl
wherrera10 Nov 19, 2015
929a805
Update portaudio.jl
wherrera10 Nov 19, 2015
c0d4da8
Update portaudio.jl
wherrera10 Nov 19, 2015
e2d81f3
Update portaudio.jl
wherrera10 Nov 20, 2015
fbd489f
Update portaudio.jl
wherrera10 Nov 20, 2015
5776cae
Update portaudio.jl
wherrera10 Nov 20, 2015
70d3cc3
Update test_single_stream_input_and_output.jl
wherrera10 Nov 20, 2015
81fcf8c
Update README.md
wherrera10 Nov 20, 2015
978752f
Update portaudio.jl
wherrera10 Nov 21, 2015
e6b5491
Update portaudio.jl
wherrera10 Nov 21, 2015
fb28a94
Update AudioIO.jl
wherrera10 Nov 21, 2015
08bb3c9
Update README.md
wherrera10 Nov 21, 2015
0dff384
Update portaudio.jl
wherrera10 Nov 22, 2015
eb98500
Delete test_single_stream_input_and_output.jl
wherrera10 Nov 22, 2015
670e834
Create read_and_write.jl
wherrera10 Nov 22, 2015
3b37b35
Update read_and_write.jl
wherrera10 Nov 22, 2015
f50df4a
Update portaudio.jl
wherrera10 Nov 22, 2015
dba68ff
Update portaudio.jl
wherrera10 Nov 22, 2015
696d042
Update portaudio.jl
wherrera10 Nov 22, 2015
7e56d86
Update portaudio.jl
wherrera10 Nov 23, 2015
b14cd37
Update read_and_write.jl
wherrera10 Nov 23, 2015
c309b95
Update portaudio.jl
wherrera10 Nov 23, 2015
e719088
Update read_and_write.jl
wherrera10 Nov 23, 2015
d67cda8
Update portaudio.jl
wherrera10 Nov 24, 2015
f9c0b50
Update read_and_write.jl
wherrera10 Nov 24, 2015
3e5fc73
Create callback_read_write.jl
wherrera10 Nov 24, 2015
a771958
Update README.md
wherrera10 Nov 24, 2015
78230b1
Update README.md
wherrera10 Nov 25, 2015
1944776
Update callback_read_write.jl
wherrera10 Nov 25, 2015
e54ecba
Update portaudio.jl
wherrera10 Nov 25, 2015
e3a5e30
Update portaudio.jl
wherrera10 Nov 26, 2015
87fc0a5
Update portaudio.jl
wherrera10 Nov 26, 2015
3b01c91
Create record_to_wav.jl
wherrera10 Nov 30, 2015
ce4554f
Update record_to_wav.jl
wherrera10 Dec 1, 2015
1296639
Update record_to_wav.jl
wherrera10 Dec 1, 2015
70eb277
Create callback_bands.jl
wherrera10 Dec 2, 2015
743418e
Update callback_bands.jl
wherrera10 Dec 2, 2015
6a03fe3
Update callback_bands.jl
wherrera10 Dec 2, 2015
17578e9
Update record_to_wav.jl
wherrera10 Dec 2, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,31 @@ AudioStream is an abstract type, which currently has a PortAudioStream subtype
that writes to the sound card, and a TestAudioStream that is used in the unit
tests.

Currently only 1 stream at a time is supported so there's no reason to provide
With the method PortAudioStream, which uses the default input and output device(s),
only 1 stream at a time is supported, so there's no reason to provide
an explicit stream to the `play` function. The stream has a root mixer field
which is an instance of the AudioMixer type, so that multiple AudioNodes
can be heard at the same time. Whenever a new frame of audio is needed by the
sound card, the stream calls the `render` method on the root audio mixer, which
will in turn call the `render` methods on any input AudioNodes that are set
up as inputs.

When a Pa_AudioStream is created via a call to
Pa_AudioStream(device_index, channels=2, input=false,
sample_rate::Integer=44100,
framesPerBuffer::Integer=2048,
show_warnings::Bool=false,
sample_format::PaSampleFormat=paInt16,
callback=0)
a single stream is opened for reading or writing. This returns a Pa_AudioStream on success,
which is used either via the functions AudioIO.read(::Pa_AudioStream) or
AudioIO.write(::Pa_AudioStream, buffer) for read and write respectively.

If nonblocking I/O is needed, a callback may be passed to the Pa_AudioStream constructor,
which is provided by the user and should return a buffer of frames if used for output and should
handle a buffer of frames as an argument if used as input. This is similar
to the methods used by PyAudio's python interface to PortAudio. See the examples also.

Installation
------------

Expand Down
1 change: 1 addition & 0 deletions REQUIRE
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
julia 0.3-
BinDeps
Compat
@osx Homebrew
@windows WinRPM
34 changes: 34 additions & 0 deletions examples/callback_bands.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-

# Examples for Julia AudioIO module

# display relative (not absolute) dB of bass, mid, treble bands

using AudioIO, DSP


"""
Custom callback for read, processes passed data array
"""
function process_read(buf)
fchunk = map(Float64, buf)
pgram = welch_pgram(fchunk, 4096, 0, fs=SRATE*CHANNELS)
pxx = power(pgram)
frqs = freq(pgram)
bass = log(sum(abs(pxx[(frqs .<= 640.0) & (frqs .> 100.0)])))
midrange = log(sum(abs(pxx[(frqs .<= 5120.0) & (frqs .> 640.0)])))
treble = log(sum(abs(pxx[(frqs .<= 20480.0) & (frqs .> 5120.0)])))
println("bass $bass, midrange $midrange, treble $treble")
end

CHUNKSIZE = 40960
FORMAT = AudioIO.paInt16
CHANNELS = Cint(2)
SRATE = 44100
istream = AudioIO.open_read(Cint(0), CHANNELS, SRATE, CHUNKSIZE,
false, FORMAT, process_read)
println("Starting callback type reading, control-C to stop")

while true
sleep(20)
end
92 changes: 92 additions & 0 deletions examples/callback_read_write.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-

# Examples for Julia AudioIO module

using AudioIO

CHUNKSIZE = 40960
FORMAT = AudioIO.paInt16
CALLBACK_DATATYPE = AudioIO.PaSampleFormat_to_T(FORMAT)
CHANNELS = 2
SRATE = 44100
RECORD_SECONDS = 3

BUFFER = zeros(Int16, SRATE * RECORD_SECONDS * 4)
BUFSIZE = SRATE * RECORD_SECONDS

"""
choose the devices for 2 channel IO
"""
function choose_input_output()
devices = get_audio_devices()
indev = -1
outdev = -1
for aud in devices
println("$(aud.device_index) $(aud.name)")
if (aud.max_input_channels == CHANNELS) & (indev == -1 )
indev = aud.device_index
end
if(aud.max_output_channels == CHANNELS) & (outdev == -1)
outdev = aud.device_index
end
end
if indev == -1
info("Appropriate input device not found.")
elseif outdev == -1
info("Appropriate output device not found.")
else
info("Using input device ", bytestring(devices[indev + 1].name),
", number ", devices[indev + 1].device_index,
" and output device ", bytestring(devices[outdev + 1].name),
", number ", devices[outdev + 1].device_index)
end
return indev, outdev
end

read_position = 0
"""
Custom callback for read, processes passed data array
"""
function store_read(buf)
read_size = length(buf)
global read_position
if read_position + read_size > length(BUFFER)
read_size = length(BUFFER) - read_position
end
if read_size > 0
BUFFER[read_position + 1 : read_position + read_size] =
buf[1: read_size]
read_position += read_size
end
end

write_position = 0
"""
Custom callback for write, returns data array
"""
function get_writeable()
write_size = CHUNKSIZE * CHANNELS
global write_position
if write_position + write_size > length(BUFFER)
write_position = length(BUFFER) - write_size
retval = 1
end
start_position = write_position + 1
write_position += write_size
buf = BUFFER[start_position: write_position]
buf
end

INS, OUTS = choose_input_output()

istream = AudioIO.open_read(INS, CHANNELS, SRATE, CHUNKSIZE,
false, FORMAT, store_read)
println("Started callback type reading device number $INS")
sleep(5)
AudioIO.Pa_CloseStream(istream.stream)

ostream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE,
false, FORMAT, get_writeable)
println("Started callback type writing device number $OUTS")
sleep(3)
AudioIO.Pa_CloseStream(ostream.stream)
110 changes: 110 additions & 0 deletions examples/read_and_write.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-

# Examples for Julia AudioIO module

using AudioIO

CHUNKSIZE = 40960
FORMAT = AudioIO.paInt16
CHANNELS = 2
SRATE = 44100
RECORD_SECONDS = 3

BUFFER = zeros(Int16, SRATE * RECORD_SECONDS * 4)
BUFSIZE = SRATE * RECORD_SECONDS

"""
choose the devices for 2 channel IO
"""
function choose_input_output()
devices = get_audio_devices()
indev = -1
outdev = -1
for aud in devices
println("$(aud.device_index) $(aud.name)")
if (aud.max_input_channels == CHANNELS) & (indev == -1 )
indev = aud.device_index
end
if(aud.max_output_channels == CHANNELS) & (outdev == -1)
outdev = aud.device_index
end
end
if indev == -1
info("Appropriate input device not found.")
elseif outdev == -1
info("Appropriate output device not found.")
else
info("Using input device ", bytestring(devices[indev + 1].name),
", number ", devices[indev + 1].device_index,
" and output device ", bytestring(devices[outdev + 1].name),
", number ", devices[outdev + 1].device_index)
end
return indev, outdev
end

"""
read from input
"""
function read_blocking(devnum, buffer)
instream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE)
buf = AudioIO.read(instream, BUFSIZE)
buflen = length(buf)
buffer[1: buflen] = buf[1: buflen]
AudioIO.Pa_CloseStream(instream.stream)
end

"""
write to output
"""
function write_blocking(devnum, buffer)
outstream = AudioIO.open_write(devnum, CHANNELS, SRATE, CHUNKSIZE)
AudioIO.write(outstream, buffer)
AudioIO.Pa_CloseStream(outstream.stream)
end

"""
Create a string of numbers representing a sinewave audio tone
"""
function make_note_buffer(frequency, amplitude, duration, srate)
N = round(Int, srate / frequency)
T = round(Int, frequency * duration) # repeat for T cycles
dt = 1.0 / srate
tone = zeros(Int16, (N + N) * T)
idx = 1
while idx < (N + N) * T
tone[idx] = round(Int, amplitude * sin(2 * pi * frequency *
idx * dt) * 32767.0)
tone[idx + 1] = tone[idx]
idx += 2
end
tone
end

"""
Write a note to output device
"""
function play_note(frequency, amplitude, duration, srate, ostream)
note = make_note_buffer(frequency, amplitude, duration, srate)
AudioIO.write(ostream, note)
end

INS, OUTS = choose_input_output()

read_blocking(INS, BUFFER)
println("Finished blocking type reading device number $INS")
println("Recording volume is $(mean(abs(BUFFER))*(100/16783))% of max")
sleep(2)

write_blocking(OUTS, BUFFER)
println("Finished blocking type writing device number $OUTS")

outstream = AudioIO.open_write(OUTS, CHANNELS, SRATE, CHUNKSIZE)
# play the C major scale
scale = [130.8, 146.8, 164.8, 174.6, 195.0, 220.0, 246.9, 261.6]
for note in scale
play_note(note, 0.1, 0.75, SRATE, outstream)
end
# up an octave
for note in scale[2:8]
play_note(2*note, 0.1, 0.75, SRATE, outstream)
end
78 changes: 78 additions & 0 deletions examples/record_to_wav.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-

# Examples for Julia AudioIO module

using AudioIO
using WAV

CHUNKSIZE = 40960
FORMAT = AudioIO.paInt16
CHANNELS = 2
SRATE = 44100
RECORD_SECONDS = 20

"""
choose the devices for 2 channel IO
"""
function choose_input_output()
devices = get_audio_devices()
indev = -1
outdev = -1
for aud in devices
println("$(aud.device_index) $(aud.name)")
if (aud.max_input_channels == CHANNELS) & (indev == -1 )
indev = aud.device_index
end
if(aud.max_output_channels == CHANNELS) & (outdev == -1)
outdev = aud.device_index
end
end
if indev == -1
info("Appropriate input device not found.")
elseif outdev == -1
info("Appropriate output device not found.")
else
info("Using input device ", bytestring(devices[indev + 1].name),
", number ", devices[indev + 1].device_index,
" and output device ", bytestring(devices[outdev + 1].name),
", number ", devices[outdev + 1].device_index)
end
return indev, outdev
end

"""
read from input
"""
function record_audio(devnum, seconds)
instream = AudioIO.open_read(devnum, CHANNELS, SRATE, CHUNKSIZE)
bufsize = seconds * SRATE
buf = AudioIO.read(instream, bufsize)
AudioIO.Pa_CloseStream(instream.stream)
buf
end

"""
write to WAV file
"""
function write_as_WAV(buffer, filename="temp.WAV")
fio = open(filename, "w")
WAV.wavwrite(deinterlace_stereo(buffer), fio, Fs=SRATE)
end

"""
interlaced to noninterlaced stereo data
"""
function deinterlace_stereo(buffer)
reshape([buffer[1:2:end]; buffer[2:2:end]],
(floor(Int, length(buffer)/2), 2))
end

INS, OUTS = choose_input_output()

println("Starting recording...")
BUF = record_audio(INS, RECORD_SECONDS)
println("Finished reading from device number $INS")
println("Recording volume was $(mean(abs(BUF))*(100/16783))% of max")

write_as_WAV(BUF)
println("Finished writing to WAV file.")
4 changes: 3 additions & 1 deletion src/AudioIO.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module AudioIO
using Compat
importall Base.Operators

# export the basic API
export play, stop, get_audio_devices
export play, stop, get_audio_devices, open_read, open_write, read, write

# default stream used when none is given
_stream = nothing
Expand Down
6 changes: 3 additions & 3 deletions src/nodes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ end

# Generates a sin tone at the given frequency

type SinOscRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer
@compat type SinOscRenderer{T<:Union{Float32, AudioNode}} <: AudioRenderer
freq::T
phase::Float32
buf::AudioBuf
Expand Down Expand Up @@ -124,7 +124,7 @@ end
Base.push!(mixer::AudioMixer, node::AudioNode) = push!(mixer.renderer.inputs, node)

#### Gain ####
type GainRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer
@compat type GainRenderer{T<:Union{Float32, AudioNode}} <: AudioRenderer
in1::AudioNode
in2::T
buf::AudioBuf
Expand Down Expand Up @@ -228,7 +228,7 @@ function play(arr::AudioBuf, args...)
end

# If the array is the wrong floating type, convert it
function play{T <: FloatingPoint}(arr::Array{T}, args...)
@compat function play{T <: AbstractFloat}(arr::Array{T}, args...)
arr = convert(AudioBuf, arr)
play(arr, args...)
end
Expand Down
Loading