#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "exp_rs.h"
#define NUM_CHANNELS 4
#define MAX_PATTERNS_PER_CHANNEL 10
typedef struct {
char* name;
char** expressions;
size_t num_expressions;
char** param_names;
Real* param_values;
size_t num_params;
struct {
char* name;
size_t arity;
NativeFunc func;
}* functions;
size_t num_functions;
} Pattern;
typedef struct {
ExprArena* arena;
ExprBatch* batch;
Pattern* current_pattern;
Real channel_gain;
Real channel_offset;
} Channel;
typedef struct {
Channel channels[NUM_CHANNELS];
ExprContext* shared_context; ExprContext* pattern_contexts[NUM_CHANNELS]; } Device;
Real custom_envelope(const Real* args, uintptr_t nargs) {
Real t = args[0];
Real attack = args[1];
Real decay = args[2];
Real sustain = args[3];
if (t < attack) {
return t / attack;
} else if (t < attack + decay) {
return 1.0 - (1.0 - sustain) * ((t - attack) / decay);
} else {
return sustain;
}
}
Real custom_oscillator(const Real* args, uintptr_t nargs) {
return sin(2.0 * M_PI * args[0] * args[1]);
}
Device* device_create() {
Device* dev = malloc(sizeof(Device));
if (!dev) return NULL;
dev->shared_context = expr_context_new();
if (!dev->shared_context) {
free(dev);
return NULL;
}
expr_context_add_function(dev->shared_context, "sin", 1, (NativeFunc)sin);
expr_context_add_function(dev->shared_context, "cos", 1, (NativeFunc)cos);
expr_context_add_function(dev->shared_context, "sqrt", 1, (NativeFunc)sqrt);
expr_context_add_function(dev->shared_context, "envelope", 5, custom_envelope);
expr_context_add_function(dev->shared_context, "osc", 2, custom_oscillator);
for (int i = 0; i < NUM_CHANNELS; i++) {
dev->channels[i].arena = expr_arena_new(32 * 1024);
dev->channels[i].batch = NULL;
dev->channels[i].current_pattern = NULL;
dev->channels[i].channel_gain = 1.0;
dev->channels[i].channel_offset = 0.0;
dev->pattern_contexts[i] = expr_context_new();
}
return dev;
}
int channel_load_pattern(Device* dev, int channel_idx, Pattern* pattern) {
if (channel_idx < 0 || channel_idx >= NUM_CHANNELS) return -1;
Channel* ch = &dev->channels[channel_idx];
if (ch->batch) {
expr_batch_free(ch->batch);
ch->batch = NULL;
}
expr_arena_reset(ch->arena);
if (ch->current_pattern) {
expr_context_free(dev->pattern_contexts[channel_idx]);
dev->pattern_contexts[channel_idx] = expr_context_new();
}
for (size_t i = 0; i < pattern->num_functions; i++) {
expr_context_add_function(
dev->pattern_contexts[channel_idx],
pattern->functions[i].name,
pattern->functions[i].arity,
pattern->functions[i].func
);
}
ch->batch = expr_batch_new(ch->arena);
if (!ch->batch) return -2;
for (size_t i = 0; i < pattern->num_expressions; i++) {
if (expr_batch_add_expression(ch->batch, pattern->expressions[i]) < 0) {
return -3;
}
}
for (size_t i = 0; i < pattern->num_params; i++) {
if (expr_batch_add_variable(ch->batch, pattern->param_names[i], pattern->param_values[i]) < 0) {
return -4;
}
}
expr_batch_add_variable(ch->batch, "ch_gain", ch->channel_gain);
expr_batch_add_variable(ch->batch, "ch_offset", ch->channel_offset);
expr_batch_add_variable(ch->batch, "ch_num", (Real)channel_idx);
ch->current_pattern = pattern;
return 0;
}
void device_evaluate(Device* dev, Real* outputs) {
for (int i = 0; i < NUM_CHANNELS; i++) {
Channel* ch = &dev->channels[i];
if (!ch->batch || !ch->current_pattern) {
outputs[i] = 0.0;
continue;
}
expr_batch_evaluate(ch->batch, dev->shared_context);
outputs[i] = expr_batch_get_result(ch->batch, 0);
}
}
void device_update_params(Device* dev, int channel_idx, Real time_param) {
if (channel_idx < 0 || channel_idx >= NUM_CHANNELS) return;
Channel* ch = &dev->channels[channel_idx];
if (!ch->batch) return;
expr_batch_set_variable(ch->batch, 0, time_param);
}
void device_destroy(Device* dev) {
if (!dev) return;
for (int i = 0; i < NUM_CHANNELS; i++) {
if (dev->channels[i].batch) {
expr_batch_free(dev->channels[i].batch);
}
if (dev->channels[i].arena) {
expr_arena_free(dev->channels[i].arena);
}
if (dev->pattern_contexts[i]) {
expr_context_free(dev->pattern_contexts[i]);
}
}
if (dev->shared_context) {
expr_context_free(dev->shared_context);
}
free(dev);
}
int main() {
Device* dev = device_create();
if (!dev) {
printf("Failed to create device\n");
return 1;
}
Pattern sine_pattern = {
.name = "Simple Sine",
.expressions = (char*[]){"sin(2 * pi * freq * time) * amp * ch_gain + ch_offset"},
.num_expressions = 1,
.param_names = (char*[]){"time", "freq", "amp", "pi"},
.param_values = (Real[]){0.0, 440.0, 1.0, M_PI},
.num_params = 4,
.functions = NULL,
.num_functions = 0
};
Pattern envelope_pattern = {
.name = "Envelope Modulated",
.expressions = (char*[]){
"osc(freq, time) * envelope(time, attack, decay, sustain, release) * amp * ch_gain"
},
.num_expressions = 1,
.param_names = (char*[]){"time", "freq", "amp", "attack", "decay", "sustain", "release"},
.param_values = (Real[]){0.0, 440.0, 1.0, 0.1, 0.2, 0.7, 0.5},
.num_params = 7,
.functions = NULL, .num_functions = 0
};
printf("Loading patterns...\n");
channel_load_pattern(dev, 0, &sine_pattern);
channel_load_pattern(dev, 1, &envelope_pattern);
channel_load_pattern(dev, 2, &sine_pattern); channel_load_pattern(dev, 3, &envelope_pattern);
Real outputs[NUM_CHANNELS];
Real sample_rate = 48000.0;
Real time_step = 1.0 / sample_rate;
printf("\nSimulating 100ms of audio...\n");
for (int sample = 0; sample < 4800; sample++) {
Real time = sample * time_step;
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
device_update_params(dev, ch, time);
}
device_evaluate(dev, outputs);
if (sample % 480 == 0) {
printf("t=%.3f: ", time);
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
printf("Ch%d=%.3f ", ch, outputs[ch]);
}
printf("\n");
}
}
printf("\nChanging channel 0 to envelope pattern...\n");
channel_load_pattern(dev, 0, &envelope_pattern);
device_update_params(dev, 0, 0.0);
device_evaluate(dev, outputs);
printf("After pattern change - Ch0: %.3f\n", outputs[0]);
device_destroy(dev);
printf("\nDevice destroyed successfully\n");
return 0;
}