|
@ -468,6 +468,8 @@ typedef struct { |
|
|
Usz tick_num; |
|
|
Usz tick_num; |
|
|
Usz ruler_spacing_y, ruler_spacing_x; |
|
|
Usz ruler_spacing_y, ruler_spacing_x; |
|
|
Tui_input_mode input_mode; |
|
|
Tui_input_mode input_mode; |
|
|
|
|
|
Usz bpm; |
|
|
|
|
|
double accum_secs; |
|
|
bool needs_remarking; |
|
|
bool needs_remarking; |
|
|
bool is_draw_dirty; |
|
|
bool is_draw_dirty; |
|
|
bool is_playing; |
|
|
bool is_playing; |
|
@ -488,6 +490,8 @@ void app_init(App_state* a) { |
|
|
a->ruler_spacing_y = 8; |
|
|
a->ruler_spacing_y = 8; |
|
|
a->ruler_spacing_x = 8; |
|
|
a->ruler_spacing_x = 8; |
|
|
a->input_mode = Tui_input_mode_normal; |
|
|
a->input_mode = Tui_input_mode_normal; |
|
|
|
|
|
a->bpm = 120; |
|
|
|
|
|
a->accum_secs = 0.0; |
|
|
a->needs_remarking = true; |
|
|
a->needs_remarking = true; |
|
|
a->is_draw_dirty = false; |
|
|
a->is_draw_dirty = false; |
|
|
a->is_playing = false; |
|
|
a->is_playing = false; |
|
@ -508,10 +512,45 @@ bool app_is_draw_dirty(App_state* a) { |
|
|
return a->is_draw_dirty || a->needs_remarking; |
|
|
return a->is_draw_dirty || a->needs_remarking; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void app_force_draw_dirty(App_state* a) { |
|
|
double app_secs_to_deadline(App_state const* a) { |
|
|
|
|
|
if (a->is_playing) { |
|
|
|
|
|
double secs_span = 60.0 / (double)a->bpm; |
|
|
|
|
|
double rem = secs_span - a->accum_secs; |
|
|
|
|
|
if (rem < 0.0) |
|
|
|
|
|
rem = 0.0; |
|
|
|
|
|
return rem; |
|
|
|
|
|
} else { |
|
|
|
|
|
return 1.0; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void app_apply_delta_secs(App_state* a, double secs) { |
|
|
|
|
|
if (a->is_playing) { |
|
|
|
|
|
a->accum_secs += secs; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void app_do_stuff(App_state* a) { |
|
|
|
|
|
double secs_span = 60.0 / (double)a->bpm; |
|
|
|
|
|
while (a->accum_secs > secs_span) { |
|
|
|
|
|
a->accum_secs -= secs_span; |
|
|
|
|
|
undo_history_push(&a->undo_hist, &a->field, a->tick_num); |
|
|
|
|
|
orca_run(a->field.buffer, a->markmap_r.buffer, a->field.height, |
|
|
|
|
|
a->field.width, a->tick_num, &a->bank, &a->oevent_list, |
|
|
|
|
|
a->piano_bits); |
|
|
|
|
|
++a->tick_num; |
|
|
|
|
|
a->piano_bits = ORCA_PIANO_BITS_NONE; |
|
|
|
|
|
a->needs_remarking = true; |
|
|
a->is_draw_dirty = true; |
|
|
a->is_draw_dirty = true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static double ms_to_sec(double ms) { |
|
|
|
|
|
return ms / 1000.0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void app_force_draw_dirty(App_state* a) { a->is_draw_dirty = true; } |
|
|
|
|
|
|
|
|
void app_draw(App_state* a, WINDOW* win) { |
|
|
void app_draw(App_state* a, WINDOW* win) { |
|
|
// We can predictavely step the next simulation tick and then use the
|
|
|
// We can predictavely step the next simulation tick and then use the
|
|
|
// resulting markmap buffer for better UI visualization. If we don't do
|
|
|
// resulting markmap buffer for better UI visualization. If we don't do
|
|
@ -676,10 +715,10 @@ void app_input_cmd(App_state* a, App_input_cmd ev) { |
|
|
case App_input_cmd_toggle_play_pause: |
|
|
case App_input_cmd_toggle_play_pause: |
|
|
if (a->is_playing) { |
|
|
if (a->is_playing) { |
|
|
a->is_playing = false; |
|
|
a->is_playing = false; |
|
|
nodelay(stdscr, FALSE); |
|
|
// nodelay(stdscr, FALSE);
|
|
|
} else { |
|
|
} else { |
|
|
a->is_playing = true; |
|
|
a->is_playing = true; |
|
|
nodelay(stdscr, TRUE); |
|
|
// nodelay(stdscr, TRUE);
|
|
|
} |
|
|
} |
|
|
a->is_draw_dirty = true; |
|
|
a->is_draw_dirty = true; |
|
|
break; |
|
|
break; |
|
@ -815,10 +854,45 @@ int main(int argc, char** argv) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
WINDOW* cont_win = NULL; |
|
|
WINDOW* cont_win = NULL; |
|
|
int cont_win_h = 0; |
|
|
int key = KEY_RESIZE; |
|
|
int cont_win_w = 0; |
|
|
wtimeout(stdscr, 0); |
|
|
|
|
|
U64 last_time = 0; |
|
|
|
|
|
// double accum_time = 0.0;
|
|
|
|
|
|
|
|
|
for (;;) { |
|
|
for (;;) { |
|
|
|
|
|
switch (key) { |
|
|
|
|
|
case ERR: { |
|
|
|
|
|
U64 diff = stm_laptime(&last_time); |
|
|
|
|
|
app_apply_delta_secs(&app_state, stm_sec(diff)); |
|
|
|
|
|
app_do_stuff(&app_state); |
|
|
|
|
|
if (app_is_draw_dirty(&app_state)) { |
|
|
|
|
|
app_draw(&app_state, cont_win); |
|
|
|
|
|
} |
|
|
|
|
|
diff = stm_laptime(&last_time); |
|
|
|
|
|
app_apply_delta_secs(&app_state, stm_sec(diff)); |
|
|
|
|
|
double secs_to_d = app_secs_to_deadline(&app_state); |
|
|
|
|
|
// fprintf(stderr, "to deadline: %f\n", secs_to_d);
|
|
|
|
|
|
if (secs_to_d < ms_to_sec(1.0)) { |
|
|
|
|
|
wtimeout(stdscr, 0); |
|
|
|
|
|
} else if (secs_to_d < ms_to_sec(3.0)) { |
|
|
|
|
|
wtimeout(stdscr, 1); |
|
|
|
|
|
} else if (secs_to_d < ms_to_sec(10.0)) { |
|
|
|
|
|
wtimeout(stdscr, 5); |
|
|
|
|
|
} else if (secs_to_d < ms_to_sec(50.0)) { |
|
|
|
|
|
wtimeout(stdscr, 10); |
|
|
|
|
|
} else { |
|
|
|
|
|
wtimeout(stdscr, 20); |
|
|
|
|
|
} |
|
|
|
|
|
//struct timespec ts;
|
|
|
|
|
|
//ts.tv_sec = 0;
|
|
|
|
|
|
//// ts.tv_nsec = 1000 * 1000 * 1;
|
|
|
|
|
|
//ts.tv_nsec = 1;
|
|
|
|
|
|
//int ret = nanosleep(&ts, NULL);
|
|
|
|
|
|
//if (ret) {
|
|
|
|
|
|
// fprintf(stderr, "interrupted sleep: %d\n", ret);
|
|
|
|
|
|
//}
|
|
|
|
|
|
} break; |
|
|
|
|
|
case KEY_RESIZE: { |
|
|
int term_height = getmaxy(stdscr); |
|
|
int term_height = getmaxy(stdscr); |
|
|
int term_width = getmaxx(stdscr); |
|
|
int term_width = getmaxx(stdscr); |
|
|
assert(term_height >= 0 && term_width >= 0); |
|
|
assert(term_height >= 0 && term_width >= 0); |
|
@ -834,36 +908,16 @@ int main(int argc, char** argv) { |
|
|
content_h -= margins_2; |
|
|
content_h -= margins_2; |
|
|
content_w -= margins_2; |
|
|
content_w -= margins_2; |
|
|
} |
|
|
} |
|
|
if (cont_win == NULL || cont_win_h != content_h || |
|
|
if (cont_win == NULL || getmaxy(cont_win) != content_h || |
|
|
cont_win_w != content_w) { |
|
|
getmaxx(cont_win) != content_w) { |
|
|
if (cont_win) { |
|
|
if (cont_win) { |
|
|
delwin(cont_win); |
|
|
delwin(cont_win); |
|
|
} |
|
|
} |
|
|
wclear(stdscr); |
|
|
wclear(stdscr); |
|
|
cont_win = derwin(stdscr, content_h, content_w, content_y, content_x); |
|
|
cont_win = derwin(stdscr, content_h, content_w, content_y, content_x); |
|
|
cont_win_h = content_h; |
|
|
|
|
|
cont_win_w = content_w; |
|
|
|
|
|
app_force_draw_dirty(&app_state); |
|
|
app_force_draw_dirty(&app_state); |
|
|
} |
|
|
} |
|
|
|
|
|
} break; |
|
|
if (app_is_draw_dirty(&app_state)) { |
|
|
|
|
|
app_draw(&app_state, cont_win); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int key; |
|
|
|
|
|
// ncurses gives us ERR if there was no user input. We'll sleep for 0
|
|
|
|
|
|
// seconds, so that we'll yield CPU time to the OS instead of looping as
|
|
|
|
|
|
// fast as possible. This avoids battery drain/excessive CPU usage. There
|
|
|
|
|
|
// are better ways to do this that waste less CPU, but they require doing a
|
|
|
|
|
|
// little more work on each individual platform (Linux, Mac, etc.)
|
|
|
|
|
|
for (;;) { |
|
|
|
|
|
key = wgetch(stdscr); |
|
|
|
|
|
if (key != ERR) |
|
|
|
|
|
break; |
|
|
|
|
|
sleep(0); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
switch (key) { |
|
|
|
|
|
case AND_CTRL('q'): |
|
|
case AND_CTRL('q'): |
|
|
goto quit; |
|
|
goto quit; |
|
|
case KEY_UP: |
|
|
case KEY_UP: |
|
@ -938,10 +992,7 @@ int main(int argc, char** argv) { |
|
|
#endif |
|
|
#endif |
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
key = wgetch(stdscr); |
|
|
// ncurses gives us the special value KEY_RESIZE if the user didn't
|
|
|
|
|
|
// actually type anything, but the terminal resized.
|
|
|
|
|
|
// bool ignored_input = ch < CHAR_MIN || ch > CHAR_MAX || ch == KEY_RESIZE;
|
|
|
|
|
|
} |
|
|
} |
|
|
quit: |
|
|
quit: |
|
|
if (cont_win) { |
|
|
if (cont_win) { |
|
|