#include #include #include #include #include #include #include "engine.h" #include "gui.h" /* microui includes */ #define MU_IMPLEMENTATION #include "microui.h" /* --------------------------------------------------------------------------- * microui callbacks * ------------------------------------------------------------------------- */ static int text_width(mu_Font font, const char *text, int len) { (void)font; if (len == -1) { len = strlen(text); } return len * 8; } static int text_height(mu_Font font) { (void)font; return 18; } /* --------------------------------------------------------------------------- * global state * ------------------------------------------------------------------------- */ static mu_Context *ctx = NULL; static int running = 1; /* engine state */ static jack_client_t *client = NULL; static int active = 0; static float bpm = 120.0f; static int loop_length = 8; /* beats */ static int current_beat = 0; static float *buffer = NULL; static int buffer_size = 0; /* --------------------------------------------------------------------------- * drawing helpers (stubs for microui) * ------------------------------------------------------------------------- */ static void draw_frame(mu_Context *ctx, mu_Rect rect, int colorid) { /* stub: we rely on ncurses for actual rendering */ (void)ctx; (void)rect; (void)colorid; } /* --------------------------------------------------------------------------- * GUI update * ------------------------------------------------------------------------- */ static void gui_update(void) { mu_begin(ctx); /* main window */ if (mu_begin_window(ctx, "Jack Looper", mu_rect(10, 10, 400, 300))) { /* transport controls */ mu_layout_row(ctx, 3, (int[]) { 80, 80, 80 }, 0); if (mu_button(ctx, active ? "Stop" : "Play")) { active = !active; if (active) { engine_start(client); } else { engine_stop(client); } } if (mu_button(ctx, "Reset")) { current_beat = 0; } if (mu_button(ctx, "Quit")) { running = 0; } /* BPM slider */ mu_layout_row(ctx, 2, (int[]) { 60, -1 }, 0); mu_label(ctx, "BPM:"); if (mu_slider(ctx, &bpm, 20.0f, 300.0f, 0, "%.0f", 0)) { engine_set_bpm(client, bpm); } /* loop length */ mu_layout_row(ctx, 2, (int[]) { 60, -1 }, 0); mu_label(ctx, "Length:"); if (mu_slider(ctx, (float*)&loop_length, 1.0f, 64.0f, 0, "%.0f", 0)) { engine_set_loop_length(client, loop_length); } /* beat indicator */ mu_layout_row(ctx, 1, (int[]) { -1 }, 0); { char buf[64]; snprintf(buf, sizeof(buf), "Beat: %d / %d", current_beat + 1, loop_length); mu_label(ctx, buf); } /* progress bar */ mu_layout_row(ctx, 1, (int[]) { -1 }, 0); { float progress = (loop_length > 0) ? (float)current_beat / (float)loop_length : 0.0f; mu_Rect r = mu_layout_next(ctx); mu_draw_control_frame(ctx, mu_get_id(ctx, &progress, sizeof(progress)), r, MU_COLOR_BASE, 0); if (progress > 0.0f) { mu_Rect fill = r; fill.w = (int)(r.w * progress); mu_draw_rect(ctx, fill, mu_color(100, 200, 100, 255)); } } mu_end_window(ctx); } mu_end(ctx); } /* --------------------------------------------------------------------------- * main loop * ------------------------------------------------------------------------- */ int gui_main(jack_client_t *jack_client, float *audio_buffer, int buf_size) { client = jack_client; buffer = audio_buffer; buffer_size = buf_size; /* initialise microui */ ctx = malloc(sizeof(mu_Context)); if (!ctx) return -1; mu_init(ctx); ctx->text_width = text_width; ctx->text_height = text_height; ctx->draw_frame = draw_frame; /* ncurses setup */ initscr(); cbreak(); noecho(); keypad(stdscr, TRUE); nodelay(stdscr, TRUE); curs_set(0); /* main loop */ while (running) { /* handle input */ int ch = getch(); if (ch != ERR) { switch (ch) { case 'q': case 'Q': running = 0; break; case ' ': active = !active; if (active) { engine_start(client); } else { engine_stop(client); } break; case KEY_UP: bpm = fminf(bpm + 5.0f, 300.0f); engine_set_bpm(client, bpm); break; case KEY_DOWN: bpm = fmaxf(bpm - 5.0f, 20.0f); engine_set_bpm(client, bpm); break; case KEY_LEFT: loop_length = (loop_length > 1) ? loop_length - 1 : 1; engine_set_loop_length(client, loop_length); break; case KEY_RIGHT: loop_length = (loop_length < 64) ? loop_length + 1 : 64; engine_set_loop_length(client, loop_length); break; default: break; } } /* update engine state */ current_beat = engine_get_current_beat(client); /* render GUI */ gui_update(); /* draw commands */ mu_Command *cmd = NULL; while (mu_next_command(ctx, &cmd)) { if (cmd->type == MU_COMMAND_TEXT) { mvprintw(cmd->text.pos.y / 18, cmd->text.pos.x / 8, "%.*s", cmd->text.len, cmd->text.text); } if (cmd->type == MU_COMMAND_RECT) { /* simple rectangle rendering using ncurses */ int x = cmd->rect.rect.x / 8; int y = cmd->rect.rect.y / 18; int w = cmd->rect.rect.w / 8; int h = cmd->rect.rect.h / 18; for (int i = 0; i < h && y + i < LINES; i++) { mvhline(y + i, x, ' ', w); } } } refresh(); usleep(16666); /* ~60 fps */ } /* cleanup */ endwin(); free(ctx); return 0; }