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

add ks2c-osx #58

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 7 additions & 1 deletion packages/cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ kabelsalat.js
src/clib/dsp
src/clib/wav.c
src/clib/alsa.md
src/kabelsalat
src/kabelsalat.js.c
src/kabelsalat
src/kabelsalat.js-osx.c
src/kabelsalat-osx
src/kabelsalat.js-pa.c
src/kabelsalat-pa
src/clib/sine_wave
src/clib/sine_wave.c
src/clib/sine_portaudio.c
src/clib/sine_portaudio
16 changes: 14 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,22 @@
"license": "AGPL-3.0-or-later",
"scripts": {
"test": "node src/cli.js",

"ks2c": "pnpm ks2c:compile && pnpm ks2c:build && pnpm ks2c:run",
"ks2c:compile": "cd src && node ks2c.js",
"ks2c:build": "cd src && gcc -o kabelsalat kabelsalat.js.c -lm",
"ks2c:run": "cd src && ./kabelsalat | sox -traw -r44100 -b32 -e float -c 2 - -tcoreaudio"
"ks2c:build_gcc": "cd src && gcc -o kabelsalat kabelsalat.js.c -lm",
"ks2c:build": "cd src && clang -Os -flto -o kabelsalat kabelsalat.js.c",
"ks2c:run": "cd src && ./kabelsalat | sox -traw -r44100 -b32 -e float -c 2 - -tcoreaudio",

"ks2c-osx": "pnpm ks2c-osx:compile && pnpm ks2c-osx:build && pnpm ks2c-osx:run",
"ks2c-osx:compile": "cd src && node ks2c-osx.js",
"ks2c-osx:build": "cd src && clang -Os -framework AudioToolbox -flto -o kabelsalat-osx kabelsalat.js-osx.c",
"ks2c-osx:run": "cd src && ./kabelsalat-osx",

"ks2c-pa": "pnpm ks2c-pa:compile && pnpm ks2c-pa:build && pnpm ks2c-pa:run",
"ks2c-pa:compile": "cd src && node ks2c-pa.js",
"ks2c-pa:build": "cd src && gcc -Os kabelsalat.js-pa.c -flto -o kabelsalat-pa -I/opt/homebrew/include -L/opt/homebrew/lib -lportaudio -lm",
"ks2c-pa:run": "cd src && ./kabelsalat-pa"
},
"dependencies": {
"@kabelsalat/core": "workspace:*",
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/clib/ugens.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
#define SAMPLE_TIME (1.0 / SAMPLE_RATE)
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define RANDOM_FLOAT ((float)arc4random() / (float)UINT32_MAX)
#define RANDOM_FLOAT ((float)arc4random() / (float)UINT32_MAX) // libbsd
//#define RANDOM_FLOAT ((float)rand() / (float)RAND_MAX) // stdlib
//#define RANDOM_FLOAT ((float)random() / (float)0x7FFFFFFF) // POSIX

// helpers

Expand Down
187 changes: 187 additions & 0 deletions packages/cli/src/ks2c-osx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#!/usr/bin/env node

// node ks2c_osx.js > test.c && clang -Os -fomit-frame-pointer -ffunction-sections -fdata-sections -g0 -flto -framework AudioToolbox test.c -o test && strip test && ./test
// pnpm ks2c

import fs from "node:fs/promises";
import * as core from "@kabelsalat/core/src/index.js";
import * as lib from "@kabelsalat/lib/src/index.js";
// for some reason, node will always take main and not module file...
import { existsSync } from "node:fs";
import path from "node:path";

async function main() {
let file = process.argv[2];
if (!file) {
// file = "kabelsalat.js";
file = "./kabelsalat.js";
console.log(`no input file given -> using "${file}"`);
}
const fileExists = existsSync(file);

if (!fileExists) {
console.log(`file "${file}" not found.`);
return;
}

const filePath = path.resolve(process.cwd(), file);
const ksCode = await fs.readFile(filePath, { encoding: "utf8" });
const ugenPath = path.resolve(process.cwd(), "./clib/ugens.c");
const ugenCode = await fs.readFile(ugenPath, { encoding: "utf8" });
Object.assign(globalThis, core);
Object.assign(globalThis, lib);
const node = core.evaluate(ksCode);
const unit = node.compile({ lang: "c" });
const cCode = ks2c(unit, ugenCode);
const outFileName = file + "-osx.c";
// console.log(cCode);
try {
await fs.writeFile(outFileName, cCode);

console.log(`written ${outFileName}`);
} catch (err) {
console.log(`error writing ${outFileName}: ${err.message}`);
}
}

main();

let ks2c = (
unit,
ugens
) => `// this file has been compiled from kabelsalat, using ks2c-osx
// this file uses AudioToolbox, which will only work on OSX.
// compile and run with:
// clang -Os -framework AudioToolbox -flto -o kabelsalat-osx kabelsalat.js-osx.c && ./kabelsalat-osx
// this will spit out a self-contained ~34kB binary that plays the compiled kabelsalat patch

#include <AudioToolbox/AudioToolbox.h>

${ugens}

#define SAMPLE_RATE 44100
#define CHANNELS 2
#define SAMPLE_TIME (1.0 / SAMPLE_RATE)

typedef struct
{
double time;
float* r;
float* o;
int osize;
float* s;
void** nodes; // wtf am i doing
} CallbackEnv;

static OSStatus DSPCallback(
void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{


CallbackEnv *env = (CallbackEnv *)inRefCon;

float *buffer = (float *)ioData->mBuffers[0].mData;
void **nodes = env->nodes;

float *o = env->o;
float *r = env->r;
float *s = env->s;

for (UInt32 j = 0; j < inNumberFrames; j++)
{
double time = env->time;

// start of autogenerated callback
memset(o, 0, env->osize); // reset outputs
${unit.src}
// end of autogenerated callback

float left = o[0];
float right = o[1];

buffer[j*CHANNELS] = left*0.3;
buffer[j*CHANNELS+1] = right*0.3;
env->time += SAMPLE_TIME;

}

return noErr;
}

void SetupAudioUnit(CallbackEnv *env)
{
AudioComponentDescription desc = {0};
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;

AudioComponent outputComponent = AudioComponentFindNext(NULL, &desc);
AudioUnit audioUnit;
AudioComponentInstanceNew(outputComponent, &audioUnit);

AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = DSPCallback;
callbackStruct.inputProcRefCon = env;

AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&callbackStruct,
sizeof(callbackStruct));

AudioStreamBasicDescription streamFormat;
streamFormat.mSampleRate = SAMPLE_RATE;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
streamFormat.mFramesPerPacket = 1;
streamFormat.mChannelsPerFrame = CHANNELS;
streamFormat.mBitsPerChannel = sizeof(float) * 8;
streamFormat.mBytesPerPacket = sizeof(float) * CHANNELS;
streamFormat.mBytesPerFrame = sizeof(float) * CHANNELS;

AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&streamFormat,
sizeof(streamFormat));

AudioUnitInitialize(audioUnit);
AudioOutputUnitStart(audioUnit);
}

int main()
{

float o[16] = {0}; // output registry
float s[16] = {0}; // source registry

// start of autogenerated init
float r[${unit.registers}] = {0}; // node registry
void *nodes[${unit.ugens.length}];
${unit.ugens
.map((ugen, i) => `nodes[${i}] = ${ugen.type}_create();`)
.join("\n")}

// end of autogenerated init

CallbackEnv env;
env.nodes = nodes;
env.r = (float *)r;
env.o = (float *)o;
env.osize = sizeof(o);
env.s = (float *)s;

SetupAudioUnit(&env);

while(true) {}

return 0;
}
`;
Loading
Loading