Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion bend2/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,9 @@ function eff_src(path: string, seen: Set<string>): string {
return "";
}
seen.add(path);
return fs.readFileSync(path, "utf8");
const dir = path.slice(0, path.lastIndexOf("/"));
return fs.readFileSync(path, "utf8").replace(/^\/\/ @src ([a-z0-9_]+\.c)$/gm,
(_: string, name: string) => eff_src(dir + "/" + name, seen));
}

// Io
Expand Down
163 changes: 1 addition & 162 deletions bend2/effs/audio_close.c
Original file line number Diff line number Diff line change
@@ -1,168 +1,7 @@
// Audio
// =====

// The ring: 4096 float32 stereo frames. The effects fill it on the
// evaluator's thread; the device's callback drains it and pads the
// rest of its buffer with silence. The same block sits in
// audio_write.c and audio_close.c under this guard, so that any one
// of the three effects compiles alone.
#ifndef IO_RING
#define IO_RING 4096u

#ifdef __OBJC__
#import <AudioToolbox/AudioToolbox.h>
#elif defined(__linux__)
#include <alsa/asoundlib.h>
#endif

typedef struct {
_Atomic(u64) read, written;
float pcm[IO_RING * 2];
#ifdef __OBJC__
AudioUnit unit;
#elif defined(__linux__)
snd_pcm_t* unit;
pthread_t pump;
_Atomic(u32) done;
#endif
} IoRing;

// n frames queued after the write; a write past the ring's room is
// dropped and the queue answered as it is.
static u64 io_ring_write(IoRing* p, const float* pcm, u32 n) {
u64 w = atomic_load_explicit(&p->written, memory_order_relaxed);
u64 r = atomic_load_explicit(&p->read, memory_order_acquire);
if (w - r + n <= IO_RING) {
u32 at = (u32)(w % IO_RING);
u32 first = n < IO_RING - at ? n : IO_RING - at;
memcpy(p->pcm + at * 2, pcm, first * 8);
memcpy(p->pcm, pcm + first * 2, (n - first) * 8);
atomic_store_explicit(&p->written, w + n, memory_order_release);
w += n;
}
return w - r;
}

static void io_ring_pull(IoRing* p, float* out, u32 frames) {
u64 r = atomic_load_explicit(&p->read, memory_order_relaxed);
u64 w = atomic_load_explicit(&p->written, memory_order_acquire);
u32 n = (u32)(w - r < frames ? w - r : frames);
u32 at = (u32)(r % IO_RING);
u32 first = n < IO_RING - at ? n : IO_RING - at;
memcpy(out, p->pcm + at * 2, first * 8);
memcpy(out + first * 2, p->pcm, (n - first) * 8);
memset(out + n * 2, 0, (frames - n) * 8);
atomic_store_explicit(&p->read, r + n, memory_order_release);
}

#ifdef __OBJC__

static OSStatus io_ring_pump(void* ctx, AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* when, UInt32 bus, UInt32 frames,
AudioBufferList* bl) {
if (bl->mNumberBuffers != 1 || bl->mBuffers[0].mNumberChannels != 2
|| bl->mBuffers[0].mDataByteSize < frames * 8) {
return kAudio_ParamError;
}
io_ring_pull(ctx, bl->mBuffers[0].mData, frames);
return noErr;
}

// The default output unit fed float32 stereo at the asked rate (the
// unit converts to the device's own).
static u32 io_ring_start(IoRing* p, u32 rate) {
AudioComponentDescription desc = { kAudioUnitType_Output,
kAudioUnitSubType_DefaultOutput, kAudioUnitManufacturer_Apple, 0, 0 };
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
if (comp == NULL || AudioComponentInstanceNew(comp, &p->unit) != noErr) {
return ENODEV;
}
AudioStreamBasicDescription fmt = { 0 };
fmt.mSampleRate = rate;
fmt.mFormatID = kAudioFormatLinearPCM;
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
fmt.mBytesPerPacket = 8;
fmt.mFramesPerPacket = 1;
fmt.mBytesPerFrame = 8;
fmt.mChannelsPerFrame = 2;
fmt.mBitsPerChannel = 32;
AURenderCallbackStruct cb = { io_ring_pump, p };
bool ok = AudioUnitSetProperty(p->unit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &fmt, sizeof fmt) == noErr
&& AudioUnitSetProperty(p->unit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &cb, sizeof cb) == noErr
&& AudioUnitInitialize(p->unit) == noErr
&& AudioOutputUnitStart(p->unit) == noErr;
return ok ? 0 : ENODEV;
}

static void io_ring_free(IoRing* p) {
if (p->unit != NULL) {
AudioOutputUnitStop(p->unit);
AudioUnitUninitialize(p->unit);
AudioComponentInstanceDispose(p->unit);
}
free(p);
}

#elif defined(__linux__)

// A thread feeds the default ALSA device 256 frames at a time (a
// write blocks until the device has room, so the ring drains at the
// device's clock).
static void* io_ring_pump(void* ctx) {
IoRing* p = ctx;
float out[256 * 2];
while (atomic_load_explicit(&p->done, memory_order_relaxed) == 0) {
io_ring_pull(p, out, 256);
snd_pcm_sframes_t n = snd_pcm_writei(p->unit, out, 256);
if (n < 0) {
snd_pcm_recover(p->unit, (int)n, 1);
}
}
return NULL;
}

static void io_ring_hush(const char* file, int line, const char* fn, int err,
const char* fmt, ...) {
}

static u32 io_ring_start(IoRing* p, u32 rate) {
snd_lib_error_set_handler(io_ring_hush);
if (snd_pcm_open(&p->unit, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
return ENODEV;
}
if (snd_pcm_set_params(p->unit, SND_PCM_FORMAT_FLOAT_LE,
SND_PCM_ACCESS_RW_INTERLEAVED, 2, rate, 1, 20000) < 0
|| pthread_create(&p->pump, NULL, io_ring_pump, p) != 0) {
return ENODEV;
}
return 0;
}

static void io_ring_free(IoRing* p) {
if (p->unit != NULL) {
atomic_store_explicit(&p->done, 1, memory_order_relaxed);
if (p->pump != 0) {
pthread_join(p->pump, NULL);
}
snd_pcm_close(p->unit);
}
free(p);
}

#else

static u32 io_ring_start(IoRing* p, u32 rate) {
return ENOTSUP;
}

static void io_ring_free(IoRing* p) {
free(p);
}

#endif
#endif
// @src audio_ring.c

Term audio_close_run(Env e, Term* f, IoWork* w) {
io_ring_free((IoRing*)(uintptr_t)io_hand_v(f[0]));
Expand Down
163 changes: 1 addition & 162 deletions bend2/effs/audio_open.c
Original file line number Diff line number Diff line change
@@ -1,168 +1,7 @@
// Audio
// =====

// The ring: 4096 float32 stereo frames. The effects fill it on the
// evaluator's thread; the device's callback drains it and pads the
// rest of its buffer with silence. The same block sits in
// audio_write.c and audio_close.c under this guard, so that any one
// of the three effects compiles alone.
#ifndef IO_RING
#define IO_RING 4096u

#ifdef __OBJC__
#import <AudioToolbox/AudioToolbox.h>
#elif defined(__linux__)
#include <alsa/asoundlib.h>
#endif

typedef struct {
_Atomic(u64) read, written;
float pcm[IO_RING * 2];
#ifdef __OBJC__
AudioUnit unit;
#elif defined(__linux__)
snd_pcm_t* unit;
pthread_t pump;
_Atomic(u32) done;
#endif
} IoRing;

// n frames queued after the write; a write past the ring's room is
// dropped and the queue answered as it is.
static u64 io_ring_write(IoRing* p, const float* pcm, u32 n) {
u64 w = atomic_load_explicit(&p->written, memory_order_relaxed);
u64 r = atomic_load_explicit(&p->read, memory_order_acquire);
if (w - r + n <= IO_RING) {
u32 at = (u32)(w % IO_RING);
u32 first = n < IO_RING - at ? n : IO_RING - at;
memcpy(p->pcm + at * 2, pcm, first * 8);
memcpy(p->pcm, pcm + first * 2, (n - first) * 8);
atomic_store_explicit(&p->written, w + n, memory_order_release);
w += n;
}
return w - r;
}

static void io_ring_pull(IoRing* p, float* out, u32 frames) {
u64 r = atomic_load_explicit(&p->read, memory_order_relaxed);
u64 w = atomic_load_explicit(&p->written, memory_order_acquire);
u32 n = (u32)(w - r < frames ? w - r : frames);
u32 at = (u32)(r % IO_RING);
u32 first = n < IO_RING - at ? n : IO_RING - at;
memcpy(out, p->pcm + at * 2, first * 8);
memcpy(out + first * 2, p->pcm, (n - first) * 8);
memset(out + n * 2, 0, (frames - n) * 8);
atomic_store_explicit(&p->read, r + n, memory_order_release);
}

#ifdef __OBJC__

static OSStatus io_ring_pump(void* ctx, AudioUnitRenderActionFlags* flags,
const AudioTimeStamp* when, UInt32 bus, UInt32 frames,
AudioBufferList* bl) {
if (bl->mNumberBuffers != 1 || bl->mBuffers[0].mNumberChannels != 2
|| bl->mBuffers[0].mDataByteSize < frames * 8) {
return kAudio_ParamError;
}
io_ring_pull(ctx, bl->mBuffers[0].mData, frames);
return noErr;
}

// The default output unit fed float32 stereo at the asked rate (the
// unit converts to the device's own).
static u32 io_ring_start(IoRing* p, u32 rate) {
AudioComponentDescription desc = { kAudioUnitType_Output,
kAudioUnitSubType_DefaultOutput, kAudioUnitManufacturer_Apple, 0, 0 };
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
if (comp == NULL || AudioComponentInstanceNew(comp, &p->unit) != noErr) {
return ENODEV;
}
AudioStreamBasicDescription fmt = { 0 };
fmt.mSampleRate = rate;
fmt.mFormatID = kAudioFormatLinearPCM;
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
fmt.mBytesPerPacket = 8;
fmt.mFramesPerPacket = 1;
fmt.mBytesPerFrame = 8;
fmt.mChannelsPerFrame = 2;
fmt.mBitsPerChannel = 32;
AURenderCallbackStruct cb = { io_ring_pump, p };
bool ok = AudioUnitSetProperty(p->unit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &fmt, sizeof fmt) == noErr
&& AudioUnitSetProperty(p->unit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &cb, sizeof cb) == noErr
&& AudioUnitInitialize(p->unit) == noErr
&& AudioOutputUnitStart(p->unit) == noErr;
return ok ? 0 : ENODEV;
}

static void io_ring_free(IoRing* p) {
if (p->unit != NULL) {
AudioOutputUnitStop(p->unit);
AudioUnitUninitialize(p->unit);
AudioComponentInstanceDispose(p->unit);
}
free(p);
}

#elif defined(__linux__)

// A thread feeds the default ALSA device 256 frames at a time (a
// write blocks until the device has room, so the ring drains at the
// device's clock).
static void* io_ring_pump(void* ctx) {
IoRing* p = ctx;
float out[256 * 2];
while (atomic_load_explicit(&p->done, memory_order_relaxed) == 0) {
io_ring_pull(p, out, 256);
snd_pcm_sframes_t n = snd_pcm_writei(p->unit, out, 256);
if (n < 0) {
snd_pcm_recover(p->unit, (int)n, 1);
}
}
return NULL;
}

static void io_ring_hush(const char* file, int line, const char* fn, int err,
const char* fmt, ...) {
}

static u32 io_ring_start(IoRing* p, u32 rate) {
snd_lib_error_set_handler(io_ring_hush);
if (snd_pcm_open(&p->unit, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
return ENODEV;
}
if (snd_pcm_set_params(p->unit, SND_PCM_FORMAT_FLOAT_LE,
SND_PCM_ACCESS_RW_INTERLEAVED, 2, rate, 1, 20000) < 0
|| pthread_create(&p->pump, NULL, io_ring_pump, p) != 0) {
return ENODEV;
}
return 0;
}

static void io_ring_free(IoRing* p) {
if (p->unit != NULL) {
atomic_store_explicit(&p->done, 1, memory_order_relaxed);
if (p->pump != 0) {
pthread_join(p->pump, NULL);
}
snd_pcm_close(p->unit);
}
free(p);
}

#else

static u32 io_ring_start(IoRing* p, u32 rate) {
return ENOTSUP;
}

static void io_ring_free(IoRing* p) {
free(p);
}

#endif
#endif
// @src audio_ring.c

Term audio_open_run(Env e, Term* f, IoWork* w) {
u32 rate = (u32)f[0];
Expand Down
Loading