From ae77cd226042464629873b088266fb176444db6a Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Sat, 21 Dec 2013 03:20:39 -0500 Subject: [PATCH] first pass at synchronizing threads, using RemoteRef and semaphore --- deps/src/Makefile | 10 ++-- deps/src/shim.c | 124 ++++++++++++++++++++++++++-------------------- src/PortAudio.jl | 62 +++++++++++++++++++---- 3 files changed, 127 insertions(+), 69 deletions(-) diff --git a/deps/src/Makefile b/deps/src/Makefile index 3a26862..246a98e 100644 --- a/deps/src/Makefile +++ b/deps/src/Makefile @@ -9,18 +9,16 @@ endif .PHONY: all clean check-env default #check-env: -#ifndef JULIA_ROOT -# $(error Environment variable JULIA_ROOT is not set.) +#ifndef JULIA_INC +# $(error Environment variable JULIA_INC is not set.) #endif -INC =-I"$(JULIA_ROOT)/usr/include" +#INC =-I"$(JULIA_INC)" FLAGS =-Wall -Wno-strict-aliasing -fno-omit-frame-pointer -fPIC CFLAGS =-g -LIBS = +LIBS =-lportaudio -lrt OBJS = shim.o -# libm can be removed later, it's just for the sin function used in testing -LIBS += -lportaudio -lm # Figure out OS and architecture OS = $(shell uname) diff --git a/deps/src/shim.c b/deps/src/shim.c index 2f14087..ef2f0f3 100644 --- a/deps/src/shim.c +++ b/deps/src/shim.c @@ -1,50 +1,80 @@ #include +#include +#include #include #include -#define SAMPLE_RATE 44100 +// some defines we need to include until PR 4997 is merged +STATIC_INLINE jl_function_t *jl_get_function(jl_module_t *m, const char *name) +{ + return (jl_function_t*) jl_get_global(m, jl_symbol(name)); +} -static int patestCallback(const void *inputBuffer, void *outputBuffer, +DLLEXPORT jl_value_t *jl_call2(jl_function_t *f, jl_value_t *a, jl_value_t *b) +{ + jl_value_t *v; + JL_TRY { + JL_GC_PUSH3(&f,&a,&b); + jl_value_t *args[2] = {a,b}; + v = jl_apply(f, args, 2); + JL_GC_POP(); + } + JL_CATCH { + v = NULL; + } + return v; +} + +static int paCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData); -static PaStream *sin_stream; +static PaStream *AudioStream; +static jl_value_t *JuliaRemote; +static sem_t CSemaphore; +static void *OutData = NULL; +static unsigned long OutFrames = 0; +static jl_function_t *RemotePutFunc = NULL; +static jl_value_t *RemotePutArg = NULL; -PaError play_sin(void) +void wake_callback_thread(void *outData, unsigned int outFrames) +{ + OutData = outData; + OutFrames = outFrames; + sem_post(&CSemaphore); +} + +PaError open_stream(unsigned int sampleRate, unsigned int bufSize, + jl_value_t *jlRemote) { PaError err; -// PaDeviceInfo *info; -// int numDevices; -// printf("Found Devices:\n"); -// numDevices = Pa_GetDeviceCount(); -// for(i = 0; i < numDevices; ++i) -// { -// info = Pa_GetDeviceInfo(i); -// printf("%s\n", info->name); -// } + JuliaRemote = jlRemote; + sem_init(&CSemaphore, 0, 0); + RemotePutFunc = jl_get_function(jl_base_module, "put"); + RemotePutArg = jl_box_int32(0); - err = Pa_OpenDefaultStream(&sin_stream, + err = Pa_OpenDefaultStream(&AudioStream, 0, /* no input channels */ - 2, /* stereo output */ + 1, /* mono output */ paFloat32, /* 32 bit floating point output */ - SAMPLE_RATE, - 1024, /* frames per buffer, i.e. the number of sample frames + sampleRate, + bufSize, /* frames per buffer, i.e. the number of sample frames that PortAudio will request from the callback. Many apps may want to use paFramesPerBufferUnspecified, which tells PortAudio to pick the best, possibly changing, buffer size.*/ - patestCallback, /* this is your callback function */ + paCallback, /* this is your callback function */ NULL); /*This is a pointer that will be passed to your callback*/ if(err != paNoError) { return err; } - err = Pa_StartStream(sin_stream); + err = Pa_StartStream(AudioStream); if(err != paNoError) { return err; @@ -53,22 +83,23 @@ PaError play_sin(void) return paNoError; } -PaError stop_sin(void) -{ - PaError err; - err = Pa_StopStream(sin_stream); - if(err != paNoError) - { - return err; - } - err = Pa_CloseStream(sin_stream); - if( err != paNoError ) - { - return err; - } - return paNoError; -} +//PaError stop_sin(void) +//{ +// PaError err; +// err = Pa_StopStream(sin_stream); +// if(err != paNoError) +// { +// return err; +// } +// +// err = Pa_CloseStream(sin_stream); +// if( err != paNoError ) +// { +// return err; +// } +// return paNoError; +//} /* @@ -76,35 +107,20 @@ PaError stop_sin(void) * It may called at interrupt level on some machines so don't do anything that * could mess up the system like calling malloc() or free(). */ -static int patestCallback(const void *inputBuffer, void *outputBuffer, +static int paCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { - float freq_l = 100; - float freq_r = 150; - static float phase_l = 0; - static float phase_r = 0; - - float *out = (float*)outputBuffer; unsigned int i; + sem_wait(&CSemaphore); for(i=0; i 2 * M_PI) - { - phase_l -= 2 * M_PI; - } - phase_r += (2 * M_PI * freq_r / SAMPLE_RATE); - if(phase_r > 2 * M_PI) - { - phase_r -= 2 * M_PI; - } - out[2*i] = sin(phase_l); - out[2*i + 1] = sin(phase_r); + ((float *)outputBuffer)[i] = ((float *)OutData)[i]; } + // TODO: copy the input data somewhere + jl_call2(RemotePutFunc, JuliaRemote, RemotePutArg); return 0; } diff --git a/src/PortAudio.jl b/src/PortAudio.jl index 2414250..81cd408 100644 --- a/src/PortAudio.jl +++ b/src/PortAudio.jl @@ -1,5 +1,3 @@ -# thanks to Gustavo Goretkin for the start on this - module PortAudio export play_sin, stop_sin @@ -11,20 +9,66 @@ typealias PaStream Void const PA_NO_ERROR = 0 + ############ Exported Functions ############# -function play_sin() - err = ccall((:play_sin, libportaudio_shim), PaError, ()) - handle_status(err) +function play_sin(sample_rate, buf_size) + jl_remote = RemoteRef() + + ccall((:open_stream, libportaudio_shim), PaError, + (Cuint, Cuint, RemoteRef), + sample_rate, buf_size, jl_remote) + + info("Launching Audio Task...") + yieldto(Task(audio_task)) + info("Audio Task Yielded") end -function stop_sin() - err = ccall((:stop_sin, libportaudio_shim), PaError, ()) - handle_status(err) -end +#function stop_sin() +# err = ccall((:stop_sin, libportaudio_shim), PaError, ()) +# handle_status(err) +#end ############ Internal Functions ############ +const sample_rate = 44100 +const buf_size = 512 +const freq = 100 +phase = 0.0 + +function process!(out_array, in_array) + global phase + for i in 1:buf_size + out_array[i] = sin(phase) + phase += 2pi * freq / sample_rate + if phase > 2pi + phase -= 2pi + end + end + return buf_size +end + +function wake_callback_thread(out_array) + ccall((:wake_callback_thread, libportaudio_shim), Void, + (Ptr{Void}, Cuint), + out_array, size(out_array, 1)) +end + +function audio_task() + info("Audio Task Launched") + in_array = zeros(buf_size) + out_array = zeros(buf_size) + while true + process!(out_array, in_array) + # wake the C code so it knows we've given it some more data + wake_callback_thread(out_array) + # wait for new data to be available from the sound card (and for it to + # have processed our last frame of data). At some point we should do + # something with the data we get from the callback + take(jl_remote) + end +end + function handle_status(err::PaError) if err != PA_NO_ERROR msg = ccall((:Pa_GetErrorText, "libportaudio"),