feat: add parallel MIDI grid with separate clip storage and view toggle
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
This commit is contained in:
104
tui.c
104
tui.c
@@ -749,12 +749,16 @@ static bool handle_rack_view(int ch) {
|
||||
static void draw_cell(int grid, int row, int col, bool selected) {
|
||||
int clip_idx = grid_to_clip_index(grid, row, col);
|
||||
AppState state = dispatcher_get_state();
|
||||
Clip *clip = &state.clips[clip_idx];
|
||||
|
||||
int y = row * CELL_HEIGHT + 3; // Offset by 2 for grid selector
|
||||
int x = col * CELL_WIDTH + 1;
|
||||
|
||||
int color = state_to_color(clip->state);
|
||||
int color;
|
||||
if (state.show_midi_grid) {
|
||||
color = state_to_color(state.midi_clips[clip_idx].state);
|
||||
} else {
|
||||
color = state_to_color(state.clips[clip_idx].state);
|
||||
}
|
||||
if (selected) {
|
||||
color = COLOR_SELECTED;
|
||||
} else if (current_mode == MODE_VISUAL && is_in_visual_selection(row, col)) {
|
||||
@@ -769,17 +773,31 @@ static void draw_cell(int grid, int row, int col, bool selected) {
|
||||
}
|
||||
}
|
||||
|
||||
mvprintw(y + 1, x + 1, "%2d", clip_idx);
|
||||
|
||||
char state_char;
|
||||
switch (clip->state) {
|
||||
case CLIP_EMPTY: state_char = ' '; break;
|
||||
case CLIP_RECORDING: state_char = 'R'; break;
|
||||
case CLIP_LOOPING: state_char = 'L'; break;
|
||||
case CLIP_STOPPED: state_char = 'S'; break;
|
||||
default: state_char = '?'; break;
|
||||
if (state.show_midi_grid) {
|
||||
MidiClip *mclip = &state.midi_clips[clip_idx];
|
||||
mvprintw(y + 1, x + 1, "%2d", clip_idx);
|
||||
char state_char;
|
||||
switch (mclip->state) {
|
||||
case CLIP_EMPTY: state_char = ' '; break;
|
||||
case CLIP_RECORDING: state_char = 'R'; break;
|
||||
case CLIP_LOOPING: state_char = 'L'; break;
|
||||
case CLIP_STOPPED: state_char = 'S'; break;
|
||||
default: state_char = '?'; break;
|
||||
}
|
||||
mvaddch(y + 1, x + 4, state_char);
|
||||
} else {
|
||||
Clip *clip = &state.clips[clip_idx];
|
||||
mvprintw(y + 1, x + 1, "%2d", clip_idx);
|
||||
char state_char;
|
||||
switch (clip->state) {
|
||||
case CLIP_EMPTY: state_char = ' '; break;
|
||||
case CLIP_RECORDING: state_char = 'R'; break;
|
||||
case CLIP_LOOPING: state_char = 'L'; break;
|
||||
case CLIP_STOPPED: state_char = 'S'; break;
|
||||
default: state_char = '?'; break;
|
||||
}
|
||||
mvaddch(y + 1, x + 4, state_char);
|
||||
}
|
||||
mvaddch(y + 1, x + 4, state_char);
|
||||
|
||||
attroff(COLOR_PAIR(color));
|
||||
}
|
||||
@@ -827,7 +845,11 @@ static void draw_grid(void) {
|
||||
|
||||
// Normal grid view
|
||||
attron(A_BOLD);
|
||||
mvprintw(0, 0, "JACK Looper - 8x8 Clip Grid (Grid %d)", selected_grid);
|
||||
if (state.show_midi_grid) {
|
||||
mvprintw(0, 0, "JACK Looper - MIDI Grid %d", selected_grid);
|
||||
} else {
|
||||
mvprintw(0, 0, "JACK Looper - Audio Grid %d", selected_grid);
|
||||
}
|
||||
attroff(A_BOLD);
|
||||
|
||||
// Draw grid selector bar at top
|
||||
@@ -852,19 +874,26 @@ static void draw_grid(void) {
|
||||
}
|
||||
|
||||
int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col);
|
||||
Clip *clip = &state.clips[clip_idx];
|
||||
|
||||
mvprintw(GRID_ROWS * CELL_HEIGHT + 3, 0,
|
||||
"Selected: Clip %d | Grid %d | State: %s | Buffer: %zu samples",
|
||||
clip_idx, selected_grid, clip_state_to_string(clip->state), clip->buffer_size);
|
||||
if (state.show_midi_grid) {
|
||||
MidiClip *mclip = &state.midi_clips[clip_idx];
|
||||
mvprintw(GRID_ROWS * CELL_HEIGHT + 3, 0,
|
||||
"Selected: Clip %d | Grid %d | State: %s | MIDI events: %d",
|
||||
clip_idx, selected_grid, clip_state_to_string(mclip->state), mclip->event_count);
|
||||
} else {
|
||||
Clip *clip = &state.clips[clip_idx];
|
||||
mvprintw(GRID_ROWS * CELL_HEIGHT + 3, 0,
|
||||
"Selected: Clip %d | Grid %d | State: %s | Buffer: %zu samples",
|
||||
clip_idx, selected_grid, clip_state_to_string(clip->state), clip->buffer_size);
|
||||
}
|
||||
|
||||
mvprintw(GRID_ROWS * CELL_HEIGHT + 4, 0,
|
||||
"Transport: %s | Clock: %s | BPM: %.1f | Quantize: %s | Threshold: %u",
|
||||
"Transport: %s | Clock: %s | BPM: %.1f | Quantize: %s | Grid: %s",
|
||||
transport_state_to_string(state.transport_state),
|
||||
clock_source_to_string(state.clock_source),
|
||||
state.bpm,
|
||||
quantize_mode_to_string(state.quantize_mode),
|
||||
(unsigned int)state.quantize_threshold);
|
||||
state.show_midi_grid ? "MIDI" : "AUDIO");
|
||||
|
||||
const char *mode_str;
|
||||
switch (current_mode) {
|
||||
@@ -893,6 +922,7 @@ static void draw_grid(void) {
|
||||
mvprintw(GRID_ROWS * CELL_HEIGHT + 19, 0, "Esc/q - Quit");
|
||||
mvprintw(GRID_ROWS * CELL_HEIGHT + 20, 0, "In fuzzy search: j/k navigate, h/l page, Enter select, Esc cancel");
|
||||
mvprintw(GRID_ROWS * CELL_HEIGHT + 21, 0, "L - Load sample into selected clip (fuzzy search)");
|
||||
mvprintw(GRID_ROWS * CELL_HEIGHT + 22, 0, "G - Toggle Audio/MIDI grid");
|
||||
attroff(COLOR_PAIR(COLOR_HELP));
|
||||
}
|
||||
|
||||
@@ -1217,7 +1247,15 @@ void tui_run(Engine *engine) {
|
||||
int count;
|
||||
int *clips = get_selected_clips(&count);
|
||||
if (clips) {
|
||||
delete_clips(clips, count);
|
||||
AppState s = dispatcher_get_state();
|
||||
if (s.show_midi_grid) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
Action action = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = clips[i] } };
|
||||
g_dispatch(action);
|
||||
}
|
||||
} else {
|
||||
delete_clips(clips, count);
|
||||
}
|
||||
free(clips);
|
||||
}
|
||||
current_mode = MODE_NORMAL;
|
||||
@@ -1257,14 +1295,26 @@ void tui_run(Engine *engine) {
|
||||
break;
|
||||
case 't': {
|
||||
int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col);
|
||||
Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip = { .clip_index = clip_idx } };
|
||||
g_dispatch(action);
|
||||
AppState s = dispatcher_get_state();
|
||||
if (s.show_midi_grid) {
|
||||
Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = clip_idx } };
|
||||
g_dispatch(action);
|
||||
} else {
|
||||
Action action = { .type = ACTION_TRIGGER_CLIP, .data.trigger_clip = { .clip_index = clip_idx } };
|
||||
g_dispatch(action);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
int clip_idx = grid_to_clip_index(selected_grid, selected_row, selected_col);
|
||||
Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip = { .clip_index = clip_idx } };
|
||||
g_dispatch(action);
|
||||
AppState s = dispatcher_get_state();
|
||||
if (s.show_midi_grid) {
|
||||
Action action = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = clip_idx } };
|
||||
g_dispatch(action);
|
||||
} else {
|
||||
Action action = { .type = ACTION_RESET_CLIP, .data.reset_clip = { .clip_index = clip_idx } };
|
||||
g_dispatch(action);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'y': {
|
||||
@@ -1335,6 +1385,12 @@ void tui_run(Engine *engine) {
|
||||
zoom_mode = !zoom_mode;
|
||||
break;
|
||||
}
|
||||
case 'G': {
|
||||
AppState s = dispatcher_get_state();
|
||||
Action a = { .type = ACTION_SET_SHOW_MIDI_GRID, .data.set_show_midi_grid = { .show = !s.show_midi_grid } };
|
||||
g_dispatch(a);
|
||||
break;
|
||||
}
|
||||
case 'L': {
|
||||
// Start fuzzy search for WAV files
|
||||
const char *samples_dir = getenv("JACK_LOOPER_SAMPLES_DIR");
|
||||
|
||||
Reference in New Issue
Block a user