/* Copyright 2021, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include #include #if defined(LINK_PLATFORM_UNIX) #include #include #include #elif defined(LINK_PLATFORM_WINDOWS) #pragma warning(push, 0) #pragma warning(disable : 4255) // 'no function prototype given' in winuser.h #pragma warning(disable : 4668) // undefined preprocessor macro in winioctl.h #pragma warning(disable : 5105) // "/wd5105" # "macro expansion producing 'defined' has // undefined behavior" in winbase.h #include #pragma warning(pop) #pragma warning(disable : 4100) // unreferenced formal parameter in main #endif #include typedef struct state { abl_link link; abl_link_session_state session_state; bool running; double quantum; } state; struct state *new_state(void) { struct state *s = malloc(sizeof(state)); s->link = abl_link_create(120); s->session_state = abl_link_create_session_state(); s->running = true; s->quantum = 4; return s; } void delete_state(state *state) { abl_link_destroy_session_state(state->session_state); abl_link_destroy(state->link); free(state); } void disable_buffered_input(void) { #if defined(LINK_PLATFORM_UNIX) struct termios t; tcgetattr(STDIN_FILENO, &t); t.c_lflag &= ~ICANON; tcsetattr(STDIN_FILENO, TCSANOW, &t); #endif } void enable_buffered_input(void) { #if defined(LINK_PLATFORM_UNIX) struct termios t; tcgetattr(STDIN_FILENO, &t); t.c_lflag |= ICANON; tcsetattr(STDIN_FILENO, TCSANOW, &t); #endif } bool wait_for_input(void) { #if defined(LINK_PLATFORM_UNIX) fd_set selectset; struct timeval timeout = {0, 50000}; int ret; FD_ZERO(&selectset); FD_SET(0, &selectset); ret = select(1, &selectset, NULL, NULL, &timeout); if (ret > 0) { return true; } #elif (LINK_PLATFORM_WINDOWS) HANDLE handle = GetStdHandle(STD_INPUT_HANDLE); if (WaitForSingleObject(handle, 50) == WAIT_OBJECT_0) { return true; } #else #error "Missing implementation" #endif return false; } void clear_line(void) { printf(" \r"); fflush(stdout); } void clear_input(void) { #if defined(LINK_PLATFORM_WINDOWS) { HANDLE handle = GetStdHandle(STD_INPUT_HANDLE); INPUT_RECORD r[512]; DWORD read; ReadConsoleInput(handle, r, 512, &read); } #endif } void print_help(void) { printf("\n\n < L I N K H U T >\n\n"); printf("usage:\n"); printf(" enable / disable Link: a\n"); printf(" start / stop: space\n"); printf(" decrease / increase tempo: w / e\n"); printf(" decrease / increase quantum: r / t\n"); printf(" enable / disable start stop sync: s\n"); printf(" quit: q\n"); } void print_state_header(void) { printf( "\nenabled | num peers | quantum | start stop sync | tempo | beats | metro\n"); } void print_state(state *state) { abl_link_capture_app_session_state(state->link, state->session_state); const uint64_t time = abl_link_clock_micros(state->link); const char *enabled = abl_link_is_enabled(state->link) ? "yes" : "no"; const uint64_t num_peers = abl_link_num_peers(state->link); const char *start_stop = abl_link_is_start_stop_sync_enabled(state->link) ? "yes" : " no"; const char *playing = abl_link_is_playing(state->session_state) ? "[playing]" : "[stopped]"; const double tempo = abl_link_tempo(state->session_state); const double beats = abl_link_beat_at_time(state->session_state, time, state->quantum); const double phase = abl_link_phase_at_time(state->session_state, time, state->quantum); printf("%7s | ", enabled); printf("%9" PRIu64 " | ", num_peers); printf("%7.f | ", state->quantum); printf("%3s %11s | ", start_stop, playing); printf("%7.2f | ", tempo); printf("%7.2f | ", beats); for (int i = 0; i < ceil(state->quantum); ++i) { if (i < phase) { printf("X"); } else { printf("O"); } } } void input(state *state) { char in; #if defined(LINK_PLATFORM_UNIX) in = (char)fgetc(stdin); #elif defined(LINK_PLATFORM_WINDOWS) HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); DWORD numCharsRead; INPUT_RECORD inputRecord; do { ReadConsoleInput(stdinHandle, &inputRecord, 1, &numCharsRead); } while ((inputRecord.EventType != KEY_EVENT) || inputRecord.Event.KeyEvent.bKeyDown); in = inputRecord.Event.KeyEvent.uChar.AsciiChar; #else #error "Missing implementation" #endif abl_link_capture_app_session_state(state->link, state->session_state); const double tempo = abl_link_tempo(state->session_state); const uint64_t timestamp = abl_link_clock_micros(state->link); const bool enabled = abl_link_is_enabled(state->link); switch (in) { case 'q': state->running = false; clear_line(); return; case 'a': abl_link_enable(state->link, !enabled); break; case 'w': abl_link_set_tempo(state->session_state, tempo - 1, timestamp); break; case 'e': abl_link_set_tempo(state->session_state, tempo + 1, timestamp); break; case 'r': state->quantum -= 1; break; case 't': state->quantum += 1; break; case 's': abl_link_enable_start_stop_sync( state->link, !abl_link_is_start_stop_sync_enabled(state->link)); break; case ' ': if (abl_link_is_playing(state->session_state)) { abl_link_set_is_playing( state->session_state, false, abl_link_clock_micros(state->link)); } else { abl_link_set_is_playing_and_request_beat_at_time(state->session_state, true, abl_link_clock_micros(state->link), 0, state->quantum); } break; } abl_link_commit_app_session_state(state->link, state->session_state); } int main(int nargs, char **args) { state *state = new_state(); print_help(); print_state_header(); disable_buffered_input(); clear_input(); while (state->running) { clear_line(); if (wait_for_input()) { input(state); } else { print_state(state); } } enable_buffered_input(); delete_state(state); return 0; }