Skip to content

Instantly share code, notes, and snippets.

@runehog
Created May 27, 2016 17:33
Show Gist options
  • Save runehog/2bac637f819318fa5d784e768f33893e to your computer and use it in GitHub Desktop.
Save runehog/2bac637f819318fa5d784e768f33893e to your computer and use it in GitHub Desktop.
use "collections"
use "time"
use "lib:portaudio_sink"
use "lib:portaudio"
type Buffer is (Array[F32], Array[U8])
class StopTimer is TimerNotify
var _a: Main
new iso create(a: Main) =>
_a = a
fun ref apply(timer: Timer, count: U64): Bool =>
_a.done()
false
fun ref cancel(timer: Timer) =>
false
actor Main
"""Sets up audio stream."""
var _env: Env
let _buffer_count: USize
let _frame_count: USize
var _preroll: USize
var _buffers: Array[Buffer]
var _index: USize
var _timers: Timers
var _timer: Timer tag
var _phasor: F64
let _phasor_inc: F64
let _gain: F64
new create(env: Env) =>
_env = env
_buffer_count = 64
_frame_count = 128
_preroll = 0 // Will be filled in by preroll().
_buffers = recover Array[Buffer] end
_index = 0
_phasor = 0.0
_phasor_inc = 880.0 / 44100.0
_gain = 0.2
_timers = Timers
let t = Timer(StopTimer(this), 10_000_000_000, 0)
_timer = t
_timers(consume t)
let open_result = @init_output_stream[I32](
_frame_count,
_buffer_count,
addressof this.add_buffer,
addressof this.preroll,
addressof this.produce,
this)
_env.out.print("got open_result: " + open_result.string())
// The stream will be started by produce() when the preroll phase is done.
be done() =>
_env.out.print("stop!")
be add_buffer(buf: Pointer[F32] iso, ready: Pointer[U8] iso) =>
let frame_count = _frame_count
let buf_array: Array[F32] iso = recover
Array[F32].from_cstring(consume buf, frame_count)
end
let ready_array: Array[U8] iso = recover
Array[U8].from_cstring(consume ready, USize(1))
end
_buffers.push((consume buf_array, consume ready_array))
be preroll() =>
_preroll = _buffers.size()
_env.out.print("preroll out! " + _preroll.string() + " buffers.")
be produce(timestamp: F64) =>
try
let buf = _buffers(_index)
// Fill the next buffer.
for i in Range[USize](0, _frame_count) do
buf._1.update(i, (_phasor * _gain).f32())
_phasor = _phasor + _phasor_inc
if _phasor > 1.0 then
_phasor = _phasor - 1.0
end
end
// Set the "ready" flag on the buffer.
buf._2.update(0, U8(1))
// Advance buffer pointer.
_index = _index + 1
if _index == _buffers.size() then
_index = 0
end
// If we're prerolling, see if it's time to start the stream.
if _preroll > 0 then
_preroll = _preroll - 1
if _preroll == 0 then
//_env.out.print("start...")
let start_result = @start_output_stream[I32]()
//_env.out.print("got start_result: " + start_result.string())
end
end
end
#include <assert.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <portaudio.h>
#include <pthread.h>
typedef struct _buffer {
uint8_t ready;
float* buf;
} _buffer;
static PaStream* g_stream = NULL;
static unsigned long g_frames_per_buffer = 0;
static _buffer* g_buffers;
static size_t g_buffer_count = 0;
static size_t g_next_buffer = 0;
static pthread_t g_produce_thread;
static sem_t g_produce_sem;
static double g_produce_timestamp;
static uint32_t g_produce_underruns;
// the ponyland callbacks
typedef void (*pony_output_add_buffer_cb)(void* pony_object, float* io_buffer, uint8_t* io_ready);
typedef void (*pony_output_preroll_cb)(void* pony_object);
typedef void (*pony_output_produce_cb)(void* pony_object, double timestamp);
static pony_output_add_buffer_cb g_add_buffer_cb = NULL;
static pony_output_preroll_cb g_preroll_cb = NULL;
static pony_output_produce_cb g_produce_cb = NULL;
static void* g_pony_object;
void init_buffers(unsigned long buffer_count) {
int i;
g_buffers = (_buffer*)malloc(sizeof(_buffer) * buffer_count);
for (i = 0; i < buffer_count; ++i) {
g_buffers[i].ready = 0;
g_buffers[i].buf = (float*) malloc(sizeof(float) * g_frames_per_buffer);
}
g_buffer_count = buffer_count;
}
void preroll() {
printf("preroll: add buffer %p preroll %p produce %p\n",
g_add_buffer_cb,
g_preroll_cb,
g_produce_cb);
// Send buffers.
int i;
for (i = 0; i < g_buffer_count; ++i) {
(*g_add_buffer_cb)(g_pony_object, g_buffers[i].buf, &g_buffers[i].ready);
}
// Signal start of preroll phase.
(*g_preroll_cb)(g_pony_object);
// Fill.
for (i = 0; i < g_buffer_count; ++i) {
(*g_produce_cb)(g_pony_object, 0.);
}
}
void* produce_thread(void* unused) {
pony_register_thread();
uint32_t underruns = g_produce_underruns;
while (1) {
int wait_result = sem_wait(&g_produce_sem);
if (wait_result != 0) {
printf("Producer thread done\n");
return;
}
if (g_produce_underruns > underruns) {
printf("%d underruns\n", g_produce_underruns - underruns);
underruns = g_produce_underruns;
}
(*g_produce_cb)(g_pony_object, g_produce_timestamp);
}
}
void init_produce_thread() {
int result;
g_produce_timestamp = 0;
result = sem_init(&g_produce_sem, 0, 0);
assert(result == 0);
pthread_attr_t attr;
pthread_attr_init(&attr);
result = pthread_create(&g_produce_thread, &attr, &produce_thread, NULL);
assert(result == 0);
}
// the portaudio callback
int output_stream_cb(const void* input,
void* output,
unsigned long frames_per_buffer,
const PaStreamCallbackTimeInfo* time_info,
PaStreamCallbackFlags status_flags,
void* user) {
assert(frames_per_buffer == g_frames_per_buffer);
if (g_buffers[g_next_buffer].ready) {
memcpy(output, g_buffers[g_next_buffer].buf, frames_per_buffer * sizeof(float));
g_buffers[g_next_buffer].ready = 0;
g_produce_timestamp = time_info->outputBufferDacTime;
sem_post(&g_produce_sem);
if (++g_next_buffer == g_buffer_count) {
g_next_buffer = 0;
}
} else {
// underrun!
g_produce_underruns++;
}
return paContinue;
}
int init_output_stream(unsigned long frames_per_buffer,
unsigned long buffer_count,
pony_output_add_buffer_cb add_buffer_cb,
pony_output_preroll_cb preroll_cb,
pony_output_produce_cb produce_cb,
void* pony_object) {
printf("init: cbs: add buffer %p preroll %p produce %p\n",
add_buffer_cb,
preroll_cb,
produce_cb);
if (NULL != g_stream) {
return paDeviceUnavailable;
}
PaError result = Pa_Initialize();
if (paNoError != result) {
return result;
}
PaStream* stream;
result = Pa_OpenDefaultStream(&stream,
0, // no inputs
1, // 1 output
paFloat32,
44100,
frames_per_buffer,
&output_stream_cb,
pony_object);
if (paNoError != result) {
return result;
}
g_stream = stream;
g_frames_per_buffer = frames_per_buffer;
g_pony_object = pony_object;
g_add_buffer_cb = add_buffer_cb;
g_preroll_cb = preroll_cb;
g_produce_cb = produce_cb;
init_buffers(buffer_count);
preroll();
return paNoError;
}
int start_output_stream() {
init_produce_thread();
PaError result = Pa_StartStream(g_stream);
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment