Created
April 13, 2025 21:34
-
-
Save iitalics/4210d64b413308c27ed4a3412d3c7bb3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <inttypes.h> | |
| #include <math.h> | |
| #include <pthread.h> | |
| #include <stdio.h> | |
| #include <unistd.h> | |
| #include <jack/jack.h> | |
| #include <jack/types.h> | |
| static pthread_mutex_t io_mx; | |
| typedef struct Synth { | |
| float t; | |
| float dt; | |
| } synth_t; | |
| const float AMPL = 0.08; | |
| const float FREQ_L = 110.0; | |
| const float FREQ_R = FREQ_L * 1.02; | |
| static void synth_init(synth_t* syn, float f, uint32_t sr) | |
| { | |
| pthread_mutex_lock(&io_mx); | |
| printf("Synth initialized at sample rate %d.\n", (int) sr); | |
| pthread_mutex_unlock(&io_mx); | |
| syn->t = 0.0; | |
| syn->dt = 2.0 * f / (float) sr; | |
| } | |
| static float synth_step(synth_t* syn) | |
| { | |
| float t = syn->t; | |
| syn->t += syn->dt; | |
| if (syn->t > 1.0) { | |
| syn->t -= 2.0; | |
| } | |
| return t * AMPL; | |
| } | |
| typedef struct App { | |
| jack_client_t* jackc; | |
| jack_port_t* playback_fl; | |
| jack_port_t* playback_fr; | |
| jack_nframes_t sample_rate; | |
| int playback_registered; | |
| int playback_connected; | |
| synth_t synth_l; | |
| synth_t synth_r; | |
| } app_t; | |
| static void app_init(app_t* app) | |
| { | |
| app->jackc = NULL; | |
| app->playback_fl = NULL; | |
| app->playback_fr = NULL; | |
| app->playback_registered = 0; | |
| app->playback_connected = 0; | |
| app->sample_rate = 0; | |
| } | |
| static void app_free(app_t* app) | |
| { | |
| if (app->jackc) { | |
| jack_client_close(app->jackc); | |
| } | |
| app->jackc = NULL; | |
| app->playback_fl = NULL; | |
| app->playback_fr = NULL; | |
| app->playback_connected = 0; | |
| } | |
| static void app_register_callback( | |
| jack_port_id_t id, | |
| int registered, | |
| void* arg) | |
| { | |
| app_t* app = arg; | |
| jack_port_t* port = jack_port_by_id(app->jackc, id); | |
| if (port == app->playback_fl) { | |
| app->playback_registered |= 1; | |
| } | |
| if (port == app->playback_fr) { | |
| app->playback_registered |= 2; | |
| } | |
| pthread_mutex_lock(&io_mx); | |
| printf( | |
| "[Callback] %s: %s\n", | |
| registered ? "Registered" : "Deregistered", | |
| jack_port_name(port)); | |
| pthread_mutex_unlock(&io_mx); | |
| } | |
| static void app_connect_callback( | |
| jack_port_id_t a, | |
| jack_port_id_t b, | |
| int connected, | |
| void* arg) | |
| { | |
| app_t* app = arg; | |
| jack_port_t* port_a = jack_port_by_id(app->jackc, a); | |
| jack_port_t* port_b = jack_port_by_id(app->jackc, b); | |
| if (connected) { | |
| if (port_a == app->playback_fl) { | |
| app->playback_connected |= 1; | |
| } | |
| if (port_a == app->playback_fr) { | |
| app->playback_connected |= 2; | |
| } | |
| } else { | |
| if (port_a == app->playback_fl) { | |
| app->playback_connected &= ~1; | |
| } | |
| if (port_a == app->playback_fr) { | |
| app->playback_connected &= ~2; | |
| } | |
| } | |
| pthread_mutex_lock(&io_mx); | |
| printf( | |
| "[Callback] %s: %s -> %s\n", | |
| connected ? "Connected" : "Disconnected", | |
| jack_port_name(port_a), | |
| jack_port_name(port_b)); | |
| pthread_mutex_unlock(&io_mx); | |
| } | |
| static void app_shutdown_callback( | |
| void* arg) | |
| { | |
| pthread_mutex_lock(&io_mx); | |
| printf("[Callback] Shutting down.\n"); | |
| pthread_mutex_unlock(&io_mx); | |
| } | |
| static int app_srate_callback( | |
| jack_nframes_t sr, | |
| void* arg) | |
| { | |
| app_t* app = arg; | |
| app->sample_rate = sr; | |
| synth_init(&app->synth_l, FREQ_L, sr); | |
| synth_init(&app->synth_r, FREQ_R, sr); | |
| return 0; | |
| } | |
| static int app_bufsz_callback( | |
| jack_nframes_t bs, | |
| void* arg) | |
| { | |
| app_t* app = arg; | |
| if (app->sample_rate == 0) { | |
| printf("Error: buffer size callback: no sample rate.\n"); | |
| return 1; | |
| } | |
| float dt_ms = (float) bs * 1000.0 / (float) app->sample_rate; | |
| pthread_mutex_lock(&io_mx); | |
| printf("[Callback] Buffer size: %d (%.1f ms)\n", (int) bs, dt_ms); | |
| pthread_mutex_unlock(&io_mx); | |
| return 0; | |
| } | |
| static int app_process_callback( | |
| jack_nframes_t nf, | |
| void* arg) | |
| { | |
| app_t* app = arg; | |
| if (app->sample_rate == 0 || app->playback_connected != 3) { | |
| // not ready | |
| return 0; | |
| } | |
| float* buffer_fl = jack_port_get_buffer(app->playback_fl, nf); | |
| float* buffer_fr = jack_port_get_buffer(app->playback_fr, nf); | |
| for (jack_nframes_t i = 0; i < nf; i++) { | |
| *buffer_fl++ = synth_step(&app->synth_l); | |
| *buffer_fr++ = synth_step(&app->synth_r); | |
| } | |
| return 0; | |
| } | |
| int main() | |
| { | |
| pthread_mutex_init(&io_mx, NULL); | |
| int rv; | |
| app_t app[1]; | |
| app_init(app); | |
| jack_options_t open_opts = JackNoStartServer; | |
| jack_status_t open_stat; | |
| app->jackc = jack_client_open("JACK test", open_opts, &open_stat); | |
| if (open_stat != 0) { | |
| printf("Error: jack_client_open: %d\n", (int) open_stat); | |
| goto fail; | |
| } | |
| jack_on_shutdown(app->jackc, app_shutdown_callback, app); | |
| jack_set_port_registration_callback(app->jackc, app_register_callback, app); | |
| jack_set_port_connect_callback(app->jackc, app_connect_callback, app); | |
| jack_set_sample_rate_callback(app->jackc, app_srate_callback, app); | |
| jack_set_buffer_size_callback(app->jackc, app_bufsz_callback, app); | |
| jack_set_process_callback(app->jackc, app_process_callback, app); | |
| pthread_mutex_lock(&io_mx); | |
| printf("Connected to JACK.\n"); | |
| printf(" Client name: '%s'\n", jack_get_client_name(app->jackc)); | |
| printf(" Realtime: %d\n", jack_is_realtime(app->jackc)); | |
| pthread_mutex_unlock(&io_mx); | |
| if (jack_activate(app->jackc) != 0) { | |
| printf("Error: jack_activate\n"); | |
| goto fail; | |
| } | |
| app->playback_fl = jack_port_register( | |
| app->jackc, | |
| "playback_FL", | |
| JACK_DEFAULT_AUDIO_TYPE, | |
| JackPortIsOutput, | |
| 0); | |
| if (!app->playback_fl) { | |
| printf("Error: jack_port_register(playback_FL)\n"); | |
| goto fail; | |
| } | |
| app->playback_fr = jack_port_register( | |
| app->jackc, | |
| "playback_FR", | |
| JACK_DEFAULT_AUDIO_TYPE, | |
| JackPortIsOutput, | |
| 0); | |
| if (!app->playback_fr) { | |
| printf("Error: jack_port_register(playback_FR)\n"); | |
| goto fail; | |
| } | |
| const char** ports; | |
| ports = jack_get_ports(app->jackc, NULL, NULL, JackPortIsInput); | |
| if (!ports[0] || !ports[1]) { | |
| printf("No ports available for playback.\n"); | |
| goto fail; | |
| } | |
| jack_connect(app->jackc, jack_port_name(app->playback_fl), ports[0]); | |
| jack_connect(app->jackc, jack_port_name(app->playback_fr), ports[1]); | |
| jack_free(ports); | |
| for (;;) { | |
| switch (getc(stdin)) { | |
| case '\n': | |
| case EOF: | |
| goto bye; | |
| } | |
| } | |
| bye: | |
| printf("Bye.\n"); | |
| rv = 0; | |
| goto cleanup; | |
| fail: | |
| rv = 1; | |
| cleanup: | |
| app_free(app); | |
| return rv; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment