diff --git a/.clang-format b/.clang-format index daff36e..a1d5124 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,146 @@ -BasedOnStyle: LLVM -ReflowComments: false +--- +# see: https://clang.llvm.org/docs/ClangFormatStyleOptions.html +Language: Cpp +# BasedOnStyle: WebKit +AccessModifierOffset: -4 +# maybe +AlignAfterOpenBracket: Align +#AlignAfterOpenBracket: DontAlign +AlignConsecutiveMacros: false +# maybe +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +# maybe -> add AlignAfterOperator +AlignOperands: false +# maybe -> true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: All +BreakBeforeBraces: WebKit +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 79 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +# maybe +IndentCaseLabels: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +# maybe here +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: Inner +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 1000000 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +# changed to 4 +TabWidth: 4 +UseCRLF: false +UseTab: Never +... -MacroBlockBegin: "^BEGIN_OPERATOR" -MacroBlockEnd: "^END_OPERATOR" diff --git a/.gitignore b/.gitignore index 0fc719f..815d981 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ + +.DS_Store +build + # Prerequisites *.d diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3f80988 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,167 @@ +cmake_minimum_required(VERSION 3.19) + +project(orca_project) + +# options +option(BUILD_MACOS_UNIVERSAL "Build Universal Externals") # not supported (you're on your own! :-) +option(ENABLE_LTO "enable link-time / interprocedural optimization") +option(BUILD_STATIC "build static executable") + +# use ccache if available +find_program(CCACHE_PROGRAM ccache) +if(CCACHE_PROGRAM) + message(STATUS "Found ccache in ${CCACHE_PROGRAM}") + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") +endif() + +if (ENABLE_LTO_IPO) + include(CheckIPOSupported) + check_ipo_supported(RESULT ipo_lto_supported OUTPUT check_lto_ipo_error) + if(ipo_lto_supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) + message(STATUS "IPO / LTO enabled") + else() + message(STATUS "IPO / LTO not supported: <${check_lto_ipo_error}>") + endif() +endif() + +message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") + +if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + if (${CMAKE_GENERATOR} MATCHES "Xcode") + if (${XCODE_VERSION} VERSION_LESS 10) + message(STATUS "Xcode 10 or higher is required. Please install from the Mac App Store.") + return () + endif () + endif () + + if (NOT CMAKE_OSX_ARCHITECTURES) + if(BUILD_MACOS_UNIVERSAL) + set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "macOS architecture" FORCE) + else() + set(CMAKE_OSX_ARCHITECTURES ${CMAKE_SYSTEM_PROCESSOR} CACHE STRING "macOS architecture" FORCE) + endif() + message("CMAKE_OSX_ARCHITECTURES set to ${CMAKE_OSX_ARCHITECTURES}") + endif() + + # i.e check if homebrew is used, set local prefix accordingly + set(local_prefix "/usr/local") + execute_process( + COMMAND brew --prefix + OUTPUT_VARIABLE homebrew_prefix + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(DEFINED homebrew_prefix) + set(HAS_HOMEBREW 1) + set(local_prefix ${homebrew_prefix}) + execute_process( + COMMAND brew --prefix portmidi + OUTPUT_VARIABLE portmidi_prefix + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(DEFINED portmidi_prefix) + set(HAS_HOMEBREW_PORTMIDI) + endif() + execute_process( + COMMAND brew --prefix ncurses + OUTPUT_VARIABLE ncurses_prefix + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(DEFINED ncurses_prefix) + set(HAS_HOMEBREW_NCURSES) + endif() + endif() + +endif() + +# ------------------------------------------------------------------------------ +# main + +set(PROJECT_NAME orca) + +set(PROJECT_SOURCES + gbuffer.c + field.c + vmio.c + sim.c + # livecoding + osc_out.c + term_util.c + sysmisc.c + thirdparty/oso.c + tui_main.c +) + +set(THIRDPARTY_DIR + ${CMAKE_SOURCE_DIR}/thirdparty +) + +set(THIRDPARTY_SOURCES + ${THIRDPARTY_DIR}/oso.c +) + +if(BUILD_STATIC) + set(portmidi_prefix ${CMAKE_BINARY_DIR}/deps) +endif() + + +add_executable( + ${PROJECT_NAME} + ${PROJECT_SOURCES} + ${THIRDPARTY_SOURCES} +) + +target_include_directories( + ${PROJECT_NAME} + PUBLIC + ${THIRDPARTY_DIR} + ${local_prefix}/include + ${ncurses_prefix}/include + ${portmidi_prefix}/include +) + +target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + _XOPEN_SOURCE_EXTENDED=1 + $<$:ORCA_OS_MAC> +) + +target_compile_options( + ${PROJECT_NAME} + PUBLIC + "-isystem thirdparty" + # common hardening + -O2 + -Wall + -Wformat=2 + -Wconversion + -Wimplicit-fallthrough + -U_FORTIFY_SOURCE + -D_FORTIFY_SOURCE + -D_GLIBCXX_ASSERTIONS + -fstack-protector-strong + # executable hardening + -fPIE +) + +target_link_directories( + ${PROJECT_NAME} + PUBLIC + ${local_prefix}/lib + ${ncurses_prefix}/lib + ${portmidi_prefix}/lib +) + +target_link_options( + ${PROJECT_NAME} + PUBLIC +) + +target_link_libraries( + ${PROJECT_NAME} + PUBLIC + $,${ncurses_prefix}/lib/libncursesw.a,-lncursesw> + $,${ncurses_prefix}/lib/libformw.a,-lformw> + $,${portmidi_prefix}/lib/libportmidi.a,-lportmidi> +) diff --git a/Makefile b/Makefile index 4991382..1c64785 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,15 @@ release: @echo "To run it, simply execute it:" >&2 @echo "$$ build/orca" >&2 +.PHONY: cmake static +cmake: + @mkdir -p build && cd build && cmake .. && make + +static: + @scripts/get_portmidi.sh + @mkdir -p build && cd build && cmake .. -DBUILD_STATIC=ON && make + + .PHONY: clean clean: @./tool clean diff --git a/README.md b/README.md index c1b2f79..4b2c06d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # ORCΛ +FORK NOTE: this fork adds cmake support to building orca and also improved static builds for macos. Otherwise its unchanged except for some clang-formatting of c files. + Orca is an [esoteric programming language](https://en.wikipedia.org/wiki/Esoteric_programming_language) and live editor designed to quickly create procedural sequencers. Every letter of the alphabet is an operation, lowercase letters execute on `*bang*`, and uppercase letters execute each frame. This is the C implementation of the [ORCΛ](https://wiki.xxiivv.com/site/orca.html) language and terminal livecoding environment. It's designed to be power efficient. It can handle large files, even if your terminal is small. diff --git a/base.h b/base.h index 446b627..5737e20 100644 --- a/base.h +++ b/base.h @@ -30,8 +30,8 @@ // (gcc / clang) or other #if defined(__GNUC__) || defined(__clang__) -#define ORCA_ASSUME_ALIGNED(_ptr, _alignment) \ - __builtin_assume_aligned(_ptr, _alignment) +#define ORCA_ASSUME_ALIGNED(_ptr, _alignment) \ + __builtin_assume_aligned(_ptr, _alignment) #define ORCA_PURE __attribute__((pure)) #define ORCA_LIKELY(_x) __builtin_expect(_x, 1) #define ORCA_UNLIKELY(_x) __builtin_expect(_x, 0) @@ -48,12 +48,14 @@ // array count, safer on gcc/clang #if defined(__GNUC__) || defined(__clang__) -#define ORCA_ASSERT_IS_ARRAY(_array) \ - (sizeof(char[1 - 2 * __builtin_types_compatible_p( \ - __typeof(_array), __typeof(&(_array)[0]))]) - \ - 1) -#define ORCA_ARRAY_COUNTOF(_array) \ - (sizeof(_array) / sizeof((_array)[0]) + ORCA_ASSERT_IS_ARRAY(_array)) +#define ORCA_ASSERT_IS_ARRAY(_array) \ + (sizeof(char[1 \ + - 2 \ + * __builtin_types_compatible_p(__typeof(_array), \ + __typeof(&(_array)[0]))]) \ + - 1) +#define ORCA_ARRAY_COUNTOF(_array) \ + (sizeof(_array) / sizeof((_array)[0]) + ORCA_ASSERT_IS_ARRAY(_array)) #else // pray #define ORCA_ARRAY_COUNTOF(_array) (sizeof(_array) / sizeof(_array[0])) @@ -76,39 +78,41 @@ typedef ssize_t Isz; typedef char Glyph; typedef U8 Mark; -ORCA_FORCEINLINE static Usz orca_round_up_power2(Usz x) { - assert(x <= SIZE_MAX / 2 + 1); - x -= 1; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; +ORCA_FORCEINLINE static Usz orca_round_up_power2(Usz x) +{ + assert(x <= SIZE_MAX / 2 + 1); + x -= 1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; #if SIZE_MAX > UINT32_MAX - x |= x >> 32; + x |= x >> 32; #endif - return x + 1; + return x + 1; } ORCA_OK_IF_UNUSED -static bool orca_is_valid_glyph(Glyph c) { - if (c >= '0' && c <= '9') - return true; - if (c >= 'A' && c <= 'Z') - return true; - if (c >= 'a' && c <= 'z') - return true; - switch (c) { - case '!': - case '#': - case '%': - case '*': - case '.': - case ':': - case ';': - case '=': - case '?': - return true; - } - return false; +static bool orca_is_valid_glyph(Glyph c) +{ + if (c >= '0' && c <= '9') + return true; + if (c >= 'A' && c <= 'Z') + return true; + if (c >= 'a' && c <= 'z') + return true; + switch (c) { + case '!': + case '#': + case '%': + case '*': + case '.': + case ':': + case ';': + case '=': + case '?': + return true; + } + return false; } diff --git a/cli_main.c b/cli_main.c index 419f3fb..f4183e0 100644 --- a/cli_main.c +++ b/cli_main.c @@ -5,7 +5,8 @@ #include "vmio.h" #include -static ORCA_NOINLINE void usage(void) { // clang-format off +static ORCA_NOINLINE void usage(void) +{ // clang-format off fprintf(stderr, "Usage: cli [options] infile\n\n" "Options:\n" @@ -16,85 +17,87 @@ fprintf(stderr, " -h or --help Print this message and exit.\n" );} // clang-format on -int main(int argc, char **argv) { - static struct option cli_options[] = {{"help", no_argument, 0, 'h'}, - {"quiet", no_argument, 0, 'q'}, - {NULL, 0, NULL, 0}}; +int main(int argc, char** argv) +{ + static struct option cli_options[] = { { "help", no_argument, 0, 'h' }, + { "quiet", no_argument, 0, 'q' }, + { NULL, 0, NULL, 0 } }; - char *input_file = NULL; - int ticks = 1; - bool print_output = true; + char* input_file = NULL; + int ticks = 1; + bool print_output = true; - for (;;) { - int c = getopt_long(argc, argv, "t:qh", cli_options, NULL); - if (c == -1) - break; - switch (c) { - case 't': - ticks = atoi(optarg); - if (ticks == 0 && strcmp(optarg, "0")) { - fprintf(stderr, - "Bad timestep argument %s.\n" - "Must be 0 or a positive integer.\n", - optarg); - return 1; - } - break; - case 'q': - print_output = false; - break; - case 'h': - usage(); - return 0; - case '?': - usage(); - return 1; + for (;;) { + int c = getopt_long(argc, argv, "t:qh", cli_options, NULL); + if (c == -1) + break; + switch (c) { + case 't': + ticks = atoi(optarg); + if (ticks == 0 && strcmp(optarg, "0")) { + fprintf(stderr, + "Bad timestep argument %s.\n" + "Must be 0 or a positive integer.\n", + optarg); + return 1; + } + break; + case 'q': + print_output = false; + break; + case 'h': + usage(); + return 0; + case '?': + usage(); + return 1; + } } - } - if (optind == argc - 1) { - input_file = argv[optind]; - } else if (optind < argc - 1) { - fprintf(stderr, "Expected only 1 file argument.\n"); - usage(); - return 1; - } + if (optind == argc - 1) { + input_file = argv[optind]; + } else if (optind < argc - 1) { + fprintf(stderr, "Expected only 1 file argument.\n"); + usage(); + return 1; + } - if (input_file == NULL) { - fprintf(stderr, "No input file.\n"); - usage(); - return 1; - } - if (ticks < 0) { - fprintf(stderr, "Time must be >= 0.\n"); - usage(); - return 1; - } + if (input_file == NULL) { + fprintf(stderr, "No input file.\n"); + usage(); + return 1; + } + if (ticks < 0) { + fprintf(stderr, "Time must be >= 0.\n"); + usage(); + return 1; + } - Field field; - field_init(&field); - Field_load_error fle = field_load_file(input_file, &field); - if (fle != Field_load_error_ok) { + Field field; + field_init(&field); + Field_load_error fle = field_load_file(input_file, &field); + if (fle != Field_load_error_ok) { + field_deinit(&field); + fprintf(stderr, "File load error: %s.\n", + field_load_error_string(fle)); + return 1; + } + Mbuf_reusable mbuf_r; + mbuf_reusable_init(&mbuf_r); + mbuf_reusable_ensure_size(&mbuf_r, field.height, field.width); + Oevent_list oevent_list; + oevent_list_init(&oevent_list); + Usz max_ticks = (Usz)ticks; + for (Usz i = 0; i < max_ticks; ++i) { + mbuffer_clear(mbuf_r.buffer, field.height, field.width); + oevent_list_clear(&oevent_list); + orca_run(field.buffer, mbuf_r.buffer, field.height, field.width, i, + &oevent_list, 0); + } + mbuf_reusable_deinit(&mbuf_r); + oevent_list_deinit(&oevent_list); + if (print_output) + field_fput(&field, stdout); field_deinit(&field); - fprintf(stderr, "File load error: %s.\n", field_load_error_string(fle)); - return 1; - } - Mbuf_reusable mbuf_r; - mbuf_reusable_init(&mbuf_r); - mbuf_reusable_ensure_size(&mbuf_r, field.height, field.width); - Oevent_list oevent_list; - oevent_list_init(&oevent_list); - Usz max_ticks = (Usz)ticks; - for (Usz i = 0; i < max_ticks; ++i) { - mbuffer_clear(mbuf_r.buffer, field.height, field.width); - oevent_list_clear(&oevent_list); - orca_run(field.buffer, mbuf_r.buffer, field.height, field.width, i, - &oevent_list, 0); - } - mbuf_reusable_deinit(&mbuf_r); - oevent_list_deinit(&oevent_list); - if (print_output) - field_fput(&field, stdout); - field_deinit(&field); - return 0; + return 0; } diff --git a/field.c b/field.c index 1a3b05b..b093909 100644 --- a/field.c +++ b/field.c @@ -2,156 +2,166 @@ #include "gbuffer.h" #include -void field_init(Field *f) { - f->buffer = NULL; - f->height = 0; - f->width = 0; +void field_init(Field* f) +{ + f->buffer = NULL; + f->height = 0; + f->width = 0; } -void field_init_fill(Field *f, Usz height, Usz width, Glyph fill_char) { - assert(height <= ORCA_Y_MAX && width <= ORCA_X_MAX); - Usz num_cells = height * width; - f->buffer = malloc(num_cells * sizeof(Glyph)); - memset(f->buffer, fill_char, num_cells); - f->height = (U16)height; - f->width = (U16)width; +void field_init_fill(Field* f, Usz height, Usz width, Glyph fill_char) +{ + assert(height <= ORCA_Y_MAX && width <= ORCA_X_MAX); + Usz num_cells = height * width; + f->buffer = malloc(num_cells * sizeof(Glyph)); + memset(f->buffer, fill_char, num_cells); + f->height = (U16)height; + f->width = (U16)width; } -void field_deinit(Field *f) { free(f->buffer); } +void field_deinit(Field* f) { free(f->buffer); } -void field_resize_raw(Field *f, Usz height, Usz width) { - assert(height <= ORCA_Y_MAX && width <= ORCA_X_MAX); - Usz cells = height * width; - f->buffer = realloc(f->buffer, cells * sizeof(Glyph)); - f->height = (U16)height; - f->width = (U16)width; +void field_resize_raw(Field* f, Usz height, Usz width) +{ + assert(height <= ORCA_Y_MAX && width <= ORCA_X_MAX); + Usz cells = height * width; + f->buffer = realloc(f->buffer, cells * sizeof(Glyph)); + f->height = (U16)height; + f->width = (U16)width; } -void field_resize_raw_if_necessary(Field *field, Usz height, Usz width) { - if (field->height != height || field->width != width) { - field_resize_raw(field, height, width); - } +void field_resize_raw_if_necessary(Field* field, Usz height, Usz width) +{ + if (field->height != height || field->width != width) { + field_resize_raw(field, height, width); + } } -void field_copy(Field *src, Field *dest) { - field_resize_raw_if_necessary(dest, src->height, src->width); - gbuffer_copy_subrect(src->buffer, dest->buffer, src->height, src->width, - dest->height, dest->width, 0, 0, 0, 0, src->height, - src->width); +void field_copy(Field* src, Field* dest) +{ + field_resize_raw_if_necessary(dest, src->height, src->width); + gbuffer_copy_subrect(src->buffer, dest->buffer, src->height, src->width, + dest->height, dest->width, 0, 0, 0, 0, src->height, + src->width); } static inline bool glyph_char_is_valid(char c) { return c >= '!' && c <= '~'; } -void field_fput(Field *f, FILE *stream) { - enum { Column_buffer_count = 4096 }; - char out_buffer[Column_buffer_count]; - Usz f_height = f->height; - Usz f_width = f->width; - Glyph *f_buffer = f->buffer; - if (f_width > Column_buffer_count - 2) - return; - for (Usz iy = 0; iy < f_height; ++iy) { - Glyph *row_p = f_buffer + f_width * iy; - for (Usz ix = 0; ix < f_width; ++ix) { - char c = row_p[ix]; - out_buffer[ix] = glyph_char_is_valid(c) ? c : '?'; +void field_fput(Field* f, FILE* stream) +{ + enum { Column_buffer_count = 4096 }; + char out_buffer[Column_buffer_count]; + Usz f_height = f->height; + Usz f_width = f->width; + Glyph* f_buffer = f->buffer; + if (f_width > Column_buffer_count - 2) + return; + for (Usz iy = 0; iy < f_height; ++iy) { + Glyph* row_p = f_buffer + f_width * iy; + for (Usz ix = 0; ix < f_width; ++ix) { + char c = row_p[ix]; + out_buffer[ix] = glyph_char_is_valid(c) ? c : '?'; + } + out_buffer[f_width] = '\n'; + out_buffer[f_width + 1] = '\0'; + fputs(out_buffer, stream); } - out_buffer[f_width] = '\n'; - out_buffer[f_width + 1] = '\0'; - fputs(out_buffer, stream); - } } -Field_load_error field_load_file(char const *filepath, Field *field) { - FILE *file = fopen(filepath, "r"); - if (file == NULL) { - return Field_load_error_cant_open_file; - } - enum { Bufsize = 4096 }; - char buf[Bufsize]; - Usz first_row_columns = 0; - Usz rows = 0; - for (;;) { - char *s = fgets(buf, Bufsize, file); - if (s == NULL) - break; - if (rows == ORCA_Y_MAX) { - fclose(file); - return Field_load_error_too_many_rows; - } - Usz len = strlen(buf); - if (len == Bufsize - 1 && buf[len - 1] != '\n' && !feof(file)) { - fclose(file); - return Field_load_error_too_many_columns; +Field_load_error field_load_file(char const* filepath, Field* field) +{ + FILE* file = fopen(filepath, "r"); + if (file == NULL) { + return Field_load_error_cant_open_file; } + enum { Bufsize = 4096 }; + char buf[Bufsize]; + Usz first_row_columns = 0; + Usz rows = 0; for (;;) { - if (len == 0) - break; - if (!isspace(buf[len - 1])) - break; - --len; + char* s = fgets(buf, Bufsize, file); + if (s == NULL) + break; + if (rows == ORCA_Y_MAX) { + fclose(file); + return Field_load_error_too_many_rows; + } + Usz len = strlen(buf); + if (len == Bufsize - 1 && buf[len - 1] != '\n' && !feof(file)) { + fclose(file); + return Field_load_error_too_many_columns; + } + for (;;) { + if (len == 0) + break; + if (!isspace(buf[len - 1])) + break; + --len; + } + if (len == 0) + continue; + if (len >= ORCA_X_MAX) { + fclose(file); + return Field_load_error_too_many_columns; + } + // quick hack until we use a proper scanner + if (rows == 0) { + first_row_columns = len; + } else if (len != first_row_columns) { + fclose(file); + return Field_load_error_not_a_rectangle; + } + field_resize_raw(field, rows + 1, first_row_columns); + Glyph* rowbuff = field->buffer + first_row_columns * rows; + for (Usz i = 0; i < len; ++i) { + char c = buf[i]; + rowbuff[i] = glyph_char_is_valid(c) ? c : '.'; + } + ++rows; } - if (len == 0) - continue; - if (len >= ORCA_X_MAX) { - fclose(file); - return Field_load_error_too_many_columns; - } - // quick hack until we use a proper scanner - if (rows == 0) { - first_row_columns = len; - } else if (len != first_row_columns) { - fclose(file); - return Field_load_error_not_a_rectangle; - } - field_resize_raw(field, rows + 1, first_row_columns); - Glyph *rowbuff = field->buffer + first_row_columns * rows; - for (Usz i = 0; i < len; ++i) { - char c = buf[i]; - rowbuff[i] = glyph_char_is_valid(c) ? c : '.'; - } - ++rows; - } - fclose(file); - return Field_load_error_ok; + fclose(file); + return Field_load_error_ok; } -char const *field_load_error_string(Field_load_error fle) { - char const *errstr = "Unknown"; - switch (fle) { - case Field_load_error_ok: - errstr = "OK"; - break; - case Field_load_error_cant_open_file: - errstr = "Unable to open file"; - break; - case Field_load_error_too_many_columns: - errstr = "Grid file has too many columns"; - break; - case Field_load_error_too_many_rows: - errstr = "Grid file has too many rows"; - break; - case Field_load_error_no_rows_read: - errstr = "Grid file has no rows"; - break; - case Field_load_error_not_a_rectangle: - errstr = "Grid file is not a rectangle"; - break; - } - return errstr; +char const* field_load_error_string(Field_load_error fle) +{ + char const* errstr = "Unknown"; + switch (fle) { + case Field_load_error_ok: + errstr = "OK"; + break; + case Field_load_error_cant_open_file: + errstr = "Unable to open file"; + break; + case Field_load_error_too_many_columns: + errstr = "Grid file has too many columns"; + break; + case Field_load_error_too_many_rows: + errstr = "Grid file has too many rows"; + break; + case Field_load_error_no_rows_read: + errstr = "Grid file has no rows"; + break; + case Field_load_error_not_a_rectangle: + errstr = "Grid file is not a rectangle"; + break; + } + return errstr; } -void mbuf_reusable_init(Mbuf_reusable *mbr) { - mbr->buffer = NULL; - mbr->capacity = 0; +void mbuf_reusable_init(Mbuf_reusable* mbr) +{ + mbr->buffer = NULL; + mbr->capacity = 0; } -void mbuf_reusable_ensure_size(Mbuf_reusable *mbr, Usz height, Usz width) { - Usz capacity = height * width; - if (mbr->capacity < capacity) { - mbr->buffer = realloc(mbr->buffer, capacity); - mbr->capacity = capacity; - } +void mbuf_reusable_ensure_size(Mbuf_reusable* mbr, Usz height, Usz width) +{ + Usz capacity = height * width; + if (mbr->capacity < capacity) { + mbr->buffer = realloc(mbr->buffer, capacity); + mbr->capacity = capacity; + } } -void mbuf_reusable_deinit(Mbuf_reusable *mbr) { free(mbr->buffer); } +void mbuf_reusable_deinit(Mbuf_reusable* mbr) { free(mbr->buffer); } diff --git a/field.h b/field.h index 1707b0e..1b36860 100644 --- a/field.h +++ b/field.h @@ -7,30 +7,30 @@ // might want to do. Not used by the VM. typedef struct { - Glyph *buffer; - U16 width, height; + Glyph* buffer; + U16 width, height; } Field; -void field_init(Field *field); -void field_init_fill(Field *field, Usz height, Usz width, Glyph fill_char); -void field_deinit(Field *field); -void field_resize_raw(Field *field, Usz height, Usz width); -void field_resize_raw_if_necessary(Field *field, Usz height, Usz width); -void field_copy(Field *src, Field *dest); -void field_fput(Field *field, FILE *stream); +void field_init(Field* field); +void field_init_fill(Field* field, Usz height, Usz width, Glyph fill_char); +void field_deinit(Field* field); +void field_resize_raw(Field* field, Usz height, Usz width); +void field_resize_raw_if_necessary(Field* field, Usz height, Usz width); +void field_copy(Field* src, Field* dest); +void field_fput(Field* field, FILE* stream); typedef enum { - Field_load_error_ok = 0, - Field_load_error_cant_open_file = 1, - Field_load_error_too_many_columns = 2, - Field_load_error_too_many_rows = 3, - Field_load_error_no_rows_read = 4, - Field_load_error_not_a_rectangle = 5, + Field_load_error_ok = 0, + Field_load_error_cant_open_file = 1, + Field_load_error_too_many_columns = 2, + Field_load_error_too_many_rows = 3, + Field_load_error_no_rows_read = 4, + Field_load_error_not_a_rectangle = 5, } Field_load_error; -Field_load_error field_load_file(char const *filepath, Field *field); +Field_load_error field_load_file(char const* filepath, Field* field); -char const *field_load_error_string(Field_load_error fle); +char const* field_load_error_string(Field_load_error fle); // A reusable buffer for the per-grid-cell flags. Similar to how Field is a // reusable buffer for Glyph, Mbuf_reusable is for Mark. The naming isn't so @@ -41,10 +41,10 @@ char const *field_load_error_string(Field_load_error fle); // that functionality. typedef struct Mbuf_reusable { - Mark *buffer; - Usz capacity; + Mark* buffer; + Usz capacity; } Mbuf_reusable; -void mbuf_reusable_init(Mbuf_reusable *mbr); -void mbuf_reusable_ensure_size(Mbuf_reusable *mbr, Usz height, Usz width); -void mbuf_reusable_deinit(Mbuf_reusable *mbr); +void mbuf_reusable_init(Mbuf_reusable* mbr); +void mbuf_reusable_ensure_size(Mbuf_reusable* mbr, Usz height, Usz width); +void mbuf_reusable_deinit(Mbuf_reusable* mbr); diff --git a/gbuffer.c b/gbuffer.c index 15339e9..bca529d 100644 --- a/gbuffer.c +++ b/gbuffer.c @@ -1,80 +1,83 @@ #include "gbuffer.h" -void gbuffer_copy_subrect(Glyph *src, Glyph *dest, Usz src_height, +void gbuffer_copy_subrect(Glyph* src, Glyph* dest, Usz src_height, Usz src_width, Usz dest_height, Usz dest_width, Usz src_y, Usz src_x, Usz dest_y, Usz dest_x, - Usz height, Usz width) { - if (src_height <= src_y || src_width <= src_x || dest_height <= dest_y || - dest_width <= dest_x) - return; - Usz ny_0 = src_height - src_y; - Usz ny_1 = dest_height - dest_y; - Usz ny = height; - if (ny_0 < ny) - ny = ny_0; - if (ny_1 < ny) - ny = ny_1; - if (ny == 0) - return; - Usz row_copy_0 = src_width - src_x; - Usz row_copy_1 = dest_width - dest_x; - Usz row_copy = width; - if (row_copy_0 < row_copy) - row_copy = row_copy_0; - if (row_copy_1 < row_copy) - row_copy = row_copy_1; - Usz copy_bytes = row_copy * sizeof(Glyph); - Glyph *src_p = src + src_y * src_width + src_x; - Glyph *dest_p = dest + dest_y * dest_width + dest_x; - Isz src_stride; - Isz dest_stride; - if (src_y >= dest_y) { - src_stride = (Isz)src_width; - dest_stride = (Isz)dest_width; - } else { - src_p += (ny - 1) * src_width; - dest_p += (ny - 1) * dest_width; - src_stride = -(Isz)src_width; - dest_stride = -(Isz)dest_width; - } - Usz iy = 0; - for (;;) { - memmove(dest_p, src_p, copy_bytes); - ++iy; - if (iy == ny) - break; - src_p += src_stride; - dest_p += dest_stride; - } + Usz height, Usz width) +{ + if (src_height <= src_y || src_width <= src_x || dest_height <= dest_y + || dest_width <= dest_x) + return; + Usz ny_0 = src_height - src_y; + Usz ny_1 = dest_height - dest_y; + Usz ny = height; + if (ny_0 < ny) + ny = ny_0; + if (ny_1 < ny) + ny = ny_1; + if (ny == 0) + return; + Usz row_copy_0 = src_width - src_x; + Usz row_copy_1 = dest_width - dest_x; + Usz row_copy = width; + if (row_copy_0 < row_copy) + row_copy = row_copy_0; + if (row_copy_1 < row_copy) + row_copy = row_copy_1; + Usz copy_bytes = row_copy * sizeof(Glyph); + Glyph* src_p = src + src_y * src_width + src_x; + Glyph* dest_p = dest + dest_y * dest_width + dest_x; + Isz src_stride; + Isz dest_stride; + if (src_y >= dest_y) { + src_stride = (Isz)src_width; + dest_stride = (Isz)dest_width; + } else { + src_p += (ny - 1) * src_width; + dest_p += (ny - 1) * dest_width; + src_stride = -(Isz)src_width; + dest_stride = -(Isz)dest_width; + } + Usz iy = 0; + for (;;) { + memmove(dest_p, src_p, copy_bytes); + ++iy; + if (iy == ny) + break; + src_p += src_stride; + dest_p += dest_stride; + } } -void gbuffer_fill_subrect(Glyph *gbuffer, Usz f_height, Usz f_width, Usz y, - Usz x, Usz height, Usz width, Glyph fill_char) { - if (y >= f_height || x >= f_width) - return; - Usz rows_0 = f_height - y; - Usz rows = height; - if (rows_0 < rows) - rows = rows_0; - if (rows == 0) - return; - Usz columns_0 = f_width - x; - Usz columns = width; - if (columns_0 < columns) - columns = columns_0; - Usz fill_bytes = columns * sizeof(Glyph); - Glyph *p = gbuffer + y * f_width + x; - Usz iy = 0; - for (;;) { - memset(p, fill_char, fill_bytes); - ++iy; - if (iy == rows) - break; - p += f_width; - } +void gbuffer_fill_subrect(Glyph* gbuffer, Usz f_height, Usz f_width, Usz y, + Usz x, Usz height, Usz width, Glyph fill_char) +{ + if (y >= f_height || x >= f_width) + return; + Usz rows_0 = f_height - y; + Usz rows = height; + if (rows_0 < rows) + rows = rows_0; + if (rows == 0) + return; + Usz columns_0 = f_width - x; + Usz columns = width; + if (columns_0 < columns) + columns = columns_0; + Usz fill_bytes = columns * sizeof(Glyph); + Glyph* p = gbuffer + y * f_width + x; + Usz iy = 0; + for (;;) { + memset(p, fill_char, fill_bytes); + ++iy; + if (iy == rows) + break; + p += f_width; + } } -void mbuffer_clear(Mark *mbuf, Usz height, Usz width) { - Usz cleared_size = height * width; - memset(mbuf, 0, cleared_size); +void mbuffer_clear(Mark* mbuf, Usz height, Usz width) +{ + Usz cleared_size = height * width; + memset(mbuf, 0, cleared_size); } diff --git a/gbuffer.h b/gbuffer.h index 3bd6271..10defe2 100644 --- a/gbuffer.h +++ b/gbuffer.h @@ -1,85 +1,91 @@ #pragma once #include "base.h" -ORCA_PURE static inline Glyph gbuffer_peek_relative(Glyph *gbuf, Usz height, +ORCA_PURE static inline Glyph gbuffer_peek_relative(Glyph* gbuf, Usz height, Usz width, Usz y, Usz x, - Isz delta_y, Isz delta_x) { - Isz y0 = (Isz)y + delta_y; - Isz x0 = (Isz)x + delta_x; - if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) - return '.'; - return gbuf[(Usz)y0 * width + (Usz)x0]; + Isz delta_y, Isz delta_x) +{ + Isz y0 = (Isz)y + delta_y; + Isz x0 = (Isz)x + delta_x; + if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) + return '.'; + return gbuf[(Usz)y0 * width + (Usz)x0]; } -static inline void gbuffer_poke(Glyph *gbuf, Usz height, Usz width, Usz y, - Usz x, Glyph g) { - assert(y < height && x < width); - (void)height; - gbuf[y * width + x] = g; +static inline void gbuffer_poke(Glyph* gbuf, Usz height, Usz width, Usz y, + Usz x, Glyph g) +{ + assert(y < height && x < width); + (void)height; + gbuf[y * width + x] = g; } -static inline void gbuffer_poke_relative(Glyph *gbuf, Usz height, Usz width, - Usz y, Usz x, Isz delta_y, Isz delta_x, - Glyph g) { - Isz y0 = (Isz)y + delta_y; - Isz x0 = (Isz)x + delta_x; - if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) - return; - gbuf[(Usz)y0 * width + (Usz)x0] = g; +static inline void gbuffer_poke_relative(Glyph* gbuf, Usz height, Usz width, + Usz y, Usz x, Isz delta_y, + Isz delta_x, Glyph g) +{ + Isz y0 = (Isz)y + delta_y; + Isz x0 = (Isz)x + delta_x; + if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) + return; + gbuf[(Usz)y0 * width + (Usz)x0] = g; } ORCA_NOINLINE -void gbuffer_copy_subrect(Glyph *src, Glyph *dest, Usz src_grid_h, +void gbuffer_copy_subrect(Glyph* src, Glyph* dest, Usz src_grid_h, Usz src_grid_w, Usz dest_grid_h, Usz dest_grid_w, Usz src_y, Usz src_x, Usz dest_y, Usz dest_x, Usz height, Usz width); ORCA_NOINLINE -void gbuffer_fill_subrect(Glyph *gbuf, Usz grid_h, Usz grid_w, Usz y, Usz x, +void gbuffer_fill_subrect(Glyph* gbuf, Usz grid_h, Usz grid_w, Usz y, Usz x, Usz height, Usz width, Glyph fill_char); typedef enum { - Mark_flag_none = 0, - Mark_flag_input = 1 << 0, - Mark_flag_output = 1 << 1, - Mark_flag_haste_input = 1 << 2, - Mark_flag_lock = 1 << 3, - Mark_flag_sleep = 1 << 4, + Mark_flag_none = 0, + Mark_flag_input = 1 << 0, + Mark_flag_output = 1 << 1, + Mark_flag_haste_input = 1 << 2, + Mark_flag_lock = 1 << 3, + Mark_flag_sleep = 1 << 4, } Mark_flags; ORCA_OK_IF_UNUSED -static Mark_flags mbuffer_peek(Mark *mbuf, Usz height, Usz width, Usz y, - Usz x) { - (void)height; - return mbuf[y * width + x]; +static Mark_flags mbuffer_peek(Mark* mbuf, Usz height, Usz width, Usz y, Usz x) +{ + (void)height; + return mbuf[y * width + x]; } ORCA_OK_IF_UNUSED -static Mark_flags mbuffer_peek_relative(Mark *mbuf, Usz height, Usz width, - Usz y, Usz x, Isz offs_y, Isz offs_x) { - Isz y0 = (Isz)y + offs_y; - Isz x0 = (Isz)x + offs_x; - if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) - return Mark_flag_none; - return mbuf[(Usz)y0 * width + (Usz)x0]; +static Mark_flags mbuffer_peek_relative(Mark* mbuf, Usz height, Usz width, + Usz y, Usz x, Isz offs_y, Isz offs_x) +{ + Isz y0 = (Isz)y + offs_y; + Isz x0 = (Isz)x + offs_x; + if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) + return Mark_flag_none; + return mbuf[(Usz)y0 * width + (Usz)x0]; } ORCA_OK_IF_UNUSED -static void mbuffer_poke_flags_or(Mark *mbuf, Usz height, Usz width, Usz y, - Usz x, Mark_flags flags) { - (void)height; - mbuf[y * width + x] |= (Mark)flags; +static void mbuffer_poke_flags_or(Mark* mbuf, Usz height, Usz width, Usz y, + Usz x, Mark_flags flags) +{ + (void)height; + mbuf[y * width + x] |= (Mark)flags; } ORCA_OK_IF_UNUSED -static void mbuffer_poke_relative_flags_or(Mark *mbuf, Usz height, Usz width, - Usz y, Usz x, Isz offs_y, Isz offs_x, - Mark_flags flags) { - Isz y0 = (Isz)y + offs_y; - Isz x0 = (Isz)x + offs_x; - if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) - return; - mbuf[(Usz)y0 * width + (Usz)x0] |= (Mark)flags; +static void mbuffer_poke_relative_flags_or(Mark* mbuf, Usz height, Usz width, + Usz y, Usz x, Isz offs_y, + Isz offs_x, Mark_flags flags) +{ + Isz y0 = (Isz)y + offs_y; + Isz x0 = (Isz)x + offs_x; + if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) + return; + mbuf[(Usz)y0 * width + (Usz)x0] |= (Mark)flags; } -void mbuffer_clear(Mark *mbuf, Usz height, Usz width); +void mbuffer_clear(Mark* mbuf, Usz height, Usz width); diff --git a/osc_out.c b/osc_out.c index 4711bd5..f344d23 100644 --- a/osc_out.c +++ b/osc_out.c @@ -8,51 +8,53 @@ #include struct Oosc_dev { - int fd; - // Just keep the whole list around, since juggling the strict-aliasing - // problems with sockaddr_storage is not worth it. - struct addrinfo *chosen; - struct addrinfo *head; + int fd; + // Just keep the whole list around, since juggling the strict-aliasing + // problems with sockaddr_storage is not worth it. + struct addrinfo* chosen; + struct addrinfo* head; }; -Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev **out_ptr, - char const *dest_addr, - char const *dest_port) { - struct addrinfo hints = {0}; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = 0; - hints.ai_flags = AI_ADDRCONFIG; - struct addrinfo *chosen = NULL; - struct addrinfo *head = NULL; - int err = getaddrinfo(dest_addr, dest_port, &hints, &head); - if (err != 0) { +Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev** out_ptr, + char const* dest_addr, + char const* dest_port) +{ + struct addrinfo hints = { 0 }; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_ADDRCONFIG; + struct addrinfo* chosen = NULL; + struct addrinfo* head = NULL; + int err = getaddrinfo(dest_addr, dest_port, &hints, &head); + if (err != 0) { #if 0 fprintf(stderr, "Failed to get address info, error: %d\n", errno); #endif - return Oosc_udp_create_error_getaddrinfo_failed; - } - // Special behavior: if no hostname was provided, we'll get loopback(s) from - // getaddrinfo. Which is good. But on systems with both an ipv4 and ipv6 - // address, we might get the ipv6 address listed first. And the OSC server, - // for example Plogue Bidule, might not support ipv6. And defaulting to the - // ipv6 loopback wouldn't work, in that case. So if there's no hostname, and - // we find an ipv4 address in the results, prefer to use that. - // - // Actually let's just prefer ipv4 completely for now + return Oosc_udp_create_error_getaddrinfo_failed; + } + // Special behavior: if no hostname was provided, we'll get loopback(s) + // from getaddrinfo. Which is good. But on systems with both an ipv4 and + // ipv6 address, we might get the ipv6 address listed first. And the OSC + // server, for example Plogue Bidule, might not support ipv6. And + // defaulting to the ipv6 loopback wouldn't work, in that case. So if + // there's no hostname, and we find an ipv4 address in the results, prefer + // to use that. + // + // Actually let's just prefer ipv4 completely for now #if 0 if (!dest_addr) { #endif - { - for (struct addrinfo *a = head; a; a = a->ai_next) { - if (a->ai_family != AF_INET) - continue; - chosen = a; - break; + { + for (struct addrinfo* a = head; a; a = a->ai_next) { + if (a->ai_family != AF_INET) + continue; + chosen = a; + break; + } } - } - if (!chosen) - chosen = head; + if (!chosen) + chosen = head; #if 0 for (struct addrinfo* a = head; a; a = a->ai_next) { char buff[INET6_ADDRSTRLEN]; @@ -71,34 +73,36 @@ Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev **out_ptr, } } #endif - int udpfd = - socket(chosen->ai_family, chosen->ai_socktype, chosen->ai_protocol); - if (udpfd < 0) { + int udpfd = socket(chosen->ai_family, chosen->ai_socktype, + chosen->ai_protocol); + if (udpfd < 0) { #if 0 fprintf(stderr, "Failed to open UDP socket, error number: %d\n", errno); #endif - freeaddrinfo(head); - return Oosc_udp_create_error_couldnt_open_socket; - } - Oosc_dev *dev = malloc(sizeof(Oosc_dev)); - dev->fd = udpfd; - dev->chosen = chosen; - dev->head = head; - *out_ptr = dev; - return Oosc_udp_create_error_ok; + freeaddrinfo(head); + return Oosc_udp_create_error_couldnt_open_socket; + } + Oosc_dev* dev = malloc(sizeof(Oosc_dev)); + dev->fd = udpfd; + dev->chosen = chosen; + dev->head = head; + *out_ptr = dev; + return Oosc_udp_create_error_ok; } -void oosc_dev_destroy(Oosc_dev *dev) { - close(dev->fd); - freeaddrinfo(dev->head); - free(dev); +void oosc_dev_destroy(Oosc_dev* dev) +{ + close(dev->fd); + freeaddrinfo(dev->head); + free(dev); } -void oosc_send_datagram(Oosc_dev *dev, char const *data, Usz size) { - ssize_t res = sendto(dev->fd, data, size, 0, dev->chosen->ai_addr, - dev->chosen->ai_addrlen); - (void)res; - // TODO handle this in UI somehow +void oosc_send_datagram(Oosc_dev* dev, char const* data, Usz size) +{ + ssize_t res = sendto(dev->fd, data, size, 0, dev->chosen->ai_addr, + dev->chosen->ai_addrlen); + (void)res; + // TODO handle this in UI somehow #if 0 if (res < 0) { fprintf(stderr, "UDP message send failed\n"); @@ -107,167 +111,174 @@ void oosc_send_datagram(Oosc_dev *dev, char const *data, Usz size) { #endif } -static bool oosc_write_strn(char *restrict buffer, Usz buffer_size, - Usz *buffer_pos, char const *restrict in_str, - Usz in_str_len) { - // no overflow check, should be fine - Usz in_plus_null = in_str_len + 1; - Usz null_pad = (4 - in_plus_null % 4) % 4; - Usz needed = in_plus_null + null_pad; - Usz cur_pos = *buffer_pos; - if (cur_pos + needed >= buffer_size) - return false; - for (Usz i = 0; i < in_str_len; ++i) { - buffer[cur_pos + i] = in_str[i]; - } - buffer[cur_pos + in_str_len] = 0; - cur_pos += in_plus_null; - for (Usz i = 0; i < null_pad; ++i) { - buffer[cur_pos + i] = 0; - } - *buffer_pos = cur_pos + null_pad; - return true; +static bool oosc_write_strn(char* restrict buffer, Usz buffer_size, + Usz* buffer_pos, char const* restrict in_str, + Usz in_str_len) +{ + // no overflow check, should be fine + Usz in_plus_null = in_str_len + 1; + Usz null_pad = (4 - in_plus_null % 4) % 4; + Usz needed = in_plus_null + null_pad; + Usz cur_pos = *buffer_pos; + if (cur_pos + needed >= buffer_size) + return false; + for (Usz i = 0; i < in_str_len; ++i) { + buffer[cur_pos + i] = in_str[i]; + } + buffer[cur_pos + in_str_len] = 0; + cur_pos += in_plus_null; + for (Usz i = 0; i < null_pad; ++i) { + buffer[cur_pos + i] = 0; + } + *buffer_pos = cur_pos + null_pad; + return true; } -void oosc_send_int32s(Oosc_dev *dev, char const *osc_address, I32 const *vals, - Usz count) { - char buffer[2048]; - Usz buf_pos = 0; - if (!oosc_write_strn(buffer, sizeof(buffer), &buf_pos, osc_address, - strlen(osc_address))) - return; - Usz typetag_str_size = 1 + count + 1; // comma, 'i'... , null - Usz typetag_str_null_pad = (4 - typetag_str_size % 4) % 4; - if (buf_pos + typetag_str_size + typetag_str_null_pad > sizeof(buffer)) - return; - buffer[buf_pos] = ','; - ++buf_pos; - for (Usz i = 0; i < count; ++i) { - buffer[buf_pos + i] = 'i'; - } - buffer[buf_pos + count] = 0; - buf_pos += count + 1; - for (Usz i = 0; i < typetag_str_null_pad; ++i) { - buffer[buf_pos + i] = 0; - } - buf_pos += typetag_str_null_pad; - Usz ints_size = count * sizeof(I32); - if (buf_pos + ints_size > sizeof(buffer)) - return; - for (Usz i = 0; i < count; ++i) { - union { - I32 i; - U32 u; - } pun; - pun.i = vals[i]; - U32 u_ne = htonl(pun.u); - memcpy(buffer + buf_pos, &u_ne, sizeof(u_ne)); - buf_pos += sizeof(u_ne); - } - oosc_send_datagram(dev, buffer, buf_pos); +void oosc_send_int32s(Oosc_dev* dev, char const* osc_address, I32 const* vals, + Usz count) +{ + char buffer[2048]; + Usz buf_pos = 0; + if (!oosc_write_strn(buffer, sizeof(buffer), &buf_pos, osc_address, + strlen(osc_address))) + return; + Usz typetag_str_size = 1 + count + 1; // comma, 'i'... , null + Usz typetag_str_null_pad = (4 - typetag_str_size % 4) % 4; + if (buf_pos + typetag_str_size + typetag_str_null_pad > sizeof(buffer)) + return; + buffer[buf_pos] = ','; + ++buf_pos; + for (Usz i = 0; i < count; ++i) { + buffer[buf_pos + i] = 'i'; + } + buffer[buf_pos + count] = 0; + buf_pos += count + 1; + for (Usz i = 0; i < typetag_str_null_pad; ++i) { + buffer[buf_pos + i] = 0; + } + buf_pos += typetag_str_null_pad; + Usz ints_size = count * sizeof(I32); + if (buf_pos + ints_size > sizeof(buffer)) + return; + for (Usz i = 0; i < count; ++i) { + union { + I32 i; + U32 u; + } pun; + pun.i = vals[i]; + U32 u_ne = htonl(pun.u); + memcpy(buffer + buf_pos, &u_ne, sizeof(u_ne)); + buf_pos += sizeof(u_ne); + } + oosc_send_datagram(dev, buffer, buf_pos); } -void susnote_list_init(Susnote_list *sl) { - sl->buffer = NULL; - sl->count = 0; - sl->capacity = 0; +void susnote_list_init(Susnote_list* sl) +{ + sl->buffer = NULL; + sl->count = 0; + sl->capacity = 0; } -void susnote_list_deinit(Susnote_list *sl) { free(sl->buffer); } +void susnote_list_deinit(Susnote_list* sl) { free(sl->buffer); } -void susnote_list_clear(Susnote_list *sl) { sl->count = 0; } +void susnote_list_clear(Susnote_list* sl) { sl->count = 0; } -void susnote_list_add_notes(Susnote_list *sl, Susnote const *restrict notes, - Usz added_count, Usz *restrict start_removed, - Usz *restrict end_removed) { - Susnote *buffer = sl->buffer; - Usz count = sl->count; - Usz cap = sl->capacity; - Usz rem = count + added_count; - Usz needed_cap = rem + added_count; - if (cap < needed_cap) { - cap = needed_cap < 16 ? 16 : orca_round_up_power2(needed_cap); - buffer = realloc(buffer, cap * sizeof(Susnote)); - sl->capacity = cap; - sl->buffer = buffer; - } - *start_removed = rem; - Usz i_in = 0; - for (; i_in < added_count; ++i_in) { - Susnote this_in = notes[i_in]; - for (Usz i_old = 0; i_old < count; ++i_old) { - Susnote this_old = buffer[i_old]; - if (this_old.chan_note == this_in.chan_note) { - buffer[i_old] = this_in; - buffer[rem] = this_old; - ++rem; - goto next_in; - } +void susnote_list_add_notes(Susnote_list* sl, Susnote const* restrict notes, + Usz added_count, Usz* restrict start_removed, + Usz* restrict end_removed) +{ + Susnote* buffer = sl->buffer; + Usz count = sl->count; + Usz cap = sl->capacity; + Usz rem = count + added_count; + Usz needed_cap = rem + added_count; + if (cap < needed_cap) { + cap = needed_cap < 16 ? 16 : orca_round_up_power2(needed_cap); + buffer = realloc(buffer, cap * sizeof(Susnote)); + sl->capacity = cap; + sl->buffer = buffer; } - buffer[count] = this_in; - ++count; - next_in:; - } - sl->count = count; - *end_removed = rem; + *start_removed = rem; + Usz i_in = 0; + for (; i_in < added_count; ++i_in) { + Susnote this_in = notes[i_in]; + for (Usz i_old = 0; i_old < count; ++i_old) { + Susnote this_old = buffer[i_old]; + if (this_old.chan_note == this_in.chan_note) { + buffer[i_old] = this_in; + buffer[rem] = this_old; + ++rem; + goto next_in; + } + } + buffer[count] = this_in; + ++count; + next_in:; + } + sl->count = count; + *end_removed = rem; } -void susnote_list_advance_time(Susnote_list *sl, double delta_time, - Usz *restrict start_removed, - Usz *restrict end_removed, - double *soonest_deadline) { - Susnote *restrict buffer = sl->buffer; - Usz count = sl->count; - *end_removed = count; - float delta_float = (float)delta_time; - float soonest = 1.0f; - for (Usz i = 0; i < count;) { - Susnote sn = buffer[i]; - sn.remaining -= delta_float; - if (sn.remaining > 0.001) { - if (sn.remaining < soonest) - soonest = sn.remaining; - buffer[i].remaining = sn.remaining; - ++i; - } else { - --count; - buffer[i] = buffer[count]; - buffer[count] = sn; +void susnote_list_advance_time(Susnote_list* sl, double delta_time, + Usz* restrict start_removed, + Usz* restrict end_removed, + double* soonest_deadline) +{ + Susnote* restrict buffer = sl->buffer; + Usz count = sl->count; + *end_removed = count; + float delta_float = (float)delta_time; + float soonest = 1.0f; + for (Usz i = 0; i < count;) { + Susnote sn = buffer[i]; + sn.remaining -= delta_float; + if (sn.remaining > 0.001) { + if (sn.remaining < soonest) + soonest = sn.remaining; + buffer[i].remaining = sn.remaining; + ++i; + } else { + --count; + buffer[i] = buffer[count]; + buffer[count] = sn; + } } - } - *start_removed = count; - *soonest_deadline = (double)soonest; - sl->count = count; + *start_removed = count; + *soonest_deadline = (double)soonest; + sl->count = count; } -void susnote_list_remove_by_chan_mask(Susnote_list *sl, Usz chan_mask, - Usz *restrict start_removed, - Usz *restrict end_removed) { - Susnote *restrict buffer = sl->buffer; - Usz count = sl->count; - *end_removed = count; - for (Usz i = 0; i < count;) { - Susnote sn = buffer[i]; - Usz chan = sn.chan_note >> 8; - if (chan_mask & 1u << chan) { - --count; - buffer[i] = buffer[count]; - buffer[count] = sn; - } else { - ++i; +void susnote_list_remove_by_chan_mask(Susnote_list* sl, Usz chan_mask, + Usz* restrict start_removed, + Usz* restrict end_removed) +{ + Susnote* restrict buffer = sl->buffer; + Usz count = sl->count; + *end_removed = count; + for (Usz i = 0; i < count;) { + Susnote sn = buffer[i]; + Usz chan = sn.chan_note >> 8; + if (chan_mask & 1u << chan) { + --count; + buffer[i] = buffer[count]; + buffer[count] = sn; + } else { + ++i; + } } - } - *start_removed = count; - sl->count = count; + *start_removed = count; + sl->count = count; } -double susnote_list_soonest_deadline(Susnote_list const *sl) { - float soonest = 1.0f; - Susnote const *buffer = sl->buffer; - for (Usz i = 0, n = sl->count; i < n; ++i) { - float rem = buffer[i].remaining; - if (rem < soonest) - soonest = rem; - } - return (double)soonest; +double susnote_list_soonest_deadline(Susnote_list const* sl) +{ + float soonest = 1.0f; + Susnote const* buffer = sl->buffer; + for (Usz i = 0, n = sl->count; i < n; ++i) { + float rem = buffer[i].remaining; + if (rem < soonest) + soonest = rem; + } + return (double)soonest; } diff --git a/osc_out.h b/osc_out.h index f522b3d..75b911c 100644 --- a/osc_out.h +++ b/osc_out.h @@ -4,22 +4,22 @@ typedef struct Oosc_dev Oosc_dev; typedef enum { - Oosc_udp_create_error_ok = 0, - Oosc_udp_create_error_getaddrinfo_failed = 1, - Oosc_udp_create_error_couldnt_open_socket = 2, + Oosc_udp_create_error_ok = 0, + Oosc_udp_create_error_getaddrinfo_failed = 1, + Oosc_udp_create_error_couldnt_open_socket = 2, } Oosc_udp_create_error; -Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev **out_ptr, - char const *dest_addr, - char const *dest_port); -void oosc_dev_destroy(Oosc_dev *dev); +Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev** out_ptr, + char const* dest_addr, + char const* dest_port); +void oosc_dev_destroy(Oosc_dev* dev); // Send a raw UDP datagram. -void oosc_send_datagram(Oosc_dev *dev, char const *data, Usz size); +void oosc_send_datagram(Oosc_dev* dev, char const* data, Usz size); // Send a list/array of 32-bit integers in OSC format to the specified "osc // address" (a path like /foo) as a UDP datagram. -void oosc_send_int32s(Oosc_dev *dev, char const *osc_address, I32 const *vals, +void oosc_send_int32s(Oosc_dev* dev, char const* osc_address, I32 const* vals, Usz count); // Susnote is for handling MIDI note sustains -- each MIDI on event should be @@ -28,29 +28,29 @@ void oosc_send_int32s(Oosc_dev *dev, char const *osc_address, I32 const *vals, // responsible for sending the note-off event. We keep a list of currently 'on' // notes so that they can have a matching 'off' sent at the correct time. typedef struct { - float remaining; - U16 chan_note; + float remaining; + U16 chan_note; } Susnote; typedef struct { - Susnote *buffer; - Usz count, capacity; + Susnote* buffer; + Usz count, capacity; } Susnote_list; -void susnote_list_init(Susnote_list *sl); -void susnote_list_deinit(Susnote_list *sl); -void susnote_list_clear(Susnote_list *sl); -void susnote_list_add_notes(Susnote_list *sl, Susnote const *restrict notes, - Usz count, Usz *restrict start_removed, - Usz *restrict end_removed); +void susnote_list_init(Susnote_list* sl); +void susnote_list_deinit(Susnote_list* sl); +void susnote_list_clear(Susnote_list* sl); +void susnote_list_add_notes(Susnote_list* sl, Susnote const* restrict notes, + Usz count, Usz* restrict start_removed, + Usz* restrict end_removed); void susnote_list_advance_time( - Susnote_list *sl, double delta_time, Usz *restrict start_removed, - Usz *restrict end_removed, + Susnote_list* sl, double delta_time, Usz* restrict start_removed, + Usz* restrict end_removed, // 1.0 if no notes remain or none are shorter than 1.0 - double *soonest_deadline); -void susnote_list_remove_by_chan_mask(Susnote_list *sl, Usz chan_mask, - Usz *restrict start_removed, - Usz *restrict end_removed); + double* soonest_deadline); +void susnote_list_remove_by_chan_mask(Susnote_list* sl, Usz chan_mask, + Usz* restrict start_removed, + Usz* restrict end_removed); // Returns 1.0 if no notes remain or none are shorter than 1.0 -double susnote_list_soonest_deadline(Susnote_list const *sl); +double susnote_list_soonest_deadline(Susnote_list const* sl); diff --git a/scripts/get_portmidi.sh b/scripts/get_portmidi.sh new file mode 100755 index 0000000..ed55737 --- /dev/null +++ b/scripts/get_portmidi.sh @@ -0,0 +1,11 @@ +PREFIX=`pwd`/build/deps + +mkdir -p build/downloads && \ + cd build/downloads && \ + git clone https://github.com/PortMidi/portmidi.git && \ + cd portmidi && \ + mkdir build && \ + cd build && \ + cmake .. -DBUILD_SHARED_LIBS=OFF && \ + cmake --build . && \ + cmake --install . --prefix ${PREFIX} diff --git a/sim.c b/sim.c index 8390255..7224ee0 100644 --- a/sim.c +++ b/sim.c @@ -9,9 +9,10 @@ static Glyph const glyph_table[36] = { 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', // 24-35 }; enum { Glyphs_index_count = sizeof glyph_table }; -static inline Glyph glyph_of(Usz index) { - assert(index < Glyphs_index_count); - return glyph_table[index]; +static inline Glyph glyph_of(Usz index) +{ + assert(index < Glyphs_index_count); + return glyph_table[index]; } static U8 const index_table[128] = { @@ -22,7 +23,8 @@ static U8 const index_table[128] = { 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 64-79 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, // 80-95 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 96-111 - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0}; // 112-127 + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0 +}; // 112-127 static ORCA_FORCEINLINE Usz index_of(Glyph c) { return index_table[c & 0x7f]; } // Reference implementation: @@ -34,58 +36,65 @@ static ORCA_FORCEINLINE Usz index_of(Glyph c) { return index_table[c & 0x7f]; } // } static ORCA_FORCEINLINE bool glyph_is_lowercase(Glyph g) { return g & 1 << 5; } -static ORCA_FORCEINLINE Glyph glyph_lowered_unsafe(Glyph g) { - return (Glyph)(g | 1 << 5); +static ORCA_FORCEINLINE Glyph glyph_lowered_unsafe(Glyph g) +{ + return (Glyph)(g | 1 << 5); } -static inline Glyph glyph_with_case(Glyph g, Glyph caser) { - enum { Case_bit = 1 << 5, Alpha_bit = 1 << 6 }; - return (Glyph)((g & ~Case_bit) | ((~g & Alpha_bit) >> 1) | - (caser & Case_bit)); +static inline Glyph glyph_with_case(Glyph g, Glyph caser) +{ + enum { Case_bit = 1 << 5, Alpha_bit = 1 << 6 }; + return (Glyph)((g & ~Case_bit) | ((~g & Alpha_bit) >> 1) + | (caser & Case_bit)); } -static ORCA_PURE bool oper_has_neighboring_bang(Glyph const *gbuf, Usz h, Usz w, - Usz y, Usz x) { - Glyph const *gp = gbuf + w * y + x; - if (x < w - 1 && gp[1] == '*') - return true; - if (x > 0 && *(gp - 1) == '*') - return true; - if (y < h - 1 && gp[w] == '*') - return true; - // note: negative array subscript on rhs of short-circuit, may cause ub if - // the arithmetic under/overflows, even if guarded the guard on lhs is false - if (y > 0 && *(gp - w) == '*') - return true; - return false; +static ORCA_PURE bool oper_has_neighboring_bang(Glyph const* gbuf, Usz h, + Usz w, Usz y, Usz x) +{ + Glyph const* gp = gbuf + w * y + x; + if (x < w - 1 && gp[1] == '*') + return true; + if (x > 0 && *(gp - 1) == '*') + return true; + if (y < h - 1 && gp[w] == '*') + return true; + // note: negative array subscript on rhs of short-circuit, may cause ub if + // the arithmetic under/overflows, even if guarded the guard on lhs is + // false + if (y > 0 && *(gp - w) == '*') + return true; + return false; } // Returns UINT8_MAX if not a valid note. -static U8 midi_note_number_of(Glyph g) { - int sharp = (g & 1 << 5) >> 5; // sharp=1 if lowercase - g &= (Glyph) ~(1 << 5); // make uppercase - if (g < 'A' || g > 'Z') // A through Z only - return UINT8_MAX; - // We want C=0, D=1, E=2, etc. A and B are equivalent to H and I. - int deg = g <= 'B' ? 'G' - 'B' + g - 'A' : g - 'C'; - return (U8)(deg / 7 * 12 + (I8[]){0, 2, 4, 5, 7, 9, 11}[deg % 7] + sharp); +static U8 midi_note_number_of(Glyph g) +{ + int sharp = (g & 1 << 5) >> 5; // sharp=1 if lowercase + g &= (Glyph) ~(1 << 5); // make uppercase + if (g < 'A' || g > 'Z') // A through Z only + return UINT8_MAX; + // We want C=0, D=1, E=2, etc. A and B are equivalent to H and I. + int deg = g <= 'B' ? 'G' - 'B' + g - 'A' : g - 'C'; + return (U8)(deg / 7 * 12 + (I8[]) { 0, 2, 4, 5, 7, 9, 11 }[deg % 7] + + sharp); } typedef struct { - Glyph *vars_slots; - Oevent_list *oevent_list; - Usz random_seed; + Glyph* vars_slots; + Oevent_list* oevent_list; + Usz random_seed; } Oper_extra_params; -static void oper_poke_and_stun(Glyph *restrict gbuffer, Mark *restrict mbuffer, - Usz height, Usz width, Usz y, Usz x, Isz delta_y, - Isz delta_x, Glyph g) { - Isz y0 = (Isz)y + delta_y; - Isz x0 = (Isz)x + delta_x; - if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) - return; - Usz offs = (Usz)y0 * width + (Usz)x0; - gbuffer[offs] = g; - mbuffer[offs] |= Mark_flag_sleep; +static void oper_poke_and_stun(Glyph* restrict gbuffer, Mark* restrict mbuffer, + Usz height, Usz width, Usz y, Usz x, + Isz delta_y, Isz delta_x, Glyph g) +{ + Isz y0 = (Isz)y + delta_y; + Isz x0 = (Isz)x + delta_x; + if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) + return; + Usz offs = (Usz)y0 * width + (Usz)x0; + gbuffer[offs] = g; + mbuffer[offs] |= Mark_flag_sleep; } // For anyone editing this in the future: the "no inline" here is deliberate. @@ -94,694 +103,699 @@ static void oper_poke_and_stun(Glyph *restrict gbuffer, Mark *restrict mbuffer, // run faster, you will need to use computed goto or assembly. #define OPER_FUNCTION_ATTRIBS ORCA_NOINLINE static void -#define BEGIN_OPERATOR(_oper_name) \ - OPER_FUNCTION_ATTRIBS oper_behavior_##_oper_name( \ - Glyph *const restrict gbuffer, Mark *const restrict mbuffer, \ - Usz const height, Usz const width, Usz const y, Usz const x, \ - Usz Tick_number, Oper_extra_params *const extra_params, \ - Mark const cell_flags, Glyph const This_oper_char) { \ - (void)gbuffer; \ - (void)mbuffer; \ - (void)height; \ - (void)width; \ - (void)y; \ - (void)x; \ - (void)Tick_number; \ - (void)extra_params; \ - (void)cell_flags; \ - (void)This_oper_char; +#define BEGIN_OPERATOR(_oper_name) \ + OPER_FUNCTION_ATTRIBS oper_behavior_##_oper_name( \ + Glyph* const restrict gbuffer, Mark* const restrict mbuffer, \ + Usz const height, Usz const width, Usz const y, Usz const x, \ + Usz Tick_number, Oper_extra_params* const extra_params, \ + Mark const cell_flags, Glyph const This_oper_char) \ + { \ + (void)gbuffer; \ + (void)mbuffer; \ + (void)height; \ + (void)width; \ + (void)y; \ + (void)x; \ + (void)Tick_number; \ + (void)extra_params; \ + (void)cell_flags; \ + (void)This_oper_char; #define END_OPERATOR } -#define PEEK(_delta_y, _delta_x) \ - gbuffer_peek_relative(gbuffer, height, width, y, x, _delta_y, _delta_x) -#define POKE(_delta_y, _delta_x, _glyph) \ - gbuffer_poke_relative(gbuffer, height, width, y, x, _delta_y, _delta_x, \ - _glyph) -#define STUN(_delta_y, _delta_x) \ - mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ - _delta_x, Mark_flag_sleep) -#define POKE_STUNNED(_delta_y, _delta_x, _glyph) \ - oper_poke_and_stun(gbuffer, mbuffer, height, width, y, x, _delta_y, \ - _delta_x, _glyph) -#define LOCK(_delta_y, _delta_x) \ - mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ - _delta_x, Mark_flag_lock) +#define PEEK(_delta_y, _delta_x) \ + gbuffer_peek_relative(gbuffer, height, width, y, x, _delta_y, _delta_x) +#define POKE(_delta_y, _delta_x, _glyph) \ + gbuffer_poke_relative(gbuffer, height, width, y, x, _delta_y, _delta_x, \ + _glyph) +#define STUN(_delta_y, _delta_x) \ + mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ + _delta_x, Mark_flag_sleep) +#define POKE_STUNNED(_delta_y, _delta_x, _glyph) \ + oper_poke_and_stun(gbuffer, mbuffer, height, width, y, x, _delta_y, \ + _delta_x, _glyph) +#define LOCK(_delta_y, _delta_x) \ + mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ + _delta_x, Mark_flag_lock) #define IN Mark_flag_input #define OUT Mark_flag_output #define NONLOCKING Mark_flag_lock #define PARAM Mark_flag_haste_input -#define LOWERCASE_REQUIRES_BANG \ - if (glyph_is_lowercase(This_oper_char) && \ - !oper_has_neighboring_bang(gbuffer, height, width, y, x)) \ - return +#define LOWERCASE_REQUIRES_BANG \ + if (glyph_is_lowercase(This_oper_char) \ + && !oper_has_neighboring_bang(gbuffer, height, width, y, x)) \ + return -#define STOP_IF_NOT_BANGED \ - if (!oper_has_neighboring_bang(gbuffer, height, width, y, x)) \ - return +#define STOP_IF_NOT_BANGED \ + if (!oper_has_neighboring_bang(gbuffer, height, width, y, x)) \ + return -#define PORT(_delta_y, _delta_x, _flags) \ - mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ - _delta_x, (_flags) ^ Mark_flag_lock) +#define PORT(_delta_y, _delta_x, _flags) \ + mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ + _delta_x, (_flags) ^ Mark_flag_lock) //////// Operators -#define UNIQUE_OPERATORS(_) \ - _('!', midicc) \ - _('#', comment) \ - _('%', midi) \ - _('*', bang) \ - _(':', midi) \ - _(';', udp) \ - _('=', osc) \ - _('?', midipb) - -#define ALPHA_OPERATORS(_) \ - _('A', add) \ - _('B', subtract) \ - _('C', clock) \ - _('D', delay) \ - _('E', movement) \ - _('F', if) \ - _('G', generator) \ - _('H', halt) \ - _('I', increment) \ - _('J', jump) \ - _('K', konkat) \ - _('L', lesser) \ - _('M', multiply) \ - _('N', movement) \ - _('O', offset) \ - _('P', push) \ - _('Q', query) \ - _('R', random) \ - _('S', movement) \ - _('T', track) \ - _('U', uclid) \ - _('V', variable) \ - _('W', movement) \ - _('X', teleport) \ - _('Y', yump) \ - _('Z', lerp) +#define UNIQUE_OPERATORS(_) \ + _('!', midicc) \ + _('#', comment) \ + _('%', midi) \ + _('*', bang) \ + _(':', midi) \ + _(';', udp) \ + _('=', osc) \ + _('?', midipb) + +#define ALPHA_OPERATORS(_) \ + _('A', add) \ + _('B', subtract) \ + _('C', clock) \ + _('D', delay) \ + _('E', movement) \ + _('F', if) \ + _('G', generator) \ + _('H', halt) \ + _('I', increment) \ + _('J', jump) \ + _('K', konkat) \ + _('L', lesser) \ + _('M', multiply) \ + _('N', movement) \ + _('O', offset) \ + _('P', push) \ + _('Q', query) \ + _('R', random) \ + _('S', movement) \ + _('T', track) \ + _('U', uclid) \ + _('V', variable) \ + _('W', movement) \ + _('X', teleport) \ + _('Y', yump) \ + _('Z', lerp) BEGIN_OPERATOR(movement) - if (glyph_is_lowercase(This_oper_char) && - !oper_has_neighboring_bang(gbuffer, height, width, y, x)) +if (glyph_is_lowercase(This_oper_char) + && !oper_has_neighboring_bang(gbuffer, height, width, y, x)) return; - Isz delta_y, delta_x; - switch (glyph_lowered_unsafe(This_oper_char)) { - case 'n': +Isz delta_y, delta_x; +switch (glyph_lowered_unsafe(This_oper_char)) { +case 'n': delta_y = -1; delta_x = 0; break; - case 'e': +case 'e': delta_y = 0; delta_x = 1; break; - case 's': +case 's': delta_y = 1; delta_x = 0; break; - case 'w': +case 'w': delta_y = 0; delta_x = -1; break; - default: +default: // could cause strict aliasing problem, maybe delta_y = 0; delta_x = 0; break; - } - Isz y0 = (Isz)y + delta_y; - Isz x0 = (Isz)x + delta_x; - if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) { +} +Isz y0 = (Isz)y + delta_y; +Isz x0 = (Isz)x + delta_x; +if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) { gbuffer[y * width + x] = '*'; return; - } - Glyph *restrict g_at_dest = gbuffer + (Usz)y0 * width + (Usz)x0; - if (*g_at_dest == '.') { +} +Glyph* restrict g_at_dest = gbuffer + (Usz)y0 * width + (Usz)x0; +if (*g_at_dest == '.') { *g_at_dest = This_oper_char; gbuffer[y * width + x] = '.'; mbuffer[(Usz)y0 * width + (Usz)x0] |= Mark_flag_sleep; - } else { +} else { gbuffer[y * width + x] = '*'; - } +} END_OPERATOR BEGIN_OPERATOR(midicc) - for (Usz i = 1; i < 4; ++i) { +for (Usz i = 1; i < 4; ++i) { PORT(0, (Isz)i, IN); - } - STOP_IF_NOT_BANGED; - Glyph channel_g = PEEK(0, 1); - Glyph control_g = PEEK(0, 2); - Glyph value_g = PEEK(0, 3); - if (channel_g == '.' || control_g == '.') +} +STOP_IF_NOT_BANGED; +Glyph channel_g = PEEK(0, 1); +Glyph control_g = PEEK(0, 2); +Glyph value_g = PEEK(0, 3); +if (channel_g == '.' || control_g == '.') return; - Usz channel = index_of(channel_g); - if (channel > 15) +Usz channel = index_of(channel_g); +if (channel > 15) return; - PORT(0, 0, OUT); - Oevent_midi_cc *oe = - (Oevent_midi_cc *)oevent_list_alloc_item(extra_params->oevent_list); - oe->oevent_type = Oevent_type_midi_cc; - oe->channel = (U8)channel; - oe->control = (U8)index_of(control_g); - oe->value = (U8)(index_of(value_g) * 127 / 35); // 0~35 -> 0~127 +PORT(0, 0, OUT); +Oevent_midi_cc* oe = (Oevent_midi_cc*)oevent_list_alloc_item( + extra_params->oevent_list); +oe->oevent_type = Oevent_type_midi_cc; +oe->channel = (U8)channel; +oe->control = (U8)index_of(control_g); +oe->value = (U8)(index_of(value_g) * 127 / 35); // 0~35 -> 0~127 END_OPERATOR BEGIN_OPERATOR(comment) - // restrict probably ok here... - Glyph const *restrict gline = gbuffer + y * width; - Mark *restrict mline = mbuffer + y * width; - Usz max_x = x + 255; - if (width < max_x) +// restrict probably ok here... +Glyph const* restrict gline = gbuffer + y * width; +Mark* restrict mline = mbuffer + y * width; +Usz max_x = x + 255; +if (width < max_x) max_x = width; - for (Usz x0 = x + 1; x0 < max_x; ++x0) { +for (Usz x0 = x + 1; x0 < max_x; ++x0) { Glyph g = gline[x0]; mline[x0] |= (Mark)Mark_flag_lock; if (g == '#') - break; - } + break; +} END_OPERATOR BEGIN_OPERATOR(bang) - gbuffer_poke(gbuffer, height, width, y, x, '.'); +gbuffer_poke(gbuffer, height, width, y, x, '.'); END_OPERATOR BEGIN_OPERATOR(midi) - for (Usz i = 1; i < 6; ++i) { +for (Usz i = 1; i < 6; ++i) { PORT(0, (Isz)i, IN); - } - STOP_IF_NOT_BANGED; - Glyph channel_g = PEEK(0, 1); - Glyph octave_g = PEEK(0, 2); - Glyph note_g = PEEK(0, 3); - Glyph velocity_g = PEEK(0, 4); - Glyph length_g = PEEK(0, 5); - U8 octave_num = (U8)index_of(octave_g); - if (octave_g == '.') +} +STOP_IF_NOT_BANGED; +Glyph channel_g = PEEK(0, 1); +Glyph octave_g = PEEK(0, 2); +Glyph note_g = PEEK(0, 3); +Glyph velocity_g = PEEK(0, 4); +Glyph length_g = PEEK(0, 5); +U8 octave_num = (U8)index_of(octave_g); +if (octave_g == '.') return; - if (octave_num > 9) +if (octave_num > 9) octave_num = 9; - U8 note_num = midi_note_number_of(note_g); - if (note_num == UINT8_MAX) +U8 note_num = midi_note_number_of(note_g); +if (note_num == UINT8_MAX) return; - Usz channel_num = index_of(channel_g); - if (channel_num > 15) +Usz channel_num = index_of(channel_g); +if (channel_num > 15) channel_num = 15; - Usz vel_num; - if (velocity_g == '.') { +Usz vel_num; +if (velocity_g == '.') { // If no velocity is specified, set it to full. vel_num = 127; - } else { +} else { vel_num = index_of(velocity_g); // MIDI notes with velocity zero are actually note-offs. (MIDI has two ways - // to send note offs. Zero-velocity is the alternate way.) If there is a zero - // velocity, we'll just not do anything. + // to send note offs. Zero-velocity is the alternate way.) If there is a + // zero velocity, we'll just not do anything. if (vel_num == 0) - return; + return; vel_num = vel_num * 8 - 1; // 1~16 -> 7~127 if (vel_num > 127) - vel_num = 127; - } - PORT(0, 0, OUT); - Oevent_midi_note *oe = - (Oevent_midi_note *)oevent_list_alloc_item(extra_params->oevent_list); - oe->oevent_type = (U8)Oevent_type_midi_note; - oe->channel = (U8)channel_num; - oe->octave = octave_num; - oe->note = note_num; - oe->velocity = (U8)vel_num; - // Mask used here to suppress bad GCC Wconversion for bitfield. This is bad - // -- we should do something smarter than this. - oe->duration = (U8)(index_of(length_g) & 0x7Fu); - oe->mono = This_oper_char == '%' ? 1 : 0; + vel_num = 127; +} +PORT(0, 0, OUT); +Oevent_midi_note* oe = (Oevent_midi_note*)oevent_list_alloc_item( + extra_params->oevent_list); +oe->oevent_type = (U8)Oevent_type_midi_note; +oe->channel = (U8)channel_num; +oe->octave = octave_num; +oe->note = note_num; +oe->velocity = (U8)vel_num; +// Mask used here to suppress bad GCC Wconversion for bitfield. This is bad +// -- we should do something smarter than this. +oe->duration = (U8)(index_of(length_g) & 0x7Fu); +oe->mono = This_oper_char == '%' ? 1 : 0; END_OPERATOR BEGIN_OPERATOR(udp) - Usz n = width - x - 1; - if (n > 16) +Usz n = width - x - 1; +if (n > 16) n = 16; - Glyph const *restrict gline = gbuffer + y * width + x + 1; - Mark *restrict mline = mbuffer + y * width + x + 1; - Glyph cpy[Oevent_udp_string_count]; - Usz i; - for (i = 0; i < n; ++i) { +Glyph const* restrict gline = gbuffer + y * width + x + 1; +Mark* restrict mline = mbuffer + y * width + x + 1; +Glyph cpy[Oevent_udp_string_count]; +Usz i; +for (i = 0; i < n; ++i) { Glyph g = gline[i]; if (g == '.') - break; + break; cpy[i] = g; mline[i] |= Mark_flag_lock; - } - n = i; - STOP_IF_NOT_BANGED; - PORT(0, 0, OUT); - Oevent_udp_string *oe = - (Oevent_udp_string *)oevent_list_alloc_item(extra_params->oevent_list); - oe->oevent_type = (U8)Oevent_type_udp_string; - oe->count = (U8)n; - for (i = 0; i < n; ++i) { +} +n = i; +STOP_IF_NOT_BANGED; +PORT(0, 0, OUT); +Oevent_udp_string* oe = (Oevent_udp_string*)oevent_list_alloc_item( + extra_params->oevent_list); +oe->oevent_type = (U8)Oevent_type_udp_string; +oe->count = (U8)n; +for (i = 0; i < n; ++i) { oe->chars[i] = cpy[i]; - } +} END_OPERATOR BEGIN_OPERATOR(osc) - PORT(0, 1, IN | PARAM); - PORT(0, 2, IN | PARAM); - Usz len = index_of(PEEK(0, 2)); - if (len > Oevent_osc_int_count) +PORT(0, 1, IN | PARAM); +PORT(0, 2, IN | PARAM); +Usz len = index_of(PEEK(0, 2)); +if (len > Oevent_osc_int_count) len = Oevent_osc_int_count; - for (Usz i = 0; i < len; ++i) { +for (Usz i = 0; i < len; ++i) { PORT(0, (Isz)i + 3, IN); - } - STOP_IF_NOT_BANGED; - Glyph g = PEEK(0, 1); - if (g != '.') { +} +STOP_IF_NOT_BANGED; +Glyph g = PEEK(0, 1); +if (g != '.') { PORT(0, 0, OUT); U8 buff[Oevent_osc_int_count]; for (Usz i = 0; i < len; ++i) { - buff[i] = (U8)index_of(PEEK(0, (Isz)i + 3)); + buff[i] = (U8)index_of(PEEK(0, (Isz)i + 3)); } - Oevent_osc_ints *oe = - &oevent_list_alloc_item(extra_params->oevent_list)->osc_ints; + Oevent_osc_ints* oe = &oevent_list_alloc_item(extra_params->oevent_list) + ->osc_ints; oe->oevent_type = (U8)Oevent_type_osc_ints; oe->glyph = g; oe->count = (U8)len; for (Usz i = 0; i < len; ++i) { - oe->numbers[i] = buff[i]; + oe->numbers[i] = buff[i]; } - } +} END_OPERATOR BEGIN_OPERATOR(midipb) - for (Usz i = 1; i < 4; ++i) { +for (Usz i = 1; i < 4; ++i) { PORT(0, (Isz)i, IN); - } - STOP_IF_NOT_BANGED; - Glyph channel_g = PEEK(0, 1); - Glyph msb_g = PEEK(0, 2); - Glyph lsb_g = PEEK(0, 3); - if (channel_g == '.') +} +STOP_IF_NOT_BANGED; +Glyph channel_g = PEEK(0, 1); +Glyph msb_g = PEEK(0, 2); +Glyph lsb_g = PEEK(0, 3); +if (channel_g == '.') return; - Usz channel = index_of(channel_g); - if (channel > 15) +Usz channel = index_of(channel_g); +if (channel > 15) return; - PORT(0, 0, OUT); - Oevent_midi_pb *oe = - (Oevent_midi_pb *)oevent_list_alloc_item(extra_params->oevent_list); - oe->oevent_type = Oevent_type_midi_pb; - oe->channel = (U8)channel; - oe->msb = (U8)(index_of(msb_g) * 127 / 35); // 0~35 -> 0~127 - oe->lsb = (U8)(index_of(lsb_g) * 127 / 35); +PORT(0, 0, OUT); +Oevent_midi_pb* oe = (Oevent_midi_pb*)oevent_list_alloc_item( + extra_params->oevent_list); +oe->oevent_type = Oevent_type_midi_pb; +oe->channel = (U8)channel; +oe->msb = (U8)(index_of(msb_g) * 127 / 35); // 0~35 -> 0~127 +oe->lsb = (U8)(index_of(lsb_g) * 127 / 35); END_OPERATOR BEGIN_OPERATOR(add) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Glyph a = PEEK(0, -1); - Glyph b = PEEK(0, 1); - Glyph g = glyph_table[(index_of(a) + index_of(b)) % Glyphs_index_count]; - POKE(1, 0, glyph_with_case(g, b)); +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Glyph a = PEEK(0, -1); +Glyph b = PEEK(0, 1); +Glyph g = glyph_table[(index_of(a) + index_of(b)) % Glyphs_index_count]; +POKE(1, 0, glyph_with_case(g, b)); END_OPERATOR BEGIN_OPERATOR(subtract) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Glyph a = PEEK(0, -1); - Glyph b = PEEK(0, 1); - Isz val = (Isz)index_of(b) - (Isz)index_of(a); - if (val < 0) +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Glyph a = PEEK(0, -1); +Glyph b = PEEK(0, 1); +Isz val = (Isz)index_of(b) - (Isz)index_of(a); +if (val < 0) val = -val; - POKE(1, 0, glyph_with_case(glyph_of((Usz)val), b)); +POKE(1, 0, glyph_with_case(glyph_of((Usz)val), b)); END_OPERATOR BEGIN_OPERATOR(clock) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Glyph b = PEEK(0, 1); - Usz rate = index_of(PEEK(0, -1)); - Usz mod_num = index_of(b); - if (rate == 0) +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Glyph b = PEEK(0, 1); +Usz rate = index_of(PEEK(0, -1)); +Usz mod_num = index_of(b); +if (rate == 0) rate = 1; - if (mod_num == 0) +if (mod_num == 0) mod_num = 8; - Glyph g = glyph_of(Tick_number / rate % mod_num); - POKE(1, 0, glyph_with_case(g, b)); +Glyph g = glyph_of(Tick_number / rate % mod_num); +POKE(1, 0, glyph_with_case(g, b)); END_OPERATOR BEGIN_OPERATOR(delay) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Usz rate = index_of(PEEK(0, -1)); - Usz mod_num = index_of(PEEK(0, 1)); - if (rate == 0) +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Usz rate = index_of(PEEK(0, -1)); +Usz mod_num = index_of(PEEK(0, 1)); +if (rate == 0) rate = 1; - if (mod_num == 0) +if (mod_num == 0) mod_num = 8; - Glyph g = Tick_number % (rate * mod_num) == 0 ? '*' : '.'; - POKE(1, 0, g); +Glyph g = Tick_number % (rate * mod_num) == 0 ? '*' : '.'; +POKE(1, 0, g); END_OPERATOR BEGIN_OPERATOR(if) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Glyph g0 = PEEK(0, -1); - Glyph g1 = PEEK(0, 1); - POKE(1, 0, g0 == g1 ? '*' : '.'); +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Glyph g0 = PEEK(0, -1); +Glyph g1 = PEEK(0, 1); +POKE(1, 0, g0 == g1 ? '*' : '.'); END_OPERATOR BEGIN_OPERATOR(generator) - LOWERCASE_REQUIRES_BANG; - Isz out_x = (Isz)index_of(PEEK(0, -3)); - Isz out_y = (Isz)index_of(PEEK(0, -2)) + 1; - Isz len = (Isz)index_of(PEEK(0, -1)); - PORT(0, -3, IN | PARAM); // x - PORT(0, -2, IN | PARAM); // y - PORT(0, -1, IN | PARAM); // len - for (Isz i = 0; i < len; ++i) { +LOWERCASE_REQUIRES_BANG; +Isz out_x = (Isz)index_of(PEEK(0, -3)); +Isz out_y = (Isz)index_of(PEEK(0, -2)) + 1; +Isz len = (Isz)index_of(PEEK(0, -1)); +PORT(0, -3, IN | PARAM); // x +PORT(0, -2, IN | PARAM); // y +PORT(0, -1, IN | PARAM); // len +for (Isz i = 0; i < len; ++i) { PORT(0, i + 1, IN); PORT(out_y, out_x + i, OUT | NONLOCKING); Glyph g = PEEK(0, i + 1); POKE_STUNNED(out_y, out_x + i, g); - } +} END_OPERATOR BEGIN_OPERATOR(halt) - LOWERCASE_REQUIRES_BANG; - PORT(1, 0, IN | PARAM); +LOWERCASE_REQUIRES_BANG; +PORT(1, 0, IN | PARAM); END_OPERATOR BEGIN_OPERATOR(increment) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, IN | OUT); - Glyph ga = PEEK(0, -1); - Glyph gb = PEEK(0, 1); - Usz rate = 1; - if (ga != '.' && ga != '*') +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, IN | OUT); +Glyph ga = PEEK(0, -1); +Glyph gb = PEEK(0, 1); +Usz rate = 1; +if (ga != '.' && ga != '*') rate = index_of(ga); - Usz max = index_of(gb); - Usz val = index_of(PEEK(1, 0)); - if (max == 0) +Usz max = index_of(gb); +Usz val = index_of(PEEK(1, 0)); +if (max == 0) max = 36; - val = val + rate; - val = val % max; - POKE(1, 0, glyph_with_case(glyph_of(val), gb)); +val = val + rate; +val = val % max; +POKE(1, 0, glyph_with_case(glyph_of(val), gb)); END_OPERATOR BEGIN_OPERATOR(jump) - LOWERCASE_REQUIRES_BANG; - Glyph g = PEEK(-1, 0); - if (g == 'J') +LOWERCASE_REQUIRES_BANG; +Glyph g = PEEK(-1, 0); +if (g == 'J') return; - PORT(-1, 0, IN); - for (Isz i = 1; i <= 256; ++i) { +PORT(-1, 0, IN); +for (Isz i = 1; i <= 256; ++i) { if (PEEK(i, 0) != This_oper_char) { - PORT(i, 0, OUT); - POKE(i, 0, g); - break; + PORT(i, 0, OUT); + POKE(i, 0, g); + break; } STUN(i, 0); - } +} END_OPERATOR // Note: this is merged from a pull request without being fully tested or // optimized BEGIN_OPERATOR(konkat) - LOWERCASE_REQUIRES_BANG; - Isz len = (Isz)index_of(PEEK(0, -1)); - if (len == 0) +LOWERCASE_REQUIRES_BANG; +Isz len = (Isz)index_of(PEEK(0, -1)); +if (len == 0) len = 1; - PORT(0, -1, IN | PARAM); - for (Isz i = 0; i < len; ++i) { +PORT(0, -1, IN | PARAM); +for (Isz i = 0; i < len; ++i) { PORT(0, i + 1, IN); Glyph var = PEEK(0, i + 1); if (var != '.') { - Usz var_idx = index_of(var); - Glyph result = extra_params->vars_slots[var_idx]; - PORT(1, i + 1, OUT); - POKE(1, i + 1, result); + Usz var_idx = index_of(var); + Glyph result = extra_params->vars_slots[var_idx]; + PORT(1, i + 1, OUT); + POKE(1, i + 1, result); } - } +} END_OPERATOR BEGIN_OPERATOR(lesser) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Glyph ga = PEEK(0, -1); - Glyph gb = PEEK(0, 1); - if (ga == '.' || gb == '.') { +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Glyph ga = PEEK(0, -1); +Glyph gb = PEEK(0, 1); +if (ga == '.' || gb == '.') { POKE(1, 0, '.'); - } else { +} else { Usz ia = index_of(ga); Usz ib = index_of(gb); Usz out = ia < ib ? ia : ib; POKE(1, 0, glyph_with_case(glyph_of(out), gb)); - } +} END_OPERATOR BEGIN_OPERATOR(multiply) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Glyph a = PEEK(0, -1); - Glyph b = PEEK(0, 1); - Glyph g = glyph_table[(index_of(a) * index_of(b)) % Glyphs_index_count]; - POKE(1, 0, glyph_with_case(g, b)); +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Glyph a = PEEK(0, -1); +Glyph b = PEEK(0, 1); +Glyph g = glyph_table[(index_of(a) * index_of(b)) % Glyphs_index_count]; +POKE(1, 0, glyph_with_case(g, b)); END_OPERATOR BEGIN_OPERATOR(offset) - LOWERCASE_REQUIRES_BANG; - Isz in_x = (Isz)index_of(PEEK(0, -2)) + 1; - Isz in_y = (Isz)index_of(PEEK(0, -1)); - PORT(0, -1, IN | PARAM); - PORT(0, -2, IN | PARAM); - PORT(in_y, in_x, IN); - PORT(1, 0, OUT); - POKE(1, 0, PEEK(in_y, in_x)); +LOWERCASE_REQUIRES_BANG; +Isz in_x = (Isz)index_of(PEEK(0, -2)) + 1; +Isz in_y = (Isz)index_of(PEEK(0, -1)); +PORT(0, -1, IN | PARAM); +PORT(0, -2, IN | PARAM); +PORT(in_y, in_x, IN); +PORT(1, 0, OUT); +POKE(1, 0, PEEK(in_y, in_x)); END_OPERATOR BEGIN_OPERATOR(push) - LOWERCASE_REQUIRES_BANG; - Usz key = index_of(PEEK(0, -2)); - Usz len = index_of(PEEK(0, -1)); - PORT(0, -1, IN | PARAM); - PORT(0, -2, IN | PARAM); - PORT(0, 1, IN); - if (len == 0) +LOWERCASE_REQUIRES_BANG; +Usz key = index_of(PEEK(0, -2)); +Usz len = index_of(PEEK(0, -1)); +PORT(0, -1, IN | PARAM); +PORT(0, -2, IN | PARAM); +PORT(0, 1, IN); +if (len == 0) return; - Isz out_x = (Isz)(key % len); - for (Usz i = 0; i < len; ++i) { +Isz out_x = (Isz)(key % len); +for (Usz i = 0; i < len; ++i) { LOCK(1, (Isz)i); - } - PORT(1, out_x, OUT); - POKE(1, out_x, PEEK(0, 1)); +} +PORT(1, out_x, OUT); +POKE(1, out_x, PEEK(0, 1)); END_OPERATOR BEGIN_OPERATOR(query) - LOWERCASE_REQUIRES_BANG; - Isz in_x = (Isz)index_of(PEEK(0, -3)) + 1; - Isz in_y = (Isz)index_of(PEEK(0, -2)); - Isz len = (Isz)index_of(PEEK(0, -1)); - Isz out_x = 1 - len; - PORT(0, -3, IN | PARAM); // x - PORT(0, -2, IN | PARAM); // y - PORT(0, -1, IN | PARAM); // len - // todo direct buffer manip - for (Isz i = 0; i < len; ++i) { +LOWERCASE_REQUIRES_BANG; +Isz in_x = (Isz)index_of(PEEK(0, -3)) + 1; +Isz in_y = (Isz)index_of(PEEK(0, -2)); +Isz len = (Isz)index_of(PEEK(0, -1)); +Isz out_x = 1 - len; +PORT(0, -3, IN | PARAM); // x +PORT(0, -2, IN | PARAM); // y +PORT(0, -1, IN | PARAM); // len +// todo direct buffer manip +for (Isz i = 0; i < len; ++i) { PORT(in_y, in_x + i, IN); PORT(1, out_x + i, OUT); Glyph g = PEEK(in_y, in_x + i); POKE(1, out_x + i, g); - } +} END_OPERATOR BEGIN_OPERATOR(random) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Glyph gb = PEEK(0, 1); - Usz a = index_of(PEEK(0, -1)); - Usz b = index_of(gb); - if (b == 0) +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Glyph gb = PEEK(0, 1); +Usz a = index_of(PEEK(0, -1)); +Usz b = index_of(gb); +if (b == 0) b = 36; - Usz min, max; - if (a == b) { +Usz min, max; +if (a == b) { POKE(1, 0, glyph_of(a)); return; - } else if (a < b) { +} else if (a < b) { min = a; max = b; - } else { +} else { min = b; max = a; - } - // Initial input params for the hash - Usz key = (extra_params->random_seed + y * width + x) ^ - (Tick_number << UINT32_C(16)); - // 32-bit shift_mult hash to evenly distribute bits - key = (key ^ UINT32_C(61)) ^ (key >> UINT32_C(16)); - key = key + (key << UINT32_C(3)); - key = key ^ (key >> UINT32_C(4)); - key = key * UINT32_C(0x27d4eb2d); - key = key ^ (key >> UINT32_C(15)); - // Hash finished. Restrict to desired range of numbers. - Usz val = key % (max - min) + min; - POKE(1, 0, glyph_with_case(glyph_of(val), gb)); +} +// Initial input params for the hash +Usz key = (extra_params->random_seed + y * width + x) + ^ (Tick_number << UINT32_C(16)); +// 32-bit shift_mult hash to evenly distribute bits +key = (key ^ UINT32_C(61)) ^ (key >> UINT32_C(16)); +key = key + (key << UINT32_C(3)); +key = key ^ (key >> UINT32_C(4)); +key = key * UINT32_C(0x27d4eb2d); +key = key ^ (key >> UINT32_C(15)); +// Hash finished. Restrict to desired range of numbers. +Usz val = key % (max - min) + min; +POKE(1, 0, glyph_with_case(glyph_of(val), gb)); END_OPERATOR BEGIN_OPERATOR(track) - LOWERCASE_REQUIRES_BANG; - Usz key = index_of(PEEK(0, -2)); - Usz len = index_of(PEEK(0, -1)); - PORT(0, -2, IN | PARAM); - PORT(0, -1, IN | PARAM); - if (len == 0) +LOWERCASE_REQUIRES_BANG; +Usz key = index_of(PEEK(0, -2)); +Usz len = index_of(PEEK(0, -1)); +PORT(0, -2, IN | PARAM); +PORT(0, -1, IN | PARAM); +if (len == 0) return; - Isz read_val_x = (Isz)(key % len) + 1; - for (Usz i = 0; i < len; ++i) { +Isz read_val_x = (Isz)(key % len) + 1; +for (Usz i = 0; i < len; ++i) { LOCK(0, (Isz)(i + 1)); - } - PORT(0, (Isz)read_val_x, IN); - PORT(1, 0, OUT); - POKE(1, 0, PEEK(0, read_val_x)); +} +PORT(0, (Isz)read_val_x, IN); +PORT(1, 0, OUT); +POKE(1, 0, PEEK(0, read_val_x)); END_OPERATOR // https://www.computermusicdesign.com/ // simplest-euclidean-rhythm-algorithm-explained/ BEGIN_OPERATOR(uclid) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, OUT); - Glyph left = PEEK(0, -1); - Usz steps = 1; - if (left != '.' && left != '*') +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, OUT); +Glyph left = PEEK(0, -1); +Usz steps = 1; +if (left != '.' && left != '*') steps = index_of(left); - Usz max = index_of(PEEK(0, 1)); - if (max == 0) +Usz max = index_of(PEEK(0, 1)); +if (max == 0) max = 8; - Usz bucket = (steps * (Tick_number + max - 1)) % max + steps; - Glyph g = (bucket >= max) ? '*' : '.'; - POKE(1, 0, g); +Usz bucket = (steps * (Tick_number + max - 1)) % max + steps; +Glyph g = (bucket >= max) ? '*' : '.'; +POKE(1, 0, g); END_OPERATOR BEGIN_OPERATOR(variable) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - Glyph left = PEEK(0, -1); - Glyph right = PEEK(0, 1); - if (left != '.') { +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +Glyph left = PEEK(0, -1); +Glyph right = PEEK(0, 1); +if (left != '.') { // Write Usz var_idx = index_of(left); extra_params->vars_slots[var_idx] = right; - } else if (right != '.') { +} else if (right != '.') { // Read PORT(1, 0, OUT); Usz var_idx = index_of(right); Glyph result = extra_params->vars_slots[var_idx]; POKE(1, 0, result); - } +} END_OPERATOR BEGIN_OPERATOR(teleport) - LOWERCASE_REQUIRES_BANG; - Isz out_x = (Isz)index_of(PEEK(0, -2)); - Isz out_y = (Isz)index_of(PEEK(0, -1)) + 1; - PORT(0, -2, IN | PARAM); // x - PORT(0, -1, IN | PARAM); // y - PORT(0, 1, IN); - PORT(out_y, out_x, OUT | NONLOCKING); - POKE_STUNNED(out_y, out_x, PEEK(0, 1)); +LOWERCASE_REQUIRES_BANG; +Isz out_x = (Isz)index_of(PEEK(0, -2)); +Isz out_y = (Isz)index_of(PEEK(0, -1)) + 1; +PORT(0, -2, IN | PARAM); // x +PORT(0, -1, IN | PARAM); // y +PORT(0, 1, IN); +PORT(out_y, out_x, OUT | NONLOCKING); +POKE_STUNNED(out_y, out_x, PEEK(0, 1)); END_OPERATOR BEGIN_OPERATOR(yump) - LOWERCASE_REQUIRES_BANG; - Glyph g = PEEK(0, -1); - if (g == 'Y') +LOWERCASE_REQUIRES_BANG; +Glyph g = PEEK(0, -1); +if (g == 'Y') return; - PORT(0, -1, IN); - for (Isz i = 1; i <= 256; ++i) { +PORT(0, -1, IN); +for (Isz i = 1; i <= 256; ++i) { if (PEEK(0, i) != This_oper_char) { - PORT(0, i, OUT); - POKE(0, i, g); - break; + PORT(0, i, OUT); + POKE(0, i, g); + break; } STUN(0, i); - } +} END_OPERATOR BEGIN_OPERATOR(lerp) - LOWERCASE_REQUIRES_BANG; - PORT(0, -1, IN | PARAM); - PORT(0, 1, IN); - PORT(1, 0, IN | OUT); - Glyph g = PEEK(0, -1); - Glyph b = PEEK(0, 1); - Isz rate = g == '.' || g == '*' ? 1 : (Isz)index_of(g); - Isz goal = (Isz)index_of(b); - Isz val = (Isz)index_of(PEEK(1, 0)); - Isz mod = val <= goal - rate ? rate : val >= goal + rate ? -rate : goal - val; - POKE(1, 0, glyph_with_case(glyph_of((Usz)(val + mod)), b)); +LOWERCASE_REQUIRES_BANG; +PORT(0, -1, IN | PARAM); +PORT(0, 1, IN); +PORT(1, 0, IN | OUT); +Glyph g = PEEK(0, -1); +Glyph b = PEEK(0, 1); +Isz rate = g == '.' || g == '*' ? 1 : (Isz)index_of(g); +Isz goal = (Isz)index_of(b); +Isz val = (Isz)index_of(PEEK(1, 0)); +Isz mod = val <= goal - rate ? rate : val >= goal + rate ? -rate : goal - val; +POKE(1, 0, glyph_with_case(glyph_of((Usz)(val + mod)), b)); END_OPERATOR //////// Run simulation -void orca_run(Glyph *restrict gbuf, Mark *restrict mbuf, Usz height, Usz width, - Usz tick_number, Oevent_list *oevent_list, Usz random_seed) { - Glyph vars_slots[Glyphs_index_count]; - memset(vars_slots, '.', sizeof(vars_slots)); - Oper_extra_params extras; - extras.vars_slots = &vars_slots[0]; - extras.oevent_list = oevent_list; - extras.random_seed = random_seed; - - for (Usz iy = 0; iy < height; ++iy) { - Glyph const *glyph_row = gbuf + iy * width; - Mark const *mark_row = mbuf + iy * width; - for (Usz ix = 0; ix < width; ++ix) { - Glyph glyph_char = glyph_row[ix]; - if (ORCA_LIKELY(glyph_char == '.')) - continue; - Mark cell_flags = mark_row[ix] & (Mark_flag_lock | Mark_flag_sleep); - if (cell_flags & (Mark_flag_lock | Mark_flag_sleep)) - continue; - switch (glyph_char) { -#define UNIQUE_CASE(_oper_char, _oper_name) \ - case _oper_char: \ - oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, tick_number, \ - &extras, cell_flags, glyph_char); \ - break; - -#define ALPHA_CASE(_upper_oper_char, _oper_name) \ - case _upper_oper_char: \ - case (char)(_upper_oper_char | 1 << 5): \ - oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, tick_number, \ - &extras, cell_flags, glyph_char); \ - break; - UNIQUE_OPERATORS(UNIQUE_CASE) - ALPHA_OPERATORS(ALPHA_CASE) +void orca_run(Glyph* restrict gbuf, Mark* restrict mbuf, Usz height, Usz width, + Usz tick_number, Oevent_list* oevent_list, Usz random_seed) +{ + Glyph vars_slots[Glyphs_index_count]; + memset(vars_slots, '.', sizeof(vars_slots)); + Oper_extra_params extras; + extras.vars_slots = &vars_slots[0]; + extras.oevent_list = oevent_list; + extras.random_seed = random_seed; + + for (Usz iy = 0; iy < height; ++iy) { + Glyph const* glyph_row = gbuf + iy * width; + Mark const* mark_row = mbuf + iy * width; + for (Usz ix = 0; ix < width; ++ix) { + Glyph glyph_char = glyph_row[ix]; + if (ORCA_LIKELY(glyph_char == '.')) + continue; + Mark cell_flags = mark_row[ix] + & (Mark_flag_lock | Mark_flag_sleep); + if (cell_flags & (Mark_flag_lock | Mark_flag_sleep)) + continue; + switch (glyph_char) { +#define UNIQUE_CASE(_oper_char, _oper_name) \ + case _oper_char: \ + oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, \ + tick_number, &extras, cell_flags, \ + glyph_char); \ + break; + +#define ALPHA_CASE(_upper_oper_char, _oper_name) \ + case _upper_oper_char: \ + case (char)(_upper_oper_char | 1 << 5): \ + oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, \ + tick_number, &extras, cell_flags, \ + glyph_char); \ + break; + UNIQUE_OPERATORS(UNIQUE_CASE) + ALPHA_OPERATORS(ALPHA_CASE) #undef UNIQUE_CASE #undef ALPHA_CASE - } + } + } } - } } diff --git a/sim.h b/sim.h index 1c10283..07a9785 100644 --- a/sim.h +++ b/sim.h @@ -2,6 +2,6 @@ #include "base.h" #include "vmio.h" -void orca_run(Glyph *restrict gbuffer, Mark *restrict mbuffer, Usz height, - Usz width, Usz tick_number, Oevent_list *oevent_list, +void orca_run(Glyph* restrict gbuffer, Mark* restrict mbuffer, Usz height, + Usz width, Usz tick_number, Oevent_list* oevent_list, Usz random_seed); diff --git a/sysmisc.c b/sysmisc.c index b91eaad..5028634 100644 --- a/sysmisc.c +++ b/sysmisc.c @@ -5,618 +5,635 @@ #include #include -static char const *const xdg_config_home_env = "XDG_CONFIG_HOME"; -static char const *const home_env = "HOME"; +static char const* const xdg_config_home_env = "XDG_CONFIG_HOME"; +static char const* const home_env = "HOME"; -void expand_home_tilde(oso **path) { - oso *s = *path; - size_t n = osolen(s); - if (n < 2) - return; - if (osoc(s)[0] != '~' || osoc(s)[1] != '/') - return; - char const *homedir = getenv(home_env); - if (!homedir) - return; - size_t hlen = strlen(homedir); - osoensurecap(&s, n + hlen - 1); - if (!s) - goto done; - memmove((char *)s + hlen, (char *)s + 1, n); // includes '\0' - memcpy((char *)s, homedir, hlen); - osopokelen(s, n + hlen - 1); +void expand_home_tilde(oso** path) +{ + oso* s = *path; + size_t n = osolen(s); + if (n < 2) + return; + if (osoc(s)[0] != '~' || osoc(s)[1] != '/') + return; + char const* homedir = getenv(home_env); + if (!homedir) + return; + size_t hlen = strlen(homedir); + osoensurecap(&s, n + hlen - 1); + if (!s) + goto done; + memmove((char*)s + hlen, (char*)s + 1, n); // includes '\0' + memcpy((char*)s, homedir, hlen); + osopokelen(s, n + hlen - 1); done: - *path = s; + *path = s; } ORCA_NOINLINE -Cboard_error cboard_copy(Glyph const *gbuffer, Usz field_height, +Cboard_error cboard_copy(Glyph const* gbuffer, Usz field_height, Usz field_width, Usz rect_y, Usz rect_x, Usz rect_h, - Usz rect_w) { - (void)field_height; - FILE *fp = + Usz rect_w) +{ + (void)field_height; + FILE* fp = #ifdef ORCA_OS_MAC - popen("pbcopy -pboard general 2>/dev/null", "w"); + popen("pbcopy -pboard general 2>/dev/null", "w"); #else - popen("xclip -i -selection clipboard 2>/dev/null", "w"); + popen("xclip -i -selection clipboard 2>/dev/null", "w"); #endif - if (!fp) - return Cboard_error_popen_failed; - for (Usz iy = 0; iy < rect_h; iy++) { - Glyph const *row = gbuffer + (rect_y + iy) * field_width + rect_x; - fwrite(row, sizeof(Glyph), rect_w, fp); - if (iy + 1 < rect_h) - fputc('\n', fp); - } - int status = pclose(fp); - return status ? Cboard_error_process_exit_error : Cboard_error_none; + if (!fp) + return Cboard_error_popen_failed; + for (Usz iy = 0; iy < rect_h; iy++) { + Glyph const* row = gbuffer + (rect_y + iy) * field_width + rect_x; + fwrite(row, sizeof(Glyph), rect_w, fp); + if (iy + 1 < rect_h) + fputc('\n', fp); + } + int status = pclose(fp); + return status ? Cboard_error_process_exit_error : Cboard_error_none; } ORCA_NOINLINE -Cboard_error cboard_paste(Glyph *gbuffer, Usz height, Usz width, Usz y, Usz x, - Usz *out_h, Usz *out_w) { - FILE *fp = +Cboard_error cboard_paste(Glyph* gbuffer, Usz height, Usz width, Usz y, Usz x, + Usz* out_h, Usz* out_w) +{ + FILE* fp = #ifdef ORCA_OS_MAC - popen("pbpaste -pboard general -Prefer txt 2>/dev/null", "r"); + popen("pbpaste -pboard general -Prefer txt 2>/dev/null", "r"); #else - popen("xclip -o -selection clipboard 2>/dev/null", "r"); + popen("xclip -o -selection clipboard 2>/dev/null", "r"); #endif - Usz start_y = y, start_x = x, max_y = y, max_x = x; - if (!fp) - return Cboard_error_popen_failed; - char inbuff[512]; - for (;;) { - size_t n = fread(inbuff, 1, sizeof inbuff, fp); - for (size_t i = 0; i < n; i++) { - char c = inbuff[i]; - if (c == '\r' || c == '\n') { - y++; - x = start_x; - continue; - } - if (c != ' ' && y < height && x < width) { - Glyph g = orca_is_valid_glyph(c) ? (Glyph)c : '.'; - gbuffer_poke(gbuffer, height, width, y, x, g); - if (x > max_x) - max_x = x; - if (y > max_y) - max_y = y; - } - x++; + Usz start_y = y, start_x = x, max_y = y, max_x = x; + if (!fp) + return Cboard_error_popen_failed; + char inbuff[512]; + for (;;) { + size_t n = fread(inbuff, 1, sizeof inbuff, fp); + for (size_t i = 0; i < n; i++) { + char c = inbuff[i]; + if (c == '\r' || c == '\n') { + y++; + x = start_x; + continue; + } + if (c != ' ' && y < height && x < width) { + Glyph g = orca_is_valid_glyph(c) ? (Glyph)c : '.'; + gbuffer_poke(gbuffer, height, width, y, x, g); + if (x > max_x) + max_x = x; + if (y > max_y) + max_y = y; + } + x++; + } + if (n < sizeof inbuff) + break; } - if (n < sizeof inbuff) - break; - } - int status = pclose(fp); - *out_h = max_y - start_y + 1; - *out_w = max_x - start_x + 1; - return status ? Cboard_error_process_exit_error : Cboard_error_none; + int status = pclose(fp); + *out_h = max_y - start_y + 1; + *out_w = max_x - start_x + 1; + return status ? Cboard_error_process_exit_error : Cboard_error_none; } ORCA_NOINLINE -Conf_read_result conf_read_line(FILE *file, char *buf, Usz bufsize, - char **out_left, Usz *out_leftsize, - char **out_right, Usz *out_rightsize) { - // a0 and a1 are the start and end positions of the left side of an "foo=bar" - // pair. b0 and b1 are the positions right side. Leading and trailing spaces - // will be removed. - Usz len, a0, a1, b0, b1; - char *s; - if (bufsize < 2) - goto insufficient_buffer; +Conf_read_result conf_read_line(FILE* file, char* buf, Usz bufsize, + char** out_left, Usz* out_leftsize, + char** out_right, Usz* out_rightsize) +{ + // a0 and a1 are the start and end positions of the left side of an + // "foo=bar" pair. b0 and b1 are the positions right side. Leading and + // trailing spaces will be removed. + Usz len, a0, a1, b0, b1; + char* s; + if (bufsize < 2) + goto insufficient_buffer; #if SIZE_MAX > INT_MAX - if (bufsize > (Usz)INT_MAX) - exit(1); // he boot too big + if (bufsize > (Usz)INT_MAX) + exit(1); // he boot too big #endif - s = fgets(buf, (int)bufsize, file); - if (!s) { - if (feof(file)) - goto eof; - goto ioerror; - } - len = strlen(buf); - if (len == bufsize - 1 && buf[len - 1] != '\n' && !feof(file)) - goto insufficient_buffer; - a0 = 0; - for (;;) { // scan for first non-space in " foo=bar" - if (a0 == len) - goto ignore; - char c = s[a0]; - if (c == ';' || c == '#') // comment line, ignore - goto ignore; - if (c == '=') // '=' before any other char, bad - goto ignore; - if (!isspace(c)) - break; - a0++; - } - a1 = a0; - for (;;) { // scan for '=' - a1++; - if (a1 == len) - goto ignore; - char c = s[a1]; - Usz x = a1; // don't include any whitespace preceeding the '=' - while (isspace(c)) { - x++; - if (x == len) - goto ignore; - c = s[x]; + s = fgets(buf, (int)bufsize, file); + if (!s) { + if (feof(file)) + goto eof; + goto ioerror; } - if (c == '=') { - b0 = x; - break; + len = strlen(buf); + if (len == bufsize - 1 && buf[len - 1] != '\n' && !feof(file)) + goto insufficient_buffer; + a0 = 0; + for (;;) { // scan for first non-space in " foo=bar" + if (a0 == len) + goto ignore; + char c = s[a0]; + if (c == ';' || c == '#') // comment line, ignore + goto ignore; + if (c == '=') // '=' before any other char, bad + goto ignore; + if (!isspace(c)) + break; + a0++; } - a1 = x; - } - for (;;) { // scan for first non-whitespace after '=' - b0++; - if (b0 == len) { // empty right side, but still valid pair - b1 = b0; - goto ok; + a1 = a0; + for (;;) { // scan for '=' + a1++; + if (a1 == len) + goto ignore; + char c = s[a1]; + Usz x = a1; // don't include any whitespace preceeding the '=' + while (isspace(c)) { + x++; + if (x == len) + goto ignore; + c = s[x]; + } + if (c == '=') { + b0 = x; + break; + } + a1 = x; } - char c = s[b0]; - if (!isspace(c)) - break; - } - b1 = b0; - for (;;) { // scan for end of useful stuff for right-side value - b1++; - if (b1 == len) - goto ok; - char c = s[b1]; - Usz x = b1; // don't include any whitespace preceeding the EOL - while (isspace(c)) { - x++; - if (x == len) - goto ok; - c = s[x]; + for (;;) { // scan for first non-whitespace after '=' + b0++; + if (b0 == len) { // empty right side, but still valid pair + b1 = b0; + goto ok; + } + char c = s[b0]; + if (!isspace(c)) + break; } - b1 = x; - } - Conf_read_result err; + b1 = b0; + for (;;) { // scan for end of useful stuff for right-side value + b1++; + if (b1 == len) + goto ok; + char c = s[b1]; + Usz x = b1; // don't include any whitespace preceeding the EOL + while (isspace(c)) { + x++; + if (x == len) + goto ok; + c = s[x]; + } + b1 = x; + } + Conf_read_result err; insufficient_buffer: - err = Conf_read_buffer_too_small; - goto fail; + err = Conf_read_buffer_too_small; + goto fail; eof: - err = Conf_read_eof; - goto fail; + err = Conf_read_eof; + goto fail; ioerror: - err = Conf_read_io_error; - goto fail; + err = Conf_read_io_error; + goto fail; fail: - *out_left = NULL; - *out_leftsize = 0; - goto null_right; + *out_left = NULL; + *out_leftsize = 0; + goto null_right; ignore: - s[len - 1] = '\0'; - *out_left = s; - *out_leftsize = len; - err = Conf_read_irrelevant; - goto null_right; + s[len - 1] = '\0'; + *out_left = s; + *out_leftsize = len; + err = Conf_read_irrelevant; + goto null_right; null_right: - *out_right = NULL; - *out_rightsize = 0; - return err; + *out_right = NULL; + *out_rightsize = 0; + return err; ok: - s[a1] = '\0'; - s[b1] = '\0'; - *out_left = s + a0; - *out_leftsize = a1 - a0; - *out_right = s + b0; - *out_rightsize = b1 - b0; - return Conf_read_left_and_right; + s[a1] = '\0'; + s[b1] = '\0'; + *out_left = s + a0; + *out_leftsize = a1 - a0; + *out_right = s + b0; + *out_rightsize = b1 - b0; + return Conf_read_left_and_right; } -bool conf_read_match(FILE **pfile, char const *const *names, Usz nameslen, - char *buf, Usz bufsize, Usz *out_index, char **out_value) { - FILE *file = *pfile; - if (!file) - return false; - char *left; - Usz leftsz, rightsz; +bool conf_read_match(FILE** pfile, char const* const* names, Usz nameslen, + char* buf, Usz bufsize, Usz* out_index, char** out_value) +{ + FILE* file = *pfile; + if (!file) + return false; + char* left; + Usz leftsz, rightsz; next_line:; - Conf_read_result res = - conf_read_line(file, buf, bufsize, &left, &leftsz, out_value, &rightsz); - switch (res) { - case Conf_read_left_and_right: - for (Usz i = 0; i < nameslen; i++) { - if (strcmp(names[i], left) == 0) { - *out_index = i; - return true; - } + Conf_read_result res = conf_read_line(file, buf, bufsize, &left, &leftsz, + out_value, &rightsz); + switch (res) { + case Conf_read_left_and_right: + for (Usz i = 0; i < nameslen; i++) { + if (strcmp(names[i], left) == 0) { + *out_index = i; + return true; + } + } + goto next_line; + case Conf_read_irrelevant: + goto next_line; + case Conf_read_buffer_too_small: + case Conf_read_eof: + case Conf_read_io_error: + break; } - goto next_line; - case Conf_read_irrelevant: - goto next_line; - case Conf_read_buffer_too_small: - case Conf_read_eof: - case Conf_read_io_error: - break; - } - fclose(file); - *pfile = NULL; - return false; + fclose(file); + *pfile = NULL; + return false; } typedef enum { - Conf_dir_ok = 0, - Conf_dir_no_home, + Conf_dir_ok = 0, + Conf_dir_no_home, } Conf_dir_error; -static Conf_dir_error try_get_conf_dir(oso **out) { - char const *xdgcfgdir = getenv(xdg_config_home_env); - if (xdgcfgdir) { - Usz xdgcfgdirlen = strlen(xdgcfgdir); - if (xdgcfgdirlen > 0) { - osoputlen(out, xdgcfgdir, xdgcfgdirlen); - return Conf_dir_ok; +static Conf_dir_error try_get_conf_dir(oso** out) +{ + char const* xdgcfgdir = getenv(xdg_config_home_env); + if (xdgcfgdir) { + Usz xdgcfgdirlen = strlen(xdgcfgdir); + if (xdgcfgdirlen > 0) { + osoputlen(out, xdgcfgdir, xdgcfgdirlen); + return Conf_dir_ok; + } } - } - char const *homedir = getenv(home_env); - if (homedir) { - Usz homedirlen = strlen(homedir); - if (homedirlen > 0) { - osoputprintf(out, "%s/.config", homedir); - return Conf_dir_ok; + char const* homedir = getenv(home_env); + if (homedir) { + Usz homedirlen = strlen(homedir); + if (homedirlen > 0) { + osoputprintf(out, "%s/.config", homedir); + return Conf_dir_ok; + } } - } - return Conf_dir_no_home; + return Conf_dir_no_home; } -static void conf_impl_catconfpath(oso **p, char const *conf_file_name, - size_t conflen) { - oso *path = *p; - size_t n = osolen(path); - osoensurecap(&path, n + 1 + conflen); - if (!path) - goto done; - ((char *)path)[n] = '/'; - memcpy((char *)path + n + 1, conf_file_name, conflen); - ((char *)path)[n + 1 + conflen] = '\0'; - osopokelen(path, n + 1 + conflen); +static void conf_impl_catconfpath(oso** p, char const* conf_file_name, + size_t conflen) +{ + oso* path = *p; + size_t n = osolen(path); + osoensurecap(&path, n + 1 + conflen); + if (!path) + goto done; + ((char*)path)[n] = '/'; + memcpy((char*)path + n + 1, conf_file_name, conflen); + ((char*)path)[n + 1 + conflen] = '\0'; + osopokelen(path, n + 1 + conflen); done: - *p = path; + *p = path; } -FILE *conf_file_open_for_reading(char const *conf_file_name) { - if (!conf_file_name) - return NULL; - oso *path = NULL; - if (try_get_conf_dir(&path)) - return NULL; - size_t conflen = strlen(conf_file_name); - if (conflen == 0) - return NULL; - conf_impl_catconfpath(&path, conf_file_name, conflen); - if (!path) - return NULL; - FILE *file = fopen(osoc(path), "r"); - osofree(path); - return file; +FILE* conf_file_open_for_reading(char const* conf_file_name) +{ + if (!conf_file_name) + return NULL; + oso* path = NULL; + if (try_get_conf_dir(&path)) + return NULL; + size_t conflen = strlen(conf_file_name); + if (conflen == 0) + return NULL; + conf_impl_catconfpath(&path, conf_file_name, conflen); + if (!path) + return NULL; + FILE* file = fopen(osoc(path), "r"); + osofree(path); + return file; } -Conf_save_start_error conf_save_start(Conf_save *p, - char const *conf_file_name) { - *p = (Conf_save){0}; - oso *dir = NULL; - Conf_save_start_error err; - if (!conf_file_name) { - err = Conf_save_start_bad_conf_name; - goto cleanup; - } - if (try_get_conf_dir(&dir)) { - err = Conf_save_start_no_home; - goto cleanup; - } - if (!dir) - goto allocfail; - osoputoso(&p->canonpath, dir); - if (!p->canonpath) - goto allocfail; - size_t namelen = strlen(conf_file_name); - if (namelen == 0) { - err = Conf_save_start_bad_conf_name; - goto cleanup; - } - conf_impl_catconfpath(&p->canonpath, conf_file_name, namelen); - if (!p->canonpath) - goto allocfail; - osoputoso(&p->temppath, p->canonpath); - if (!p->temppath) - goto allocfail; - osocat(&p->temppath, ".tmp"); - if (!p->temppath) - goto allocfail; - // Remove old temp file if it exists. If it exists and we can't remove it, - // error. - if (unlink(osoc(p->temppath)) == -1 && errno != ENOENT) { - switch (errno) { - case ENOTDIR: - err = Conf_save_start_conf_dir_not_dir; - break; - case EACCES: - err = Conf_save_start_temp_file_perm_denied; - break; - default: - err = Conf_save_start_old_temp_file_stuck; - break; +Conf_save_start_error conf_save_start(Conf_save* p, char const* conf_file_name) +{ + *p = (Conf_save) { 0 }; + oso* dir = NULL; + Conf_save_start_error err; + if (!conf_file_name) { + err = Conf_save_start_bad_conf_name; + goto cleanup; + } + if (try_get_conf_dir(&dir)) { + err = Conf_save_start_no_home; + goto cleanup; + } + if (!dir) + goto allocfail; + osoputoso(&p->canonpath, dir); + if (!p->canonpath) + goto allocfail; + size_t namelen = strlen(conf_file_name); + if (namelen == 0) { + err = Conf_save_start_bad_conf_name; + goto cleanup; + } + conf_impl_catconfpath(&p->canonpath, conf_file_name, namelen); + if (!p->canonpath) + goto allocfail; + osoputoso(&p->temppath, p->canonpath); + if (!p->temppath) + goto allocfail; + osocat(&p->temppath, ".tmp"); + if (!p->temppath) + goto allocfail; + // Remove old temp file if it exists. If it exists and we can't remove it, + // error. + if (unlink(osoc(p->temppath)) == -1 && errno != ENOENT) { + switch (errno) { + case ENOTDIR: + err = Conf_save_start_conf_dir_not_dir; + break; + case EACCES: + err = Conf_save_start_temp_file_perm_denied; + break; + default: + err = Conf_save_start_old_temp_file_stuck; + break; + } + goto cleanup; } - goto cleanup; - } - p->tempfile = fopen(osoc(p->temppath), "w"); - if (!p->tempfile) { - // Try to create config dir, in case it doesn't exist. (XDG says we should - // do this, and use mode 0700.) - mkdir(osoc(dir), 0700); p->tempfile = fopen(osoc(p->temppath), "w"); - } - if (!p->tempfile) { - err = Conf_save_start_temp_file_open_failed; - goto cleanup; - } - // This may be left as NULL. - p->origfile = fopen(osoc(p->canonpath), "r"); - // We did it, boys. - osofree(dir); - return Conf_save_start_ok; + if (!p->tempfile) { + // Try to create config dir, in case it doesn't exist. (XDG says we + // should do this, and use mode 0700.) + mkdir(osoc(dir), 0700); + p->tempfile = fopen(osoc(p->temppath), "w"); + } + if (!p->tempfile) { + err = Conf_save_start_temp_file_open_failed; + goto cleanup; + } + // This may be left as NULL. + p->origfile = fopen(osoc(p->canonpath), "r"); + // We did it, boys. + osofree(dir); + return Conf_save_start_ok; allocfail: - err = Conf_save_start_alloc_failed; + err = Conf_save_start_alloc_failed; cleanup: - osofree(dir); - conf_save_cancel(p); - return err; + osofree(dir); + conf_save_cancel(p); + return err; } -void conf_save_cancel(Conf_save *p) { - osofree(p->canonpath); - osofree(p->temppath); - if (p->origfile) - fclose(p->origfile); - if (p->tempfile) - fclose(p->tempfile); - *p = (Conf_save){0}; +void conf_save_cancel(Conf_save* p) +{ + osofree(p->canonpath); + osofree(p->temppath); + if (p->origfile) + fclose(p->origfile); + if (p->tempfile) + fclose(p->tempfile); + *p = (Conf_save) { 0 }; } -Conf_save_commit_error conf_save_commit(Conf_save *p) { - Conf_save_commit_error err; - fclose(p->tempfile); - p->tempfile = NULL; - if (p->origfile) { - fclose(p->origfile); - p->origfile = NULL; - } - // This isn't really atomic. But if we want to close and move a file - // simultaneously, I think we have to use OS-specific facilities. So I guess - // this is the best we can do for now. I could be wrong, though. But I - // couldn't find any good information about it. - if (rename(osoc(p->temppath), osoc(p->canonpath)) == -1) { - err = Conf_save_commit_rename_failed; - goto cleanup; - } - err = Conf_save_commit_ok; +Conf_save_commit_error conf_save_commit(Conf_save* p) +{ + Conf_save_commit_error err; + fclose(p->tempfile); + p->tempfile = NULL; + if (p->origfile) { + fclose(p->origfile); + p->origfile = NULL; + } + // This isn't really atomic. But if we want to close and move a file + // simultaneously, I think we have to use OS-specific facilities. So I + // guess this is the best we can do for now. I could be wrong, though. But + // I couldn't find any good information about it. + if (rename(osoc(p->temppath), osoc(p->canonpath)) == -1) { + err = Conf_save_commit_rename_failed; + goto cleanup; + } + err = Conf_save_commit_ok; cleanup: - conf_save_cancel(p); - return err; + conf_save_cancel(p); + return err; } -char const *ezconf_w_errorstring(Ezconf_w_error error) { - switch (error) { - case Ezconf_w_ok: - return "No error"; - case Ezconf_w_bad_conf_name: - return "Bad config file name"; - case Ezconf_w_oom: - return "Out of memory"; - case Ezconf_w_no_home: - return "Unable to resolve $XDG_CONFIG_HOME or $HOME"; - case Ezconf_w_mkdir_failed: - return "Unable to create $XDG_CONFIG_HOME or $HOME/.config directory"; - case Ezconf_w_conf_dir_not_dir: - return "Config directory path is not a directory"; - case Ezconf_w_old_temp_file_stuck: - return "Unable to remove old .conf.tmp file"; - case Ezconf_w_temp_file_perm_denied: - return "Permission denied for config directory"; - case Ezconf_w_temp_open_failed: - return "Unable to open .conf.tmp for writing"; - case Ezconf_w_temp_fsync_failed: - return "fsync() reported an when writing temp file.\n" - "Refusing to continue."; - case Ezconf_w_temp_close_failed: - return "Unable to close temp file"; - case Ezconf_w_rename_failed: - return "Unable to rename .conf.tmp to .conf"; - case Ezconf_w_line_too_long: - return "Line in file is too long"; - case Ezconf_w_existing_read_error: - return "Error when reading existing configuration file"; - case Ezconf_w_unknown_error: - break; - } - return "Unknown"; +char const* ezconf_w_errorstring(Ezconf_w_error error) +{ + switch (error) { + case Ezconf_w_ok: + return "No error"; + case Ezconf_w_bad_conf_name: + return "Bad config file name"; + case Ezconf_w_oom: + return "Out of memory"; + case Ezconf_w_no_home: + return "Unable to resolve $XDG_CONFIG_HOME or $HOME"; + case Ezconf_w_mkdir_failed: + return "Unable to create $XDG_CONFIG_HOME or $HOME/.config directory"; + case Ezconf_w_conf_dir_not_dir: + return "Config directory path is not a directory"; + case Ezconf_w_old_temp_file_stuck: + return "Unable to remove old .conf.tmp file"; + case Ezconf_w_temp_file_perm_denied: + return "Permission denied for config directory"; + case Ezconf_w_temp_open_failed: + return "Unable to open .conf.tmp for writing"; + case Ezconf_w_temp_fsync_failed: + return "fsync() reported an when writing temp file.\n" + "Refusing to continue."; + case Ezconf_w_temp_close_failed: + return "Unable to close temp file"; + case Ezconf_w_rename_failed: + return "Unable to rename .conf.tmp to .conf"; + case Ezconf_w_line_too_long: + return "Line in file is too long"; + case Ezconf_w_existing_read_error: + return "Error when reading existing configuration file"; + case Ezconf_w_unknown_error: + break; + } + return "Unknown"; } -void ezconf_r_start(Ezconf_r *ezcr, char const *conf_file_name) { - ezcr->file = conf_file_open_for_reading(conf_file_name); - ezcr->index = 0; - ezcr->value = NULL; +void ezconf_r_start(Ezconf_r* ezcr, char const* conf_file_name) +{ + ezcr->file = conf_file_open_for_reading(conf_file_name); + ezcr->index = 0; + ezcr->value = NULL; } -bool ezconf_r_step(Ezconf_r *ezcr, char const *const *names, size_t nameslen) { - return conf_read_match(&ezcr->file, names, nameslen, ezcr->buffer, - sizeof ezcr->buffer, &ezcr->index, &ezcr->value); +bool ezconf_r_step(Ezconf_r* ezcr, char const* const* names, size_t nameslen) +{ + return conf_read_match(&ezcr->file, names, nameslen, ezcr->buffer, + sizeof ezcr->buffer, &ezcr->index, &ezcr->value); } enum { - Confwflag_add_newline = 1 << 0, - Ezconf_opt_written = 1 << 0, + Confwflag_add_newline = 1 << 0, + Ezconf_opt_written = 1 << 0, }; -void ezconf_w_start(Ezconf_w *ezcw, Ezconf_opt *optsbuffer, size_t buffercap, - char const *conf_file_name) { - *ezcw = (Ezconf_w){.save = {0}}; // Weird to silence clang warning - ezcw->opts = optsbuffer; - ezcw->optscap = buffercap; - Ezconf_w_error error = Ezconf_w_unknown_error; - switch (conf_save_start(&ezcw->save, conf_file_name)) { - case Conf_save_start_ok: - error = Ezconf_w_ok; - ezcw->file = ezcw->save.tempfile; - break; - case Conf_save_start_bad_conf_name: - error = Ezconf_w_bad_conf_name; - break; - case Conf_save_start_alloc_failed: - error = Ezconf_w_oom; - break; - case Conf_save_start_no_home: - error = Ezconf_w_no_home; - break; - case Conf_save_start_mkdir_failed: - error = Ezconf_w_mkdir_failed; - break; - case Conf_save_start_conf_dir_not_dir: - error = Ezconf_w_conf_dir_not_dir; - break; - case Conf_save_start_old_temp_file_stuck: - error = Ezconf_w_old_temp_file_stuck; - break; - case Conf_save_start_temp_file_perm_denied: - error = Ezconf_w_temp_file_perm_denied; - break; - case Conf_save_start_temp_file_open_failed: - error = Ezconf_w_temp_open_failed; - break; - } - ezcw->error = error; +void ezconf_w_start(Ezconf_w* ezcw, Ezconf_opt* optsbuffer, size_t buffercap, + char const* conf_file_name) +{ + *ezcw = (Ezconf_w) { .save = { 0 } }; // Weird to silence clang warning + ezcw->opts = optsbuffer; + ezcw->optscap = buffercap; + Ezconf_w_error error = Ezconf_w_unknown_error; + switch (conf_save_start(&ezcw->save, conf_file_name)) { + case Conf_save_start_ok: + error = Ezconf_w_ok; + ezcw->file = ezcw->save.tempfile; + break; + case Conf_save_start_bad_conf_name: + error = Ezconf_w_bad_conf_name; + break; + case Conf_save_start_alloc_failed: + error = Ezconf_w_oom; + break; + case Conf_save_start_no_home: + error = Ezconf_w_no_home; + break; + case Conf_save_start_mkdir_failed: + error = Ezconf_w_mkdir_failed; + break; + case Conf_save_start_conf_dir_not_dir: + error = Ezconf_w_conf_dir_not_dir; + break; + case Conf_save_start_old_temp_file_stuck: + error = Ezconf_w_old_temp_file_stuck; + break; + case Conf_save_start_temp_file_perm_denied: + error = Ezconf_w_temp_file_perm_denied; + break; + case Conf_save_start_temp_file_open_failed: + error = Ezconf_w_temp_open_failed; + break; + } + ezcw->error = error; } -void ezconf_w_addopt(Ezconf_w *ezcw, char const *key, intptr_t id) { - size_t count = ezcw->optscount, cap = ezcw->optscap; - if (count == cap) - return; - ezcw->opts[count] = (Ezconf_opt){.name = key, .id = id, .flags = 0}; - ezcw->optscount = count + 1; +void ezconf_w_addopt(Ezconf_w* ezcw, char const* key, intptr_t id) +{ + size_t count = ezcw->optscount, cap = ezcw->optscap; + if (count == cap) + return; + ezcw->opts[count] = (Ezconf_opt) { .name = key, .id = id, .flags = 0 }; + ezcw->optscount = count + 1; } -bool ezconf_w_step(Ezconf_w *ezcw) { - uint32_t stateflags = ezcw->stateflags; - FILE *origfile = ezcw->save.origfile, *tempfile = ezcw->save.tempfile; - Ezconf_opt *opts = ezcw->opts, *chosen = NULL; - size_t optscount = ezcw->optscount; - if (ezcw->error || !tempfile) // Already errored or finished ok - return false; - // If we set a flag to write a closing newline the last time we were called, - // write it now. - if (stateflags & Confwflag_add_newline) { - fputs("\n", tempfile); - stateflags &= ~(uint32_t)Confwflag_add_newline; - } - if (!optscount) - goto commit; - if (!origfile) - goto write_leftovers; - for (;;) { // Scan through file looking for known keys in key=value lines - char linebuff[1024]; - char *left, *right; - size_t leftsz, rightsz; - Conf_read_result res = conf_read_line(origfile, linebuff, sizeof linebuff, - &left, &leftsz, &right, &rightsz); - switch (res) { - case Conf_read_left_and_right: { - for (size_t i = 0; i < optscount; i++) { - char const *name = opts[i].name; - if (!name) - continue; - if (strcmp(name, left) != 0) - continue; - // If we already wrote this one, comment out the line instead, and move - // on to the next line. - if (opts[i].flags & (uint8_t)Ezconf_opt_written) { - fputs("# ", tempfile); - goto write_landr; - } - chosen = opts + i; - goto return_for_writing; - } - write_landr: - fputs(left, tempfile); - fputs(" = ", tempfile); - fputs(right, tempfile); - fputs("\n", tempfile); - continue; +bool ezconf_w_step(Ezconf_w* ezcw) +{ + uint32_t stateflags = ezcw->stateflags; + FILE *origfile = ezcw->save.origfile, *tempfile = ezcw->save.tempfile; + Ezconf_opt *opts = ezcw->opts, *chosen = NULL; + size_t optscount = ezcw->optscount; + if (ezcw->error || !tempfile) // Already errored or finished ok + return false; + // If we set a flag to write a closing newline the last time we were + // called, write it now. + if (stateflags & Confwflag_add_newline) { + fputs("\n", tempfile); + stateflags &= ~(uint32_t)Confwflag_add_newline; } - case Conf_read_irrelevant: - fputs(left, tempfile); - fputs("\n", tempfile); - continue; - case Conf_read_eof: - goto end_original; - case Conf_read_buffer_too_small: - ezcw->error = Ezconf_w_line_too_long; - goto cancel; - case Conf_read_io_error: - ezcw->error = Ezconf_w_existing_read_error; - goto cancel; + if (!optscount) + goto commit; + if (!origfile) + goto write_leftovers; + for (;;) { // Scan through file looking for known keys in key=value lines + char linebuff[1024]; + char *left, *right; + size_t leftsz, rightsz; + Conf_read_result res = conf_read_line(origfile, linebuff, + sizeof linebuff, &left, &leftsz, + &right, &rightsz); + switch (res) { + case Conf_read_left_and_right: { + for (size_t i = 0; i < optscount; i++) { + char const* name = opts[i].name; + if (!name) + continue; + if (strcmp(name, left) != 0) + continue; + // If we already wrote this one, comment out the line instead, + // and move on to the next line. + if (opts[i].flags & (uint8_t)Ezconf_opt_written) { + fputs("# ", tempfile); + goto write_landr; + } + chosen = opts + i; + goto return_for_writing; + } + write_landr: + fputs(left, tempfile); + fputs(" = ", tempfile); + fputs(right, tempfile); + fputs("\n", tempfile); + continue; + } + case Conf_read_irrelevant: + fputs(left, tempfile); + fputs("\n", tempfile); + continue; + case Conf_read_eof: + goto end_original; + case Conf_read_buffer_too_small: + ezcw->error = Ezconf_w_line_too_long; + goto cancel; + case Conf_read_io_error: + ezcw->error = Ezconf_w_existing_read_error; + goto cancel; + } } - } end_original: // Don't need original file anymore - fclose(origfile); - ezcw->save.origfile = origfile = NULL; + fclose(origfile); + ezcw->save.origfile = origfile = NULL; write_leftovers: // Write out any guys that weren't in original file. - for (;;) { // Find the first guy that wasn't already written. - if (!optscount) - goto commit; - chosen = opts; - // Drop the guy from the front of the list. This is to reduce super-linear - // complexity growth as the number of conf key-value pairs are increased. - // (Otherwise, we iterate the full set of guys on each call during the - // "write the leftovers" phase.) - opts++; - optscount--; - if (!(chosen->flags & (uint8_t)Ezconf_opt_written)) - break; - } - // Once control has reached here, we're going to return true to the caller. - // Which means we expect to be called at least one more time. So update the - // pointers stored in the persistent state, so that we don't have to scan - // through as much of this list next time. (This might even end up finishing - // it off, making it empty.) - ezcw->opts = opts; - ezcw->optscount = optscount; + for (;;) { // Find the first guy that wasn't already written. + if (!optscount) + goto commit; + chosen = opts; + // Drop the guy from the front of the list. This is to reduce + // super-linear complexity growth as the number of conf key-value pairs + // are increased. (Otherwise, we iterate the full set of guys on each + // call during the "write the leftovers" phase.) + opts++; + optscount--; + if (!(chosen->flags & (uint8_t)Ezconf_opt_written)) + break; + } + // Once control has reached here, we're going to return true to the caller. + // Which means we expect to be called at least one more time. So update the + // pointers stored in the persistent state, so that we don't have to scan + // through as much of this list next time. (This might even end up + // finishing it off, making it empty.) + ezcw->opts = opts; + ezcw->optscount = optscount; return_for_writing: - chosen->flags |= (uint8_t)Ezconf_opt_written; - fputs(chosen->name, tempfile); - fputs(" = ", tempfile); - ezcw->optid = chosen->id; - stateflags |= (uint32_t)Confwflag_add_newline; - ezcw->stateflags = stateflags; - return true; + chosen->flags |= (uint8_t)Ezconf_opt_written; + fputs(chosen->name, tempfile); + fputs(" = ", tempfile); + ezcw->optid = chosen->id; + stateflags |= (uint32_t)Confwflag_add_newline; + ezcw->stateflags = stateflags; + return true; cancel: - conf_save_cancel(&ezcw->save); - // ^- Sets tempfile to null, which we use as a guard at the top of this - // function. - ezcw->file = NULL; - ezcw->stateflags = 0; - return false; + conf_save_cancel(&ezcw->save); + // ^- Sets tempfile to null, which we use as a guard at the top of this + // function. + ezcw->file = NULL; + ezcw->stateflags = 0; + return false; commit:; - Ezconf_w_error error = Ezconf_w_unknown_error; - switch (conf_save_commit(&ezcw->save)) { - case Conf_save_commit_ok: - error = Ezconf_w_ok; - break; - case Conf_save_commit_temp_fsync_failed: - error = Ezconf_w_temp_fsync_failed; - break; - case Conf_save_commit_temp_close_failed: - error = Ezconf_w_temp_close_failed; - break; - case Conf_save_commit_rename_failed: - error = Ezconf_w_rename_failed; - break; - } - ezcw->file = NULL; - ezcw->error = error; - ezcw->stateflags = 0; - return false; + Ezconf_w_error error = Ezconf_w_unknown_error; + switch (conf_save_commit(&ezcw->save)) { + case Conf_save_commit_ok: + error = Ezconf_w_ok; + break; + case Conf_save_commit_temp_fsync_failed: + error = Ezconf_w_temp_fsync_failed; + break; + case Conf_save_commit_temp_close_failed: + error = Ezconf_w_temp_close_failed; + break; + case Conf_save_commit_rename_failed: + error = Ezconf_w_rename_failed; + break; + } + ezcw->file = NULL; + ezcw->error = error; + ezcw->stateflags = 0; + return false; } diff --git a/sysmisc.h b/sysmisc.h index 561a2c1..03be4d7 100644 --- a/sysmisc.h +++ b/sysmisc.h @@ -3,64 +3,65 @@ #include // FILE cannot be forward declared struct oso; -void expand_home_tilde(struct oso **path); +void expand_home_tilde(struct oso** path); typedef enum { - Cboard_error_none = 0, - Cboard_error_unavailable, - Cboard_error_popen_failed, - Cboard_error_process_exit_error, + Cboard_error_none = 0, + Cboard_error_unavailable, + Cboard_error_popen_failed, + Cboard_error_process_exit_error, } Cboard_error; -Cboard_error cboard_copy(Glyph const *gbuffer, Usz field_height, +Cboard_error cboard_copy(Glyph const* gbuffer, Usz field_height, Usz field_width, Usz rect_y, Usz rect_x, Usz rect_h, Usz rect_w); -Cboard_error cboard_paste(Glyph *gbuffer, Usz height, Usz width, Usz y, Usz x, - Usz *out_h, Usz *out_w); +Cboard_error cboard_paste(Glyph* gbuffer, Usz height, Usz width, Usz y, Usz x, + Usz* out_h, Usz* out_w); typedef enum { - Conf_read_left_and_right = 0, // left and right will be set - Conf_read_irrelevant, // only left will be set - Conf_read_buffer_too_small, // neither will be set - Conf_read_eof, // " - Conf_read_io_error, // " + Conf_read_left_and_right = 0, // left and right will be set + Conf_read_irrelevant, // only left will be set + Conf_read_buffer_too_small, // neither will be set + Conf_read_eof, // " + Conf_read_io_error, // " } Conf_read_result; -Conf_read_result conf_read_line(FILE *file, char *buf, Usz bufsize, - char **out_left, Usz *out_leftlen, - char **out_right, Usz *out_rightlen); +Conf_read_result conf_read_line(FILE* file, char* buf, Usz bufsize, + char** out_left, Usz* out_leftlen, + char** out_right, Usz* out_rightlen); -bool conf_read_match(FILE **pfile, char const *const *names, Usz nameslen, - char *buf, Usz bufsize, Usz *out_index, char **out_value); +bool conf_read_match(FILE** pfile, char const* const* names, Usz nameslen, + char* buf, Usz bufsize, Usz* out_index, char** out_value); -FILE *conf_file_open_for_reading(char const *conf_file_name); +FILE* conf_file_open_for_reading(char const* conf_file_name); typedef struct { - FILE *origfile, *tempfile; - struct oso *canonpath, *temppath; + FILE *origfile, *tempfile; + struct oso *canonpath, *temppath; } Conf_save; typedef enum { - Conf_save_start_ok = 0, - Conf_save_start_bad_conf_name, - Conf_save_start_alloc_failed, - Conf_save_start_no_home, - Conf_save_start_mkdir_failed, - Conf_save_start_conf_dir_not_dir, - Conf_save_start_temp_file_perm_denied, - Conf_save_start_old_temp_file_stuck, - Conf_save_start_temp_file_open_failed, + Conf_save_start_ok = 0, + Conf_save_start_bad_conf_name, + Conf_save_start_alloc_failed, + Conf_save_start_no_home, + Conf_save_start_mkdir_failed, + Conf_save_start_conf_dir_not_dir, + Conf_save_start_temp_file_perm_denied, + Conf_save_start_old_temp_file_stuck, + Conf_save_start_temp_file_open_failed, } Conf_save_start_error; typedef enum { - Conf_save_commit_ok = 0, - Conf_save_commit_temp_fsync_failed, - Conf_save_commit_temp_close_failed, - Conf_save_commit_rename_failed, + Conf_save_commit_ok = 0, + Conf_save_commit_temp_fsync_failed, + Conf_save_commit_temp_close_failed, + Conf_save_commit_rename_failed, } Conf_save_commit_error; -Conf_save_start_error conf_save_start(Conf_save *p, char const *conf_file_name); +Conf_save_start_error conf_save_start(Conf_save* p, + char const* conf_file_name); // `*p` may be passed in uninitialized or zeroed -- either is fine. If the // return value is `Conf_save_start_ok`, then you must call either // `conf_save_cancel()` or `conf_save_commit()`, otherwise file handles and @@ -74,63 +75,63 @@ Conf_save_start_error conf_save_start(Conf_save *p, char const *conf_file_name); // there to be no existing config file. It might be the first time a config // file is being written. -void conf_save_cancel(Conf_save *p); +void conf_save_cancel(Conf_save* p); // Cancels a config save. Closes any file handles and frees any necessary // strings. Calling with a zeroed `*p` is fine, but don't call it with // uninitialized data. Afterwards, `*p` will be zeroed. -Conf_save_commit_error conf_save_commit(Conf_save *p); +Conf_save_commit_error conf_save_commit(Conf_save* p); // Finishes. Do not call this with a zeroed `*p`. Afterwards, `*p` will be // zeroed. // Just playing around with this design typedef struct { - FILE *file; - Usz index; - char *value; - char buffer[1024]; + FILE* file; + Usz index; + char* value; + char buffer[1024]; } Ezconf_r; -void ezconf_r_start(Ezconf_r *ezcr, char const *conf_file_name); -bool ezconf_r_step(Ezconf_r *ezcr, char const *const *names, Usz nameslen); +void ezconf_r_start(Ezconf_r* ezcr, char const* conf_file_name); +bool ezconf_r_step(Ezconf_r* ezcr, char const* const* names, Usz nameslen); typedef enum { - Ezconf_w_ok = 0, - Ezconf_w_bad_conf_name, - Ezconf_w_oom, - Ezconf_w_no_home, - Ezconf_w_mkdir_failed, - Ezconf_w_conf_dir_not_dir, - Ezconf_w_old_temp_file_stuck, - Ezconf_w_temp_file_perm_denied, - Ezconf_w_temp_open_failed, - Ezconf_w_temp_fsync_failed, - Ezconf_w_temp_close_failed, - Ezconf_w_rename_failed, - Ezconf_w_line_too_long, - Ezconf_w_existing_read_error, - Ezconf_w_unknown_error, + Ezconf_w_ok = 0, + Ezconf_w_bad_conf_name, + Ezconf_w_oom, + Ezconf_w_no_home, + Ezconf_w_mkdir_failed, + Ezconf_w_conf_dir_not_dir, + Ezconf_w_old_temp_file_stuck, + Ezconf_w_temp_file_perm_denied, + Ezconf_w_temp_open_failed, + Ezconf_w_temp_fsync_failed, + Ezconf_w_temp_close_failed, + Ezconf_w_rename_failed, + Ezconf_w_line_too_long, + Ezconf_w_existing_read_error, + Ezconf_w_unknown_error, } Ezconf_w_error; -char const *ezconf_w_errorstring(Ezconf_w_error error); +char const* ezconf_w_errorstring(Ezconf_w_error error); typedef struct { - char const *name; - intptr_t id; - uint8_t flags; + char const* name; + intptr_t id; + uint8_t flags; } Ezconf_opt; typedef struct { - Conf_save save; - Ezconf_opt *opts; - size_t optscount, optscap; - intptr_t optid; - FILE *file; - Ezconf_w_error error; - uint32_t stateflags; + Conf_save save; + Ezconf_opt* opts; + size_t optscount, optscap; + intptr_t optid; + FILE* file; + Ezconf_w_error error; + uint32_t stateflags; } Ezconf_w; -void ezconf_w_start(Ezconf_w *ezcw, Ezconf_opt *optsbuffer, size_t buffercap, - char const *conf_file_name); -void ezconf_w_addopt(Ezconf_w *ezcw, char const *key, intptr_t id); -bool ezconf_w_step(Ezconf_w *ezcw); +void ezconf_w_start(Ezconf_w* ezcw, Ezconf_opt* optsbuffer, size_t buffercap, + char const* conf_file_name); +void ezconf_w_addopt(Ezconf_w* ezcw, char const* key, intptr_t id); +bool ezconf_w_step(Ezconf_w* ezcw); diff --git a/term_util.c b/term_util.c index 53ba92c..6a8dd02 100644 --- a/term_util.c +++ b/term_util.c @@ -3,18 +3,20 @@ #include #include -void term_util_init_colors() { - if (has_colors()) { - // Enable color - start_color(); - use_default_colors(); - for (int ifg = 0; ifg < Colors_count; ++ifg) { - for (int ibg = 0; ibg < Colors_count; ++ibg) { - int res = init_pair((short int)(1 + ifg * Colors_count + ibg), - (short int)(ifg - 1), (short int)(ibg - 1)); - (void)res; - // Might fail on Linux virtual console/terminal for a couple of colors. - // Just ignore. +void term_util_init_colors(void) +{ + if (has_colors()) { + // Enable color + start_color(); + use_default_colors(); + for (int ifg = 0; ifg < Colors_count; ++ifg) { + for (int ibg = 0; ibg < Colors_count; ++ibg) { + int res = init_pair((short int)(1 + ifg * Colors_count + ibg), + (short int)(ifg - 1), + (short int)(ibg - 1)); + (void)res; + // Might fail on Linux virtual console/terminal for a couple of + // colors. Just ignore. #if 0 if (res == ERR) { endwin(); @@ -23,52 +25,53 @@ void term_util_init_colors() { exit(1); } #endif - } + } + } } - } } -#define ORCA_CONTAINER_OF(ptr, type, member) \ - ((type *)((char *)(1 ? (ptr) : &((type *)0)->member) - \ - offsetof(type, member))) +#define ORCA_CONTAINER_OF(ptr, type, member) \ + ((type*)((char*)(1 ? (ptr) : &((type*)0)->member) \ + - offsetof(type, member))) struct Qmsg { - Qblock qblock; - Qmsg_dismiss_mode dismiss_mode; + Qblock qblock; + Qmsg_dismiss_mode dismiss_mode; }; typedef struct Qmenu_item { - char const *text; - int id; - U8 owns_string : 1, is_spacer : 1; + char const* text; + int id; + U8 owns_string : 1, is_spacer : 1; } Qmenu_item; struct Qmenu { - Qblock qblock; - Qmenu_item *items; - Usz items_count, items_cap; - int current_item, id; - U8 needs_reprint : 1, is_frontmost : 1; + Qblock qblock; + Qmenu_item* items; + Usz items_count, items_cap; + int current_item, id; + U8 needs_reprint : 1, is_frontmost : 1; }; struct Qform { - Qblock qblock; - FORM *ncurses_form; - FIELD *ncurses_fields[32]; - Usz fields_count; - int id; + Qblock qblock; + FORM* ncurses_form; + FIELD* ncurses_fields[32]; + Usz fields_count; + int id; }; -static void qmenu_free(Qmenu *qm); -static void qform_free(Qform *qf); -ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm); +static void qmenu_free(Qmenu* qm); +static void qform_free(Qform* qf); +ORCA_NOINLINE static void qmenu_reprint(Qmenu* qm); Qnav_stack qnav_stack; -void qnav_init() { qnav_stack = (Qnav_stack){0}; } -void qnav_deinit() { - while (qnav_stack.top) - qnav_stack_pop(); +void qnav_init(void) { qnav_stack = (Qnav_stack) { 0 }; } +void qnav_deinit(void) +{ + while (qnav_stack.top) + qnav_stack_pop(); } // Set new y and x coordinates for the top and left of a Qblock based on the // position of the Qblock "below" it in the stack. (Below meaning its order in @@ -77,621 +80,664 @@ void qnav_deinit() { // you've finished doing the rest of the setup on the Qblock. The y and x // fields can be junk, though, since this function writes to them without // reading them. -static ORCA_NOINLINE void qnav_reposition_block(Qblock *qb) { - int top = 0, left = 0; - Qblock *prev = qb->down; - if (!prev) - goto done; - int total_h, total_w; - getmaxyx(qb->outer_window, total_h, total_w); - WINDOW *w = prev->outer_window; - int prev_y = prev->y, prev_x = prev->x, prev_h, prev_w; - getmaxyx(w, prev_h, prev_w); - // Start by trying to position the item to the right of the previous item. - left = prev_x + prev_w + 0; - int term_h, term_w; - getmaxyx(stdscr, term_h, term_w); - // Check if we'll run out of room if we position the new item to the right - // of the existing item (with the same Y position.) - if (left + total_w > term_w) { - // If we have enough room if we position just below the previous item in - // the stack, do that instead of positioning to the right of it. - if (prev_x + total_w <= term_w && total_h < term_h - (prev_y + prev_h)) { - top = prev_y + prev_h; - left = prev_x; - } - // If the item doesn't fit there, but it's less wide than the terminal, - // right-align it to the edge of the terminal. - else if (total_w < term_w) { - left = term_w - total_w; - } - // Otherwise, just start the layout over at Y=0,X=0 - else { - left = 0; +static ORCA_NOINLINE void qnav_reposition_block(Qblock* qb) +{ + int top = 0, left = 0; + Qblock* prev = qb->down; + if (!prev) + goto done; + int total_h, total_w; + getmaxyx(qb->outer_window, total_h, total_w); + WINDOW* w = prev->outer_window; + int prev_y = prev->y, prev_x = prev->x, prev_h, prev_w; + getmaxyx(w, prev_h, prev_w); + // Start by trying to position the item to the right of the previous item. + left = prev_x + prev_w + 0; + int term_h, term_w; + getmaxyx(stdscr, term_h, term_w); + // Check if we'll run out of room if we position the new item to the right + // of the existing item (with the same Y position.) + if (left + total_w > term_w) { + // If we have enough room if we position just below the previous item + // in the stack, do that instead of positioning to the right of it. + if (prev_x + total_w <= term_w + && total_h < term_h - (prev_y + prev_h)) { + top = prev_y + prev_h; + left = prev_x; + } + // If the item doesn't fit there, but it's less wide than the terminal, + // right-align it to the edge of the terminal. + else if (total_w < term_w) { + left = term_w - total_w; + } + // Otherwise, just start the layout over at Y=0,X=0 + else { + left = 0; + } } - } done: - qb->y = top; - qb->x = left; + qb->y = top; + qb->x = left; } -static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) { +static ORCA_NOINLINE void qnav_stack_push(Qblock* qb, int height, int width) +{ #ifndef NDEBUG - for (Qblock *i = qnav_stack.top; i; i = i->down) { - assert(i != qb); - } + for (Qblock* i = qnav_stack.top; i; i = i->down) { + assert(i != qb); + } #endif - int total_h = height + 2, total_w = width + 2; - if (qnav_stack.top) - qnav_stack.top->up = qb; - else - qnav_stack.bottom = qb; - qb->down = qnav_stack.top; - qnav_stack.top = qb; - qb->outer_window = newpad(total_h, total_w); - qb->content_window = subpad(qb->outer_window, height, width, 1, 1); - qnav_reposition_block(qb); - qnav_stack.occlusion_dirty = true; -} - -Qblock *qnav_top_block() { return qnav_stack.top; } - -void qblock_init(Qblock *qb, Qblock_type_tag tag) { - *qb = (Qblock){0}; - qb->tag = tag; -} - -void qnav_free_block(Qblock *qb) { - switch (qb->tag) { - case Qblock_type_qmsg: { - Qmsg *qm = qmsg_of(qb); - free(qm); - break; - } - case Qblock_type_qmenu: - qmenu_free(qmenu_of(qb)); - break; - case Qblock_type_qform: - qform_free(qform_of(qb)); - break; - } -} - -void qnav_stack_pop(void) { - assert(qnav_stack.top); - if (!qnav_stack.top) - return; - Qblock *qb = qnav_stack.top; - qnav_stack.top = qb->down; - if (qnav_stack.top) - qnav_stack.top->up = NULL; - else - qnav_stack.bottom = NULL; - qnav_stack.occlusion_dirty = true; - WINDOW *content_window = qb->content_window; - WINDOW *outer_window = qb->outer_window; - // erase any stuff underneath where this window is, in case it's outside of - // the grid in an area that isn't actively redraw - werase(outer_window); - wnoutrefresh(outer_window); - qnav_free_block(qb); - delwin(content_window); - delwin(outer_window); -} - -bool qnav_draw(void) { - bool drew_any = false; - if (!qnav_stack.bottom) - goto done; - int term_h, term_w; - getmaxyx(stdscr, term_h, term_w); - for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) { - bool is_frontmost = qb == qnav_stack.top; - if (qnav_stack.occlusion_dirty) - qblock_print_frame(qb, is_frontmost); + int total_h = height + 2, total_w = width + 2; + if (qnav_stack.top) + qnav_stack.top->up = qb; + else + qnav_stack.bottom = qb; + qb->down = qnav_stack.top; + qnav_stack.top = qb; + qb->outer_window = newpad(total_h, total_w); + qb->content_window = subpad(qb->outer_window, height, width, 1, 1); + qnav_reposition_block(qb); + qnav_stack.occlusion_dirty = true; +} + +Qblock* qnav_top_block(void) { return qnav_stack.top; } + +void qblock_init(Qblock* qb, Qblock_type_tag tag) +{ + *qb = (Qblock) { 0 }; + qb->tag = tag; +} + +void qnav_free_block(Qblock* qb) +{ switch (qb->tag) { - case Qblock_type_qmsg: - break; - case Qblock_type_qmenu: { - Qmenu *qm = qmenu_of(qb); - if (qm->is_frontmost != is_frontmost) { - qm->is_frontmost = is_frontmost; - qm->needs_reprint = 1; - } - if (qm->needs_reprint) { - qmenu_reprint(qm); - qm->needs_reprint = 0; - } - break; + case Qblock_type_qmsg: { + Qmsg* qm = qmsg_of(qb); + free(qm); + break; } + case Qblock_type_qmenu: + qmenu_free(qmenu_of(qb)); + break; case Qblock_type_qform: - break; + qform_free(qform_of(qb)); + break; } - touchwin(qb->outer_window); // here? or after continue? - if (term_h < 1 || term_w < 1) - continue; - int qbwin_h, qbwin_w; - getmaxyx(qb->outer_window, qbwin_h, qbwin_w); - int qbwin_endy = qb->y + qbwin_h; - int qbwin_endx = qb->x + qbwin_w; - if (qbwin_endy >= term_h) - qbwin_endy = term_h - 1; - if (qbwin_endx >= term_w) - qbwin_endx = term_w - 1; - if (qb->y >= qbwin_endy || qb->x >= qbwin_endx) - continue; - pnoutrefresh(qb->outer_window, 0, 0, qb->y, qb->x, qbwin_endy, qbwin_endx); - drew_any = true; - } -done: - qnav_stack.occlusion_dirty = false; - return drew_any; } -void qnav_adjust_term_size(void) { - if (!qnav_stack.bottom) - return; - for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) - qnav_reposition_block(qb); - qnav_stack.occlusion_dirty = true; -} - -void qblock_print_border(Qblock *qb, unsigned int attr) { - wborder(qb->outer_window, ACS_VLINE | attr, ACS_VLINE | attr, - ACS_HLINE | attr, ACS_HLINE | attr, ACS_ULCORNER | attr, - ACS_URCORNER | attr, ACS_LLCORNER | attr, ACS_LRCORNER | attr); -} - -void qblock_print_title(Qblock *qb, char const *title, int attr) { - wmove(qb->outer_window, 0, 1); - attr_t attrs = A_NORMAL; - short pair = 0; - wattr_get(qb->outer_window, &attrs, &pair, NULL); - wattrset(qb->outer_window, attr); - waddch(qb->outer_window, ' '); - waddstr(qb->outer_window, title); - waddch(qb->outer_window, ' '); - wattr_set(qb->outer_window, attrs, pair, NULL); -} - -void qblock_set_title(Qblock *qb, char const *title) { qb->title = title; } - -void qblock_print_frame(Qblock *qb, bool active) { - qblock_print_border(qb, active ? A_NORMAL : A_DIM); - if (qb->title) { - qblock_print_title(qb, qb->title, active ? A_NORMAL : A_DIM); - } - if (qb->tag == Qblock_type_qform) { - Qform *qf = qform_of(qb); - if (qf->ncurses_form) { - pos_form_cursor(qf->ncurses_form); +void qnav_stack_pop(void) +{ + assert(qnav_stack.top); + if (!qnav_stack.top) + return; + Qblock* qb = qnav_stack.top; + qnav_stack.top = qb->down; + if (qnav_stack.top) + qnav_stack.top->up = NULL; + else + qnav_stack.bottom = NULL; + qnav_stack.occlusion_dirty = true; + WINDOW* content_window = qb->content_window; + WINDOW* outer_window = qb->outer_window; + // erase any stuff underneath where this window is, in case it's outside of + // the grid in an area that isn't actively redraw + werase(outer_window); + wnoutrefresh(outer_window); + qnav_free_block(qb); + delwin(content_window); + delwin(outer_window); +} + +bool qnav_draw(void) +{ + bool drew_any = false; + if (!qnav_stack.bottom) + goto done; + int term_h, term_w; + getmaxyx(stdscr, term_h, term_w); + for (Qblock* qb = qnav_stack.bottom; qb; qb = qb->up) { + bool is_frontmost = qb == qnav_stack.top; + if (qnav_stack.occlusion_dirty) + qblock_print_frame(qb, is_frontmost); + switch (qb->tag) { + case Qblock_type_qmsg: + break; + case Qblock_type_qmenu: { + Qmenu* qm = qmenu_of(qb); + if (qm->is_frontmost != is_frontmost) { + qm->is_frontmost = is_frontmost; + qm->needs_reprint = 1; + } + if (qm->needs_reprint) { + qmenu_reprint(qm); + qm->needs_reprint = 0; + } + break; + } + case Qblock_type_qform: + break; + } + touchwin(qb->outer_window); // here? or after continue? + if (term_h < 1 || term_w < 1) + continue; + int qbwin_h, qbwin_w; + getmaxyx(qb->outer_window, qbwin_h, qbwin_w); + int qbwin_endy = qb->y + qbwin_h; + int qbwin_endx = qb->x + qbwin_w; + if (qbwin_endy >= term_h) + qbwin_endy = term_h - 1; + if (qbwin_endx >= term_w) + qbwin_endx = term_w - 1; + if (qb->y >= qbwin_endy || qb->x >= qbwin_endx) + continue; + pnoutrefresh(qb->outer_window, 0, 0, qb->y, qb->x, qbwin_endy, + qbwin_endx); + drew_any = true; + } +done: + qnav_stack.occlusion_dirty = false; + return drew_any; +} + +void qnav_adjust_term_size(void) +{ + if (!qnav_stack.bottom) + return; + for (Qblock* qb = qnav_stack.bottom; qb; qb = qb->up) + qnav_reposition_block(qb); + qnav_stack.occlusion_dirty = true; +} + +void qblock_print_border(Qblock* qb, unsigned int attr) +{ + wborder(qb->outer_window, ACS_VLINE | attr, ACS_VLINE | attr, + ACS_HLINE | attr, ACS_HLINE | attr, ACS_ULCORNER | attr, + ACS_URCORNER | attr, ACS_LLCORNER | attr, ACS_LRCORNER | attr); +} + +void qblock_print_title(Qblock* qb, char const* title, int attr) +{ + wmove(qb->outer_window, 0, 1); + attr_t attrs = A_NORMAL; + short pair = 0; + wattr_get(qb->outer_window, &attrs, &pair, NULL); + wattrset(qb->outer_window, attr); + waddch(qb->outer_window, ' '); + waddstr(qb->outer_window, title); + waddch(qb->outer_window, ' '); + wattr_set(qb->outer_window, attrs, pair, NULL); +} + +void qblock_set_title(Qblock* qb, char const* title) { qb->title = title; } + +void qblock_print_frame(Qblock* qb, bool active) +{ + qblock_print_border(qb, active ? A_NORMAL : A_DIM); + if (qb->title) { + qblock_print_title(qb, qb->title, active ? A_NORMAL : A_DIM); + } + if (qb->tag == Qblock_type_qform) { + Qform* qf = qform_of(qb); + if (qf->ncurses_form) { + pos_form_cursor(qf->ncurses_form); + } } - } } -WINDOW *qmsg_window(Qmsg *qm) { return qm->qblock.content_window; } - -void qmsg_set_title(Qmsg *qm, char const *title) { - qblock_set_title(&qm->qblock, title); +WINDOW* qmsg_window(Qmsg* qm) { return qm->qblock.content_window; } + +void qmsg_set_title(Qmsg* qm, char const* title) +{ + qblock_set_title(&qm->qblock, title); +} + +void qmsg_set_dismiss_mode(Qmsg* qm, Qmsg_dismiss_mode mode) +{ + if (qm->dismiss_mode == mode) + return; + qm->dismiss_mode = mode; +} + +Qmsg* qmsg_push(int height, int width) +{ + Qmsg* qm = malloc(sizeof(Qmsg)); + qblock_init(&qm->qblock, Qblock_type_qmsg); + qm->dismiss_mode = Qmsg_dismiss_mode_explicitly; + qnav_stack_push(&qm->qblock, height, width); + return qm; +} + +Qmsg* qmsg_printf_push(char const* title, char const* fmt, ...) +{ + int titlewidth = title ? (int)strlen(title) : 0; + va_list ap; + va_start(ap, fmt); + int msgbytes = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + char* buffer = malloc((Usz)msgbytes + 1); + if (!buffer) + exit(1); + va_start(ap, fmt); + int printedbytes = vsnprintf(buffer, (Usz)msgbytes + 1, fmt, ap); + va_end(ap); + if (printedbytes != msgbytes) + exit(1); // todo better handling? + int lines = 1; + int curlinewidth = 0; + int maxlinewidth = 0; + for (int i = 0; i < msgbytes; i++) { + if (buffer[i] == '\n') { + buffer[i] = '\0'; // This is terrifying :) + lines++; + if (curlinewidth > maxlinewidth) + maxlinewidth = curlinewidth; + curlinewidth = 0; + } else { + curlinewidth++; + } + } + if (curlinewidth > maxlinewidth) + maxlinewidth = curlinewidth; + int width = titlewidth > maxlinewidth ? titlewidth : maxlinewidth; + width += 2; // 1 padding on left and right each + Qmsg* msg = qmsg_push(lines, + width); // no wrapping yet, no real wcwidth, etc + WINDOW* msgw = qmsg_window(msg); + int i = 0; + int offset = 0; + for (;;) { + if (offset == msgbytes + 1) + break; + int numbytes = (int)strlen(buffer + offset); + wmove(msgw, i, 1); + waddstr(msgw, buffer + offset); + offset += numbytes + 1; + i++; + } + free(buffer); + if (title) + qmsg_set_title(msg, title); + return msg; +} + +bool qmsg_drive(Qmsg* qm, int key, Qmsg_action* out_action) +{ + *out_action = (Qmsg_action) { 0 }; + Qmsg_dismiss_mode dm = qm->dismiss_mode; + switch (dm) { + case Qmsg_dismiss_mode_explicitly: + break; + case Qmsg_dismiss_mode_easily: + out_action->dismiss = true; + return true; + case Qmsg_dismiss_mode_passthrough: + out_action->dismiss = true; + out_action->passthrough = true; + return true; + } + switch (key) { + case ' ': + case 27: + case '\r': + case KEY_ENTER: + out_action->dismiss = true; + return true; + } + return false; } -void qmsg_set_dismiss_mode(Qmsg *qm, Qmsg_dismiss_mode mode) { - if (qm->dismiss_mode == mode) - return; - qm->dismiss_mode = mode; -} - -Qmsg *qmsg_push(int height, int width) { - Qmsg *qm = malloc(sizeof(Qmsg)); - qblock_init(&qm->qblock, Qblock_type_qmsg); - qm->dismiss_mode = Qmsg_dismiss_mode_explicitly; - qnav_stack_push(&qm->qblock, height, width); - return qm; -} - -Qmsg *qmsg_printf_push(char const *title, char const *fmt, ...) { - int titlewidth = title ? (int)strlen(title) : 0; - va_list ap; - va_start(ap, fmt); - int msgbytes = vsnprintf(NULL, 0, fmt, ap); - va_end(ap); - char *buffer = malloc((Usz)msgbytes + 1); - if (!buffer) - exit(1); - va_start(ap, fmt); - int printedbytes = vsnprintf(buffer, (Usz)msgbytes + 1, fmt, ap); - va_end(ap); - if (printedbytes != msgbytes) - exit(1); // todo better handling? - int lines = 1; - int curlinewidth = 0; - int maxlinewidth = 0; - for (int i = 0; i < msgbytes; i++) { - if (buffer[i] == '\n') { - buffer[i] = '\0'; // This is terrifying :) - lines++; - if (curlinewidth > maxlinewidth) - maxlinewidth = curlinewidth; - curlinewidth = 0; - } else { - curlinewidth++; +Qmsg* qmsg_of(Qblock* qb) { return ORCA_CONTAINER_OF(qb, Qmsg, qblock); } + +Qmenu* qmenu_create(int id) +{ + Qmenu* qm = (Qmenu*)malloc(sizeof(Qmenu)); + qblock_init(&qm->qblock, Qblock_type_qmenu); + qm->items = NULL; + qm->items_count = 0; + qm->items_cap = 0; + qm->current_item = 0; + qm->id = id; + qm->needs_reprint = 1; + qm->is_frontmost = 0; + return qm; +} +void qmenu_destroy(Qmenu* qm) { qmenu_free(qm); } +int qmenu_id(Qmenu const* qm) { return qm->id; } +static ORCA_NOINLINE Qmenu_item* qmenu_allocitems(Qmenu* qm, Usz count) +{ + Usz old_count = qm->items_count; + if (old_count > SIZE_MAX - count) // overflow + exit(1); + Usz new_count = old_count + count; + Usz items_cap = qm->items_cap; + Qmenu_item* items = qm->items; + if (new_count > items_cap) { + // todo overflow check, realloc fail check + Usz new_cap = new_count < 32 ? 32 : orca_round_up_power2(new_count); + Usz new_size = new_cap * sizeof(Qmenu_item); + Qmenu_item* new_items = (Qmenu_item*)realloc(items, new_size); + if (!new_items) + exit(1); + items = new_items; + items_cap = new_cap; + qm->items = new_items; + qm->items_cap = new_cap; + } + qm->items_count = new_count; + return items + old_count; +} +ORCA_NOINLINE static void qmenu_reprint(Qmenu* qm) +{ + WINDOW* win = qm->qblock.content_window; + Qmenu_item* items = qm->items; + bool isfront = qm->is_frontmost; + werase(win); + for (Usz i = 0, n = qm->items_count; i < n; ++i) { + bool iscur = items[i].id == qm->current_item; + wattrset(win, isfront ? iscur ? A_BOLD : A_NORMAL : A_DIM); + wmove(win, (int)i, iscur ? 1 : 3); + if (iscur) + waddstr(win, "> "); + waddstr(win, items[i].text); } - } - if (curlinewidth > maxlinewidth) - maxlinewidth = curlinewidth; - int width = titlewidth > maxlinewidth ? titlewidth : maxlinewidth; - width += 2; // 1 padding on left and right each - Qmsg *msg = qmsg_push(lines, width); // no wrapping yet, no real wcwidth, etc - WINDOW *msgw = qmsg_window(msg); - int i = 0; - int offset = 0; - for (;;) { - if (offset == msgbytes + 1) - break; - int numbytes = (int)strlen(buffer + offset); - wmove(msgw, i, 1); - waddstr(msgw, buffer + offset); - offset += numbytes + 1; - i++; - } - free(buffer); - if (title) - qmsg_set_title(msg, title); - return msg; -} - -bool qmsg_drive(Qmsg *qm, int key, Qmsg_action *out_action) { - *out_action = (Qmsg_action){0}; - Qmsg_dismiss_mode dm = qm->dismiss_mode; - switch (dm) { - case Qmsg_dismiss_mode_explicitly: - break; - case Qmsg_dismiss_mode_easily: - out_action->dismiss = true; - return true; - case Qmsg_dismiss_mode_passthrough: - out_action->dismiss = true; - out_action->passthrough = true; - return true; - } - switch (key) { - case ' ': - case 27: - case '\r': - case KEY_ENTER: - out_action->dismiss = true; - return true; - } - return false; -} - -Qmsg *qmsg_of(Qblock *qb) { return ORCA_CONTAINER_OF(qb, Qmsg, qblock); } - -Qmenu *qmenu_create(int id) { - Qmenu *qm = (Qmenu *)malloc(sizeof(Qmenu)); - qblock_init(&qm->qblock, Qblock_type_qmenu); - qm->items = NULL; - qm->items_count = 0; - qm->items_cap = 0; - qm->current_item = 0; - qm->id = id; - qm->needs_reprint = 1; - qm->is_frontmost = 0; - return qm; -} -void qmenu_destroy(Qmenu *qm) { qmenu_free(qm); } -int qmenu_id(Qmenu const *qm) { return qm->id; } -static ORCA_NOINLINE Qmenu_item *qmenu_allocitems(Qmenu *qm, Usz count) { - Usz old_count = qm->items_count; - if (old_count > SIZE_MAX - count) // overflow - exit(1); - Usz new_count = old_count + count; - Usz items_cap = qm->items_cap; - Qmenu_item *items = qm->items; - if (new_count > items_cap) { - // todo overflow check, realloc fail check - Usz new_cap = new_count < 32 ? 32 : orca_round_up_power2(new_count); - Usz new_size = new_cap * sizeof(Qmenu_item); - Qmenu_item *new_items = (Qmenu_item *)realloc(items, new_size); - if (!new_items) - exit(1); - items = new_items; - items_cap = new_cap; - qm->items = new_items; - qm->items_cap = new_cap; - } - qm->items_count = new_count; - return items + old_count; -} -ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm) { - WINDOW *win = qm->qblock.content_window; - Qmenu_item *items = qm->items; - bool isfront = qm->is_frontmost; - werase(win); - for (Usz i = 0, n = qm->items_count; i < n; ++i) { - bool iscur = items[i].id == qm->current_item; - wattrset(win, isfront ? iscur ? A_BOLD : A_NORMAL : A_DIM); - wmove(win, (int)i, iscur ? 1 : 3); - if (iscur) - waddstr(win, "> "); - waddstr(win, items[i].text); - } -} -void qmenu_set_title(Qmenu *qm, char const *title) { - qblock_set_title(&qm->qblock, title); -} -void qmenu_add_choice(Qmenu *qm, int id, char const *text) { - assert(id != 0); - Qmenu_item *item = qmenu_allocitems(qm, 1); - item->text = text; - item->id = id; - item->owns_string = false; - item->is_spacer = false; - if (!qm->current_item) - qm->current_item = id; } -void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) { - va_list ap; - va_start(ap, fmt); - int textsize = vsnprintf(NULL, 0, fmt, ap); - va_end(ap); - char *buffer = malloc((Usz)textsize + 1); - if (!buffer) - exit(1); - va_start(ap, fmt); - int printedsize = vsnprintf(buffer, (Usz)textsize + 1, fmt, ap); - va_end(ap); - if (printedsize != textsize) - exit(1); // todo better handling? - Qmenu_item *item = qmenu_allocitems(qm, 1); - item->text = buffer; - item->id = id; - item->owns_string = true; - item->is_spacer = false; - if (!qm->current_item) +void qmenu_set_title(Qmenu* qm, char const* title) +{ + qblock_set_title(&qm->qblock, title); +} +void qmenu_add_choice(Qmenu* qm, int id, char const* text) +{ + assert(id != 0); + Qmenu_item* item = qmenu_allocitems(qm, 1); + item->text = text; + item->id = id; + item->owns_string = false; + item->is_spacer = false; + if (!qm->current_item) + qm->current_item = id; +} +void qmenu_add_printf(Qmenu* qm, int id, char const* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int textsize = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + char* buffer = malloc((Usz)textsize + 1); + if (!buffer) + exit(1); + va_start(ap, fmt); + int printedsize = vsnprintf(buffer, (Usz)textsize + 1, fmt, ap); + va_end(ap); + if (printedsize != textsize) + exit(1); // todo better handling? + Qmenu_item* item = qmenu_allocitems(qm, 1); + item->text = buffer; + item->id = id; + item->owns_string = true; + item->is_spacer = false; + if (!qm->current_item) + qm->current_item = id; +} +void qmenu_add_spacer(Qmenu* qm) +{ + Qmenu_item* item = qmenu_allocitems(qm, 1); + item->text = " "; + item->id = 0; + item->owns_string = false; + item->is_spacer = true; +} +void qmenu_set_current_item(Qmenu* qm, int id) +{ + if (qm->current_item == id) + return; qm->current_item = id; + qm->needs_reprint = 1; } -void qmenu_add_spacer(Qmenu *qm) { - Qmenu_item *item = qmenu_allocitems(qm, 1); - item->text = " "; - item->id = 0; - item->owns_string = false; - item->is_spacer = true; +int qmenu_current_item(Qmenu* qm) { return qm->current_item; } +void qmenu_push_to_nav(Qmenu* qm) +{ + // Probably a programming error if there are no items. Make the menu + // visible so the programmer knows something went wrong. + if (qm->items_count == 0) + qmenu_add_spacer(qm); + Usz n = qm->items_count; + Qmenu_item* items = qm->items; + int menu_min_h = (int)n, menu_min_w = 0; + for (Usz i = 0; i < n; ++i) { + int item_w = (int)strlen(items[i].text); + if (item_w > menu_min_w) + menu_min_w = item_w; + } + menu_min_w += 3 + 1; // left " > " plus 1 empty space on right + if (qm->qblock.title) { + // Stupid lack of wcswidth() means we can't know how wide this string + // is actually displayed. Just fake it for now, until we have Unicode + // strings in the UI. Then we get sad. + int title_w = (int)strlen(qm->qblock.title) + 2; + if (title_w > menu_min_w) + menu_min_w = title_w; + } + qnav_stack_push(&qm->qblock, menu_min_h, menu_min_w); } -void qmenu_set_current_item(Qmenu *qm, int id) { - if (qm->current_item == id) - return; - qm->current_item = id; - qm->needs_reprint = 1; -} -int qmenu_current_item(Qmenu *qm) { return qm->current_item; } -void qmenu_push_to_nav(Qmenu *qm) { - // Probably a programming error if there are no items. Make the menu visible - // so the programmer knows something went wrong. - if (qm->items_count == 0) - qmenu_add_spacer(qm); - Usz n = qm->items_count; - Qmenu_item *items = qm->items; - int menu_min_h = (int)n, menu_min_w = 0; - for (Usz i = 0; i < n; ++i) { - int item_w = (int)strlen(items[i].text); - if (item_w > menu_min_w) - menu_min_w = item_w; - } - menu_min_w += 3 + 1; // left " > " plus 1 empty space on right - if (qm->qblock.title) { - // Stupid lack of wcswidth() means we can't know how wide this string is - // actually displayed. Just fake it for now, until we have Unicode strings - // in the UI. Then we get sad. - int title_w = (int)strlen(qm->qblock.title) + 2; - if (title_w > menu_min_w) - menu_min_w = title_w; - } - qnav_stack_push(&qm->qblock, menu_min_h, menu_min_w); -} - -static void qmenu_free(Qmenu *qm) { - Qmenu_item *items = qm->items; - for (Usz i = 0, n = qm->items_count; i < n; ++i) { - if (items[i].owns_string) - free((void *)items[i].text); - } - free(qm->items); - free(qm); -} - -ORCA_NOINLINE static void qmenu_drive_upordown(Qmenu *qm, bool downwards) { - Qmenu_item *items = qm->items; - Usz n = qm->items_count; - if (n <= 1) + +static void qmenu_free(Qmenu* qm) +{ + Qmenu_item* items = qm->items; + for (Usz i = 0, n = qm->items_count; i < n; ++i) { + if (items[i].owns_string) + free((void*)items[i].text); + } + free(qm->items); + free(qm); +} + +ORCA_NOINLINE static void qmenu_drive_upordown(Qmenu* qm, bool downwards) +{ + Qmenu_item* items = qm->items; + Usz n = qm->items_count; + if (n <= 1) + return; + int cur_id = qm->current_item; + Usz starting = 0; + for (; starting < n; ++starting) { + if (items[starting].id == cur_id) + goto found; + } return; - int cur_id = qm->current_item; - Usz starting = 0; - for (; starting < n; ++starting) { - if (items[starting].id == cur_id) - goto found; - } - return; found:; - Usz current = starting; - for (;;) { - if (downwards && current < n - 1) - current++; - else if (!downwards && current > 0) - current--; - if (current == starting) - break; - if (!items[current].is_spacer) - break; - } - if (current != starting) { - qm->current_item = items[current].id; - qm->needs_reprint = 1; - } + Usz current = starting; + for (;;) { + if (downwards && current < n - 1) + current++; + else if (!downwards && current > 0) + current--; + if (current == starting) + break; + if (!items[current].is_spacer) + break; + } + if (current != starting) { + qm->current_item = items[current].id; + qm->needs_reprint = 1; + } } -bool qmenu_drive(Qmenu *qm, int key, Qmenu_action *out_action) { - switch (key) { - case 27: { - out_action->any.type = Qmenu_action_type_canceled; - return true; - } - case ' ': - case '\r': - case KEY_ENTER: - out_action->picked.type = Qmenu_action_type_picked; - out_action->picked.id = qm->current_item; - return true; - case KEY_UP: - qmenu_drive_upordown(qm, false); - return false; - case KEY_DOWN: - qmenu_drive_upordown(qm, true); +bool qmenu_drive(Qmenu* qm, int key, Qmenu_action* out_action) +{ + switch (key) { + case 27: { + out_action->any.type = Qmenu_action_type_canceled; + return true; + } + case ' ': + case '\r': + case KEY_ENTER: + out_action->picked.type = Qmenu_action_type_picked; + out_action->picked.id = qm->current_item; + return true; + case KEY_UP: + qmenu_drive_upordown(qm, false); + return false; + case KEY_DOWN: + qmenu_drive_upordown(qm, true); + return false; + } return false; - } - return false; } -Qmenu *qmenu_of(Qblock *qb) { return ORCA_CONTAINER_OF(qb, Qmenu, qblock); } - -bool qmenu_top_is_menu(int id) { - Qblock *qb = qnav_top_block(); - if (!qb) - return false; - if (qb->tag != Qblock_type_qmenu) - return false; - Qmenu *qm = qmenu_of(qb); - return qm->id == id; -} - -Qform *qform_create(int id) { - Qform *qf = (Qform *)malloc(sizeof(Qform)); - qblock_init(&qf->qblock, Qblock_type_qform); - qf->ncurses_form = NULL; - qf->ncurses_fields[0] = NULL; - qf->fields_count = 0; - qf->id = id; - return qf; -} -static void qform_free(Qform *qf) { - curs_set(0); - unpost_form(qf->ncurses_form); - free_form(qf->ncurses_form); - for (Usz i = 0; i < qf->fields_count; ++i) { - free_field(qf->ncurses_fields[i]); - } - free(qf); -} -int qform_id(Qform const *qf) { return qf->id; } -Qform *qform_of(Qblock *qb) { return ORCA_CONTAINER_OF(qb, Qform, qblock); } -void qform_set_title(Qform *qf, char const *title) { - qblock_set_title(&qf->qblock, title); -} -void qform_add_line_input(Qform *qf, int id, char const *initial) { - FIELD *f = new_field(1, 30, 0, 0, 0, 0); - if (initial) - set_field_buffer(f, 0, initial); - set_field_userptr(f, (void *)(intptr_t)(id)); - field_opts_off(f, O_WRAP | O_BLANK | O_STATIC); - qf->ncurses_fields[qf->fields_count] = f; - ++qf->fields_count; - qf->ncurses_fields[qf->fields_count] = NULL; -} -void qform_push_to_nav(Qform *qf) { - qf->ncurses_form = new_form(qf->ncurses_fields); - int form_min_h, form_min_w; - scale_form(qf->ncurses_form, &form_min_h, &form_min_w); - qnav_stack_push(&qf->qblock, form_min_h, form_min_w); - set_form_win(qf->ncurses_form, qf->qblock.outer_window); - set_form_sub(qf->ncurses_form, qf->qblock.content_window); - post_form(qf->ncurses_form); - // quick'n'dirty cursor change for now - curs_set(1); - form_driver(qf->ncurses_form, REQ_END_LINE); -} -void qform_single_line_input(int id, char const *title, char const *initial) { - Qform *qf = qform_create(id); - qform_set_title(qf, title); - qform_add_line_input(qf, 1, initial); - qform_push_to_nav(qf); -} -bool qform_drive(Qform *qf, int key, Qform_action *out_action) { - switch (key) { - case 27: - out_action->any.type = Qform_action_type_canceled; - return true; - case CTRL_PLUS('a'): - form_driver(qf->ncurses_form, REQ_BEG_LINE); - return false; - case CTRL_PLUS('e'): +Qmenu* qmenu_of(Qblock* qb) { return ORCA_CONTAINER_OF(qb, Qmenu, qblock); } + +bool qmenu_top_is_menu(int id) +{ + Qblock* qb = qnav_top_block(); + if (!qb) + return false; + if (qb->tag != Qblock_type_qmenu) + return false; + Qmenu* qm = qmenu_of(qb); + return qm->id == id; +} + +Qform* qform_create(int id) +{ + Qform* qf = (Qform*)malloc(sizeof(Qform)); + qblock_init(&qf->qblock, Qblock_type_qform); + qf->ncurses_form = NULL; + qf->ncurses_fields[0] = NULL; + qf->fields_count = 0; + qf->id = id; + return qf; +} +static void qform_free(Qform* qf) +{ + curs_set(0); + unpost_form(qf->ncurses_form); + free_form(qf->ncurses_form); + for (Usz i = 0; i < qf->fields_count; ++i) { + free_field(qf->ncurses_fields[i]); + } + free(qf); +} +int qform_id(Qform const* qf) { return qf->id; } +Qform* qform_of(Qblock* qb) { return ORCA_CONTAINER_OF(qb, Qform, qblock); } +void qform_set_title(Qform* qf, char const* title) +{ + qblock_set_title(&qf->qblock, title); +} +void qform_add_line_input(Qform* qf, int id, char const* initial) +{ + FIELD* f = new_field(1, 30, 0, 0, 0, 0); + if (initial) + set_field_buffer(f, 0, initial); + set_field_userptr(f, (void*)(intptr_t)(id)); + field_opts_off(f, O_WRAP | O_BLANK | O_STATIC); + qf->ncurses_fields[qf->fields_count] = f; + ++qf->fields_count; + qf->ncurses_fields[qf->fields_count] = NULL; +} +void qform_push_to_nav(Qform* qf) +{ + qf->ncurses_form = new_form(qf->ncurses_fields); + int form_min_h, form_min_w; + scale_form(qf->ncurses_form, &form_min_h, &form_min_w); + qnav_stack_push(&qf->qblock, form_min_h, form_min_w); + set_form_win(qf->ncurses_form, qf->qblock.outer_window); + set_form_sub(qf->ncurses_form, qf->qblock.content_window); + post_form(qf->ncurses_form); + // quick'n'dirty cursor change for now + curs_set(1); form_driver(qf->ncurses_form, REQ_END_LINE); +} +void qform_single_line_input(int id, char const* title, char const* initial) +{ + Qform* qf = qform_create(id); + qform_set_title(qf, title); + qform_add_line_input(qf, 1, initial); + qform_push_to_nav(qf); +} +bool qform_drive(Qform* qf, int key, Qform_action* out_action) +{ + switch (key) { + case 27: + out_action->any.type = Qform_action_type_canceled; + return true; + case CTRL_PLUS('a'): + form_driver(qf->ncurses_form, REQ_BEG_LINE); + return false; + case CTRL_PLUS('e'): + form_driver(qf->ncurses_form, REQ_END_LINE); + return false; + case CTRL_PLUS('b'): + form_driver(qf->ncurses_form, REQ_PREV_CHAR); + return false; + case CTRL_PLUS('f'): + form_driver(qf->ncurses_form, REQ_NEXT_CHAR); + return false; + case CTRL_PLUS('k'): + form_driver(qf->ncurses_form, REQ_CLR_EOL); + return false; + case KEY_RIGHT: + form_driver(qf->ncurses_form, REQ_RIGHT_CHAR); + return false; + case KEY_LEFT: + form_driver(qf->ncurses_form, REQ_LEFT_CHAR); + return false; + case 127: // backspace in terminal.app, apparently + case KEY_BACKSPACE: + case CTRL_PLUS('h'): + form_driver(qf->ncurses_form, REQ_DEL_PREV); + return false; + case '\r': + case KEY_ENTER: + out_action->any.type = Qform_action_type_submitted; + return true; + } + form_driver(qf->ncurses_form, key); return false; - case CTRL_PLUS('b'): - form_driver(qf->ncurses_form, REQ_PREV_CHAR); - return false; - case CTRL_PLUS('f'): - form_driver(qf->ncurses_form, REQ_NEXT_CHAR); - return false; - case CTRL_PLUS('k'): - form_driver(qf->ncurses_form, REQ_CLR_EOL); - return false; - case KEY_RIGHT: - form_driver(qf->ncurses_form, REQ_RIGHT_CHAR); - return false; - case KEY_LEFT: - form_driver(qf->ncurses_form, REQ_LEFT_CHAR); - return false; - case 127: // backspace in terminal.app, apparently - case KEY_BACKSPACE: - case CTRL_PLUS('h'): - form_driver(qf->ncurses_form, REQ_DEL_PREV); - return false; - case '\r': - case KEY_ENTER: - out_action->any.type = Qform_action_type_submitted; +} +static Usz size_without_trailing_spaces(char const* str) +{ + Usz size = strlen(str); + for (;;) { + if (size == 0) + break; + if (!isspace(str[size - 1])) + break; + --size; + } + return size; +} +static FIELD* qform_find_field(Qform const* qf, int id) +{ + Usz count = qf->fields_count; + for (Usz i = 0; i < count; ++i) { + FIELD* f = qf->ncurses_fields[i]; + if ((int)(intptr_t)field_userptr(f) == id) + return f; + } + return NULL; +} +bool qform_get_text_line(Qform const* qf, int id, oso** out) +{ + FIELD* f = qform_find_field(qf, id); + if (!f) + return false; + form_driver(qf->ncurses_form, REQ_VALIDATION); + char* buf = field_buffer(f, 0); + if (!buf) + return false; + Usz trimmed = size_without_trailing_spaces(buf); + osoputlen(out, buf, trimmed); return true; - } - form_driver(qf->ncurses_form, key); - return false; -} -static Usz size_without_trailing_spaces(char const *str) { - Usz size = strlen(str); - for (;;) { - if (size == 0) - break; - if (!isspace(str[size - 1])) - break; - --size; - } - return size; -} -static FIELD *qform_find_field(Qform const *qf, int id) { - Usz count = qf->fields_count; - for (Usz i = 0; i < count; ++i) { - FIELD *f = qf->ncurses_fields[i]; - if ((int)(intptr_t)field_userptr(f) == id) - return f; - } - return NULL; -} -bool qform_get_text_line(Qform const *qf, int id, oso **out) { - FIELD *f = qform_find_field(qf, id); - if (!f) - return false; - form_driver(qf->ncurses_form, REQ_VALIDATION); - char *buf = field_buffer(f, 0); - if (!buf) - return false; - Usz trimmed = size_without_trailing_spaces(buf); - osoputlen(out, buf, trimmed); - return true; -} -bool qform_get_single_text_line(Qform const *qf, struct oso **out) { - return qform_get_text_line(qf, 1, out); -} -oso *qform_get_nonempty_single_line_input(Qform *qf) { - oso *s = NULL; - if (qform_get_text_line(qf, 1, &s) && osolen(s) > 0) - return s; - osofree(s); - return NULL; +} +bool qform_get_single_text_line(Qform const* qf, struct oso** out) +{ + return qform_get_text_line(qf, 1, out); +} +oso* qform_get_nonempty_single_line_input(Qform* qf) +{ + oso* s = NULL; + if (qform_get_text_line(qf, 1, &s) && osolen(s) > 0) + return s; + osofree(s); + return NULL; } diff --git a/term_util.h b/term_util.h index c0a7a75..413985b 100644 --- a/term_util.h +++ b/term_util.h @@ -11,62 +11,63 @@ #define ORCA_TERM_UTIL_PRINTF(...) #endif -#define CTRL_PLUS(c) ((c)&037) +#define CTRL_PLUS(c) ((c) & 037) struct oso; typedef enum { - C_natural, - C_black, - C_red, - C_green, - C_yellow, - C_blue, - C_magenta, - C_cyan, - C_white, + C_natural, + C_black, + C_red, + C_green, + C_yellow, + C_blue, + C_magenta, + C_cyan, + C_white, } Color_name; enum { - Colors_count = C_white + 1, + Colors_count = C_white + 1, }; enum { - Cdef_normal = COLOR_PAIR(1), + Cdef_normal = COLOR_PAIR(1), }; typedef enum { - A_normal = A_NORMAL, - A_bold = A_BOLD, - A_dim = A_DIM, - A_standout = A_STANDOUT, - A_reverse = A_REVERSE, + A_normal = A_NORMAL, + A_bold = A_BOLD, + A_dim = A_DIM, + A_standout = A_STANDOUT, + A_reverse = A_REVERSE, } Term_attr; static ORCA_FORCEINLINE ORCA_OK_IF_UNUSED attr_t fg_bg(Color_name fg, - Color_name bg) { - return COLOR_PAIR(1 + fg * Colors_count + bg); + Color_name bg) +{ + return COLOR_PAIR(1 + fg * Colors_count + bg); } void term_util_init_colors(void); typedef enum { - Qblock_type_qmsg, - Qblock_type_qmenu, - Qblock_type_qform, + Qblock_type_qmsg, + Qblock_type_qmenu, + Qblock_type_qform, } Qblock_type_tag; typedef struct Qblock { - Qblock_type_tag tag; - WINDOW *outer_window, *content_window; - char const *title; - struct Qblock *down, *up; - int y, x; + Qblock_type_tag tag; + WINDOW *outer_window, *content_window; + char const* title; + struct Qblock *down, *up; + int y, x; } Qblock; typedef struct { - Qblock *top, *bottom; - bool occlusion_dirty; + Qblock *top, *bottom; + bool occlusion_dirty; } Qnav_stack; typedef struct Qmsg Qmsg; @@ -74,95 +75,95 @@ typedef struct Qmsg Qmsg; typedef struct Qmenu Qmenu; typedef enum { - Qmenu_action_type_canceled, - Qmenu_action_type_picked, + Qmenu_action_type_canceled, + Qmenu_action_type_picked, } Qmenu_action_type; typedef struct { - Qmenu_action_type type; + Qmenu_action_type type; } Qmenu_action_any; typedef struct { - Qmenu_action_type type; - int id; + Qmenu_action_type type; + int id; } Qmenu_action_picked; typedef union { - Qmenu_action_any any; - Qmenu_action_picked picked; + Qmenu_action_any any; + Qmenu_action_picked picked; } Qmenu_action; typedef struct Qform Qform; typedef enum { - Qform_action_type_canceled, - Qform_action_type_submitted, + Qform_action_type_canceled, + Qform_action_type_submitted, } Qform_action_type; typedef struct { - Qform_action_type type; + Qform_action_type type; } Qform_action_any; typedef union { - Qform_action_any any; + Qform_action_any any; } Qform_action; typedef enum { - Qmsg_dismiss_mode_explicitly, // Space, return, escape dismisses. Default. - Qmsg_dismiss_mode_easily, // Any key dismisses. - Qmsg_dismiss_mode_passthrough, // Easily, and pass through key event. + Qmsg_dismiss_mode_explicitly, // Space, return, escape dismisses. Default. + Qmsg_dismiss_mode_easily, // Any key dismisses. + Qmsg_dismiss_mode_passthrough, // Easily, and pass through key event. } Qmsg_dismiss_mode; typedef struct { - bool dismiss : 1, passthrough : 1; + bool dismiss : 1, passthrough : 1; } Qmsg_action; void qnav_init(void); void qnav_deinit(void); -Qblock *qnav_top_block(void); +Qblock* qnav_top_block(void); void qnav_stack_pop(void); bool qnav_draw(void); // also clear qnav_stack.occlusion_dirty void qnav_adjust_term_size(void); -void qblock_print_frame(Qblock *qb, bool active); -void qblock_set_title(Qblock *qb, char const *title); +void qblock_print_frame(Qblock* qb, bool active); +void qblock_set_title(Qblock* qb, char const* title); -Qmsg *qmsg_push(int height, int width); -Qmsg *qmsg_printf_push(char const *title, char const *fmt, ...) +Qmsg* qmsg_push(int height, int width); +Qmsg* qmsg_printf_push(char const* title, char const* fmt, ...) ORCA_TERM_UTIL_PRINTF(2, 3); -WINDOW *qmsg_window(Qmsg *qm); -void qmsg_set_title(Qmsg *qm, char const *title); -void qmsg_set_dismiss_mode(Qmsg *qm, Qmsg_dismiss_mode mode); -bool qmsg_drive(Qmsg *qm, int key, Qmsg_action *out_action); -Qmsg *qmsg_of(Qblock *qb); +WINDOW* qmsg_window(Qmsg* qm); +void qmsg_set_title(Qmsg* qm, char const* title); +void qmsg_set_dismiss_mode(Qmsg* qm, Qmsg_dismiss_mode mode); +bool qmsg_drive(Qmsg* qm, int key, Qmsg_action* out_action); +Qmsg* qmsg_of(Qblock* qb); -Qmenu *qmenu_create(int id); +Qmenu* qmenu_create(int id); // Useful if menu creation needs to be aborted part-way. Otherwise, no need to // call -- pushing the qmenu to the qnav stack transfers ownership. (Still // working on this design, not sure yet.) -void qmenu_destroy(Qmenu *qm); -int qmenu_id(Qmenu const *qm); -void qmenu_set_title(Qmenu *qm, char const *title); -void qmenu_add_choice(Qmenu *qm, int id, char const *text); -void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) +void qmenu_destroy(Qmenu* qm); +int qmenu_id(Qmenu const* qm); +void qmenu_set_title(Qmenu* qm, char const* title); +void qmenu_add_choice(Qmenu* qm, int id, char const* text); +void qmenu_add_printf(Qmenu* qm, int id, char const* fmt, ...) ORCA_TERM_UTIL_PRINTF(3, 4); -void qmenu_add_spacer(Qmenu *qm); -void qmenu_set_current_item(Qmenu *qm, int id); -void qmenu_push_to_nav(Qmenu *qm); -int qmenu_current_item(Qmenu *qm); -bool qmenu_drive(Qmenu *qm, int key, Qmenu_action *out_action); -Qmenu *qmenu_of(Qblock *qb); +void qmenu_add_spacer(Qmenu* qm); +void qmenu_set_current_item(Qmenu* qm, int id); +void qmenu_push_to_nav(Qmenu* qm); +int qmenu_current_item(Qmenu* qm); +bool qmenu_drive(Qmenu* qm, int key, Qmenu_action* out_action); +Qmenu* qmenu_of(Qblock* qb); bool qmenu_top_is_menu(int id); -Qform *qform_create(int id); -int qform_id(Qform const *qf); -Qform *qform_of(Qblock *qb); -void qform_set_title(Qform *qf, char const *title); -void qform_add_line_input(Qform *qf, int id, char const *initial); -void qform_push_to_nav(Qform *qf); -void qform_single_line_input(int id, char const *title, char const* initial); -bool qform_drive(Qform *qf, int key, Qform_action *out_action); -bool qform_get_text_line(Qform const *qf, int id, struct oso **out); -bool qform_get_single_text_line(Qform const *qf, struct oso **out); -struct oso *qform_get_nonempty_single_line_input(Qform *qf); +Qform* qform_create(int id); +int qform_id(Qform const* qf); +Qform* qform_of(Qblock* qb); +void qform_set_title(Qform* qf, char const* title); +void qform_add_line_input(Qform* qf, int id, char const* initial); +void qform_push_to_nav(Qform* qf); +void qform_single_line_input(int id, char const* title, char const* initial); +bool qform_drive(Qform* qf, int key, Qform_action* out_action); +bool qform_get_text_line(Qform const* qf, int id, struct oso** out); +bool qform_get_single_text_line(Qform const* qf, struct oso** out); +struct oso* qform_get_nonempty_single_line_input(Qform* qf); extern Qnav_stack qnav_stack; diff --git a/thirdparty/oso.c b/thirdparty/oso.c index f4785dc..7beea63 100644 --- a/thirdparty/oso.c +++ b/thirdparty/oso.c @@ -18,212 +18,233 @@ #endif #define OSO_INTERNAL OSO_NOINLINE static -#define OSO_HDR(s) ((oso_header *)s - 1) +#define OSO_HDR(s) ((oso_header*)s - 1) #define OSO_CAP_MAX (SIZE_MAX - (sizeof(oso_header) + 1)) typedef struct oso { - size_t len, cap; + size_t len, cap; } oso_header; -OSO_INTERNAL oso *oso_impl_reallochdr(oso_header *hdr, size_t new_cap) { - if (hdr) { - oso_header *new_hdr = realloc(hdr, sizeof(oso_header) + new_cap + 1); - if (!new_hdr) { - free(hdr); - return NULL; +OSO_INTERNAL oso* oso_impl_reallochdr(oso_header* hdr, size_t new_cap) +{ + if (hdr) { + oso_header* new_hdr = realloc(hdr, sizeof(oso_header) + new_cap + 1); + if (!new_hdr) { + free(hdr); + return NULL; + } + new_hdr->cap = new_cap; + return new_hdr + 1; } - new_hdr->cap = new_cap; - return new_hdr + 1; - } - hdr = malloc(sizeof(oso_header) + new_cap + 1); - if (!hdr) - return NULL; - hdr->len = 0; - hdr->cap = new_cap; - ((char *)(hdr + 1))[0] = '\0'; - return hdr + 1; -} -OSO_INTERNAL oso *oso_impl_catvprintf(oso *s, char const *fmt, va_list ap) { - size_t old_len; - int required; - va_list cpy; - va_copy(cpy, ap); - required = vsnprintf(NULL, 0, fmt, cpy); - va_end(cpy); - osomakeroomfor(&s, (size_t)required); - if (!s) - return NULL; - old_len = OSO_HDR(s)->len; - vsnprintf((char *)s + old_len, (size_t)required + 1, fmt, ap); - OSO_HDR(s)->len = old_len + (size_t)required; - return s; + hdr = malloc(sizeof(oso_header) + new_cap + 1); + if (!hdr) + return NULL; + hdr->len = 0; + hdr->cap = new_cap; + ((char*)(hdr + 1))[0] = '\0'; + return hdr + 1; +} +OSO_INTERNAL oso* oso_impl_catvprintf(oso* s, char const* fmt, va_list ap) +{ + size_t old_len; + int required; + va_list cpy; + va_copy(cpy, ap); + required = vsnprintf(NULL, 0, fmt, cpy); + va_end(cpy); + osomakeroomfor(&s, (size_t)required); + if (!s) + return NULL; + old_len = OSO_HDR(s)->len; + vsnprintf((char*)s + old_len, (size_t)required + 1, fmt, ap); + OSO_HDR(s)->len = old_len + (size_t)required; + return s; } OSO_NOINLINE -void osoensurecap(oso **p, size_t new_cap) { - oso *s = *p; - if (new_cap > OSO_CAP_MAX) { +void osoensurecap(oso** p, size_t new_cap) +{ + oso* s = *p; + if (new_cap > OSO_CAP_MAX) { + if (s) { + free(OSO_HDR(s)); + *p = NULL; + } + return; + } + oso_header* hdr = NULL; if (s) { - free(OSO_HDR(s)); - *p = NULL; + hdr = OSO_HDR(s); + if (hdr->cap >= new_cap) + return; } - return; - } - oso_header *hdr = NULL; - if (s) { - hdr = OSO_HDR(s); - if (hdr->cap >= new_cap) - return; - } - *p = oso_impl_reallochdr(hdr, new_cap); + *p = oso_impl_reallochdr(hdr, new_cap); } OSO_NOINLINE -void osomakeroomfor(oso **p, size_t add_len) { - oso *s = *p; - oso_header *hdr = NULL; - size_t new_cap; - if (s) { - hdr = OSO_HDR(s); - size_t len = hdr->len, cap = hdr->cap; - if (len > OSO_CAP_MAX - add_len) { // overflow, goodnight - free(hdr); - *p = NULL; - return; +void osomakeroomfor(oso** p, size_t add_len) +{ + oso* s = *p; + oso_header* hdr = NULL; + size_t new_cap; + if (s) { + hdr = OSO_HDR(s); + size_t len = hdr->len, cap = hdr->cap; + if (len > OSO_CAP_MAX - add_len) { // overflow, goodnight + free(hdr); + *p = NULL; + return; + } + new_cap = len + add_len; + if (cap >= new_cap) + return; + } else { + if (add_len > OSO_CAP_MAX) + return; + new_cap = add_len; } - new_cap = len + add_len; - if (cap >= new_cap) - return; - } else { - if (add_len > OSO_CAP_MAX) - return; - new_cap = add_len; - } - *p = oso_impl_reallochdr(hdr, new_cap); + *p = oso_impl_reallochdr(hdr, new_cap); } -void osoput(oso **p, char const *restrict cstr) { - osoputlen(p, cstr, strlen(cstr)); +void osoput(oso** p, char const* restrict cstr) +{ + osoputlen(p, cstr, strlen(cstr)); } OSO_NOINLINE -void osoputlen(oso **p, char const *restrict cstr, size_t len) { - oso *s = *p; - osoensurecap(&s, len); - if (s) { - OSO_HDR(s)->len = len; - memcpy((char *)s, cstr, len); - ((char *)s)[len] = '\0'; - } - *p = s; -} -void osoputoso(oso **p, oso const *other) { - if (!other) - return; - osoputlen(p, (char const *)other, OSO_HDR(other)->len); -} -void osoputvprintf(oso **p, char const *fmt, va_list ap) { - oso *s = *p; - if (s) { - OSO_HDR(s)->len = 0; - ((char *)s)[0] = '\0'; - } - *p = oso_impl_catvprintf(s, fmt, ap); +void osoputlen(oso** p, char const* restrict cstr, size_t len) +{ + oso* s = *p; + osoensurecap(&s, len); + if (s) { + OSO_HDR(s)->len = len; + memcpy((char*)s, cstr, len); + ((char*)s)[len] = '\0'; + } + *p = s; } -void osoputprintf(oso **p, char const *fmt, ...) { - oso *s = *p; - if (s) { - OSO_HDR(s)->len = 0; - ((char *)s)[0] = '\0'; - } - va_list ap; - va_start(ap, fmt); - *p = oso_impl_catvprintf(s, fmt, ap); - va_end(ap); -} -void osocat(oso **p, char const *cstr) { osocatlen(p, cstr, strlen(cstr)); } +void osoputoso(oso** p, oso const* other) +{ + if (!other) + return; + osoputlen(p, (char const*)other, OSO_HDR(other)->len); +} +void osoputvprintf(oso** p, char const* fmt, va_list ap) +{ + oso* s = *p; + if (s) { + OSO_HDR(s)->len = 0; + ((char*)s)[0] = '\0'; + } + *p = oso_impl_catvprintf(s, fmt, ap); +} +void osoputprintf(oso** p, char const* fmt, ...) +{ + oso* s = *p; + if (s) { + OSO_HDR(s)->len = 0; + ((char*)s)[0] = '\0'; + } + va_list ap; + va_start(ap, fmt); + *p = oso_impl_catvprintf(s, fmt, ap); + va_end(ap); +} +void osocat(oso** p, char const* cstr) { osocatlen(p, cstr, strlen(cstr)); } OSO_NOINLINE -void osocatlen(oso **p, char const *cstr, size_t len) { - oso *s = *p; - osomakeroomfor(&s, len); - if (s) { - oso_header *hdr = OSO_HDR(s); - size_t curr_len = hdr->len; - memcpy((char *)s + curr_len, cstr, len); - ((char *)s)[curr_len + len] = '\0'; - hdr->len = curr_len + len; - } - *p = s; -} -void osocatoso(oso **p, oso const *other) { - if (!other) - return; - osocatlen(p, (char const *)other, OSO_HDR(other)->len); -} -void osocatvprintf(oso **p, char const *fmt, va_list ap) { - *p = oso_impl_catvprintf(*p, fmt, ap); -} -void osocatprintf(oso **p, char const *fmt, ...) { - va_list ap; - va_start(ap, fmt); - *p = oso_impl_catvprintf(*p, fmt, ap); - va_end(ap); -} -void osoclear(oso **p) { - oso *s = *p; - if (!s) - return; - OSO_HDR(s)->len = 0; - ((char *)s)[0] = '\0'; -} -void osofree(oso *s) { - if (s) - free(OSO_HDR(s)); -} -void osowipe(oso **p) { - osofree(*p); - *p = NULL; -} -void ososwap(oso **a, oso **b) { - oso *tmp = *a; - *a = *b; - *b = tmp; -} -void osopokelen(oso *s, size_t len) { OSO_HDR(s)->len = len; } -size_t osolen(oso const *s) { return s ? OSO_HDR(s)->len : 0; } -size_t osocap(oso const *s) { return s ? OSO_HDR(s)->cap : 0; } -void osolencap(oso const *s, size_t *out_len, size_t *out_cap) { - if (!s) { - *out_len = 0; - *out_cap = 0; - return; - } - oso_header *hdr = OSO_HDR(s); - *out_len = hdr->len; - *out_cap = hdr->cap; -} -size_t osoavail(oso const *s) { - if (!s) - return 0; - oso_header *h = OSO_HDR(s); - return h->cap - h->len; +void osocatlen(oso** p, char const* cstr, size_t len) +{ + oso* s = *p; + osomakeroomfor(&s, len); + if (s) { + oso_header* hdr = OSO_HDR(s); + size_t curr_len = hdr->len; + memcpy((char*)s + curr_len, cstr, len); + ((char*)s)[curr_len + len] = '\0'; + hdr->len = curr_len + len; + } + *p = s; +} +void osocatoso(oso** p, oso const* other) +{ + if (!other) + return; + osocatlen(p, (char const*)other, OSO_HDR(other)->len); +} +void osocatvprintf(oso** p, char const* fmt, va_list ap) +{ + *p = oso_impl_catvprintf(*p, fmt, ap); +} +void osocatprintf(oso** p, char const* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + *p = oso_impl_catvprintf(*p, fmt, ap); + va_end(ap); +} +void osoclear(oso** p) +{ + oso* s = *p; + if (!s) + return; + OSO_HDR(s)->len = 0; + ((char*)s)[0] = '\0'; +} +void osofree(oso* s) +{ + if (s) + free(OSO_HDR(s)); +} +void osowipe(oso** p) +{ + osofree(*p); + *p = NULL; +} +void ososwap(oso** a, oso** b) +{ + oso* tmp = *a; + *a = *b; + *b = tmp; +} +void osopokelen(oso* s, size_t len) { OSO_HDR(s)->len = len; } +size_t osolen(oso const* s) { return s ? OSO_HDR(s)->len : 0; } +size_t osocap(oso const* s) { return s ? OSO_HDR(s)->cap : 0; } +void osolencap(oso const* s, size_t* out_len, size_t* out_cap) +{ + if (!s) { + *out_len = 0; + *out_cap = 0; + return; + } + oso_header* hdr = OSO_HDR(s); + *out_len = hdr->len; + *out_cap = hdr->cap; +} +size_t osoavail(oso const* s) +{ + if (!s) + return 0; + oso_header* h = OSO_HDR(s); + return h->cap - h->len; } -void osotrim(oso *restrict s, char const *restrict cut_set) { - if (!s) - return; - char *str, *end, *start_pos, *end_pos; - start_pos = str = (char *)s; - end_pos = end = str + OSO_HDR(s)->len - 1; - while (start_pos <= end && strchr(cut_set, *start_pos)) - start_pos++; - while (end_pos > start_pos && strchr(cut_set, *end_pos)) - end_pos--; - size_t len = (start_pos > end_pos) ? 0 : ((size_t)(end_pos - start_pos) + 1); - OSO_HDR(s)->len = len; - if (str != start_pos) - memmove(str, start_pos, len); - str[len] = '\0'; +void osotrim(oso* restrict s, char const* restrict cut_set) +{ + if (!s) + return; + char *str, *end, *start_pos, *end_pos; + start_pos = str = (char*)s; + end_pos = end = str + OSO_HDR(s)->len - 1; + while (start_pos <= end && strchr(cut_set, *start_pos)) + start_pos++; + while (end_pos > start_pos && strchr(cut_set, *end_pos)) + end_pos--; + size_t len = (start_pos > end_pos) ? 0 + : ((size_t)(end_pos - start_pos) + 1); + OSO_HDR(s)->len = len; + if (str != start_pos) + memmove(str, start_pos, len); + str[len] = '\0'; } #undef OSO_HDR diff --git a/thirdparty/oso.h b/thirdparty/oso.h index 9a59fd6..aa5b9a8 100644 --- a/thirdparty/oso.h +++ b/thirdparty/oso.h @@ -108,40 +108,40 @@ typedef struct oso oso; -#define osoc(s) ((char const *)s) +#define osoc(s) ((char const*)s) -void osoput(oso **p, char const *cstr) OSO_NONNULL(); +void osoput(oso** p, char const* cstr) OSO_NONNULL(); // ^- Copies the '\0'-terminated string into the `oso *` string located at // `*p`. If `*p` is null or there isn't enough capacity to hold `cstr`, it // will be reallocated. The pointer value at `*p` will be updated if // necessary. `*p` and `cstr` must not point to overlapping memory. -void osoputlen(oso **p, char const *cstr, size_t len) OSO_NONNULL(); +void osoputlen(oso** p, char const* cstr, size_t len) OSO_NONNULL(); // ^- Same as above, but uses an additional parameter that specifies the length // of `cstr, and `cstr` does not have to be '\0'-terminated. // `*p` and `cstr` must not point to overlapping memory. -void osoputoso(oso **p, oso const *other) OSO_NONNULL(1); +void osoputoso(oso** p, oso const* other) OSO_NONNULL(1); // ^- Same as above, but using another `oso`. `*p` and `other` must not point // to overlapping memory. -void osoputvprintf(oso **p, char const *fmt, va_list ap) OSO_NONNULL(1, 2) +void osoputvprintf(oso** p, char const* fmt, va_list ap) OSO_NONNULL(1, 2) OSO_PRINTF(2, 0); -void osoputprintf(oso **p, char const *fmt, ...) OSO_NONNULL(1, 2) +void osoputprintf(oso** p, char const* fmt, ...) OSO_NONNULL(1, 2) OSO_PRINTF(2, 3); // ^- Same as above, but do it by using printf. -void osocat(oso **p, char const *cstr) OSO_NONNULL(); -void osocatlen(oso **p, char const *cstr, size_t len) OSO_NONNULL(); -void osocatoso(oso **p, oso const *other) OSO_NONNULL(1); -void osocatvprintf(oso **p, char const *fmt, va_list ap) OSO_NONNULL(1, 2) +void osocat(oso** p, char const* cstr) OSO_NONNULL(); +void osocatlen(oso** p, char const* cstr, size_t len) OSO_NONNULL(); +void osocatoso(oso** p, oso const* other) OSO_NONNULL(1); +void osocatvprintf(oso** p, char const* fmt, va_list ap) OSO_NONNULL(1, 2) OSO_PRINTF(2, 0); -void osocatprintf(oso **p, char const *fmt, ...) OSO_NONNULL(1, 2) +void osocatprintf(oso** p, char const* fmt, ...) OSO_NONNULL(1, 2) OSO_PRINTF(2, 3); // ^- Append string to oso string. Same rules as `osoput` family. -void osoensurecap(oso **p, size_t cap) OSO_NONNULL(); +void osoensurecap(oso** p, size_t cap) OSO_NONNULL(); // ^- Ensure that s has at least `cap` memory allocated for it. This does not // care about the strlen of the characters or the prefixed length count -- // only the backing memory allocation. -void osomakeroomfor(oso **p, size_t len) OSO_NONNULL(); +void osomakeroomfor(oso** p, size_t len) OSO_NONNULL(); // ^- Ensure that s has enough allocated space after the '\0'-terminnation // character to hold an additional add_len characters. It does not adjust // the `length` number value, only the capacity, if necessary. @@ -151,31 +151,31 @@ void osomakeroomfor(oso **p, size_t len) OSO_NONNULL(); // also use them if you're going to modify the string buffer manually in // your own code, and need to create some space in the buffer. -void osoclear(oso **p) OSO_NONNULL(); +void osoclear(oso** p) OSO_NONNULL(); // ^- Set len to 0, write '\0' at pos 0. Leaves allocated memory in place. -void osofree(oso *s); +void osofree(oso* s); // ^- You know. And calling with null is allowed. -void osowipe(oso **p) OSO_NONNULL(); +void osowipe(oso** p) OSO_NONNULL(); // ^- It's like `osofree()`, except you give it a ptr-to-ptr, and it also sets // `*p` to null for you when it's done freeing the memory. -void ososwap(oso **a, oso **b) OSO_NONNULL(); +void ososwap(oso** a, oso** b) OSO_NONNULL(); // ^- Swaps the two pointers. Yeah, that's all it does. Why? Because it's // common when dealing memory management for individually allocated strings // and changing between old and new string values. -void osopokelen(oso *s, size_t len) OSO_NONNULL(); +void osopokelen(oso* s, size_t len) OSO_NONNULL(); // ^- Manually updates length field. Doesn't do anything else for you. -size_t osolen(oso const *s); +size_t osolen(oso const* s); // ^- Bytes in use by the string (not including the '\0' character.) -size_t osocap(oso const *s); +size_t osocap(oso const* s); // ^- Bytes allocated on heap (not including the '\0' terminator.) -void osolencap(oso const *s, size_t *out_len, size_t *out_cap) +void osolencap(oso const* s, size_t* out_len, size_t* out_cap) OSO_NONNULL(2, 3); // ^- Get both the len and the cap in one call. -size_t osoavail(oso const *s); +size_t osoavail(oso const* s); // ^- osocap(s) - osolen(s) -void osotrim(oso *restrict s, char const *restrict cut_set) OSO_NONNULL(2); +void osotrim(oso* restrict s, char const* restrict cut_set) OSO_NONNULL(2); // ^- Remove the characters in `cut_set` from the beginning and ending of `s`. #undef OSO_PRINTF diff --git a/thirdparty/sokol_time.h b/thirdparty/sokol_time.h index cacfb13..b659f6e 100644 --- a/thirdparty/sokol_time.h +++ b/thirdparty/sokol_time.h @@ -61,16 +61,16 @@ Copyright (c) 2018 Andre Weissflog This software is provided 'as-is', without any express or implied warranty. - In no event will the authors be held liable for any damages arising from the - use of this software. + In no event will the authors be held liable for any damages arising from + the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software in a - product, an acknowledgment in the product documentation would be + claim that you wrote the original software. If you use this software in + a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not @@ -82,7 +82,7 @@ #include #ifndef SOKOL_API_DECL - #define SOKOL_API_DECL extern +#define SOKOL_API_DECL extern #endif #ifdef __cplusplus @@ -103,22 +103,23 @@ SOKOL_API_DECL double stm_ns(uint64_t ticks); } /* extern "C" */ #endif -/*-- IMPLEMENTATION ----------------------------------------------------------*/ +/*-- IMPLEMENTATION + * ----------------------------------------------------------*/ #ifdef SOKOL_IMPL #ifndef SOKOL_API_IMPL - #define SOKOL_API_IMPL +#define SOKOL_API_IMPL #endif #ifndef SOKOL_ASSERT - #include - #define SOKOL_ASSERT(c) assert(c) +#include +#define SOKOL_ASSERT(c) assert(c) #endif #ifndef _SOKOL_PRIVATE - #if defined(__GNUC__) - #define _SOKOL_PRIVATE __attribute__((unused)) static - #else - #define _SOKOL_PRIVATE static - #endif +#if defined(__GNUC__) +#define _SOKOL_PRIVATE __attribute__((unused)) static +#else +#define _SOKOL_PRIVATE static +#endif #endif static int _stm_initialized; @@ -142,7 +143,9 @@ static uint64_t _stm_posix_start; see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 */ #if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) -_SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { +_SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, + int64_t denom) +{ int64_t q = value / denom; int64_t r = value % denom; return q * numer + r * numer / denom; @@ -150,55 +153,62 @@ _SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) #endif -SOKOL_API_IMPL void stm_setup(void) { +SOKOL_API_IMPL void stm_setup(void) +{ SOKOL_ASSERT(0 == _stm_initialized); _stm_initialized = 1; - #if defined(_WIN32) - QueryPerformanceFrequency(&_stm_win_freq); - QueryPerformanceCounter(&_stm_win_start); - #elif defined(__APPLE__) && defined(__MACH__) - mach_timebase_info(&_stm_osx_timebase); - _stm_osx_start = mach_absolute_time(); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - _stm_posix_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; - #endif +#if defined(_WIN32) + QueryPerformanceFrequency(&_stm_win_freq); + QueryPerformanceCounter(&_stm_win_start); +#elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_stm_osx_timebase); + _stm_osx_start = mach_absolute_time(); +#else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _stm_posix_start = (uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec; +#endif } -SOKOL_API_IMPL uint64_t stm_now(void) { +SOKOL_API_IMPL uint64_t stm_now(void) +{ SOKOL_ASSERT(_stm_initialized); uint64_t now; - #if defined(_WIN32) - LARGE_INTEGER qpc_t; - QueryPerformanceCounter(&qpc_t); - now = int64_muldiv(qpc_t.QuadPart - _stm_win_start.QuadPart, 1000000000, _stm_win_freq.QuadPart); - #elif defined(__APPLE__) && defined(__MACH__) - const uint64_t mach_now = mach_absolute_time() - _stm_osx_start; - now = int64_muldiv(mach_now, _stm_osx_timebase.numer, _stm_osx_timebase.denom); - #else - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm_posix_start; - #endif +#if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = int64_muldiv(qpc_t.QuadPart - _stm_win_start.QuadPart, 1000000000, + _stm_win_freq.QuadPart); +#elif defined(__APPLE__) && defined(__MACH__) + const uint64_t mach_now = mach_absolute_time() - _stm_osx_start; + now = int64_muldiv(mach_now, _stm_osx_timebase.numer, + _stm_osx_timebase.denom); +#else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec) + - _stm_posix_start; +#endif return now; } -SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { +SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) +{ if (new_ticks > old_ticks) { return new_ticks - old_ticks; - } - else { + } else { /* FIXME: this should be a value that converts to a non-null double */ return 1; } } -SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { +SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) +{ return stm_diff(stm_now(), start_ticks); } -SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { +SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) +{ SOKOL_ASSERT(last_time); uint64_t dt = 0; uint64_t now = stm_now(); @@ -209,20 +219,17 @@ SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { return dt; } -SOKOL_API_IMPL double stm_sec(uint64_t ticks) { +SOKOL_API_IMPL double stm_sec(uint64_t ticks) +{ return (double)ticks / 1000000000.0; } -SOKOL_API_IMPL double stm_ms(uint64_t ticks) { +SOKOL_API_IMPL double stm_ms(uint64_t ticks) +{ return (double)ticks / 1000000.0; } -SOKOL_API_IMPL double stm_us(uint64_t ticks) { - return (double)ticks / 1000.0; -} +SOKOL_API_IMPL double stm_us(uint64_t ticks) { return (double)ticks / 1000.0; } -SOKOL_API_IMPL double stm_ns(uint64_t ticks) { - return (double)ticks; -} +SOKOL_API_IMPL double stm_ns(uint64_t ticks) { return (double)ticks; } #endif /* SOKOL_IMPL */ - diff --git a/tui_main.c b/tui_main.c index 8848ff3..70b1c71 100644 --- a/tui_main.c +++ b/tui_main.c @@ -30,7 +30,8 @@ static int spin_track_timeout = 0; #define staticni ORCA_NOINLINE static -staticni void usage(void) { // clang-format off +staticni void usage(void) +{ // clang-format off fprintf(stderr, "Usage: orca [options] [file]\n\n" "General options:\n" @@ -58,375 +59,387 @@ fprintf(stderr, );} // clang-format on typedef enum { - Glyph_class_unknown, - Glyph_class_grid, - Glyph_class_comment, - Glyph_class_uppercase, - Glyph_class_lowercase, - Glyph_class_movement, - Glyph_class_numeric, - Glyph_class_bang, + Glyph_class_unknown, + Glyph_class_grid, + Glyph_class_comment, + Glyph_class_uppercase, + Glyph_class_lowercase, + Glyph_class_movement, + Glyph_class_numeric, + Glyph_class_bang, } Glyph_class; -static Glyph_class glyph_class_of(Glyph glyph) { - if (glyph == '.') - return Glyph_class_grid; - if (glyph >= '0' && glyph <= '9') - return Glyph_class_numeric; - switch (glyph) { - case 'N': - case 'n': - case 'E': - case 'e': - case 'S': - case 's': - case 'W': - case 'w': - return Glyph_class_movement; - case '!': - case ':': - case ';': - case '=': - case '%': - case '?': - return Glyph_class_lowercase; - case '*': - return Glyph_class_bang; - case '#': - return Glyph_class_comment; - } - if (glyph >= 'A' && glyph <= 'Z') - return Glyph_class_uppercase; - if (glyph >= 'a' && glyph <= 'z') - return Glyph_class_lowercase; - return Glyph_class_unknown; -} - -static attr_t term_attrs_of_cell(Glyph g, Mark m) { - Glyph_class gclass = glyph_class_of(g); - attr_t attr = A_normal; - switch (gclass) { - case Glyph_class_unknown: - attr = A_bold | fg_bg(C_red, C_natural); - break; - case Glyph_class_grid: - attr = A_bold | fg_bg(C_black, C_natural); - break; - case Glyph_class_comment: - attr = A_dim | Cdef_normal; - break; - case Glyph_class_uppercase: - attr = A_normal | fg_bg(C_black, C_cyan); - break; - case Glyph_class_lowercase: - case Glyph_class_movement: - case Glyph_class_numeric: - attr = A_bold | Cdef_normal; - break; - case Glyph_class_bang: - attr = A_bold | Cdef_normal; - break; - } - if (gclass != Glyph_class_comment) { - if ((m & (Mark_flag_lock | Mark_flag_input)) == - (Mark_flag_lock | Mark_flag_input)) { - // Standard locking input - attr = A_normal | Cdef_normal; - } else if ((m & Mark_flag_input) == Mark_flag_input) { - // Non-locking input - attr = A_normal | Cdef_normal; - } else if (m & Mark_flag_lock) { - // Locked only - attr = A_dim | Cdef_normal; - } - } - if (m & Mark_flag_output) { - attr = A_reverse; - } - if (m & Mark_flag_haste_input) { - attr = A_bold | fg_bg(C_cyan, C_natural); - } - return attr; +static Glyph_class glyph_class_of(Glyph glyph) +{ + if (glyph == '.') + return Glyph_class_grid; + if (glyph >= '0' && glyph <= '9') + return Glyph_class_numeric; + switch (glyph) { + case 'N': + case 'n': + case 'E': + case 'e': + case 'S': + case 's': + case 'W': + case 'w': + return Glyph_class_movement; + case '!': + case ':': + case ';': + case '=': + case '%': + case '?': + return Glyph_class_lowercase; + case '*': + return Glyph_class_bang; + case '#': + return Glyph_class_comment; + } + if (glyph >= 'A' && glyph <= 'Z') + return Glyph_class_uppercase; + if (glyph >= 'a' && glyph <= 'z') + return Glyph_class_lowercase; + return Glyph_class_unknown; +} + +static attr_t term_attrs_of_cell(Glyph g, Mark m) +{ + Glyph_class gclass = glyph_class_of(g); + attr_t attr = A_normal; + switch (gclass) { + case Glyph_class_unknown: + attr = A_bold | fg_bg(C_red, C_natural); + break; + case Glyph_class_grid: + attr = A_bold | fg_bg(C_black, C_natural); + break; + case Glyph_class_comment: + attr = A_dim | Cdef_normal; + break; + case Glyph_class_uppercase: + attr = A_normal | fg_bg(C_black, C_cyan); + break; + case Glyph_class_lowercase: + case Glyph_class_movement: + case Glyph_class_numeric: + attr = A_bold | Cdef_normal; + break; + case Glyph_class_bang: + attr = A_bold | Cdef_normal; + break; + } + if (gclass != Glyph_class_comment) { + if ((m & (Mark_flag_lock | Mark_flag_input)) + == (Mark_flag_lock | Mark_flag_input)) { + // Standard locking input + attr = A_normal | Cdef_normal; + } else if ((m & Mark_flag_input) == Mark_flag_input) { + // Non-locking input + attr = A_normal | Cdef_normal; + } else if (m & Mark_flag_lock) { + // Locked only + attr = A_dim | Cdef_normal; + } + } + if (m & Mark_flag_output) { + attr = A_reverse; + } + if (m & Mark_flag_haste_input) { + attr = A_bold | fg_bg(C_cyan, C_natural); + } + return attr; } typedef enum { - Ged_input_mode_normal = 0, - Ged_input_mode_append, - Ged_input_mode_selresize, - Ged_input_mode_slide, + Ged_input_mode_normal = 0, + Ged_input_mode_append, + Ged_input_mode_selresize, + Ged_input_mode_slide, } Ged_input_mode; typedef struct { - Usz y, x, h, w; + Usz y, x, h, w; } Ged_cursor; -void ged_cursor_init(Ged_cursor *tc) { - tc->x = tc->y = 0; - tc->w = tc->h = 1; -} - -static void ged_cursor_move_relative(Ged_cursor *tc, Usz field_h, Usz field_w, - Isz delta_y, Isz delta_x) { - Isz y0 = (Isz)tc->y + delta_y; - Isz x0 = (Isz)tc->x + delta_x; - if (y0 >= (Isz)field_h) - y0 = (Isz)field_h - 1; - if (y0 < 0) - y0 = 0; - if (x0 >= (Isz)field_w) - x0 = (Isz)field_w - 1; - if (x0 < 0) - x0 = 0; - tc->y = (Usz)y0; - tc->x = (Usz)x0; -} - -staticni void draw_grid_cursor(WINDOW *win, int draw_y, int draw_x, int draw_h, - int draw_w, Glyph const *gbuffer, Usz field_h, +void ged_cursor_init(Ged_cursor* tc) +{ + tc->x = tc->y = 0; + tc->w = tc->h = 1; +} + +static void ged_cursor_move_relative(Ged_cursor* tc, Usz field_h, Usz field_w, + Isz delta_y, Isz delta_x) +{ + Isz y0 = (Isz)tc->y + delta_y; + Isz x0 = (Isz)tc->x + delta_x; + if (y0 >= (Isz)field_h) + y0 = (Isz)field_h - 1; + if (y0 < 0) + y0 = 0; + if (x0 >= (Isz)field_w) + x0 = (Isz)field_w - 1; + if (x0 < 0) + x0 = 0; + tc->y = (Usz)y0; + tc->x = (Usz)x0; +} + +staticni void draw_grid_cursor(WINDOW* win, int draw_y, int draw_x, int draw_h, + int draw_w, Glyph const* gbuffer, Usz field_h, Usz field_w, int scroll_y, int scroll_x, Usz cursor_y, Usz cursor_x, Usz cursor_h, Usz cursor_w, Ged_input_mode input_mode, - bool is_playing) { - (void)input_mode; - if (cursor_y >= field_h || cursor_x >= field_w) - return; - if (scroll_y < 0) { - draw_y += -scroll_y; - scroll_y = 0; - } - if (scroll_x < 0) { - draw_x += -scroll_x; - scroll_x = 0; - } - Usz offset_y = (Usz)scroll_y; - Usz offset_x = (Usz)scroll_x; - if (offset_y >= field_h || offset_x >= field_w) - return; - if (draw_y >= draw_h || draw_x >= draw_w) - return; - attr_t const curs_attr = A_reverse | A_bold | fg_bg(C_yellow, C_natural); - if (offset_y <= cursor_y && offset_x <= cursor_x) { - Usz cdraw_y = cursor_y - offset_y + (Usz)draw_y; - Usz cdraw_x = cursor_x - offset_x + (Usz)draw_x; - if (cdraw_y < (Usz)draw_h && cdraw_x < (Usz)draw_w) { - Glyph beneath = gbuffer[cursor_y * field_w + cursor_x]; - char displayed; - if (beneath == '.') { - displayed = is_playing ? '@' : '~'; - } else { - displayed = beneath; - } - chtype ch = (chtype)displayed | curs_attr; - wmove(win, (int)cdraw_y, (int)cdraw_x); - waddchnstr(win, &ch, 1); - } - } - - // Early out for selection area that won't have any visual effect - if (cursor_h <= 1 && cursor_w <= 1) - return; + bool is_playing) +{ + (void)input_mode; + if (cursor_y >= field_h || cursor_x >= field_w) + return; + if (scroll_y < 0) { + draw_y += -scroll_y; + scroll_y = 0; + } + if (scroll_x < 0) { + draw_x += -scroll_x; + scroll_x = 0; + } + Usz offset_y = (Usz)scroll_y; + Usz offset_x = (Usz)scroll_x; + if (offset_y >= field_h || offset_x >= field_w) + return; + if (draw_y >= draw_h || draw_x >= draw_w) + return; + attr_t const curs_attr = A_reverse | A_bold | fg_bg(C_yellow, C_natural); + if (offset_y <= cursor_y && offset_x <= cursor_x) { + Usz cdraw_y = cursor_y - offset_y + (Usz)draw_y; + Usz cdraw_x = cursor_x - offset_x + (Usz)draw_x; + if (cdraw_y < (Usz)draw_h && cdraw_x < (Usz)draw_w) { + Glyph beneath = gbuffer[cursor_y * field_w + cursor_x]; + char displayed; + if (beneath == '.') { + displayed = is_playing ? '@' : '~'; + } else { + displayed = beneath; + } + chtype ch = (chtype)displayed | curs_attr; + wmove(win, (int)cdraw_y, (int)cdraw_x); + waddchnstr(win, &ch, 1); + } + } - // Now mutate visually selected area under grid to have the selection color - // attributes. (This will rewrite the attributes on the cursor character we - // wrote above, but if it was the only character that would have been - // changed, we already early-outed.) - // - // We'll do this by reading back the characters on the grid from the curses - // window buffer, changing the attributes, then writing it back. This is - // easier than pulling the glyphs from the gbuffer, since we already did the - // ruler calculations to turn . into +, and we don't need special behavior - // for any other attributes (e.g. we don't show a special state for selected - // uppercase characters.) - // - // First, confine cursor selection to the grid field/gbuffer that actually - // exists, in case the cursor selection exceeds the area of the field. - Usz sel_rows = field_h - cursor_y; - if (cursor_h < sel_rows) - sel_rows = cursor_h; - Usz sel_cols = field_w - cursor_x; - if (cursor_w < sel_cols) - sel_cols = cursor_w; - // Now, confine the selection area to what's visible on screen. Kind of - // tricky since we have to handle it being partially visible from any edge on - // any axis, and we have to be mindful overflow. - Usz vis_sel_y; - Usz vis_sel_x; - if (offset_y > cursor_y) { - vis_sel_y = 0; - Usz sub_y = offset_y - cursor_y; - if (sub_y > sel_rows) - sel_rows = 0; - else - sel_rows -= sub_y; - } else { - vis_sel_y = cursor_y - offset_y; - } - if (offset_x > cursor_x) { - vis_sel_x = 0; - Usz sub_x = offset_x - cursor_x; - if (sub_x > sel_cols) - sel_cols = 0; - else - sel_cols -= sub_x; - } else { - vis_sel_x = cursor_x - offset_x; - } - vis_sel_y += (Usz)draw_y; - vis_sel_x += (Usz)draw_x; - if (vis_sel_y >= (Usz)draw_h || vis_sel_x >= (Usz)draw_w) - return; - Usz vis_sel_h = (Usz)draw_h - vis_sel_y; - Usz vis_sel_w = (Usz)draw_w - vis_sel_x; - if (sel_rows < vis_sel_h) - vis_sel_h = sel_rows; - if (sel_cols < vis_sel_w) - vis_sel_w = sel_cols; - if (vis_sel_w == 0 || vis_sel_h == 0) - return; - enum { Bufcount = 4096 }; - chtype chbuffer[Bufcount]; - if (Bufcount < vis_sel_w) - vis_sel_w = Bufcount; - for (Usz iy = 0; iy < vis_sel_h; ++iy) { - int at_y = (int)(vis_sel_y + iy); - int num = mvwinchnstr(win, at_y, (int)vis_sel_x, chbuffer, (int)vis_sel_w); - for (int ix = 0; ix < num; ++ix) { - chbuffer[ix] = (chtype)((chbuffer[ix] & (A_CHARTEXT | A_ALTCHARSET)) | - (chtype)curs_attr); + // Early out for selection area that won't have any visual effect + if (cursor_h <= 1 && cursor_w <= 1) + return; + + // Now mutate visually selected area under grid to have the selection color + // attributes. (This will rewrite the attributes on the cursor character we + // wrote above, but if it was the only character that would have been + // changed, we already early-outed.) + // + // We'll do this by reading back the characters on the grid from the curses + // window buffer, changing the attributes, then writing it back. This is + // easier than pulling the glyphs from the gbuffer, since we already did + // the ruler calculations to turn . into +, and we don't need special + // behavior for any other attributes (e.g. we don't show a special state + // for selected uppercase characters.) + // + // First, confine cursor selection to the grid field/gbuffer that actually + // exists, in case the cursor selection exceeds the area of the field. + Usz sel_rows = field_h - cursor_y; + if (cursor_h < sel_rows) + sel_rows = cursor_h; + Usz sel_cols = field_w - cursor_x; + if (cursor_w < sel_cols) + sel_cols = cursor_w; + // Now, confine the selection area to what's visible on screen. Kind of + // tricky since we have to handle it being partially visible from any edge + // on any axis, and we have to be mindful overflow. + Usz vis_sel_y; + Usz vis_sel_x; + if (offset_y > cursor_y) { + vis_sel_y = 0; + Usz sub_y = offset_y - cursor_y; + if (sub_y > sel_rows) + sel_rows = 0; + else + sel_rows -= sub_y; + } else { + vis_sel_y = cursor_y - offset_y; + } + if (offset_x > cursor_x) { + vis_sel_x = 0; + Usz sub_x = offset_x - cursor_x; + if (sub_x > sel_cols) + sel_cols = 0; + else + sel_cols -= sub_x; + } else { + vis_sel_x = cursor_x - offset_x; + } + vis_sel_y += (Usz)draw_y; + vis_sel_x += (Usz)draw_x; + if (vis_sel_y >= (Usz)draw_h || vis_sel_x >= (Usz)draw_w) + return; + Usz vis_sel_h = (Usz)draw_h - vis_sel_y; + Usz vis_sel_w = (Usz)draw_w - vis_sel_x; + if (sel_rows < vis_sel_h) + vis_sel_h = sel_rows; + if (sel_cols < vis_sel_w) + vis_sel_w = sel_cols; + if (vis_sel_w == 0 || vis_sel_h == 0) + return; + enum { Bufcount = 4096 }; + chtype chbuffer[Bufcount]; + if (Bufcount < vis_sel_w) + vis_sel_w = Bufcount; + for (Usz iy = 0; iy < vis_sel_h; ++iy) { + int at_y = (int)(vis_sel_y + iy); + int num = mvwinchnstr(win, at_y, (int)vis_sel_x, chbuffer, + (int)vis_sel_w); + for (int ix = 0; ix < num; ++ix) { + chbuffer[ix] = (chtype)((chbuffer[ix] + & (A_CHARTEXT | A_ALTCHARSET)) + | (chtype)curs_attr); + } + waddchnstr(win, chbuffer, (int)num); } - waddchnstr(win, chbuffer, (int)num); - } } typedef struct Undo_node { - Field field; - Usz tick_num; - struct Undo_node *prev, *next; + Field field; + Usz tick_num; + struct Undo_node *prev, *next; } Undo_node; typedef struct { - Undo_node *first, *last; - Usz count, limit; + Undo_node *first, *last; + Usz count, limit; } Undo_history; -static void undo_history_init(Undo_history *hist, Usz limit) { - *hist = (Undo_history){0}; - hist->limit = limit; -} -static void undo_history_deinit(Undo_history *hist) { - Undo_node *a = hist->first; - while (a) { - Undo_node *b = a->next; - field_deinit(&a->field); - free(a); - a = b; - } -} - -staticni bool undo_history_push(Undo_history *hist, Field *field, - Usz tick_num) { - if (hist->limit == 0) - return false; - Undo_node *new_node; - if (hist->count == hist->limit) { - new_node = hist->first; - if (new_node == hist->last) { - hist->first = NULL; - hist->last = NULL; +static void undo_history_init(Undo_history* hist, Usz limit) +{ + *hist = (Undo_history) { 0 }; + hist->limit = limit; +} +static void undo_history_deinit(Undo_history* hist) +{ + Undo_node* a = hist->first; + while (a) { + Undo_node* b = a->next; + field_deinit(&a->field); + free(a); + a = b; + } +} + +staticni bool undo_history_push(Undo_history* hist, Field* field, Usz tick_num) +{ + if (hist->limit == 0) + return false; + Undo_node* new_node; + if (hist->count == hist->limit) { + new_node = hist->first; + if (new_node == hist->last) { + hist->first = NULL; + hist->last = NULL; + } else { + hist->first = new_node->next; + hist->first->prev = NULL; + } } else { - hist->first = new_node->next; - hist->first->prev = NULL; - } - } else { - new_node = malloc(sizeof(Undo_node)); - if (!new_node) - return false; - ++hist->count; - field_init(&new_node->field); - } - field_copy(field, &new_node->field); - new_node->tick_num = tick_num; - if (hist->last) { - hist->last->next = new_node; - new_node->prev = hist->last; - } else { - hist->first = new_node; + new_node = malloc(sizeof(Undo_node)); + if (!new_node) + return false; + ++hist->count; + field_init(&new_node->field); + } + field_copy(field, &new_node->field); + new_node->tick_num = tick_num; + if (hist->last) { + hist->last->next = new_node; + new_node->prev = hist->last; + } else { + hist->first = new_node; + hist->last = new_node; + new_node->prev = NULL; + } + new_node->next = NULL; hist->last = new_node; - new_node->prev = NULL; - } - new_node->next = NULL; - hist->last = new_node; - return true; + return true; } -staticni void undo_history_pop(Undo_history *hist, Field *out_field, - Usz *out_tick_num) { - Undo_node *last = hist->last; - if (!last) - return; - field_copy(&last->field, out_field); - *out_tick_num = last->tick_num; - if (hist->first == last) { - hist->first = NULL; - hist->last = NULL; - } else { - Undo_node *new_last = last->prev; - new_last->next = NULL; - hist->last = new_last; - } - field_deinit(&last->field); - free(last); - --hist->count; -} - -staticni void undo_history_apply(Undo_history *hist, Field *out_field, - Usz *out_tick_num) { - Undo_node *last = hist->last; - if (!last) - return; - field_copy(&last->field, out_field); - *out_tick_num = last->tick_num; -} - -static Usz undo_history_count(Undo_history *hist) { return hist->count; } - -staticni void print_activity_indicator(WINDOW *win, Usz activity_counter) { - // 7 segments that can each light up as Colors different colors. - // This gives us Colors^Segments total configurations. - enum { Segments = 7, Colors = 4 }; - Usz states = 1; // calculate Colors^Segments - for (Usz i = 0; i < Segments; ++i) - states *= Colors; - // Wrap the counter to the range of displayable configurations. - Usz val = activity_counter % states; - chtype lamps[Colors]; +staticni void undo_history_pop(Undo_history* hist, Field* out_field, + Usz* out_tick_num) +{ + Undo_node* last = hist->last; + if (!last) + return; + field_copy(&last->field, out_field); + *out_tick_num = last->tick_num; + if (hist->first == last) { + hist->first = NULL; + hist->last = NULL; + } else { + Undo_node* new_last = last->prev; + new_last->next = NULL; + hist->last = new_last; + } + field_deinit(&last->field); + free(last); + --hist->count; +} + +staticni void undo_history_apply(Undo_history* hist, Field* out_field, + Usz* out_tick_num) +{ + Undo_node* last = hist->last; + if (!last) + return; + field_copy(&last->field, out_field); + *out_tick_num = last->tick_num; +} + +static Usz undo_history_count(Undo_history* hist) { return hist->count; } + +staticni void print_activity_indicator(WINDOW* win, Usz activity_counter) +{ + // 7 segments that can each light up as Colors different colors. + // This gives us Colors^Segments total configurations. + enum { Segments = 7, Colors = 4 }; + Usz states = 1; // calculate Colors^Segments + for (Usz i = 0; i < Segments; ++i) + states *= Colors; + // Wrap the counter to the range of displayable configurations. + Usz val = activity_counter % states; + chtype lamps[Colors]; #if 1 // Appearance where segments are always lit - lamps[0] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; - lamps[1] = ACS_HLINE | fg_bg(C_white, C_natural) | A_normal; - lamps[2] = ACS_HLINE | A_bold; - lamps[3] = lamps[1]; + lamps[0] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; + lamps[1] = ACS_HLINE | fg_bg(C_white, C_natural) | A_normal; + lamps[2] = ACS_HLINE | A_bold; + lamps[3] = lamps[1]; #elif 0 // Brighter appearance where segments are always lit - lamps[0] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; - lamps[1] = ACS_HLINE | A_normal; - lamps[2] = ACS_HLINE | A_bold; - lamps[3] = lamps[1]; + lamps[0] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; + lamps[1] = ACS_HLINE | A_normal; + lamps[2] = ACS_HLINE | A_bold; + lamps[3] = lamps[1]; #else // Appearance where segments can turn off completely - lamps[0] = ' '; - lamps[1] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; - lamps[2] = ACS_HLINE | A_normal; - lamps[3] = lamps[1]; + lamps[0] = ' '; + lamps[1] = ACS_HLINE | fg_bg(C_black, C_natural) | A_bold; + lamps[2] = ACS_HLINE | A_normal; + lamps[3] = lamps[1]; #endif - chtype buffer[Segments]; - for (Usz i = 0; i < Segments; ++i) { - // Instead of a left-to-right, straightforward ascending least-to-most - // significant digits display, we'll display it as a spiral. - Usz j = i % 2 ? (6 - i / 2) : (i / 2); - buffer[j] = lamps[val % Colors]; - val = val / Colors; - } - waddchnstr(win, buffer, Segments); - // If you want to see what various combinations of colors and attributes look - // like in different terminals. + chtype buffer[Segments]; + for (Usz i = 0; i < Segments; ++i) { + // Instead of a left-to-right, straightforward ascending least-to-most + // significant digits display, we'll display it as a spiral. + Usz j = i % 2 ? (6 - i / 2) : (i / 2); + buffer[j] = lamps[val % Colors]; + val = val / Colors; + } + waddchnstr(win, buffer, Segments); + // If you want to see what various combinations of colors and attributes + // look like in different terminals. #if 0 waddch(win, 'a' | fg_bg(C_black, C_natural) | A_dim); waddch(win, 'b' | fg_bg(C_black, C_natural) | A_normal); @@ -440,312 +453,323 @@ staticni void print_activity_indicator(WINDOW *win, Usz activity_counter) { #endif } -staticni void advance_faketab(WINDOW *win, int offset_x, int tabstop) { - if (tabstop < 1) - return; - int y, x, h, w; - getyx(win, y, x); - getmaxyx(win, h, w); - (void)h; - x = ((x + tabstop - 1) / tabstop) * tabstop + offset_x % tabstop; - if (w < 1) - w = 1; - if (x >= w) - x = w - 1; - wmove(win, y, x); -} - -staticni void draw_hud(WINDOW *win, int win_y, int win_x, int height, int width, - char const *filename, Usz field_h, Usz field_w, - Usz ruler_spacing_y, Usz ruler_spacing_x, Usz tick_num, - Usz bpm, Ged_cursor const *ged_cursor, - Ged_input_mode input_mode, Usz activity_counter) { - (void)height; - (void)width; - enum { Tabstop = 8 }; - wmove(win, win_y, win_x); - wprintw(win, "%zux%zu", field_w, field_h); - advance_faketab(win, win_x, Tabstop); - wprintw(win, "%zu/%zu", ruler_spacing_x, ruler_spacing_y); - advance_faketab(win, win_x, Tabstop); - wprintw(win, "%zuf", tick_num); - advance_faketab(win, win_x, Tabstop); - wprintw(win, "%zu", bpm); - advance_faketab(win, win_x, Tabstop); - print_activity_indicator(win, activity_counter); - wmove(win, win_y + 1, win_x); - wprintw(win, "%zu,%zu", ged_cursor->x, ged_cursor->y); - advance_faketab(win, win_x, Tabstop); - wprintw(win, "%zu:%zu", ged_cursor->w, ged_cursor->h); - advance_faketab(win, win_x, Tabstop); - switch (input_mode) { - case Ged_input_mode_normal: +staticni void advance_faketab(WINDOW* win, int offset_x, int tabstop) +{ + if (tabstop < 1) + return; + int y, x, h, w; + getyx(win, y, x); + getmaxyx(win, h, w); + (void)h; + x = ((x + tabstop - 1) / tabstop) * tabstop + offset_x % tabstop; + if (w < 1) + w = 1; + if (x >= w) + x = w - 1; + wmove(win, y, x); +} + +staticni void draw_hud(WINDOW* win, int win_y, int win_x, int height, + int width, char const* filename, Usz field_h, + Usz field_w, Usz ruler_spacing_y, Usz ruler_spacing_x, + Usz tick_num, Usz bpm, Ged_cursor const* ged_cursor, + Ged_input_mode input_mode, Usz activity_counter) +{ + (void)height; + (void)width; + enum { Tabstop = 8 }; + wmove(win, win_y, win_x); + wprintw(win, "%zux%zu", field_w, field_h); + advance_faketab(win, win_x, Tabstop); + wprintw(win, "%zu/%zu", ruler_spacing_x, ruler_spacing_y); + advance_faketab(win, win_x, Tabstop); + wprintw(win, "%zuf", tick_num); + advance_faketab(win, win_x, Tabstop); + wprintw(win, "%zu", bpm); + advance_faketab(win, win_x, Tabstop); + print_activity_indicator(win, activity_counter); + wmove(win, win_y + 1, win_x); + wprintw(win, "%zu,%zu", ged_cursor->x, ged_cursor->y); + advance_faketab(win, win_x, Tabstop); + wprintw(win, "%zu:%zu", ged_cursor->w, ged_cursor->h); + advance_faketab(win, win_x, Tabstop); + switch (input_mode) { + case Ged_input_mode_normal: + wattrset(win, A_normal); + waddstr(win, "insert"); + break; + case Ged_input_mode_append: + wattrset(win, A_bold); + waddstr(win, "append"); + break; + case Ged_input_mode_selresize: + wattrset(win, A_bold); + waddstr(win, "select"); + break; + case Ged_input_mode_slide: + wattrset(win, A_reverse); + waddstr(win, "slide"); + break; + } + advance_faketab(win, win_x, Tabstop); wattrset(win, A_normal); - waddstr(win, "insert"); - break; - case Ged_input_mode_append: - wattrset(win, A_bold); - waddstr(win, "append"); - break; - case Ged_input_mode_selresize: - wattrset(win, A_bold); - waddstr(win, "select"); - break; - case Ged_input_mode_slide: - wattrset(win, A_reverse); - waddstr(win, "slide"); - break; - } - advance_faketab(win, win_x, Tabstop); - wattrset(win, A_normal); - waddstr(win, filename); -} - -staticni void draw_glyphs_grid(WINDOW *win, int draw_y, int draw_x, int draw_h, - int draw_w, Glyph const *restrict gbuffer, - Mark const *restrict mbuffer, Usz field_h, + waddstr(win, filename); +} + +staticni void draw_glyphs_grid(WINDOW* win, int draw_y, int draw_x, int draw_h, + int draw_w, Glyph const* restrict gbuffer, + Mark const* restrict mbuffer, Usz field_h, Usz field_w, Usz offset_y, Usz offset_x, Usz ruler_spacing_y, Usz ruler_spacing_x, - bool use_fancy_dots, bool use_fancy_rulers) { - assert(draw_y >= 0 && draw_x >= 0); - assert(draw_h >= 0 && draw_w >= 0); - enum { Bufcount = 4096 }; - chtype chbuffer[Bufcount]; - // todo buffer limit - if (offset_y >= field_h || offset_x >= field_w) - return; - if (draw_y >= draw_h || draw_x >= draw_w) - return; - Usz rows = (Usz)(draw_h - draw_y); - if (field_h - offset_y < rows) - rows = field_h - offset_y; - Usz cols = (Usz)(draw_w - draw_x); - if (field_w - offset_x < cols) - cols = field_w - offset_x; - if (Bufcount < cols) - cols = Bufcount; - if (rows == 0 || cols == 0) - return; - bool use_rulers = ruler_spacing_y != 0 && ruler_spacing_x != 0; - chtype bullet = use_fancy_dots ? ACS_BULLET : '.'; - enum { T = 1 << 0, B = 1 << 1, L = 1 << 2, R = 1 << 3 }; - chtype rs[(T | B | L | R) + 1]; - if (use_rulers) { - for (Usz i = 0; i < sizeof rs / sizeof(chtype); ++i) - rs[i] = '+'; - if (use_fancy_rulers) { - rs[T | L] = ACS_ULCORNER; - rs[T | R] = ACS_URCORNER; - rs[B | L] = ACS_LLCORNER; - rs[B | R] = ACS_LRCORNER; - rs[T] = ACS_TTEE; - rs[B] = ACS_BTEE; - rs[L] = ACS_LTEE; - rs[R] = ACS_RTEE; - } - } - for (Usz iy = 0; iy < rows; ++iy) { - Usz line_offset = (offset_y + iy) * field_w + offset_x; - Glyph const *g_row = gbuffer + line_offset; - Mark const *m_row = mbuffer + line_offset; - bool use_y_ruler = use_rulers && (iy + offset_y) % ruler_spacing_y == 0; - for (Usz ix = 0; ix < cols; ++ix) { - Glyph g = g_row[ix]; - Mark m = m_row[ix]; - chtype ch; - if (g == '.') { - if (use_y_ruler && (ix + offset_x) % ruler_spacing_x == 0) { - int p = 0; // clang-format off + bool use_fancy_dots, bool use_fancy_rulers) +{ + assert(draw_y >= 0 && draw_x >= 0); + assert(draw_h >= 0 && draw_w >= 0); + enum { Bufcount = 4096 }; + chtype chbuffer[Bufcount]; + // todo buffer limit + if (offset_y >= field_h || offset_x >= field_w) + return; + if (draw_y >= draw_h || draw_x >= draw_w) + return; + Usz rows = (Usz)(draw_h - draw_y); + if (field_h - offset_y < rows) + rows = field_h - offset_y; + Usz cols = (Usz)(draw_w - draw_x); + if (field_w - offset_x < cols) + cols = field_w - offset_x; + if (Bufcount < cols) + cols = Bufcount; + if (rows == 0 || cols == 0) + return; + bool use_rulers = ruler_spacing_y != 0 && ruler_spacing_x != 0; + chtype bullet = use_fancy_dots ? ACS_BULLET : '.'; + enum { T = 1 << 0, B = 1 << 1, L = 1 << 2, R = 1 << 3 }; + chtype rs[(T | B | L | R) + 1]; + if (use_rulers) { + for (Usz i = 0; i < sizeof rs / sizeof(chtype); ++i) + rs[i] = '+'; + if (use_fancy_rulers) { + rs[T | L] = ACS_ULCORNER; + rs[T | R] = ACS_URCORNER; + rs[B | L] = ACS_LLCORNER; + rs[B | R] = ACS_LRCORNER; + rs[T] = ACS_TTEE; + rs[B] = ACS_BTEE; + rs[L] = ACS_LTEE; + rs[R] = ACS_RTEE; + } + } + for (Usz iy = 0; iy < rows; ++iy) { + Usz line_offset = (offset_y + iy) * field_w + offset_x; + Glyph const* g_row = gbuffer + line_offset; + Mark const* m_row = mbuffer + line_offset; + bool use_y_ruler = use_rulers + && (iy + offset_y) % ruler_spacing_y == 0; + for (Usz ix = 0; ix < cols; ++ix) { + Glyph g = g_row[ix]; + Mark m = m_row[ix]; + chtype ch; + if (g == '.') { + if (use_y_ruler && (ix + offset_x) % ruler_spacing_x == 0) { + int p = 0; // clang-format off if (iy + offset_y == 0 ) p |= T; if (iy + offset_y + 1 == field_h) p |= B; if (ix + offset_x == 0 ) p |= L; if (ix + offset_x + 1 == field_w) p |= R; ch = rs[p]; // clang-format on - } else { - ch = bullet; + } else { + ch = bullet; + } + } else { + ch = (chtype)g; + } + attr_t attrs = term_attrs_of_cell(g, m); + chbuffer[ix] = ch | attrs; } - } else { - ch = (chtype)g; - } - attr_t attrs = term_attrs_of_cell(g, m); - chbuffer[ix] = ch | attrs; + wmove(win, draw_y + (int)iy, draw_x); + waddchnstr(win, chbuffer, (int)cols); } - wmove(win, draw_y + (int)iy, draw_x); - waddchnstr(win, chbuffer, (int)cols); - } } staticni void draw_glyphs_grid_scrolled( - WINDOW *win, int draw_y, int draw_x, int draw_h, int draw_w, - Glyph const *restrict gbuffer, Mark const *restrict mbuffer, Usz field_h, + WINDOW* win, int draw_y, int draw_x, int draw_h, int draw_w, + Glyph const* restrict gbuffer, Mark const* restrict mbuffer, Usz field_h, Usz field_w, int scroll_y, int scroll_x, Usz ruler_spacing_y, - Usz ruler_spacing_x, bool use_fancy_dots, bool use_fancy_rulers) { - if (scroll_y < 0) { - draw_y += -scroll_y; - scroll_y = 0; - } - if (scroll_x < 0) { - draw_x += -scroll_x; - scroll_x = 0; - } - draw_glyphs_grid(win, draw_y, draw_x, draw_h, draw_w, gbuffer, mbuffer, - field_h, field_w, (Usz)scroll_y, (Usz)scroll_x, - ruler_spacing_y, ruler_spacing_x, use_fancy_dots, - use_fancy_rulers); -} - -static void ged_cursor_confine(Ged_cursor *tc, Usz height, Usz width) { - if (height == 0 || width == 0) - return; - if (tc->y >= height) - tc->y = height - 1; - if (tc->x >= width) - tc->x = width - 1; -} - -staticni void draw_oevent_list(WINDOW *win, Oevent_list const *oevent_list) { - wmove(win, 0, 0); - int win_h = getmaxy(win); - wprintw(win, "Count: %d", (int)oevent_list->count); - for (Usz i = 0, num_events = oevent_list->count; i < num_events; ++i) { - int cury = getcury(win); - if (cury + 1 >= win_h) - return; - wmove(win, cury + 1, 0); - Oevent const *ev = oevent_list->buffer + i; - Oevent_types evt = ev->any.oevent_type; - switch (evt) { - case Oevent_type_midi_note: { - Oevent_midi_note const *em = &ev->midi_note; - wprintw( - win, - "MIDI Note\tchannel %d\toctave %d\tnote %d\tvelocity %d\tlength %d", - (int)em->channel, (int)em->octave, (int)em->note, (int)em->velocity, - (int)em->duration); - break; - } - case Oevent_type_midi_cc: { - Oevent_midi_cc const *ec = &ev->midi_cc; - wprintw(win, "MIDI CC\tchannel %d\tcontrol %d\tvalue %d", - (int)ec->channel, (int)ec->control, (int)ec->value); - break; - } - case Oevent_type_midi_pb: { - Oevent_midi_pb const *ep = &ev->midi_pb; - wprintw(win, "MIDI PB\tchannel %d\tmsb %d\tlsb %d", (int)ep->channel, - (int)ep->msb, (int)ep->lsb); - break; - } - case Oevent_type_osc_ints: { - Oevent_osc_ints const *eo = &ev->osc_ints; - wprintw(win, "OSC\t%c\tcount: %d ", eo->glyph, eo->count); - waddch(win, ACS_VLINE); - for (Usz j = 0; j < eo->count; ++j) { - wprintw(win, " %d", eo->numbers[j]); - } - break; - } - case Oevent_type_udp_string: { - Oevent_udp_string const *eo = &ev->udp_string; - wprintw(win, "UDP\tcount %d\t", (int)eo->count); - for (Usz j = 0; j < (Usz)eo->count; ++j) { - waddch(win, (chtype)eo->chars[j]); - } - break; - } - } - } -} - -staticni void ged_resize_grid(Field *field, Mbuf_reusable *mbr, Usz new_height, - Usz new_width, Usz tick_num, Field *scratch_field, - Undo_history *undo_hist, Ged_cursor *ged_cursor) { - assert(new_height > 0 && new_width > 0); - undo_history_push(undo_hist, field, tick_num); - field_copy(field, scratch_field); - field_resize_raw(field, new_height, new_width); - // junky copies until i write a smarter thing - memset(field->buffer, '.', new_height * new_width * sizeof(Glyph)); - gbuffer_copy_subrect(scratch_field->buffer, field->buffer, - scratch_field->height, scratch_field->width, - field->height, field->width, 0, 0, 0, 0, - scratch_field->height, scratch_field->width); - ged_cursor_confine(ged_cursor, new_height, new_width); - mbuf_reusable_ensure_size(mbr, new_height, new_width); -} - -staticni Usz adjust_rulers_humanized(Usz ruler, Usz in, Isz delta_rulers) { - // slightly more confusing because desired grid sizes are +1 (e.g. ruler of - // length 8 wants to snap to 25 and 33, not 24 and 32). also this math is - // sloppy. - assert(ruler > 0); - if (in == 0) - return delta_rulers > 0 ? ruler * (Usz)delta_rulers : 1; - // could overflow if inputs are big - if (delta_rulers < 0) - in += ruler - 1; - Isz n = ((Isz)in - 1) / (Isz)ruler + delta_rulers; - if (n < 0) - n = 0; - return ruler * (Usz)n + 1; + Usz ruler_spacing_x, bool use_fancy_dots, bool use_fancy_rulers) +{ + if (scroll_y < 0) { + draw_y += -scroll_y; + scroll_y = 0; + } + if (scroll_x < 0) { + draw_x += -scroll_x; + scroll_x = 0; + } + draw_glyphs_grid(win, draw_y, draw_x, draw_h, draw_w, gbuffer, mbuffer, + field_h, field_w, (Usz)scroll_y, (Usz)scroll_x, + ruler_spacing_y, ruler_spacing_x, use_fancy_dots, + use_fancy_rulers); +} + +static void ged_cursor_confine(Ged_cursor* tc, Usz height, Usz width) +{ + if (height == 0 || width == 0) + return; + if (tc->y >= height) + tc->y = height - 1; + if (tc->x >= width) + tc->x = width - 1; +} + +staticni void draw_oevent_list(WINDOW* win, Oevent_list const* oevent_list) +{ + wmove(win, 0, 0); + int win_h = getmaxy(win); + wprintw(win, "Count: %d", (int)oevent_list->count); + for (Usz i = 0, num_events = oevent_list->count; i < num_events; ++i) { + int cury = getcury(win); + if (cury + 1 >= win_h) + return; + wmove(win, cury + 1, 0); + Oevent const* ev = oevent_list->buffer + i; + Oevent_types evt = ev->any.oevent_type; + switch (evt) { + case Oevent_type_midi_note: { + Oevent_midi_note const* em = &ev->midi_note; + wprintw(win, + "MIDI Note\tchannel %d\toctave %d\tnote %d\tvelocity " + "%d\tlength %d", + (int)em->channel, (int)em->octave, (int)em->note, + (int)em->velocity, (int)em->duration); + break; + } + case Oevent_type_midi_cc: { + Oevent_midi_cc const* ec = &ev->midi_cc; + wprintw(win, "MIDI CC\tchannel %d\tcontrol %d\tvalue %d", + (int)ec->channel, (int)ec->control, (int)ec->value); + break; + } + case Oevent_type_midi_pb: { + Oevent_midi_pb const* ep = &ev->midi_pb; + wprintw(win, "MIDI PB\tchannel %d\tmsb %d\tlsb %d", + (int)ep->channel, (int)ep->msb, (int)ep->lsb); + break; + } + case Oevent_type_osc_ints: { + Oevent_osc_ints const* eo = &ev->osc_ints; + wprintw(win, "OSC\t%c\tcount: %d ", eo->glyph, eo->count); + waddch(win, ACS_VLINE); + for (Usz j = 0; j < eo->count; ++j) { + wprintw(win, " %d", eo->numbers[j]); + } + break; + } + case Oevent_type_udp_string: { + Oevent_udp_string const* eo = &ev->udp_string; + wprintw(win, "UDP\tcount %d\t", (int)eo->count); + for (Usz j = 0; j < (Usz)eo->count; ++j) { + waddch(win, (chtype)eo->chars[j]); + } + break; + } + } + } +} + +staticni void ged_resize_grid(Field* field, Mbuf_reusable* mbr, Usz new_height, + Usz new_width, Usz tick_num, + Field* scratch_field, Undo_history* undo_hist, + Ged_cursor* ged_cursor) +{ + assert(new_height > 0 && new_width > 0); + undo_history_push(undo_hist, field, tick_num); + field_copy(field, scratch_field); + field_resize_raw(field, new_height, new_width); + // junky copies until i write a smarter thing + memset(field->buffer, '.', new_height * new_width * sizeof(Glyph)); + gbuffer_copy_subrect(scratch_field->buffer, field->buffer, + scratch_field->height, scratch_field->width, + field->height, field->width, 0, 0, 0, 0, + scratch_field->height, scratch_field->width); + ged_cursor_confine(ged_cursor, new_height, new_width); + mbuf_reusable_ensure_size(mbr, new_height, new_width); +} + +staticni Usz adjust_rulers_humanized(Usz ruler, Usz in, Isz delta_rulers) +{ + // slightly more confusing because desired grid sizes are +1 (e.g. ruler of + // length 8 wants to snap to 25 and 33, not 24 and 32). also this math is + // sloppy. + assert(ruler > 0); + if (in == 0) + return delta_rulers > 0 ? ruler * (Usz)delta_rulers : 1; + // could overflow if inputs are big + if (delta_rulers < 0) + in += ruler - 1; + Isz n = ((Isz)in - 1) / (Isz)ruler + delta_rulers; + if (n < 0) + n = 0; + return ruler * (Usz)n + 1; } // Resizes by number of ruler divisions, and snaps size to closest division in // a way a human would expect. Adds +1 to the output, so grid resulting size is // 1 unit longer than the actual ruler length. -staticni bool ged_resize_grid_snap_ruler(Field *field, Mbuf_reusable *mbr, +staticni bool ged_resize_grid_snap_ruler(Field* field, Mbuf_reusable* mbr, Usz ruler_y, Usz ruler_x, Isz delta_h, Isz delta_w, Usz tick_num, - Field *scratch_field, - Undo_history *undo_hist, - Ged_cursor *ged_cursor) { - assert(ruler_y > 0); - assert(ruler_x > 0); - Usz field_h = field->height; - Usz field_w = field->width; - assert(field_h > 0); - assert(field_w > 0); - if (ruler_y == 0 || ruler_x == 0 || field_h == 0 || field_w == 0) - return false; - Usz new_field_h = field_h; - Usz new_field_w = field_w; - if (delta_h != 0) - new_field_h = adjust_rulers_humanized(ruler_y, field_h, delta_h); - if (delta_w != 0) - new_field_w = adjust_rulers_humanized(ruler_x, field_w, delta_w); - if (new_field_h > ORCA_Y_MAX) - new_field_h = ORCA_Y_MAX; - if (new_field_w > ORCA_X_MAX) - new_field_w = ORCA_X_MAX; - if (new_field_h == field_h && new_field_w == field_w) - return false; - ged_resize_grid(field, mbr, new_field_h, new_field_w, tick_num, scratch_field, - undo_hist, ged_cursor); - return true; + Field* scratch_field, + Undo_history* undo_hist, + Ged_cursor* ged_cursor) +{ + assert(ruler_y > 0); + assert(ruler_x > 0); + Usz field_h = field->height; + Usz field_w = field->width; + assert(field_h > 0); + assert(field_w > 0); + if (ruler_y == 0 || ruler_x == 0 || field_h == 0 || field_w == 0) + return false; + Usz new_field_h = field_h; + Usz new_field_w = field_w; + if (delta_h != 0) + new_field_h = adjust_rulers_humanized(ruler_y, field_h, delta_h); + if (delta_w != 0) + new_field_w = adjust_rulers_humanized(ruler_x, field_w, delta_w); + if (new_field_h > ORCA_Y_MAX) + new_field_h = ORCA_Y_MAX; + if (new_field_w > ORCA_X_MAX) + new_field_w = ORCA_X_MAX; + if (new_field_h == field_h && new_field_w == field_w) + return false; + ged_resize_grid(field, mbr, new_field_h, new_field_w, tick_num, + scratch_field, undo_hist, ged_cursor); + return true; } typedef enum { - Midi_mode_type_null, - Midi_mode_type_osc_bidule, + Midi_mode_type_null, + Midi_mode_type_osc_bidule, #ifdef FEAT_PORTMIDI - Midi_mode_type_portmidi, + Midi_mode_type_portmidi, #endif } Midi_mode_type; typedef struct { - Midi_mode_type type; + Midi_mode_type type; } Midi_mode_any; typedef struct { - Midi_mode_type type; - char const *path; + Midi_mode_type type; + char const* path; } Midi_mode_osc_bidule; #ifdef FEAT_PORTMIDI typedef struct { - Midi_mode_type type; - PmDeviceID device_id; - PortMidiStream *stream; + Midi_mode_type type; + PmDeviceID device_id; + PortMidiStream* stream; } Midi_mode_portmidi; // Not sure whether it's OK to call Pm_Terminate() without having a successful // call to Pm_Initialize() -- let's just treat it with tweezers. @@ -753,315 +777,336 @@ static bool portmidi_is_initialized = false; #endif typedef union { - Midi_mode_any any; - Midi_mode_osc_bidule osc_bidule; + Midi_mode_any any; + Midi_mode_osc_bidule osc_bidule; #ifdef FEAT_PORTMIDI - Midi_mode_portmidi portmidi; + Midi_mode_portmidi portmidi; #endif } Midi_mode; -void midi_mode_init_null(Midi_mode *mm) { mm->any.type = Midi_mode_type_null; } -void midi_mode_init_osc_bidule(Midi_mode *mm, char const *path) { - mm->osc_bidule.type = Midi_mode_type_osc_bidule; - mm->osc_bidule.path = path; +void midi_mode_init_null(Midi_mode* mm) { mm->any.type = Midi_mode_type_null; } +void midi_mode_init_osc_bidule(Midi_mode* mm, char const* path) +{ + mm->osc_bidule.type = Midi_mode_type_osc_bidule; + mm->osc_bidule.path = path; } #ifdef FEAT_PORTMIDI enum { - Portmidi_artificial_latency = 1, + Portmidi_artificial_latency = 1, }; struct { - U64 clock_base; - bool did_init; + U64 clock_base; + bool did_init; } portmidi_global_data; -static PmTimestamp portmidi_timestamp_now(void) { - if (!portmidi_global_data.did_init) { - portmidi_global_data.did_init = true; - portmidi_global_data.clock_base = stm_now(); - } - return (PmTimestamp)(stm_ms(stm_since(portmidi_global_data.clock_base))); -} -static PmTimestamp portmidi_timeproc(void *time_info) { - (void)time_info; - return portmidi_timestamp_now(); -} -static PmError portmidi_init_if_necessary(void) { - if (portmidi_is_initialized) +static PmTimestamp portmidi_timestamp_now(void) +{ + if (!portmidi_global_data.did_init) { + portmidi_global_data.did_init = true; + portmidi_global_data.clock_base = stm_now(); + } + return (PmTimestamp)(stm_ms(stm_since(portmidi_global_data.clock_base))); +} +static PmTimestamp portmidi_timeproc(void* time_info) +{ + (void)time_info; + return portmidi_timestamp_now(); +} +static PmError portmidi_init_if_necessary(void) +{ + if (portmidi_is_initialized) + return 0; + PmError e = Pm_Initialize(); + if (e) + return e; + portmidi_is_initialized = true; return 0; - PmError e = Pm_Initialize(); - if (e) - return e; - portmidi_is_initialized = true; - return 0; -} -staticni PmError midi_mode_init_portmidi(Midi_mode *mm, PmDeviceID dev_id) { - PmError e = portmidi_init_if_necessary(); - if (e) - goto fail; - e = Pm_OpenOutput(&mm->portmidi.stream, dev_id, NULL, 128, portmidi_timeproc, - NULL, Portmidi_artificial_latency); - if (e) - goto fail; - mm->portmidi.type = Midi_mode_type_portmidi; - mm->portmidi.device_id = dev_id; - return pmNoError; +} +staticni PmError midi_mode_init_portmidi(Midi_mode* mm, PmDeviceID dev_id) +{ + PmError e = portmidi_init_if_necessary(); + if (e) + goto fail; + e = Pm_OpenOutput(&mm->portmidi.stream, dev_id, NULL, 128, + portmidi_timeproc, NULL, Portmidi_artificial_latency); + if (e) + goto fail; + mm->portmidi.type = Midi_mode_type_portmidi; + mm->portmidi.device_id = dev_id; + return pmNoError; fail: - midi_mode_init_null(mm); - return e; + midi_mode_init_null(mm); + return e; } // Returns true on success. todo currently output only -staticni bool portmidi_find_device_id_by_name(char const *name, Usz namelen, - PmError *out_pmerror, - PmDeviceID *out_id) { - *out_pmerror = portmidi_init_if_necessary(); - if (*out_pmerror) +staticni bool portmidi_find_device_id_by_name(char const* name, Usz namelen, + PmError* out_pmerror, + PmDeviceID* out_id) +{ + *out_pmerror = portmidi_init_if_necessary(); + if (*out_pmerror) + return false; + int num = Pm_CountDevices(); + for (int i = 0; i < num; ++i) { + PmDeviceInfo const* info = Pm_GetDeviceInfo(i); + if (!info || !info->output) + continue; + Usz len = strlen(info->name); + if (len != namelen) + continue; + if (strncmp(name, info->name, namelen) == 0) { + *out_id = i; + return true; + } + } return false; - int num = Pm_CountDevices(); - for (int i = 0; i < num; ++i) { - PmDeviceInfo const *info = Pm_GetDeviceInfo(i); +} +static bool portmidi_find_name_of_device_id(PmDeviceID id, + PmError* out_pmerror, + oso** out_name) +{ + *out_pmerror = portmidi_init_if_necessary(); + if (*out_pmerror) + return false; + int num = Pm_CountDevices(); + if (id < 0 || id >= num) + return false; + PmDeviceInfo const* info = Pm_GetDeviceInfo(id); if (!info || !info->output) - continue; - Usz len = strlen(info->name); - if (len != namelen) - continue; - if (strncmp(name, info->name, namelen) == 0) { - *out_id = i; - return true; - } - } - return false; -} -static bool portmidi_find_name_of_device_id(PmDeviceID id, PmError *out_pmerror, - oso **out_name) { - *out_pmerror = portmidi_init_if_necessary(); - if (*out_pmerror) - return false; - int num = Pm_CountDevices(); - if (id < 0 || id >= num) - return false; - PmDeviceInfo const *info = Pm_GetDeviceInfo(id); - if (!info || !info->output) - return false; - osoput(out_name, info->name); - return true; + return false; + osoput(out_name, info->name); + return true; } #endif -staticni void midi_mode_deinit(Midi_mode *mm) { - switch (mm->any.type) { - case Midi_mode_type_null: - case Midi_mode_type_osc_bidule: - break; +staticni void midi_mode_deinit(Midi_mode* mm) +{ + switch (mm->any.type) { + case Midi_mode_type_null: + case Midi_mode_type_osc_bidule: + break; #ifdef FEAT_PORTMIDI - case Midi_mode_type_portmidi: - // Because PortMidi seems to work correctly ony more platforms when using - // its timing stuff, we are using it. And because we are using it, and - // because it may be buffering events for sending 'later', we might have - // pending outgoing MIDI events. We'll need to wait until they finish being - // before calling Pm_Close, otherwise users could have problems like MIDI - // notes being stuck on. This is slow and blocking, but not much we can do - // about it right now. - // - // TODO use nansleep on platforms that support it. - for (U64 start = stm_now(); - stm_ms(stm_since(start)) <= (double)Portmidi_artificial_latency;) - sleep(0); - Pm_Close(mm->portmidi.stream); - break; + case Midi_mode_type_portmidi: + // Because PortMidi seems to work correctly ony more platforms when + // using its timing stuff, we are using it. And because we are using + // it, and because it may be buffering events for sending 'later', we + // might have pending outgoing MIDI events. We'll need to wait until + // they finish being before calling Pm_Close, otherwise users could + // have problems like MIDI notes being stuck on. This is slow and + // blocking, but not much we can do about it right now. + // + // TODO use nansleep on platforms that support it. + for (U64 start = stm_now(); + stm_ms(stm_since(start)) <= (double)Portmidi_artificial_latency;) + sleep(0); + Pm_Close(mm->portmidi.stream); + break; #endif - } + } } typedef struct { - Field field; - Field scratch_field; - Field clipboard_field; - Mbuf_reusable mbuf_r; - Undo_history undo_hist; - Oevent_list oevent_list; - Oevent_list scratch_oevent_list; - Susnote_list susnote_list; - Ged_cursor ged_cursor; - Usz tick_num; - Usz ruler_spacing_y, ruler_spacing_x; - Ged_input_mode input_mode; - Usz bpm; - U64 clock; - double accum_secs; - double time_to_next_note_off; - Oosc_dev *oosc_dev; - Midi_mode midi_mode; - Usz activity_counter; - Usz random_seed; - Usz drag_start_y, drag_start_x; - int win_h, win_w; - int softmargin_y, softmargin_x; - int grid_h; - int grid_scroll_y, grid_scroll_x; // not sure if i like this being int - U8 midi_bclock_sixths; // 0..5, holds 6th of the quarter note step - bool needs_remarking : 1; - bool is_draw_dirty : 1; - bool is_playing : 1; - bool midi_bclock : 1; - bool draw_event_list : 1; - bool is_mouse_down : 1; - bool is_mouse_dragging : 1; - bool is_hud_visible : 1; + Field field; + Field scratch_field; + Field clipboard_field; + Mbuf_reusable mbuf_r; + Undo_history undo_hist; + Oevent_list oevent_list; + Oevent_list scratch_oevent_list; + Susnote_list susnote_list; + Ged_cursor ged_cursor; + Usz tick_num; + Usz ruler_spacing_y, ruler_spacing_x; + Ged_input_mode input_mode; + Usz bpm; + U64 clock; + double accum_secs; + double time_to_next_note_off; + Oosc_dev* oosc_dev; + Midi_mode midi_mode; + Usz activity_counter; + Usz random_seed; + Usz drag_start_y, drag_start_x; + int win_h, win_w; + int softmargin_y, softmargin_x; + int grid_h; + int grid_scroll_y, grid_scroll_x; // not sure if i like this being int + U8 midi_bclock_sixths; // 0..5, holds 6th of the quarter note step + bool needs_remarking : 1; + bool is_draw_dirty : 1; + bool is_playing : 1; + bool midi_bclock : 1; + bool draw_event_list : 1; + bool is_mouse_down : 1; + bool is_mouse_dragging : 1; + bool is_hud_visible : 1; } Ged; -static void ged_init(Ged *a, Usz undo_limit, Usz init_bpm, Usz init_seed) { - field_init(&a->field); - field_init(&a->scratch_field); - field_init(&a->clipboard_field); - mbuf_reusable_init(&a->mbuf_r); - undo_history_init(&a->undo_hist, undo_limit); - oevent_list_init(&a->oevent_list); - oevent_list_init(&a->scratch_oevent_list); - susnote_list_init(&a->susnote_list); - ged_cursor_init(&a->ged_cursor); - a->tick_num = 0; - a->ruler_spacing_y = a->ruler_spacing_x = 8; - a->input_mode = Ged_input_mode_normal; - a->bpm = init_bpm; - a->clock = 0; - a->accum_secs = 0.0; - a->time_to_next_note_off = 1.0; - a->oosc_dev = NULL; - midi_mode_init_null(&a->midi_mode); - a->activity_counter = 0; - a->random_seed = init_seed; - a->drag_start_y = a->drag_start_x = 0; - a->win_h = a->win_w = 0; - a->softmargin_y = a->softmargin_x = 0; - a->grid_h = 0; - a->grid_scroll_y = a->grid_scroll_x = 0; - a->midi_bclock_sixths = 0; - a->needs_remarking = true; - a->is_draw_dirty = false; - a->is_playing = false; - a->midi_bclock = false; - a->draw_event_list = false; - a->is_mouse_down = false; - a->is_mouse_dragging = false; - a->is_hud_visible = false; -} - -static void ged_deinit(Ged *a) { - field_deinit(&a->field); - field_deinit(&a->scratch_field); - field_deinit(&a->clipboard_field); - mbuf_reusable_deinit(&a->mbuf_r); - undo_history_deinit(&a->undo_hist); - oevent_list_deinit(&a->oevent_list); - oevent_list_deinit(&a->scratch_oevent_list); - susnote_list_deinit(&a->susnote_list); - if (a->oosc_dev) - oosc_dev_destroy(a->oosc_dev); - midi_mode_deinit(&a->midi_mode); -} - -static bool ged_is_draw_dirty(Ged *a) { - return a->is_draw_dirty || a->needs_remarking; -} - -staticni void send_midi_3bytes(Oosc_dev *oosc_dev, Midi_mode const *midi_mode, - int status, int byte1, int byte2) { - switch (midi_mode->any.type) { - case Midi_mode_type_null: - break; - case Midi_mode_type_osc_bidule: { - if (!oosc_dev) - break; - oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, - (int[]){status, byte1, byte2}, 3); - break; - } +static void ged_init(Ged* a, Usz undo_limit, Usz init_bpm, Usz init_seed) +{ + field_init(&a->field); + field_init(&a->scratch_field); + field_init(&a->clipboard_field); + mbuf_reusable_init(&a->mbuf_r); + undo_history_init(&a->undo_hist, undo_limit); + oevent_list_init(&a->oevent_list); + oevent_list_init(&a->scratch_oevent_list); + susnote_list_init(&a->susnote_list); + ged_cursor_init(&a->ged_cursor); + a->tick_num = 0; + a->ruler_spacing_y = a->ruler_spacing_x = 8; + a->input_mode = Ged_input_mode_normal; + a->bpm = init_bpm; + a->clock = 0; + a->accum_secs = 0.0; + a->time_to_next_note_off = 1.0; + a->oosc_dev = NULL; + midi_mode_init_null(&a->midi_mode); + a->activity_counter = 0; + a->random_seed = init_seed; + a->drag_start_y = a->drag_start_x = 0; + a->win_h = a->win_w = 0; + a->softmargin_y = a->softmargin_x = 0; + a->grid_h = 0; + a->grid_scroll_y = a->grid_scroll_x = 0; + a->midi_bclock_sixths = 0; + a->needs_remarking = true; + a->is_draw_dirty = false; + a->is_playing = false; + a->midi_bclock = false; + a->draw_event_list = false; + a->is_mouse_down = false; + a->is_mouse_dragging = false; + a->is_hud_visible = false; +} + +static void ged_deinit(Ged* a) +{ + field_deinit(&a->field); + field_deinit(&a->scratch_field); + field_deinit(&a->clipboard_field); + mbuf_reusable_deinit(&a->mbuf_r); + undo_history_deinit(&a->undo_hist); + oevent_list_deinit(&a->oevent_list); + oevent_list_deinit(&a->scratch_oevent_list); + susnote_list_deinit(&a->susnote_list); + if (a->oosc_dev) + oosc_dev_destroy(a->oosc_dev); + midi_mode_deinit(&a->midi_mode); +} + +static bool ged_is_draw_dirty(Ged* a) +{ + return a->is_draw_dirty || a->needs_remarking; +} + +staticni void send_midi_3bytes(Oosc_dev* oosc_dev, Midi_mode const* midi_mode, + int status, int byte1, int byte2) +{ + switch (midi_mode->any.type) { + case Midi_mode_type_null: + break; + case Midi_mode_type_osc_bidule: { + if (!oosc_dev) + break; + oosc_send_int32s(oosc_dev, midi_mode->osc_bidule.path, + (int[]) { status, byte1, byte2 }, 3); + break; + } #ifdef FEAT_PORTMIDI - case Midi_mode_type_portmidi: { - // timestamp is totally fake, to prevent problems with some MIDI systems - // getting angry if there's no timestamping info. - // - // Eventually, we will want to create real timestamps based on a real orca - // clock, instead of ad-hoc at the last moment like this. When we do that, - // we'll need to thread the timestamping/timing info through the function - // calls, instead of creating it at the last moment here. (This timestamp - // is actually 'useless', because it doesn't convey any additional - // information. But if we don't provide it, at least to PortMidi, some - // people's MIDI setups may malfunction and have terrible timing problems.) - PmTimestamp pm_timestamp = portmidi_timestamp_now(); - PmError pme = Pm_WriteShort(midi_mode->portmidi.stream, pm_timestamp, - Pm_Message(status, byte1, byte2)); - (void)pme; - break; - } + case Midi_mode_type_portmidi: { + // timestamp is totally fake, to prevent problems with some MIDI + // systems getting angry if there's no timestamping info. + // + // Eventually, we will want to create real timestamps based on a real + // orca clock, instead of ad-hoc at the last moment like this. When we + // do that, we'll need to thread the timestamping/timing info through + // the function calls, instead of creating it at the last moment here. + // (This timestamp is actually 'useless', because it doesn't convey any + // additional information. But if we don't provide it, at least to + // PortMidi, some people's MIDI setups may malfunction and have + // terrible timing problems.) + PmTimestamp pm_timestamp = portmidi_timestamp_now(); + PmError pme = Pm_WriteShort(midi_mode->portmidi.stream, pm_timestamp, + Pm_Message(status, byte1, byte2)); + (void)pme; + break; + } #endif - } + } } -static void send_midi_chan_msg(Oosc_dev *oosc_dev, Midi_mode const *midi_mode, +static void send_midi_chan_msg(Oosc_dev* oosc_dev, Midi_mode const* midi_mode, int type /*0..15*/, int chan /*0.. 15*/, - int byte1 /*0..127*/, int byte2 /*0..127*/) { - send_midi_3bytes(oosc_dev, midi_mode, type << 4 | chan, byte1, byte2); + int byte1 /*0..127*/, int byte2 /*0..127*/) +{ + send_midi_3bytes(oosc_dev, midi_mode, type << 4 | chan, byte1, byte2); } -static void send_midi_byte(Oosc_dev *oosc_dev, Midi_mode const *midi_mode, - int x) { - // PortMidi wants 0 and 0 for the unused bytes. Likewise, Bidule's - // MIDI-via-OSC won't accept the message unless there are at least all 3 - // bytes, with the second 2 set to zero. - send_midi_3bytes(oosc_dev, midi_mode, x, 0, 0); +static void send_midi_byte(Oosc_dev* oosc_dev, Midi_mode const* midi_mode, + int x) +{ + // PortMidi wants 0 and 0 for the unused bytes. Likewise, Bidule's + // MIDI-via-OSC won't accept the message unless there are at least all 3 + // bytes, with the second 2 set to zero. + send_midi_3bytes(oosc_dev, midi_mode, x, 0, 0); } staticni void // -send_midi_note_offs(Oosc_dev *oosc_dev, Midi_mode *midi_mode, - Susnote const *start, Susnote const *end) { - for (; start != end; ++start) { +send_midi_note_offs(Oosc_dev* oosc_dev, Midi_mode* midi_mode, + Susnote const* start, Susnote const* end) +{ + for (; start != end; ++start) { #if 0 float under = start->remaining; if (under < 0.0) { fprintf(stderr, "cutoff slop: %f\n", under); } #endif - U16 chan_note = start->chan_note; - send_midi_chan_msg(oosc_dev, midi_mode, 0x8, chan_note >> 8, - chan_note & 0xFF, 0); - } + U16 chan_note = start->chan_note; + send_midi_chan_msg(oosc_dev, midi_mode, 0x8, chan_note >> 8, + chan_note & 0xFF, 0); + } } -static void send_control_message(Oosc_dev *oosc_dev, char const *osc_address) { - if (!oosc_dev) - return; - oosc_send_int32s(oosc_dev, osc_address, NULL, 0); +static void send_control_message(Oosc_dev* oosc_dev, char const* osc_address) +{ + if (!oosc_dev) + return; + oosc_send_int32s(oosc_dev, osc_address, NULL, 0); } -static void send_num_message(Oosc_dev *oosc_dev, char const *osc_address, - I32 num) { - if (!oosc_dev) - return; - I32 nums[1]; - nums[0] = num; - oosc_send_int32s(oosc_dev, osc_address, nums, ORCA_ARRAY_COUNTOF(nums)); +static void send_num_message(Oosc_dev* oosc_dev, char const* osc_address, + I32 num) +{ + if (!oosc_dev) + return; + I32 nums[1]; + nums[0] = num; + oosc_send_int32s(oosc_dev, osc_address, nums, ORCA_ARRAY_COUNTOF(nums)); } -staticni void apply_time_to_sustained_notes(Oosc_dev *oosc_dev, - Midi_mode *midi_mode, +staticni void apply_time_to_sustained_notes(Oosc_dev* oosc_dev, + Midi_mode* midi_mode, double time_elapsed, - Susnote_list *susnote_list, - double *next_note_off_deadline) { - Usz start_removed, end_removed; - susnote_list_advance_time(susnote_list, time_elapsed, &start_removed, - &end_removed, next_note_off_deadline); - if (ORCA_UNLIKELY(start_removed != end_removed)) { - Susnote const *restrict susnotes_off = susnote_list->buffer; - send_midi_note_offs(oosc_dev, midi_mode, susnotes_off + start_removed, - susnotes_off + end_removed); - } -} - -staticni void ged_stop_all_sustained_notes(Ged *a) { - Susnote_list *sl = &a->susnote_list; - send_midi_note_offs(a->oosc_dev, &a->midi_mode, sl->buffer, - sl->buffer + sl->count); - susnote_list_clear(sl); - a->time_to_next_note_off = 1.0; + Susnote_list* susnote_list, + double* next_note_off_deadline) +{ + Usz start_removed, end_removed; + susnote_list_advance_time(susnote_list, time_elapsed, &start_removed, + &end_removed, next_note_off_deadline); + if (ORCA_UNLIKELY(start_removed != end_removed)) { + Susnote const* restrict susnotes_off = susnote_list->buffer; + send_midi_note_offs(oosc_dev, midi_mode, susnotes_off + start_removed, + susnotes_off + end_removed); + } +} + +staticni void ged_stop_all_sustained_notes(Ged* a) +{ + Susnote_list* sl = &a->susnote_list; + send_midi_note_offs(a->oosc_dev, &a->midi_mode, sl->buffer, + sl->buffer + sl->count); + susnote_list_clear(sl); + a->time_to_next_note_off = 1.0; } // The way orca handles MIDI sustains, timing, and overlapping note-ons (plus @@ -1071,895 +1116,942 @@ staticni void ged_stop_all_sustained_notes(Ged *a) { // seems redundant/weird", that's because it is, not because there's a good // reason. -staticni void send_output_events(Oosc_dev *oosc_dev, Midi_mode *midi_mode, - Usz bpm, Susnote_list *susnote_list, - Oevent const *events, Usz count) { - enum { Midi_on_capacity = 512 }; - typedef struct { - U8 channel; - U8 note_number; - U8 velocity; - } Midi_note_on; - typedef struct { - U8 note_number; - U8 velocity; - U8 duration; - } Midi_mono_on; - Midi_note_on midi_note_ons[Midi_on_capacity]; - Midi_mono_on midi_mono_ons[16]; // Keep only a single one per channel - Susnote new_susnotes[Midi_on_capacity]; - Usz midi_note_count = 0; - Usz monofied_chans = 0; // bitset of channels with new mono notes - double frame_secs = 60.0 / (double)bpm / 4.0; - - for (Usz i = 0; i < count; ++i) { - Oevent const *e = events + i; - switch ((Oevent_types)e->any.oevent_type) { - case Oevent_type_midi_note: { - if (midi_note_count == Midi_on_capacity) - break; - Oevent_midi_note const *em = &e->midi_note; - Usz note_number = (Usz)(12u * em->octave + em->note); - if (note_number > 127) - note_number = 127; - Usz channel = em->channel; - if (channel > 15) - break; - if (em->mono) { - // 'mono' note-ons are strange. The more typical branch you'd expect to - // see, where you can play multiple notes per channel, is below. - monofied_chans |= 1u << (channel & 0xFu); - midi_mono_ons[channel] = (Midi_mono_on){.note_number = (U8)note_number, - .velocity = em->velocity, - .duration = em->duration}; - } else { - midi_note_ons[midi_note_count] = - (Midi_note_on){.channel = (U8)channel, - .note_number = (U8)note_number, - .velocity = em->velocity}; - new_susnotes[midi_note_count] = - (Susnote){.remaining = (float)(frame_secs * (double)em->duration), - .chan_note = (U16)((channel << 8u) | note_number)}; - ++midi_note_count; - } - break; - } - case Oevent_type_midi_cc: { - Oevent_midi_cc const *ec = &e->midi_cc; - // Note that we're not preserving the exact order of MIDI events as - // emitted by the orca VM. Notes and CCs that are emitted in the same - // step will always have the CCs sent first. Not sure if this is OK or - // not. If it's not OK, we can either loop again a second time to always - // send CCs after notes, or if that's not also OK, we can make the stack - // buffer more complicated and interleave the CCs in it. - send_midi_chan_msg(oosc_dev, midi_mode, 0xb, ec->channel, ec->control, - ec->value); - break; - } - case Oevent_type_midi_pb: { - Oevent_midi_pb const *ep = &e->midi_pb; - // Same caveat regarding ordering with MIDI CC also applies here. - send_midi_chan_msg(oosc_dev, midi_mode, 0xe, ep->channel, ep->lsb, - ep->msb); - break; - } - case Oevent_type_osc_ints: { - // kinda lame - if (!oosc_dev) - continue; - Oevent_osc_ints const *eo = &e->osc_ints; - char path[] = {'/', eo->glyph, '\0'}; - I32 ints[ORCA_ARRAY_COUNTOF(eo->numbers)]; - Usz nnum = eo->count; - for (Usz inum = 0; inum < nnum; ++inum) { - ints[inum] = eo->numbers[inum]; - } - oosc_send_int32s(oosc_dev, path, ints, nnum); - break; - } - case Oevent_type_udp_string: { - if (!oosc_dev) - continue; - Oevent_udp_string const *eo = &e->udp_string; - oosc_send_datagram(oosc_dev, eo->chars, eo->count); - break; - } - } - } +staticni void send_output_events(Oosc_dev* oosc_dev, Midi_mode* midi_mode, + Usz bpm, Susnote_list* susnote_list, + Oevent const* events, Usz count) +{ + enum { Midi_on_capacity = 512 }; + typedef struct { + U8 channel; + U8 note_number; + U8 velocity; + } Midi_note_on; + typedef struct { + U8 note_number; + U8 velocity; + U8 duration; + } Midi_mono_on; + Midi_note_on midi_note_ons[Midi_on_capacity]; + Midi_mono_on midi_mono_ons[16]; // Keep only a single one per channel + Susnote new_susnotes[Midi_on_capacity]; + Usz midi_note_count = 0; + Usz monofied_chans = 0; // bitset of channels with new mono notes + double frame_secs = 60.0 / (double)bpm / 4.0; + + for (Usz i = 0; i < count; ++i) { + Oevent const* e = events + i; + switch ((Oevent_types)e->any.oevent_type) { + case Oevent_type_midi_note: { + if (midi_note_count == Midi_on_capacity) + break; + Oevent_midi_note const* em = &e->midi_note; + Usz note_number = (Usz)(12u * em->octave + em->note); + if (note_number > 127) + note_number = 127; + Usz channel = em->channel; + if (channel > 15) + break; + if (em->mono) { + // 'mono' note-ons are strange. The more typical branch you'd + // expect to see, where you can play multiple notes per + // channel, is below. + monofied_chans |= 1u << (channel & 0xFu); + midi_mono_ons[channel] = (Midi_mono_on) { + .note_number = (U8)note_number, + .velocity = em->velocity, + .duration = em->duration + }; + } else { + midi_note_ons[midi_note_count] = (Midi_note_on) { + .channel = (U8)channel, + .note_number = (U8)note_number, + .velocity = em->velocity + }; + new_susnotes[midi_note_count] = (Susnote) { + .remaining = (float)(frame_secs * (double)em->duration), + .chan_note = (U16)((channel << 8u) | note_number) + }; + ++midi_note_count; + } + break; + } + case Oevent_type_midi_cc: { + Oevent_midi_cc const* ec = &e->midi_cc; + // Note that we're not preserving the exact order of MIDI events as + // emitted by the orca VM. Notes and CCs that are emitted in the + // same step will always have the CCs sent first. Not sure if this + // is OK or not. If it's not OK, we can either loop again a second + // time to always send CCs after notes, or if that's not also OK, + // we can make the stack buffer more complicated and interleave the + // CCs in it. + send_midi_chan_msg(oosc_dev, midi_mode, 0xb, ec->channel, + ec->control, ec->value); + break; + } + case Oevent_type_midi_pb: { + Oevent_midi_pb const* ep = &e->midi_pb; + // Same caveat regarding ordering with MIDI CC also applies here. + send_midi_chan_msg(oosc_dev, midi_mode, 0xe, ep->channel, ep->lsb, + ep->msb); + break; + } + case Oevent_type_osc_ints: { + // kinda lame + if (!oosc_dev) + continue; + Oevent_osc_ints const* eo = &e->osc_ints; + char path[] = { '/', eo->glyph, '\0' }; + I32 ints[ORCA_ARRAY_COUNTOF(eo->numbers)]; + Usz nnum = eo->count; + for (Usz inum = 0; inum < nnum; ++inum) { + ints[inum] = eo->numbers[inum]; + } + oosc_send_int32s(oosc_dev, path, ints, nnum); + break; + } + case Oevent_type_udp_string: { + if (!oosc_dev) + continue; + Oevent_udp_string const* eo = &e->udp_string; + oosc_send_datagram(oosc_dev, eo->chars, eo->count); + break; + } + } + } do_note_ons: - if (midi_note_count > 0) { - Usz start_note_offs, end_note_offs; - susnote_list_add_notes(susnote_list, new_susnotes, midi_note_count, - &start_note_offs, &end_note_offs); - if (start_note_offs != end_note_offs) { - Susnote const *restrict susnotes_off = susnote_list->buffer; - send_midi_note_offs(oosc_dev, midi_mode, susnotes_off + start_note_offs, - susnotes_off + end_note_offs); - } - for (Usz i = 0; i < midi_note_count; ++i) { - Midi_note_on mno = midi_note_ons[i]; - send_midi_chan_msg(oosc_dev, midi_mode, 0x9, mno.channel, mno.note_number, - mno.velocity); - } - } - if (monofied_chans) { - // The behavior we end up with is that if regular note-ons are played in - // the same frame/step as a mono, the regular note-ons will have the actual - // MIDI note on sent, followed immediately by a MIDI note off. I don't know - // if this is good or not. - Usz start_note_offs, end_note_offs; - susnote_list_remove_by_chan_mask(susnote_list, monofied_chans, - &start_note_offs, &end_note_offs); - if (start_note_offs != end_note_offs) { - Susnote const *restrict susnotes_off = susnote_list->buffer; - send_midi_note_offs(oosc_dev, midi_mode, susnotes_off + start_note_offs, - susnotes_off + end_note_offs); - } - midi_note_count = 0; // We're going to use this list again. Reset it. - for (Usz i = 0; i < 16; i++) { // Add these notes to list of note-ons - if (!(monofied_chans & 1u << i)) - continue; - midi_note_ons[midi_note_count] = - (Midi_note_on){.channel = (U8)i, - .note_number = midi_mono_ons[i].note_number, - .velocity = midi_mono_ons[i].velocity}; - new_susnotes[midi_note_count] = (Susnote){ - .remaining = (float)(frame_secs * (double)midi_mono_ons[i].duration), - .chan_note = (U16)((i << 8u) | midi_mono_ons[i].note_number)}; - midi_note_count++; - } - monofied_chans = false; - goto do_note_ons; // lol super wasteful for doing susnotes again - } -} - -staticni void ged_clear_osc_udp(Ged *a) { - if (a->oosc_dev) { - if (a->midi_mode.any.type == Midi_mode_type_osc_bidule) { - ged_stop_all_sustained_notes(a); - } - oosc_dev_destroy(a->oosc_dev); - a->oosc_dev = NULL; - } + if (midi_note_count > 0) { + Usz start_note_offs, end_note_offs; + susnote_list_add_notes(susnote_list, new_susnotes, midi_note_count, + &start_note_offs, &end_note_offs); + if (start_note_offs != end_note_offs) { + Susnote const* restrict susnotes_off = susnote_list->buffer; + send_midi_note_offs(oosc_dev, midi_mode, + susnotes_off + start_note_offs, + susnotes_off + end_note_offs); + } + for (Usz i = 0; i < midi_note_count; ++i) { + Midi_note_on mno = midi_note_ons[i]; + send_midi_chan_msg(oosc_dev, midi_mode, 0x9, mno.channel, + mno.note_number, mno.velocity); + } + } + if (monofied_chans) { + // The behavior we end up with is that if regular note-ons are played + // in the same frame/step as a mono, the regular note-ons will have the + // actual MIDI note on sent, followed immediately by a MIDI note off. I + // don't know if this is good or not. + Usz start_note_offs, end_note_offs; + susnote_list_remove_by_chan_mask(susnote_list, monofied_chans, + &start_note_offs, &end_note_offs); + if (start_note_offs != end_note_offs) { + Susnote const* restrict susnotes_off = susnote_list->buffer; + send_midi_note_offs(oosc_dev, midi_mode, + susnotes_off + start_note_offs, + susnotes_off + end_note_offs); + } + midi_note_count = 0; // We're going to use this list again. Reset it. + for (Usz i = 0; i < 16; i++) { // Add these notes to list of note-ons + if (!(monofied_chans & 1u << i)) + continue; + midi_note_ons[midi_note_count] = (Midi_note_on) { + .channel = (U8)i, + .note_number = midi_mono_ons[i].note_number, + .velocity = midi_mono_ons[i].velocity + }; + new_susnotes[midi_note_count] = (Susnote) { + .remaining = (float)(frame_secs + * (double)midi_mono_ons[i].duration), + .chan_note = (U16)((i << 8u) | midi_mono_ons[i].note_number) + }; + midi_note_count++; + } + monofied_chans = false; + goto do_note_ons; // lol super wasteful for doing susnotes again + } } -static bool ged_is_using_osc_udp(Ged *a) { return (bool)a->oosc_dev; } -static bool ged_set_osc_udp(Ged *a, char const *dest_addr, - char const *dest_port) { - ged_clear_osc_udp(a); - if (dest_port) { - Oosc_udp_create_error err = - oosc_dev_create_udp(&a->oosc_dev, dest_addr, dest_port); - if (err) { - return false; + +staticni void ged_clear_osc_udp(Ged* a) +{ + if (a->oosc_dev) { + if (a->midi_mode.any.type == Midi_mode_type_osc_bidule) { + ged_stop_all_sustained_notes(a); + } + oosc_dev_destroy(a->oosc_dev); + a->oosc_dev = NULL; + } +} +static bool ged_is_using_osc_udp(Ged* a) { return (bool)a->oosc_dev; } +static bool ged_set_osc_udp(Ged* a, char const* dest_addr, + char const* dest_port) +{ + ged_clear_osc_udp(a); + if (dest_port) { + Oosc_udp_create_error err = oosc_dev_create_udp(&a->oosc_dev, + dest_addr, dest_port); + if (err) { + return false; + } } - } - return true; + return true; } static ORCA_FORCEINLINE double ms_to_sec(double ms) { return ms / 1000.0; } -static double ged_secs_to_deadline(Ged const *a) { - if (!a->is_playing) - return 1.0; - double secs_span = 60.0 / (double)a->bpm / 4.0; - // If MIDI beat clock output is enabled, we need to send an event every 24 - // parts per quarter note. Since we've already divided quarter notes into 4 - // for ORCA's timing semantics, divide it by a further 6. This same logic is - // mirrored in ged_do_stuff(). - if (a->midi_bclock) - secs_span /= 6.0; - double rem = secs_span - (stm_sec(stm_since(a->clock)) + a->accum_secs); - double next_note_off = a->time_to_next_note_off; - if (next_note_off < rem) - rem = next_note_off; - if (rem < 0.0) - rem = 0.0; - return rem; -} - -staticni void clear_and_run_vm(Glyph *restrict gbuf, Mark *restrict mbuf, - Usz height, Usz width, Usz tick_number, - Oevent_list *oevent_list, Usz random_seed) { - mbuffer_clear(mbuf, height, width); - oevent_list_clear(oevent_list); - orca_run(gbuf, mbuf, height, width, tick_number, oevent_list, random_seed); +static double ged_secs_to_deadline(Ged const* a) +{ + if (!a->is_playing) + return 1.0; + double secs_span = 60.0 / (double)a->bpm / 4.0; + // If MIDI beat clock output is enabled, we need to send an event every 24 + // parts per quarter note. Since we've already divided quarter notes into 4 + // for ORCA's timing semantics, divide it by a further 6. This same logic + // is mirrored in ged_do_stuff(). + if (a->midi_bclock) + secs_span /= 6.0; + double rem = secs_span - (stm_sec(stm_since(a->clock)) + a->accum_secs); + double next_note_off = a->time_to_next_note_off; + if (next_note_off < rem) + rem = next_note_off; + if (rem < 0.0) + rem = 0.0; + return rem; } -staticni void ged_do_stuff(Ged *a) { - if (!a->is_playing) - return; - double secs_span = 60.0 / (double)a->bpm / 4.0; - if (a->midi_bclock) // see also ged_secs_to_deadline() - secs_span /= 6.0; - Oosc_dev *oosc_dev = a->oosc_dev; - Midi_mode *midi_mode = &a->midi_mode; - bool crossed_deadline = false; +staticni void clear_and_run_vm(Glyph* restrict gbuf, Mark* restrict mbuf, + Usz height, Usz width, Usz tick_number, + Oevent_list* oevent_list, Usz random_seed) +{ + mbuffer_clear(mbuf, height, width); + oevent_list_clear(oevent_list); + orca_run(gbuf, mbuf, height, width, tick_number, oevent_list, random_seed); +} + +staticni void ged_do_stuff(Ged* a) +{ + if (!a->is_playing) + return; + double secs_span = 60.0 / (double)a->bpm / 4.0; + if (a->midi_bclock) // see also ged_secs_to_deadline() + secs_span /= 6.0; + Oosc_dev* oosc_dev = a->oosc_dev; + Midi_mode* midi_mode = &a->midi_mode; + bool crossed_deadline = false; #if TIME_DEBUG - Usz spins = 0; - U64 spin_start = stm_now(); - (void)spin_start; + Usz spins = 0; + U64 spin_start = stm_now(); + (void)spin_start; #endif - for (;;) { - U64 now = stm_now(); - U64 diff = stm_diff(now, a->clock); - double sdiff = stm_sec(diff) + a->accum_secs; - if (sdiff >= secs_span) { - a->clock = now; - a->accum_secs = sdiff - secs_span; + for (;;) { + U64 now = stm_now(); + U64 diff = stm_diff(now, a->clock); + double sdiff = stm_sec(diff) + a->accum_secs; + if (sdiff >= secs_span) { + a->clock = now; + a->accum_secs = sdiff - secs_span; #if TIME_DEBUG - if (a->accum_secs > 0.000001) { - fprintf(stderr, "late: %.2f u-secs\n", a->accum_secs * 1000 * 1000); - if (a->accum_secs > 0.00005) { - fprintf(stderr, "guilty timeout: %d\n", spin_track_timeout); - } - } + if (a->accum_secs > 0.000001) { + fprintf(stderr, "late: %.2f u-secs\n", + a->accum_secs * 1000 * 1000); + if (a->accum_secs > 0.00005) { + fprintf(stderr, "guilty timeout: %d\n", + spin_track_timeout); + } + } #endif - crossed_deadline = true; - break; - } - if (secs_span - sdiff > ms_to_sec(0.1)) - break; + crossed_deadline = true; + break; + } + if (secs_span - sdiff > ms_to_sec(0.1)) + break; #if TIME_DEBUG - ++spins; + ++spins; #endif - } + } #if TIME_DEBUG - if (spins > 0) { - fprintf(stderr, "%d spins in %f us with timeout %d\n", (int)spins, - stm_us(stm_since(spin_start)), spin_track_timeout); - } + if (spins > 0) { + fprintf(stderr, "%d spins in %f us with timeout %d\n", (int)spins, + stm_us(stm_since(spin_start)), spin_track_timeout); + } #endif - if (!crossed_deadline) - return; - if (a->midi_bclock) { - send_midi_byte(oosc_dev, midi_mode, 0xF8); // MIDI beat clock - Usz sixths = a->midi_bclock_sixths; - a->midi_bclock_sixths = (U8)((sixths + 1) % 6); - if (sixths != 0) - return; - } - apply_time_to_sustained_notes(oosc_dev, midi_mode, secs_span, - &a->susnote_list, &a->time_to_next_note_off); - clear_and_run_vm(a->field.buffer, a->mbuf_r.buffer, a->field.height, - a->field.width, a->tick_num, &a->oevent_list, - a->random_seed); - ++a->tick_num; - a->needs_remarking = true; - a->is_draw_dirty = true; - - Usz count = a->oevent_list.count; - if (count > 0) { - send_output_events(oosc_dev, midi_mode, a->bpm, &a->susnote_list, - a->oevent_list.buffer, count); - a->activity_counter += count; - } -} - -static inline Isz isz_clamp(Isz x, Isz low, Isz high) { - return x < low ? low : x > high ? high : x; + if (!crossed_deadline) + return; + if (a->midi_bclock) { + send_midi_byte(oosc_dev, midi_mode, 0xF8); // MIDI beat clock + Usz sixths = a->midi_bclock_sixths; + a->midi_bclock_sixths = (U8)((sixths + 1) % 6); + if (sixths != 0) + return; + } + apply_time_to_sustained_notes(oosc_dev, midi_mode, secs_span, + &a->susnote_list, &a->time_to_next_note_off); + clear_and_run_vm(a->field.buffer, a->mbuf_r.buffer, a->field.height, + a->field.width, a->tick_num, &a->oevent_list, + a->random_seed); + ++a->tick_num; + a->needs_remarking = true; + a->is_draw_dirty = true; + + Usz count = a->oevent_list.count; + if (count > 0) { + send_output_events(oosc_dev, midi_mode, a->bpm, &a->susnote_list, + a->oevent_list.buffer, count); + a->activity_counter += count; + } +} + +static inline Isz isz_clamp(Isz x, Isz low, Isz high) +{ + return x < low ? low : x > high ? high : x; } // todo cleanup to use proper unsigned/signed w/ overflow check staticni Isz scroll_offset_on_axis_for_cursor_pos(Isz win_len, Isz cont_len, Isz cursor_pos, Isz pad, - Isz cur_scroll) { - if (win_len <= 0 || cont_len <= 0) - return 0; - if (cont_len <= win_len) - return -((win_len - cont_len) / 2); - if (pad * 2 >= win_len) { - pad = (win_len - 1) / 2; - } - Isz min_vis_scroll = cursor_pos - win_len + 1 + pad; - Isz max_vis_scroll = cursor_pos - pad; - Isz new_scroll; - if (cur_scroll < min_vis_scroll) - new_scroll = min_vis_scroll; - else if (cur_scroll > max_vis_scroll) - new_scroll = max_vis_scroll; - else - new_scroll = cur_scroll; - return isz_clamp(new_scroll, 0, cont_len - win_len); -} - -staticni void ged_make_cursor_visible(Ged *a) { - int grid_h = a->grid_h; - int cur_scr_y = a->grid_scroll_y; - int cur_scr_x = a->grid_scroll_x; - int new_scr_y = (int)scroll_offset_on_axis_for_cursor_pos( - grid_h, (Isz)a->field.height, (Isz)a->ged_cursor.y, 5, cur_scr_y); - int new_scr_x = (int)scroll_offset_on_axis_for_cursor_pos( - a->win_w, (Isz)a->field.width, (Isz)a->ged_cursor.x, 5, cur_scr_x); - if (new_scr_y == cur_scr_y && new_scr_x == cur_scr_x) - return; - a->grid_scroll_y = new_scr_y; - a->grid_scroll_x = new_scr_x; - a->is_draw_dirty = true; + Isz cur_scroll) +{ + if (win_len <= 0 || cont_len <= 0) + return 0; + if (cont_len <= win_len) + return -((win_len - cont_len) / 2); + if (pad * 2 >= win_len) { + pad = (win_len - 1) / 2; + } + Isz min_vis_scroll = cursor_pos - win_len + 1 + pad; + Isz max_vis_scroll = cursor_pos - pad; + Isz new_scroll; + if (cur_scroll < min_vis_scroll) + new_scroll = min_vis_scroll; + else if (cur_scroll > max_vis_scroll) + new_scroll = max_vis_scroll; + else + new_scroll = cur_scroll; + return isz_clamp(new_scroll, 0, cont_len - win_len); +} + +staticni void ged_make_cursor_visible(Ged* a) +{ + int grid_h = a->grid_h; + int cur_scr_y = a->grid_scroll_y; + int cur_scr_x = a->grid_scroll_x; + int new_scr_y = (int)scroll_offset_on_axis_for_cursor_pos( + grid_h, (Isz)a->field.height, (Isz)a->ged_cursor.y, 5, cur_scr_y); + int new_scr_x = (int)scroll_offset_on_axis_for_cursor_pos( + a->win_w, (Isz)a->field.width, (Isz)a->ged_cursor.x, 5, cur_scr_x); + if (new_scr_y == cur_scr_y && new_scr_x == cur_scr_x) + return; + a->grid_scroll_y = new_scr_y; + a->grid_scroll_x = new_scr_x; + a->is_draw_dirty = true; } enum { Hud_height = 2 }; -staticni void ged_update_internal_geometry(Ged *a) { - int win_h = a->win_h; - int softmargin_y = a->softmargin_y; - bool show_hud = win_h > Hud_height + 1; - int grid_h = show_hud ? win_h - Hud_height : win_h; - if (grid_h > a->field.height) { - int halfy = (grid_h - a->field.height + 1) / 2; - grid_h -= halfy < softmargin_y ? halfy : softmargin_y; - } - a->grid_h = grid_h; - a->is_draw_dirty = true; - a->is_hud_visible = show_hud; -} - -staticni void ged_set_window_size(Ged *a, int win_h, int win_w, - int softmargin_y, int softmargin_x) { - if (a->win_h == win_h && a->win_w == win_w && - a->softmargin_y == softmargin_y && a->softmargin_x == softmargin_x) - return; - a->win_h = win_h; - a->win_w = win_w; - a->softmargin_y = softmargin_y; - a->softmargin_x = softmargin_x; - ged_update_internal_geometry(a); - ged_make_cursor_visible(a); -} - -staticni void ged_draw(Ged *a, WINDOW *win, char const *filename, - bool use_fancy_dots, bool use_fancy_rulers) { - // We can predictavely step the next simulation tick and then use the - // resulting mark buffer for better UI visualization. If we don't do this, - // after loading a fresh file or after the user performs some edit (or even - // after a regular simulation step), the new glyph buffer won't have had - // phase 0 of the simulation run, which means the ports and other flags won't - // be set on the mark buffer, so the colors for disabled cells, ports, etc. - // won't be set. - // - // We can just perform a simulation step using the current state, keep the - // mark buffer that it produces, then roll back the glyph buffer to where it - // was before. This should produce results similar to having specialized UI - // code that looks at each glyph and figures out the ports, etc. - if (a->needs_remarking && !a->is_playing) { - field_resize_raw_if_necessary(&a->scratch_field, a->field.height, - a->field.width); - field_copy(&a->field, &a->scratch_field); - mbuf_reusable_ensure_size(&a->mbuf_r, a->field.height, a->field.width); - clear_and_run_vm(a->scratch_field.buffer, a->mbuf_r.buffer, a->field.height, - a->field.width, a->tick_num, &a->scratch_oevent_list, - a->random_seed); - a->needs_remarking = false; - } - int win_w = a->win_w; - draw_glyphs_grid_scrolled( - win, 0, 0, a->grid_h, win_w, a->field.buffer, a->mbuf_r.buffer, - a->field.height, a->field.width, a->grid_scroll_y, a->grid_scroll_x, - a->ruler_spacing_y, a->ruler_spacing_x, use_fancy_dots, use_fancy_rulers); - draw_grid_cursor(win, 0, 0, a->grid_h, win_w, a->field.buffer, - a->field.height, a->field.width, a->grid_scroll_y, - a->grid_scroll_x, a->ged_cursor.y, a->ged_cursor.x, - a->ged_cursor.h, a->ged_cursor.w, a->input_mode, - a->is_playing); - if (a->is_hud_visible) { - filename = filename ? filename : "unnamed"; - int hud_x = win_w > 50 + a->softmargin_x * 2 ? a->softmargin_x : 0; - draw_hud(win, a->grid_h, hud_x, Hud_height, win_w, filename, - a->field.height, a->field.width, a->ruler_spacing_y, - a->ruler_spacing_x, a->tick_num, a->bpm, &a->ged_cursor, - a->input_mode, a->activity_counter); - } - if (a->draw_event_list) - draw_oevent_list(win, &a->oevent_list); - a->is_draw_dirty = false; -} - -staticni void ged_send_osc_bpm(Ged *a, I32 bpm) { - send_num_message(a->oosc_dev, "/orca/bpm", bpm); -} - -staticni void ged_adjust_bpm(Ged *a, Isz delta_bpm) { - Isz new_bpm = (Isz)a->bpm; - if (delta_bpm < 0 || new_bpm < INT_MAX - delta_bpm) - new_bpm += delta_bpm; - else - new_bpm = INT_MAX; - if (new_bpm < 1) - new_bpm = 1; - if ((Usz)new_bpm != a->bpm) { - a->bpm = (Usz)new_bpm; +staticni void ged_update_internal_geometry(Ged* a) +{ + int win_h = a->win_h; + int softmargin_y = a->softmargin_y; + bool show_hud = win_h > Hud_height + 1; + int grid_h = show_hud ? win_h - Hud_height : win_h; + if (grid_h > a->field.height) { + int halfy = (grid_h - a->field.height + 1) / 2; + grid_h -= halfy < softmargin_y ? halfy : softmargin_y; + } + a->grid_h = grid_h; a->is_draw_dirty = true; - ged_send_osc_bpm(a, (I32)new_bpm); - } + a->is_hud_visible = show_hud; +} + +staticni void ged_set_window_size(Ged* a, int win_h, int win_w, + int softmargin_y, int softmargin_x) +{ + if (a->win_h == win_h && a->win_w == win_w + && a->softmargin_y == softmargin_y && a->softmargin_x == softmargin_x) + return; + a->win_h = win_h; + a->win_w = win_w; + a->softmargin_y = softmargin_y; + a->softmargin_x = softmargin_x; + ged_update_internal_geometry(a); + ged_make_cursor_visible(a); +} + +staticni void ged_draw(Ged* a, WINDOW* win, char const* filename, + bool use_fancy_dots, bool use_fancy_rulers) +{ + // We can predictavely step the next simulation tick and then use the + // resulting mark buffer for better UI visualization. If we don't do this, + // after loading a fresh file or after the user performs some edit (or even + // after a regular simulation step), the new glyph buffer won't have had + // phase 0 of the simulation run, which means the ports and other flags + // won't be set on the mark buffer, so the colors for disabled cells, + // ports, etc. won't be set. + // + // We can just perform a simulation step using the current state, keep the + // mark buffer that it produces, then roll back the glyph buffer to where + // it was before. This should produce results similar to having specialized + // UI code that looks at each glyph and figures out the ports, etc. + if (a->needs_remarking && !a->is_playing) { + field_resize_raw_if_necessary(&a->scratch_field, a->field.height, + a->field.width); + field_copy(&a->field, &a->scratch_field); + mbuf_reusable_ensure_size(&a->mbuf_r, a->field.height, a->field.width); + clear_and_run_vm(a->scratch_field.buffer, a->mbuf_r.buffer, + a->field.height, a->field.width, a->tick_num, + &a->scratch_oevent_list, a->random_seed); + a->needs_remarking = false; + } + int win_w = a->win_w; + draw_glyphs_grid_scrolled( + win, 0, 0, a->grid_h, win_w, a->field.buffer, a->mbuf_r.buffer, + a->field.height, a->field.width, a->grid_scroll_y, a->grid_scroll_x, + a->ruler_spacing_y, a->ruler_spacing_x, use_fancy_dots, + use_fancy_rulers); + draw_grid_cursor(win, 0, 0, a->grid_h, win_w, a->field.buffer, + a->field.height, a->field.width, a->grid_scroll_y, + a->grid_scroll_x, a->ged_cursor.y, a->ged_cursor.x, + a->ged_cursor.h, a->ged_cursor.w, a->input_mode, + a->is_playing); + if (a->is_hud_visible) { + filename = filename ? filename : "unnamed"; + int hud_x = win_w > 50 + a->softmargin_x * 2 ? a->softmargin_x : 0; + draw_hud(win, a->grid_h, hud_x, Hud_height, win_w, filename, + a->field.height, a->field.width, a->ruler_spacing_y, + a->ruler_spacing_x, a->tick_num, a->bpm, &a->ged_cursor, + a->input_mode, a->activity_counter); + } + if (a->draw_event_list) + draw_oevent_list(win, &a->oevent_list); + a->is_draw_dirty = false; +} + +staticni void ged_send_osc_bpm(Ged* a, I32 bpm) +{ + send_num_message(a->oosc_dev, "/orca/bpm", bpm); +} + +staticni void ged_adjust_bpm(Ged* a, Isz delta_bpm) +{ + Isz new_bpm = (Isz)a->bpm; + if (delta_bpm < 0 || new_bpm < INT_MAX - delta_bpm) + new_bpm += delta_bpm; + else + new_bpm = INT_MAX; + if (new_bpm < 1) + new_bpm = 1; + if ((Usz)new_bpm != a->bpm) { + a->bpm = (Usz)new_bpm; + a->is_draw_dirty = true; + ged_send_osc_bpm(a, (I32)new_bpm); + } } -static void ged_move_cursor_relative(Ged *a, Isz delta_y, Isz delta_x) { - ged_cursor_move_relative(&a->ged_cursor, a->field.height, a->field.width, - delta_y, delta_x); - ged_make_cursor_visible(a); - a->is_draw_dirty = true; +static void ged_move_cursor_relative(Ged* a, Isz delta_y, Isz delta_x) +{ + ged_cursor_move_relative(&a->ged_cursor, a->field.height, a->field.width, + delta_y, delta_x); + ged_make_cursor_visible(a); + a->is_draw_dirty = true; } -static Usz guarded_selection_axis_resize(Usz x, int delta) { - if (delta < 0) { - if (delta > INT_MIN && (Usz)(-delta) < x) { - x -= (Usz)(-delta); +static Usz guarded_selection_axis_resize(Usz x, int delta) +{ + if (delta < 0) { + if (delta > INT_MIN && (Usz)(-delta) < x) { + x -= (Usz)(-delta); + } + } else if (x < SIZE_MAX - (Usz)delta) { + x += (Usz)delta; } - } else if (x < SIZE_MAX - (Usz)delta) { - x += (Usz)delta; - } - return x; + return x; } -staticni void ged_modify_selection_size(Ged *a, int delta_y, int delta_x) { - Usz cur_h = a->ged_cursor.h, cur_w = a->ged_cursor.w; - Usz new_h = guarded_selection_axis_resize(cur_h, delta_y); - Usz new_w = guarded_selection_axis_resize(cur_w, delta_x); - if (cur_h != new_h || cur_w != new_w) { - a->ged_cursor.h = new_h; - a->ged_cursor.w = new_w; - a->is_draw_dirty = true; - } +staticni void ged_modify_selection_size(Ged* a, int delta_y, int delta_x) +{ + Usz cur_h = a->ged_cursor.h, cur_w = a->ged_cursor.w; + Usz new_h = guarded_selection_axis_resize(cur_h, delta_y); + Usz new_w = guarded_selection_axis_resize(cur_w, delta_x); + if (cur_h != new_h || cur_w != new_w) { + a->ged_cursor.h = new_h; + a->ged_cursor.w = new_w; + a->is_draw_dirty = true; + } } -staticni bool ged_try_selection_clipped_to_field(Ged const *a, Usz *out_y, - Usz *out_x, Usz *out_h, - Usz *out_w) { - Usz curs_y = a->ged_cursor.y, curs_x = a->ged_cursor.x; - Usz curs_h = a->ged_cursor.h, curs_w = a->ged_cursor.w; - Usz field_h = a->field.height, field_w = a->field.width; - if (curs_y >= field_h || curs_x >= field_w) - return false; - if (field_h - curs_y < curs_h) - curs_h = field_h - curs_y; - if (field_w - curs_x < curs_w) - curs_w = field_w - curs_x; - *out_y = curs_y; - *out_x = curs_x; - *out_h = curs_h; - *out_w = curs_w; - return true; -} - -staticni bool ged_slide_selection(Ged *a, int delta_y, int delta_x) { - Usz curs_y_0, curs_x_0, curs_h_0, curs_w_0; - Usz curs_y_1, curs_x_1, curs_h_1, curs_w_1; - if (!ged_try_selection_clipped_to_field(a, &curs_y_0, &curs_x_0, &curs_h_0, - &curs_w_0)) - return false; - ged_move_cursor_relative(a, delta_y, delta_x); - if (!ged_try_selection_clipped_to_field(a, &curs_y_1, &curs_x_1, &curs_h_1, - &curs_w_1)) - return false; - // Don't create a history entry if nothing is going to happen. - if (curs_y_0 == curs_y_1 && curs_x_0 == curs_x_1 && curs_h_0 == curs_h_1 && - curs_w_0 == curs_w_1) - return false; - undo_history_push(&a->undo_hist, &a->field, a->tick_num); - Usz field_h = a->field.height; - Usz field_w = a->field.width; - gbuffer_copy_subrect(a->field.buffer, a->field.buffer, field_h, field_w, - field_h, field_w, curs_y_0, curs_x_0, curs_y_1, curs_x_1, - curs_h_0, curs_w_0); - // Erase/clear the area that was within the selection rectangle in the - // starting position, but wasn't written to during the copy. (In other words, - // this is the area that was 'left behind' when we moved the selection - // rectangle, plus any area that was along the bottom and right edge of the - // field that didn't have anything to copy to it when the selection rectangle - // extended outside of the field.) - Usz ey, eh, ex, ew; - if (curs_y_1 > curs_y_0) { - ey = curs_y_0; - eh = curs_y_1 - curs_y_0; - } else { - ey = curs_y_1 + curs_h_0; - eh = (curs_y_0 + curs_h_0) - ey; - } - if (curs_x_1 > curs_x_0) { - ex = curs_x_0; - ew = curs_x_1 - curs_x_0; - } else { - ex = curs_x_1 + curs_w_0; - ew = (curs_x_0 + curs_w_0) - ex; - } - gbuffer_fill_subrect(a->field.buffer, field_h, field_w, ey, curs_x_0, eh, - curs_w_0, '.'); - gbuffer_fill_subrect(a->field.buffer, field_h, field_w, curs_y_0, ex, - curs_h_0, ew, '.'); - a->needs_remarking = true; - return true; +staticni bool ged_try_selection_clipped_to_field(Ged const* a, Usz* out_y, + Usz* out_x, Usz* out_h, + Usz* out_w) +{ + Usz curs_y = a->ged_cursor.y, curs_x = a->ged_cursor.x; + Usz curs_h = a->ged_cursor.h, curs_w = a->ged_cursor.w; + Usz field_h = a->field.height, field_w = a->field.width; + if (curs_y >= field_h || curs_x >= field_w) + return false; + if (field_h - curs_y < curs_h) + curs_h = field_h - curs_y; + if (field_w - curs_x < curs_w) + curs_w = field_w - curs_x; + *out_y = curs_y; + *out_x = curs_x; + *out_h = curs_h; + *out_w = curs_w; + return true; +} + +staticni bool ged_slide_selection(Ged* a, int delta_y, int delta_x) +{ + Usz curs_y_0, curs_x_0, curs_h_0, curs_w_0; + Usz curs_y_1, curs_x_1, curs_h_1, curs_w_1; + if (!ged_try_selection_clipped_to_field(a, &curs_y_0, &curs_x_0, &curs_h_0, + &curs_w_0)) + return false; + ged_move_cursor_relative(a, delta_y, delta_x); + if (!ged_try_selection_clipped_to_field(a, &curs_y_1, &curs_x_1, &curs_h_1, + &curs_w_1)) + return false; + // Don't create a history entry if nothing is going to happen. + if (curs_y_0 == curs_y_1 && curs_x_0 == curs_x_1 && curs_h_0 == curs_h_1 + && curs_w_0 == curs_w_1) + return false; + undo_history_push(&a->undo_hist, &a->field, a->tick_num); + Usz field_h = a->field.height; + Usz field_w = a->field.width; + gbuffer_copy_subrect(a->field.buffer, a->field.buffer, field_h, field_w, + field_h, field_w, curs_y_0, curs_x_0, curs_y_1, + curs_x_1, curs_h_0, curs_w_0); + // Erase/clear the area that was within the selection rectangle in the + // starting position, but wasn't written to during the copy. (In other + // words, this is the area that was 'left behind' when we moved the + // selection rectangle, plus any area that was along the bottom and right + // edge of the field that didn't have anything to copy to it when the + // selection rectangle extended outside of the field.) + Usz ey, eh, ex, ew; + if (curs_y_1 > curs_y_0) { + ey = curs_y_0; + eh = curs_y_1 - curs_y_0; + } else { + ey = curs_y_1 + curs_h_0; + eh = (curs_y_0 + curs_h_0) - ey; + } + if (curs_x_1 > curs_x_0) { + ex = curs_x_0; + ew = curs_x_1 - curs_x_0; + } else { + ex = curs_x_1 + curs_w_0; + ew = (curs_x_0 + curs_w_0) - ex; + } + gbuffer_fill_subrect(a->field.buffer, field_h, field_w, ey, curs_x_0, eh, + curs_w_0, '.'); + gbuffer_fill_subrect(a->field.buffer, field_h, field_w, curs_y_0, ex, + curs_h_0, ew, '.'); + a->needs_remarking = true; + return true; } typedef enum { - Ged_dir_up, - Ged_dir_down, - Ged_dir_left, - Ged_dir_right, + Ged_dir_up, + Ged_dir_down, + Ged_dir_left, + Ged_dir_right, } Ged_dir; -staticni void ged_dir_input(Ged *a, Ged_dir dir, int step_length) { - switch (a->input_mode) { - case Ged_input_mode_normal: - case Ged_input_mode_append: - switch (dir) { - case Ged_dir_up: - ged_move_cursor_relative(a, -step_length, 0); - break; - case Ged_dir_down: - ged_move_cursor_relative(a, step_length, 0); - break; - case Ged_dir_left: - ged_move_cursor_relative(a, 0, -step_length); - break; - case Ged_dir_right: - ged_move_cursor_relative(a, 0, step_length); - break; - } - break; - case Ged_input_mode_selresize: - switch (dir) { - case Ged_dir_up: - ged_modify_selection_size(a, -step_length, 0); - break; - case Ged_dir_down: - ged_modify_selection_size(a, step_length, 0); - break; - case Ged_dir_left: - ged_modify_selection_size(a, 0, -step_length); - break; - case Ged_dir_right: - ged_modify_selection_size(a, 0, step_length); - break; - } - break; - case Ged_input_mode_slide: - switch (dir) { - case Ged_dir_up: - ged_slide_selection(a, -step_length, 0); - break; - case Ged_dir_down: - ged_slide_selection(a, step_length, 0); - break; - case Ged_dir_left: - ged_slide_selection(a, 0, -step_length); - break; - case Ged_dir_right: - ged_slide_selection(a, 0, step_length); - break; - } - break; - } +staticni void ged_dir_input(Ged* a, Ged_dir dir, int step_length) +{ + switch (a->input_mode) { + case Ged_input_mode_normal: + case Ged_input_mode_append: + switch (dir) { + case Ged_dir_up: + ged_move_cursor_relative(a, -step_length, 0); + break; + case Ged_dir_down: + ged_move_cursor_relative(a, step_length, 0); + break; + case Ged_dir_left: + ged_move_cursor_relative(a, 0, -step_length); + break; + case Ged_dir_right: + ged_move_cursor_relative(a, 0, step_length); + break; + } + break; + case Ged_input_mode_selresize: + switch (dir) { + case Ged_dir_up: + ged_modify_selection_size(a, -step_length, 0); + break; + case Ged_dir_down: + ged_modify_selection_size(a, step_length, 0); + break; + case Ged_dir_left: + ged_modify_selection_size(a, 0, -step_length); + break; + case Ged_dir_right: + ged_modify_selection_size(a, 0, step_length); + break; + } + break; + case Ged_input_mode_slide: + switch (dir) { + case Ged_dir_up: + ged_slide_selection(a, -step_length, 0); + break; + case Ged_dir_down: + ged_slide_selection(a, step_length, 0); + break; + case Ged_dir_left: + ged_slide_selection(a, 0, -step_length); + break; + case Ged_dir_right: + ged_slide_selection(a, 0, step_length); + break; + } + break; + } } static Usz view_to_scrolled_grid(Usz field_len, Usz visual_coord, - int scroll_offset) { - if (field_len == 0) - return 0; - if (scroll_offset < 0) { - if ((Usz)(-scroll_offset) <= visual_coord) { - visual_coord -= (Usz)(-scroll_offset); - } else { - visual_coord = 0; - } - } else { - visual_coord += (Usz)scroll_offset; - } - if (visual_coord >= field_len) - visual_coord = field_len - 1; - return visual_coord; -} - -ORCA_OK_IF_UNUSED staticni void ged_mouse_event(Ged *a, Usz vis_y, Usz vis_x, - mmask_t mouse_bstate) { - if (mouse_bstate & BUTTON1_RELEASED) { - // hard-disables tracking, but also disables further mouse stuff. - // mousemask() with our original parameters seems to work to get into the - // state we want, though. - // - // printf("\033[?1003l\n"); - mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); - a->is_mouse_down = false; - a->is_mouse_dragging = false; - a->drag_start_y = 0; - a->drag_start_x = 0; - } else if ((mouse_bstate & BUTTON1_PRESSED) || a->is_mouse_down) { - Usz y = view_to_scrolled_grid(a->field.height, vis_y, a->grid_scroll_y); - Usz x = view_to_scrolled_grid(a->field.width, vis_x, a->grid_scroll_x); - if (!a->is_mouse_down) { - // some sequence to hopefully make terminal start reporting all further - // mouse movement events. 'REPORT_MOUSE_POSITION' alone in the mousemask - // doesn't seem to work, at least not for xterm. we need to set it only - // only when needed, otherwise some terminals will send movement updates - // when we don't want them. - printf("\033[?1003h\n"); - // need to do this or double clicking can cause terminal state to get - // corrupted, since we're bypassing curses here. might cause flicker. - // wish i could figure out why report mouse position isn't working on its - // own. - fflush(stdout); - wclear(stdscr); - a->is_mouse_down = true; - a->ged_cursor.y = y; - a->ged_cursor.x = x; - a->ged_cursor.h = 1; - a->ged_cursor.w = 1; - a->is_draw_dirty = true; + int scroll_offset) +{ + if (field_len == 0) + return 0; + if (scroll_offset < 0) { + if ((Usz)(-scroll_offset) <= visual_coord) { + visual_coord -= (Usz)(-scroll_offset); + } else { + visual_coord = 0; + } } else { - if (!a->is_mouse_dragging && - (y != a->ged_cursor.y || x != a->ged_cursor.x)) { - a->is_mouse_dragging = true; - a->drag_start_y = a->ged_cursor.y; - a->drag_start_x = a->ged_cursor.x; - } - if (a->is_mouse_dragging) { - Usz tcy = a->drag_start_y; - Usz tcx = a->drag_start_x; - Usz loy = y < tcy ? y : tcy; - Usz lox = x < tcx ? x : tcx; - Usz hiy = y > tcy ? y : tcy; - Usz hix = x > tcx ? x : tcx; - a->ged_cursor.y = loy; - a->ged_cursor.x = lox; - a->ged_cursor.h = hiy - loy + 1; - a->ged_cursor.w = hix - lox + 1; - a->is_draw_dirty = true; - } + visual_coord += (Usz)scroll_offset; + } + if (visual_coord >= field_len) + visual_coord = field_len - 1; + return visual_coord; +} + +ORCA_OK_IF_UNUSED staticni void ged_mouse_event(Ged* a, Usz vis_y, Usz vis_x, + mmask_t mouse_bstate) +{ + if (mouse_bstate & BUTTON1_RELEASED) { + // hard-disables tracking, but also disables further mouse stuff. + // mousemask() with our original parameters seems to work to get into + // the state we want, though. + // + // printf("\033[?1003l\n"); + mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); + a->is_mouse_down = false; + a->is_mouse_dragging = false; + a->drag_start_y = 0; + a->drag_start_x = 0; + } else if ((mouse_bstate & BUTTON1_PRESSED) || a->is_mouse_down) { + Usz y = view_to_scrolled_grid(a->field.height, vis_y, + a->grid_scroll_y); + Usz x = view_to_scrolled_grid(a->field.width, vis_x, a->grid_scroll_x); + if (!a->is_mouse_down) { + // some sequence to hopefully make terminal start reporting all + // further mouse movement events. 'REPORT_MOUSE_POSITION' alone in + // the mousemask doesn't seem to work, at least not for xterm. we + // need to set it only only when needed, otherwise some terminals + // will send movement updates when we don't want them. + printf("\033[?1003h\n"); + // need to do this or double clicking can cause terminal state to + // get corrupted, since we're bypassing curses here. might cause + // flicker. wish i could figure out why report mouse position isn't + // working on its own. + fflush(stdout); + wclear(stdscr); + a->is_mouse_down = true; + a->ged_cursor.y = y; + a->ged_cursor.x = x; + a->ged_cursor.h = 1; + a->ged_cursor.w = 1; + a->is_draw_dirty = true; + } else { + if (!a->is_mouse_dragging + && (y != a->ged_cursor.y || x != a->ged_cursor.x)) { + a->is_mouse_dragging = true; + a->drag_start_y = a->ged_cursor.y; + a->drag_start_x = a->ged_cursor.x; + } + if (a->is_mouse_dragging) { + Usz tcy = a->drag_start_y; + Usz tcx = a->drag_start_x; + Usz loy = y < tcy ? y : tcy; + Usz lox = x < tcx ? x : tcx; + Usz hiy = y > tcy ? y : tcy; + Usz hix = x > tcx ? x : tcx; + a->ged_cursor.y = loy; + a->ged_cursor.x = lox; + a->ged_cursor.h = hiy - loy + 1; + a->ged_cursor.w = hix - lox + 1; + a->is_draw_dirty = true; + } + } } - } #if defined(NCURSES_MOUSE_VERSION) && NCURSES_MOUSE_VERSION >= 2 - else { - if (mouse_bstate & BUTTON4_PRESSED) { - a->grid_scroll_y -= 1; - a->is_draw_dirty = true; - } else if (mouse_bstate & BUTTON5_PRESSED) { - a->grid_scroll_y += 1; - a->is_draw_dirty = true; - } - } + else { + if (mouse_bstate & BUTTON4_PRESSED) { + a->grid_scroll_y -= 1; + a->is_draw_dirty = true; + } else if (mouse_bstate & BUTTON5_PRESSED) { + a->grid_scroll_y += 1; + a->is_draw_dirty = true; + } + } #endif } -staticni void ged_adjust_rulers_relative(Ged *a, Isz delta_y, Isz delta_x) { - Isz new_y = (Isz)a->ruler_spacing_y + delta_y; - Isz new_x = (Isz)a->ruler_spacing_x + delta_x; - if (new_y < 4) - new_y = 4; - else if (new_y > 16) - new_y = 16; - if (new_x < 4) - new_x = 4; - else if (new_x > 16) - new_x = 16; - if ((Usz)new_y == a->ruler_spacing_y && (Usz)new_x == a->ruler_spacing_x) - return; - a->ruler_spacing_y = (Usz)new_y; - a->ruler_spacing_x = (Usz)new_x; - a->is_draw_dirty = true; -} - -staticni void ged_resize_grid_relative(Ged *a, Isz delta_y, Isz delta_x) { - ged_resize_grid_snap_ruler(&a->field, &a->mbuf_r, a->ruler_spacing_y, - a->ruler_spacing_x, delta_y, delta_x, a->tick_num, - &a->scratch_field, &a->undo_hist, &a->ged_cursor); - a->needs_remarking = true; // could check if we actually resized - a->is_draw_dirty = true; - ged_update_internal_geometry(a); - ged_make_cursor_visible(a); -} - -staticni void ged_write_character(Ged *a, char c) { - undo_history_push(&a->undo_hist, &a->field, a->tick_num); - gbuffer_poke(a->field.buffer, a->field.height, a->field.width, - a->ged_cursor.y, a->ged_cursor.x, c); - // Indicate we want the next simulation step to be run predictavely, - // so that we can use the reulsting mark buffer for UI visualization. - // This is "expensive", so it could be skipped for non-interactive - // input in situations where max throughput is necessary. - a->needs_remarking = true; - if (a->input_mode == Ged_input_mode_append) { - ged_cursor_move_relative(&a->ged_cursor, a->field.height, a->field.width, 0, - 1); - } - a->is_draw_dirty = true; -} - -staticni bool ged_fill_selection_with_char(Ged *a, Glyph c) { - Usz curs_y, curs_x, curs_h, curs_w; - if (!ged_try_selection_clipped_to_field(a, &curs_y, &curs_x, &curs_h, - &curs_w)) - return false; - gbuffer_fill_subrect(a->field.buffer, a->field.height, a->field.width, curs_y, - curs_x, curs_h, curs_w, c); - return true; +staticni void ged_adjust_rulers_relative(Ged* a, Isz delta_y, Isz delta_x) +{ + Isz new_y = (Isz)a->ruler_spacing_y + delta_y; + Isz new_x = (Isz)a->ruler_spacing_x + delta_x; + if (new_y < 4) + new_y = 4; + else if (new_y > 16) + new_y = 16; + if (new_x < 4) + new_x = 4; + else if (new_x > 16) + new_x = 16; + if ((Usz)new_y == a->ruler_spacing_y && (Usz)new_x == a->ruler_spacing_x) + return; + a->ruler_spacing_y = (Usz)new_y; + a->ruler_spacing_x = (Usz)new_x; + a->is_draw_dirty = true; } -staticni bool ged_copy_selection_to_clipbard(Ged *a) { - Usz curs_y, curs_x, curs_h, curs_w; - if (!ged_try_selection_clipped_to_field(a, &curs_y, &curs_x, &curs_h, - &curs_w)) - return false; - Usz field_h = a->field.height; - Usz field_w = a->field.width; - Field *cb_field = &a->clipboard_field; - field_resize_raw_if_necessary(cb_field, curs_h, curs_w); - gbuffer_copy_subrect(a->field.buffer, cb_field->buffer, field_h, field_w, - curs_h, curs_w, curs_y, curs_x, 0, 0, curs_h, curs_w); - return true; -} - -staticni void ged_input_character(Ged *a, char c) { - switch (a->input_mode) { - case Ged_input_mode_append: - ged_write_character(a, c); - break; - case Ged_input_mode_normal: - case Ged_input_mode_selresize: - case Ged_input_mode_slide: - if (a->ged_cursor.h <= 1 && a->ged_cursor.w <= 1) { - ged_write_character(a, c); - } else { - undo_history_push(&a->undo_hist, &a->field, a->tick_num); - ged_fill_selection_with_char(a, c); - a->needs_remarking = true; - a->is_draw_dirty = true; - } - break; - } +staticni void ged_resize_grid_relative(Ged* a, Isz delta_y, Isz delta_x) +{ + ged_resize_grid_snap_ruler(&a->field, &a->mbuf_r, a->ruler_spacing_y, + a->ruler_spacing_x, delta_y, delta_x, + a->tick_num, &a->scratch_field, &a->undo_hist, + &a->ged_cursor); + a->needs_remarking = true; // could check if we actually resized + a->is_draw_dirty = true; + ged_update_internal_geometry(a); + ged_make_cursor_visible(a); } -typedef enum { - Ged_input_cmd_undo, - Ged_input_cmd_toggle_append_mode, - Ged_input_cmd_toggle_selresize_mode, - Ged_input_cmd_toggle_slide_mode, - Ged_input_cmd_step_forward, - Ged_input_cmd_toggle_show_event_list, - Ged_input_cmd_toggle_play_pause, - Ged_input_cmd_cut, - Ged_input_cmd_copy, - Ged_input_cmd_paste, - Ged_input_cmd_escape, -} Ged_input_cmd; - -staticni void ged_set_playing(Ged *a, bool playing) { - if (playing == a->is_playing) - return; - if (playing) { +staticni void ged_write_character(Ged* a, char c) +{ undo_history_push(&a->undo_hist, &a->field, a->tick_num); - a->is_playing = true; - a->clock = stm_now(); - a->midi_bclock_sixths = 0; - // dumb'n'dirty, get us close to the next step time, but not quite - a->accum_secs = 60.0 / (double)a->bpm / 4.0; - if (a->midi_bclock) { - send_midi_byte(a->oosc_dev, &a->midi_mode, 0xFA); // "start" - a->accum_secs /= 6.0; - } - a->accum_secs -= 0.0001; - send_control_message(a->oosc_dev, "/orca/started"); - } else { - ged_stop_all_sustained_notes(a); - a->is_playing = false; - send_control_message(a->oosc_dev, "/orca/stopped"); - if (a->midi_bclock) - send_midi_byte(a->oosc_dev, &a->midi_mode, 0xFC); // "stop" - } - a->is_draw_dirty = true; -} - -staticni void ged_input_cmd(Ged *a, Ged_input_cmd ev) { - switch (ev) { - case Ged_input_cmd_undo: - if (undo_history_count(&a->undo_hist) == 0) - break; - if (a->is_playing) - undo_history_apply(&a->undo_hist, &a->field, &a->tick_num); - else - undo_history_pop(&a->undo_hist, &a->field, &a->tick_num); - ged_cursor_confine(&a->ged_cursor, a->field.height, a->field.width); - ged_update_internal_geometry(a); - ged_make_cursor_visible(a); - a->needs_remarking = true; - a->is_draw_dirty = true; - break; - case Ged_input_cmd_toggle_append_mode: - a->input_mode = a->input_mode == Ged_input_mode_append - ? Ged_input_mode_normal - : Ged_input_mode_append; - a->is_draw_dirty = true; - break; - case Ged_input_cmd_toggle_selresize_mode: - a->input_mode = a->input_mode == Ged_input_mode_selresize - ? Ged_input_mode_normal - : Ged_input_mode_selresize; - a->is_draw_dirty = true; - break; - case Ged_input_cmd_toggle_slide_mode: - a->input_mode = a->input_mode == Ged_input_mode_slide - ? Ged_input_mode_normal - : Ged_input_mode_slide; - a->is_draw_dirty = true; - break; - case Ged_input_cmd_step_forward: - undo_history_push(&a->undo_hist, &a->field, a->tick_num); - clear_and_run_vm(a->field.buffer, a->mbuf_r.buffer, a->field.height, - a->field.width, a->tick_num, &a->oevent_list, - a->random_seed); - ++a->tick_num; - a->activity_counter += a->oevent_list.count; + gbuffer_poke(a->field.buffer, a->field.height, a->field.width, + a->ged_cursor.y, a->ged_cursor.x, c); + // Indicate we want the next simulation step to be run predictavely, + // so that we can use the reulsting mark buffer for UI visualization. + // This is "expensive", so it could be skipped for non-interactive + // input in situations where max throughput is necessary. a->needs_remarking = true; + if (a->input_mode == Ged_input_mode_append) { + ged_cursor_move_relative(&a->ged_cursor, a->field.height, + a->field.width, 0, 1); + } a->is_draw_dirty = true; - break; - case Ged_input_cmd_toggle_play_pause: - ged_set_playing(a, !a->is_playing); - break; - case Ged_input_cmd_toggle_show_event_list: - a->draw_event_list = !a->draw_event_list; - a->is_draw_dirty = true; - break; - case Ged_input_cmd_cut: - if (ged_copy_selection_to_clipbard(a)) { - undo_history_push(&a->undo_hist, &a->field, a->tick_num); - ged_fill_selection_with_char(a, '.'); - a->needs_remarking = true; - a->is_draw_dirty = true; - } - break; - case Ged_input_cmd_copy: - ged_copy_selection_to_clipbard(a); - break; - case Ged_input_cmd_paste: { +} + +staticni bool ged_fill_selection_with_char(Ged* a, Glyph c) +{ + Usz curs_y, curs_x, curs_h, curs_w; + if (!ged_try_selection_clipped_to_field(a, &curs_y, &curs_x, &curs_h, + &curs_w)) + return false; + gbuffer_fill_subrect(a->field.buffer, a->field.height, a->field.width, + curs_y, curs_x, curs_h, curs_w, c); + return true; +} + +staticni bool ged_copy_selection_to_clipbard(Ged* a) +{ + Usz curs_y, curs_x, curs_h, curs_w; + if (!ged_try_selection_clipped_to_field(a, &curs_y, &curs_x, &curs_h, + &curs_w)) + return false; Usz field_h = a->field.height; Usz field_w = a->field.width; - Usz curs_y = a->ged_cursor.y; - Usz curs_x = a->ged_cursor.x; - if (curs_y >= field_h || curs_x >= field_w) - break; - Field *cb_field = &a->clipboard_field; - Usz cbfield_h = cb_field->height; - Usz cbfield_w = cb_field->width; - Usz cpy_h = cbfield_h; - Usz cpy_w = cbfield_w; - if (field_h - curs_y < cpy_h) - cpy_h = field_h - curs_y; - if (field_w - curs_x < cpy_w) - cpy_w = field_w - curs_x; - if (cpy_h == 0 || cpy_w == 0) - break; - undo_history_push(&a->undo_hist, &a->field, a->tick_num); - gbuffer_copy_subrect(cb_field->buffer, a->field.buffer, cbfield_h, - cbfield_w, field_h, field_w, 0, 0, curs_y, curs_x, - cpy_h, cpy_w); - a->ged_cursor.h = cpy_h; - a->ged_cursor.w = cpy_w; - a->needs_remarking = true; + Field* cb_field = &a->clipboard_field; + field_resize_raw_if_necessary(cb_field, curs_h, curs_w); + gbuffer_copy_subrect(a->field.buffer, cb_field->buffer, field_h, field_w, + curs_h, curs_w, curs_y, curs_x, 0, 0, curs_h, curs_w); + return true; +} + +staticni void ged_input_character(Ged* a, char c) +{ + switch (a->input_mode) { + case Ged_input_mode_append: + ged_write_character(a, c); + break; + case Ged_input_mode_normal: + case Ged_input_mode_selresize: + case Ged_input_mode_slide: + if (a->ged_cursor.h <= 1 && a->ged_cursor.w <= 1) { + ged_write_character(a, c); + } else { + undo_history_push(&a->undo_hist, &a->field, a->tick_num); + ged_fill_selection_with_char(a, c); + a->needs_remarking = true; + a->is_draw_dirty = true; + } + break; + } +} + +typedef enum { + Ged_input_cmd_undo, + Ged_input_cmd_toggle_append_mode, + Ged_input_cmd_toggle_selresize_mode, + Ged_input_cmd_toggle_slide_mode, + Ged_input_cmd_step_forward, + Ged_input_cmd_toggle_show_event_list, + Ged_input_cmd_toggle_play_pause, + Ged_input_cmd_cut, + Ged_input_cmd_copy, + Ged_input_cmd_paste, + Ged_input_cmd_escape, +} Ged_input_cmd; + +staticni void ged_set_playing(Ged* a, bool playing) +{ + if (playing == a->is_playing) + return; + if (playing) { + undo_history_push(&a->undo_hist, &a->field, a->tick_num); + a->is_playing = true; + a->clock = stm_now(); + a->midi_bclock_sixths = 0; + // dumb'n'dirty, get us close to the next step time, but not quite + a->accum_secs = 60.0 / (double)a->bpm / 4.0; + if (a->midi_bclock) { + send_midi_byte(a->oosc_dev, &a->midi_mode, 0xFA); // "start" + a->accum_secs /= 6.0; + } + a->accum_secs -= 0.0001; + send_control_message(a->oosc_dev, "/orca/started"); + } else { + ged_stop_all_sustained_notes(a); + a->is_playing = false; + send_control_message(a->oosc_dev, "/orca/stopped"); + if (a->midi_bclock) + send_midi_byte(a->oosc_dev, &a->midi_mode, 0xFC); // "stop" + } a->is_draw_dirty = true; - break; - } - case Ged_input_cmd_escape: - if (a->input_mode != Ged_input_mode_normal) { - a->input_mode = Ged_input_mode_normal; - a->is_draw_dirty = true; - } else if (a->ged_cursor.h != 1 || a->ged_cursor.w != 1) { - a->ged_cursor.h = 1; - a->ged_cursor.w = 1; - a->is_draw_dirty = true; - } - break; - } -} - -static bool hacky_try_save(Field *field, char const *filename) { - if (!filename) - return false; - if (field->height == 0 || field->width == 0) - return false; - FILE *f = fopen(filename, "w"); - if (!f) - return false; - field_fput(field, f); - fclose(f); - return true; +} + +staticni void ged_input_cmd(Ged* a, Ged_input_cmd ev) +{ + switch (ev) { + case Ged_input_cmd_undo: + if (undo_history_count(&a->undo_hist) == 0) + break; + if (a->is_playing) + undo_history_apply(&a->undo_hist, &a->field, &a->tick_num); + else + undo_history_pop(&a->undo_hist, &a->field, &a->tick_num); + ged_cursor_confine(&a->ged_cursor, a->field.height, a->field.width); + ged_update_internal_geometry(a); + ged_make_cursor_visible(a); + a->needs_remarking = true; + a->is_draw_dirty = true; + break; + case Ged_input_cmd_toggle_append_mode: + a->input_mode = a->input_mode == Ged_input_mode_append + ? Ged_input_mode_normal + : Ged_input_mode_append; + a->is_draw_dirty = true; + break; + case Ged_input_cmd_toggle_selresize_mode: + a->input_mode = a->input_mode == Ged_input_mode_selresize + ? Ged_input_mode_normal + : Ged_input_mode_selresize; + a->is_draw_dirty = true; + break; + case Ged_input_cmd_toggle_slide_mode: + a->input_mode = a->input_mode == Ged_input_mode_slide + ? Ged_input_mode_normal + : Ged_input_mode_slide; + a->is_draw_dirty = true; + break; + case Ged_input_cmd_step_forward: + undo_history_push(&a->undo_hist, &a->field, a->tick_num); + clear_and_run_vm(a->field.buffer, a->mbuf_r.buffer, a->field.height, + a->field.width, a->tick_num, &a->oevent_list, + a->random_seed); + ++a->tick_num; + a->activity_counter += a->oevent_list.count; + a->needs_remarking = true; + a->is_draw_dirty = true; + break; + case Ged_input_cmd_toggle_play_pause: + ged_set_playing(a, !a->is_playing); + break; + case Ged_input_cmd_toggle_show_event_list: + a->draw_event_list = !a->draw_event_list; + a->is_draw_dirty = true; + break; + case Ged_input_cmd_cut: + if (ged_copy_selection_to_clipbard(a)) { + undo_history_push(&a->undo_hist, &a->field, a->tick_num); + ged_fill_selection_with_char(a, '.'); + a->needs_remarking = true; + a->is_draw_dirty = true; + } + break; + case Ged_input_cmd_copy: + ged_copy_selection_to_clipbard(a); + break; + case Ged_input_cmd_paste: { + Usz field_h = a->field.height; + Usz field_w = a->field.width; + Usz curs_y = a->ged_cursor.y; + Usz curs_x = a->ged_cursor.x; + if (curs_y >= field_h || curs_x >= field_w) + break; + Field* cb_field = &a->clipboard_field; + Usz cbfield_h = cb_field->height; + Usz cbfield_w = cb_field->width; + Usz cpy_h = cbfield_h; + Usz cpy_w = cbfield_w; + if (field_h - curs_y < cpy_h) + cpy_h = field_h - curs_y; + if (field_w - curs_x < cpy_w) + cpy_w = field_w - curs_x; + if (cpy_h == 0 || cpy_w == 0) + break; + undo_history_push(&a->undo_hist, &a->field, a->tick_num); + gbuffer_copy_subrect(cb_field->buffer, a->field.buffer, cbfield_h, + cbfield_w, field_h, field_w, 0, 0, curs_y, curs_x, + cpy_h, cpy_w); + a->ged_cursor.h = cpy_h; + a->ged_cursor.w = cpy_w; + a->needs_remarking = true; + a->is_draw_dirty = true; + break; + } + case Ged_input_cmd_escape: + if (a->input_mode != Ged_input_mode_normal) { + a->input_mode = Ged_input_mode_normal; + a->is_draw_dirty = true; + } else if (a->ged_cursor.h != 1 || a->ged_cursor.w != 1) { + a->ged_cursor.h = 1; + a->ged_cursor.w = 1; + a->is_draw_dirty = true; + } + break; + } +} + +static bool hacky_try_save(Field* field, char const* filename) +{ + if (!filename) + return false; + if (field->height == 0 || field->width == 0) + return false; + FILE* f = fopen(filename, "w"); + if (!f) + return false; + field_fput(field, f); + fclose(f); + return true; } // @@ -1967,167 +2059,180 @@ static bool hacky_try_save(Field *field, char const *filename) { // enum { - Main_menu_id = 1, - Open_form_id, - Save_as_form_id, - Set_tempo_form_id, - Set_grid_dims_form_id, - Autofit_menu_id, - Confirm_new_file_menu_id, - Cosmetics_menu_id, - Osc_menu_id, - Osc_output_address_form_id, - Osc_output_port_form_id, - Playback_menu_id, - Set_soft_margins_form_id, - Set_fancy_grid_dots_menu_id, - Set_fancy_grid_rulers_menu_id, + Main_menu_id = 1, + Open_form_id, + Save_as_form_id, + Set_tempo_form_id, + Set_grid_dims_form_id, + Autofit_menu_id, + Confirm_new_file_menu_id, + Cosmetics_menu_id, + Osc_menu_id, + Osc_output_address_form_id, + Osc_output_port_form_id, + Playback_menu_id, + Set_soft_margins_form_id, + Set_fancy_grid_dots_menu_id, + Set_fancy_grid_rulers_menu_id, #ifdef FEAT_PORTMIDI - Portmidi_output_device_menu_id, + Portmidi_output_device_menu_id, #endif }; enum { - Autofit_nicely_id = 1, - Autofit_tightly_id, + Autofit_nicely_id = 1, + Autofit_tightly_id, }; enum { - Confirm_new_file_reject_id = 1, - Confirm_new_file_accept_id, + Confirm_new_file_reject_id = 1, + Confirm_new_file_accept_id, }; enum { - Main_menu_quit = 1, - Main_menu_controls, - Main_menu_opers_guide, - Main_menu_new, - Main_menu_open, - Main_menu_save, - Main_menu_save_as, - Main_menu_set_tempo, - Main_menu_set_grid_dims, - Main_menu_autofit_grid, - Main_menu_about, - Main_menu_cosmetics, - Main_menu_playback, - Main_menu_osc, + Main_menu_quit = 1, + Main_menu_controls, + Main_menu_opers_guide, + Main_menu_new, + Main_menu_open, + Main_menu_save, + Main_menu_save_as, + Main_menu_set_tempo, + Main_menu_set_grid_dims, + Main_menu_autofit_grid, + Main_menu_about, + Main_menu_cosmetics, + Main_menu_playback, + Main_menu_osc, #ifdef FEAT_PORTMIDI - Main_menu_choose_portmidi_output, + Main_menu_choose_portmidi_output, #endif }; -static void push_main_menu(void) { - Qmenu *qm = qmenu_create(Main_menu_id); - qmenu_set_title(qm, "ORCA"); - qmenu_add_choice(qm, Main_menu_new, "New"); - qmenu_add_choice(qm, Main_menu_open, "Open..."); - qmenu_add_choice(qm, Main_menu_save, "Save"); - qmenu_add_choice(qm, Main_menu_save_as, "Save As..."); - qmenu_add_spacer(qm); - qmenu_add_choice(qm, Main_menu_set_tempo, "Set BPM..."); - qmenu_add_choice(qm, Main_menu_set_grid_dims, "Set Grid Size..."); - qmenu_add_choice(qm, Main_menu_autofit_grid, "Auto-fit Grid"); - qmenu_add_spacer(qm); - qmenu_add_choice(qm, Main_menu_osc, "OSC Output..."); +static void push_main_menu(void) +{ + Qmenu* qm = qmenu_create(Main_menu_id); + qmenu_set_title(qm, "ORCA"); + qmenu_add_choice(qm, Main_menu_new, "New"); + qmenu_add_choice(qm, Main_menu_open, "Open..."); + qmenu_add_choice(qm, Main_menu_save, "Save"); + qmenu_add_choice(qm, Main_menu_save_as, "Save As..."); + qmenu_add_spacer(qm); + qmenu_add_choice(qm, Main_menu_set_tempo, "Set BPM..."); + qmenu_add_choice(qm, Main_menu_set_grid_dims, "Set Grid Size..."); + qmenu_add_choice(qm, Main_menu_autofit_grid, "Auto-fit Grid"); + qmenu_add_spacer(qm); + qmenu_add_choice(qm, Main_menu_osc, "OSC Output..."); #ifdef FEAT_PORTMIDI - qmenu_add_choice(qm, Main_menu_choose_portmidi_output, "MIDI Output..."); + qmenu_add_choice(qm, Main_menu_choose_portmidi_output, "MIDI Output..."); #endif - qmenu_add_spacer(qm); - qmenu_add_choice(qm, Main_menu_playback, "Clock & Timing..."); - qmenu_add_choice(qm, Main_menu_cosmetics, "Appearance..."); - qmenu_add_spacer(qm); - qmenu_add_choice(qm, Main_menu_controls, "Controls..."); - qmenu_add_choice(qm, Main_menu_opers_guide, "Operators..."); - qmenu_add_choice(qm, Main_menu_about, "About ORCA..."); - qmenu_add_spacer(qm); - qmenu_add_choice(qm, Main_menu_quit, "Quit"); - qmenu_push_to_nav(qm); -} - -staticni void pop_qnav_if_main_menu(void) { - Qblock *qb = qnav_top_block(); - if (qb && qb->tag == Qblock_type_qmenu && - qmenu_id(qmenu_of(qb)) == Main_menu_id) - qnav_stack_pop(); + qmenu_add_spacer(qm); + qmenu_add_choice(qm, Main_menu_playback, "Clock & Timing..."); + qmenu_add_choice(qm, Main_menu_cosmetics, "Appearance..."); + qmenu_add_spacer(qm); + qmenu_add_choice(qm, Main_menu_controls, "Controls..."); + qmenu_add_choice(qm, Main_menu_opers_guide, "Operators..."); + qmenu_add_choice(qm, Main_menu_about, "About ORCA..."); + qmenu_add_spacer(qm); + qmenu_add_choice(qm, Main_menu_quit, "Quit"); + qmenu_push_to_nav(qm); +} + +staticni void pop_qnav_if_main_menu(void) +{ + Qblock* qb = qnav_top_block(); + if (qb && qb->tag == Qblock_type_qmenu + && qmenu_id(qmenu_of(qb)) == Main_menu_id) + qnav_stack_pop(); } -static void push_confirm_new_file_menu(void) { - Qmenu *qm = qmenu_create(Confirm_new_file_menu_id); - qmenu_set_title(qm, "Are you sure?"); - qmenu_add_choice(qm, Confirm_new_file_reject_id, "Cancel"); - qmenu_add_choice(qm, Confirm_new_file_accept_id, "Create New File"); - qmenu_push_to_nav(qm); +static void push_confirm_new_file_menu(void) +{ + Qmenu* qm = qmenu_create(Confirm_new_file_menu_id); + qmenu_set_title(qm, "Are you sure?"); + qmenu_add_choice(qm, Confirm_new_file_reject_id, "Cancel"); + qmenu_add_choice(qm, Confirm_new_file_accept_id, "Create New File"); + qmenu_push_to_nav(qm); } -static void push_autofit_menu(void) { - Qmenu *qm = qmenu_create(Autofit_menu_id); - qmenu_set_title(qm, "Auto-fit Grid"); - qmenu_add_choice(qm, Autofit_nicely_id, "Nicely"); - qmenu_add_choice(qm, Autofit_tightly_id, "Tightly"); - qmenu_push_to_nav(qm); +static void push_autofit_menu(void) +{ + Qmenu* qm = qmenu_create(Autofit_menu_id); + qmenu_set_title(qm, "Auto-fit Grid"); + qmenu_add_choice(qm, Autofit_nicely_id, "Nicely"); + qmenu_add_choice(qm, Autofit_tightly_id, "Tightly"); + qmenu_push_to_nav(qm); } enum { - Cosmetics_soft_margins_id = 1, - Cosmetics_grid_dots_id, - Cosmetics_grid_rulers_id, + Cosmetics_soft_margins_id = 1, + Cosmetics_grid_dots_id, + Cosmetics_grid_rulers_id, }; -static void push_cosmetics_menu(void) { - Qmenu *qm = qmenu_create(Cosmetics_menu_id); - qmenu_set_title(qm, "Appearance"); - qmenu_add_choice(qm, Cosmetics_soft_margins_id, "Margins..."); - qmenu_add_choice(qm, Cosmetics_grid_dots_id, "Grid dots..."); - qmenu_add_choice(qm, Cosmetics_grid_rulers_id, "Grid rulers..."); - qmenu_push_to_nav(qm); -} -static void push_soft_margins_form(int init_y, int init_x) { - char buff[128]; - int snres = snprintf(buff, sizeof buff, "%dx%d", init_x, init_y); - char const *inistr = snres > 0 && (Usz)snres < sizeof buff ? buff : "2x1"; - qform_single_line_input(Set_soft_margins_form_id, "Set Margins", inistr); -} -static void push_plainorfancy_menu(int menu_id, char const *title, - bool initial_fancy) { - Qmenu *qm = qmenu_create(menu_id); - qmenu_set_title(qm, title); - qmenu_add_printf(qm, 1, "(%c) Fancy", initial_fancy ? '*' : ' '); - qmenu_add_printf(qm, 2, "(%c) Plain", !initial_fancy ? '*' : ' '); - if (!initial_fancy) - qmenu_set_current_item(qm, 2); - qmenu_push_to_nav(qm); +static void push_cosmetics_menu(void) +{ + Qmenu* qm = qmenu_create(Cosmetics_menu_id); + qmenu_set_title(qm, "Appearance"); + qmenu_add_choice(qm, Cosmetics_soft_margins_id, "Margins..."); + qmenu_add_choice(qm, Cosmetics_grid_dots_id, "Grid dots..."); + qmenu_add_choice(qm, Cosmetics_grid_rulers_id, "Grid rulers..."); + qmenu_push_to_nav(qm); +} +static void push_soft_margins_form(int init_y, int init_x) +{ + char buff[128]; + int snres = snprintf(buff, sizeof buff, "%dx%d", init_x, init_y); + char const* inistr = snres > 0 && (Usz)snres < sizeof buff ? buff : "2x1"; + qform_single_line_input(Set_soft_margins_form_id, "Set Margins", inistr); +} +static void push_plainorfancy_menu(int menu_id, char const* title, + bool initial_fancy) +{ + Qmenu* qm = qmenu_create(menu_id); + qmenu_set_title(qm, title); + qmenu_add_printf(qm, 1, "(%c) Fancy", initial_fancy ? '*' : ' '); + qmenu_add_printf(qm, 2, "(%c) Plain", !initial_fancy ? '*' : ' '); + if (!initial_fancy) + qmenu_set_current_item(qm, 2); + qmenu_push_to_nav(qm); } enum { - Osc_menu_output_enabledisable = 1, - Osc_menu_output_address, - Osc_menu_output_port, + Osc_menu_output_enabledisable = 1, + Osc_menu_output_address, + Osc_menu_output_port, }; -static void push_osc_menu(bool output_enabled) { - Qmenu *qm = qmenu_create(Osc_menu_id); - qmenu_set_title(qm, "OSC Output"); - qmenu_add_printf(qm, Osc_menu_output_enabledisable, "[%c] OSC Output Enabled", - output_enabled ? '*' : ' '); - qmenu_add_choice(qm, Osc_menu_output_address, "OSC Output Address..."); - qmenu_add_choice(qm, Osc_menu_output_port, "OSC Output Port..."); - qmenu_push_to_nav(qm); -} -static void push_osc_output_address_form(char const *initial) { - qform_single_line_input(Osc_output_address_form_id, "Set OSC Output Address", - initial); -} -static void push_osc_output_port_form(char const *initial) { - qform_single_line_input(Osc_output_port_form_id, "Set OSC Output Port", - initial); +static void push_osc_menu(bool output_enabled) +{ + Qmenu* qm = qmenu_create(Osc_menu_id); + qmenu_set_title(qm, "OSC Output"); + qmenu_add_printf(qm, Osc_menu_output_enabledisable, + "[%c] OSC Output Enabled", output_enabled ? '*' : ' '); + qmenu_add_choice(qm, Osc_menu_output_address, "OSC Output Address..."); + qmenu_add_choice(qm, Osc_menu_output_port, "OSC Output Port..."); + qmenu_push_to_nav(qm); +} +static void push_osc_output_address_form(char const* initial) +{ + qform_single_line_input(Osc_output_address_form_id, + "Set OSC Output Address", initial); +} +static void push_osc_output_port_form(char const* initial) +{ + qform_single_line_input(Osc_output_port_form_id, "Set OSC Output Port", + initial); } enum { - Playback_menu_midi_bclock = 1, + Playback_menu_midi_bclock = 1, }; -static void push_playback_menu(bool midi_bclock_enabled) { - Qmenu *qm = qmenu_create(Playback_menu_id); - qmenu_set_title(qm, "Clock & Timing"); - qmenu_add_printf(qm, Playback_menu_midi_bclock, "[%c] Send MIDI Beat Clock", - midi_bclock_enabled ? '*' : ' '); - qmenu_push_to_nav(qm); -} -static void push_about_msg(void) { - // clang-format off +static void push_playback_menu(bool midi_bclock_enabled) +{ + Qmenu* qm = qmenu_create(Playback_menu_id); + qmenu_set_title(qm, "Clock & Timing"); + qmenu_add_printf(qm, Playback_menu_midi_bclock, + "[%c] Send MIDI Beat Clock", + midi_bclock_enabled ? '*' : ' '); + qmenu_push_to_nav(qm); +} +static void push_about_msg(void) +{ + // clang-format off static char const* logo[] = { "lqqqk|lqqqk|lqqqk|lqqqk", "x x|x j|x |lqqqu", @@ -2135,280 +2240,293 @@ static void push_about_msg(void) { }; static char const* footer = "Live Programming Environment"; - // clang-format on - int rows = (int)ORCA_ARRAY_COUNTOF(logo); - int cols = (int)strlen(logo[0]); - int hpad = 5, tpad = 2, bpad = 2; - int sep = 1; - int footer_len = (int)strlen(footer); - int width = footer_len; - if (cols > width) - width = cols; - width += hpad * 2; - int logo_left_pad = (width - cols) / 2; - int footer_left_pad = (width - footer_len) / 2; - Qmsg *qm = qmsg_push(tpad + rows + sep + 1 + bpad, width); - WINDOW *w = qmsg_window(qm); - for (int row = 0; row < rows; ++row) { - wmove(w, row + tpad, logo_left_pad); - wattrset(w, A_BOLD); - for (int col = 0; col < cols; ++col) { - char c = logo[row][col]; - chtype ch; - if (c == ' ') - ch = (chtype)' '; - else if (c == '|') - ch = ACS_VLINE | (chtype)fg_bg(C_black, C_natural) | A_BOLD; - else - ch = NCURSES_ACS(c) | A_BOLD; - waddch(w, ch); - } - } - wattrset(w, A_DIM); - wmove(w, tpad + rows + sep, footer_left_pad); - waddstr(w, footer); -} -static void push_controls_msg(void) { - struct Ctrl_item { - char const *input; - char const *desc; - }; - static struct Ctrl_item items[] = { - {"Ctrl+Q", "Quit"}, - {"Arrow Keys", "Move Cursor"}, - {"Ctrl+D or F1", "Open Main Menu"}, - {"0-9, A-Z, a-z,", "Insert Character"}, - {"! : % / = # *", NULL}, - {"Spacebar", "Play/Pause"}, - {"Ctrl+Z or Ctrl+U", "Undo"}, - {"Ctrl+X", "Cut"}, - {"Ctrl+C", "Copy"}, - {"Ctrl+V", "Paste"}, - {"Ctrl+S", "Save"}, - {"Ctrl+F", "Frame Step Forward"}, - {"Ctrl+R", "Reset Frame Number"}, - {"Ctrl+I or Insert", "Append/Overwrite Mode"}, - // {"/", "Key Trigger Mode"}, - {"' (quote)", "Rectangle Selection Mode"}, - {"Shift+Arrow Keys", "Adjust Rectangle Selection"}, - {"Alt+Arrow Keys", "Slide Selection"}, - {"` (grave) or ~", "Slide Selection Mode"}, - {"Escape", "Return to Normal Mode or Deselect"}, - {"( ) _ + [ ] { }", "Adjust Grid Size and Rulers"}, - {"< and >", "Adjust BPM"}, - {"?", "Controls (this message)"}, - }; - int w_input = 0; - int w_desc = 0; - for (Usz i = 0; i < ORCA_ARRAY_COUNTOF(items); ++i) { - // use wcswidth instead of strlen if you need wide char support. but note - // that won't be useful for UTF-8 or unicode chars in higher plane (emoji, - // complex zwj, etc.) - if (items[i].input) { - int wl = (int)strlen(items[i].input); - if (wl > w_input) - w_input = wl; - } - if (items[i].desc) { - int wr = (int)strlen(items[i].desc); - if (wr > w_desc) - w_desc = wr; - } - } - int mid_pad = 2; - int total_width = 1 + w_input + mid_pad + w_desc + 1; - Qmsg *qm = qmsg_push(ORCA_ARRAY_COUNTOF(items), total_width); - qmsg_set_title(qm, "Controls"); - WINDOW *w = qmsg_window(qm); - for (int i = 0; i < (int)ORCA_ARRAY_COUNTOF(items); ++i) { - if (items[i].input) { - wmove(w, i, 1 + w_input - (int)strlen(items[i].input)); - waddstr(w, items[i].input); - } - if (items[i].desc) { - wmove(w, i, 1 + w_input + mid_pad); - waddstr(w, items[i].desc); - } - } -} -static void push_opers_guide_msg(void) { - struct Guide_item { - char glyph; - char const *name; - char const *desc; - }; - static struct Guide_item items[] = { - {'A', "add", "Outputs sum of inputs."}, - {'B', "subtract", "Outputs difference of inputs."}, - {'C', "clock", "Outputs modulo of frame."}, - {'D', "delay", "Bangs on modulo of frame."}, - {'E', "east", "Moves eastward, or bangs."}, - {'F', "if", "Bangs if inputs are equal."}, - {'G', "generator", "Writes operands with offset."}, - {'H', "halt", "Halts southward operand."}, - {'I', "increment", "Increments southward operand."}, - {'J', "jumper", "Outputs northward operand."}, - {'K', "konkat", "Reads multiple variables."}, - {'L', "lesser", "Outputs smallest input."}, - {'M', "multiply", "Outputs product of inputs."}, - {'N', "north", "Moves Northward, or bangs."}, - {'O', "read", "Reads operand with offset."}, - {'P', "push", "Writes eastward operand."}, - {'Q', "query", "Reads operands with offset."}, - {'R', "random", "Outputs random value."}, - {'S', "south", "Moves southward, or bangs."}, - {'T', "track", "Reads eastward operand."}, - {'U', "uclid", "Bangs on Euclidean rhythm."}, - {'V', "variable", "Reads and writes variable."}, - {'W', "west", "Moves westward, or bangs."}, - {'X', "write", "Writes operand with offset."}, - {'Y', "jymper", "Outputs westward operand."}, - {'Z', "lerp", "Transitions operand to target."}, - {'*', "bang", "Bangs neighboring operands."}, - {'#', "comment", "Halts line."}, - // {'*', "self", "Sends ORCA command."}, - {':', "midi", "Sends MIDI note."}, - {'!', "cc", "Sends MIDI control change."}, - {'?', "pb", "Sends MIDI pitch bend."}, - // {'%', "mono", "Sends MIDI monophonic note."}, - {'=', "osc", "Sends OSC message."}, - {';', "udp", "Sends UDP message."}, - }; - int w_desc = 0; - for (Usz i = 0; i < ORCA_ARRAY_COUNTOF(items); ++i) { - if (items[i].desc) { - int wr = (int)strlen(items[i].desc); - if (wr > w_desc) - w_desc = wr; - } - } - int left_pad = 1, mid_pad = 1, right_pad = 1; - int total_width = left_pad + 1 + mid_pad + w_desc + right_pad; - Qmsg *qm = qmsg_push(ORCA_ARRAY_COUNTOF(items), total_width); - qmsg_set_title(qm, "Operators"); - WINDOW *w = qmsg_window(qm); - for (int i = 0; i < (int)ORCA_ARRAY_COUNTOF(items); ++i) { - wmove(w, i, left_pad); - waddch(w, (chtype)items[i].glyph | A_bold); - wmove(w, i, left_pad + 1 + mid_pad); - wattrset(w, A_normal); - waddstr(w, items[i].desc); - } -} -static void push_open_form(char const *initial) { - qform_single_line_input(Open_form_id, "Open", initial); -} -staticni bool try_save_with_msg(Field *field, oso const *str) { - if (!osolen(str)) - return false; - bool ok = hacky_try_save(field, osoc(str)); - if (ok) { - Qmsg *qm = qmsg_printf_push(NULL, "Saved to:\n%s", osoc(str)); - qmsg_set_dismiss_mode(qm, Qmsg_dismiss_mode_passthrough); - } else { - qmsg_printf_push("Error Saving File", "Unable to save file to:\n%s", - osoc(str)); - } - return ok; -} -static void push_save_as_form(char const *initial) { - qform_single_line_input(Save_as_form_id, "Save As", initial); -} -static void push_set_tempo_form(Usz initial) { - char buff[64]; - int snres = snprintf(buff, sizeof buff, "%zu", initial); - char const *inistr = snres > 0 && (Usz)snres < sizeof buff ? buff : "120"; - qform_single_line_input(Set_tempo_form_id, "Set BPM", inistr); -} -static void push_set_grid_dims_form(Usz init_height, Usz init_width) { - char buff[128]; - int snres = snprintf(buff, sizeof buff, "%zux%zu", init_width, init_height); - char const *inistr = snres > 0 && (Usz)snres < sizeof buff ? buff : "57x25"; - qform_single_line_input(Set_grid_dims_form_id, "Set Grid Size", inistr); + // clang-format on + int rows = (int)ORCA_ARRAY_COUNTOF(logo); + int cols = (int)strlen(logo[0]); + int hpad = 5, tpad = 2, bpad = 2; + int sep = 1; + int footer_len = (int)strlen(footer); + int width = footer_len; + if (cols > width) + width = cols; + width += hpad * 2; + int logo_left_pad = (width - cols) / 2; + int footer_left_pad = (width - footer_len) / 2; + Qmsg* qm = qmsg_push(tpad + rows + sep + 1 + bpad, width); + WINDOW* w = qmsg_window(qm); + for (int row = 0; row < rows; ++row) { + wmove(w, row + tpad, logo_left_pad); + wattrset(w, A_BOLD); + for (int col = 0; col < cols; ++col) { + char c = logo[row][col]; + chtype ch; + if (c == ' ') + ch = (chtype)' '; + else if (c == '|') + ch = ACS_VLINE | (chtype)fg_bg(C_black, C_natural) | A_BOLD; + else + ch = NCURSES_ACS(c) | A_BOLD; + waddch(w, ch); + } + } + wattrset(w, A_DIM); + wmove(w, tpad + rows + sep, footer_left_pad); + waddstr(w, footer); +} +static void push_controls_msg(void) +{ + struct Ctrl_item { + char const* input; + char const* desc; + }; + static struct Ctrl_item items[] = { + { "Ctrl+Q", "Quit" }, + { "Arrow Keys", "Move Cursor" }, + { "Ctrl+D or F1", "Open Main Menu" }, + { "0-9, A-Z, a-z,", "Insert Character" }, + { "! : % / = # *", NULL }, + { "Spacebar", "Play/Pause" }, + { "Ctrl+Z or Ctrl+U", "Undo" }, + { "Ctrl+X", "Cut" }, + { "Ctrl+C", "Copy" }, + { "Ctrl+V", "Paste" }, + { "Ctrl+S", "Save" }, + { "Ctrl+F", "Frame Step Forward" }, + { "Ctrl+R", "Reset Frame Number" }, + { "Ctrl+I or Insert", "Append/Overwrite Mode" }, + // {"/", "Key Trigger Mode"}, + { "' (quote)", "Rectangle Selection Mode" }, + { "Shift+Arrow Keys", "Adjust Rectangle Selection" }, + { "Alt+Arrow Keys", "Slide Selection" }, + { "` (grave) or ~", "Slide Selection Mode" }, + { "Escape", "Return to Normal Mode or Deselect" }, + { "( ) _ + [ ] { }", "Adjust Grid Size and Rulers" }, + { "< and >", "Adjust BPM" }, + { "?", "Controls (this message)" }, + }; + int w_input = 0; + int w_desc = 0; + for (Usz i = 0; i < ORCA_ARRAY_COUNTOF(items); ++i) { + // use wcswidth instead of strlen if you need wide char support. but + // note that won't be useful for UTF-8 or unicode chars in higher plane + // (emoji, complex zwj, etc.) + if (items[i].input) { + int wl = (int)strlen(items[i].input); + if (wl > w_input) + w_input = wl; + } + if (items[i].desc) { + int wr = (int)strlen(items[i].desc); + if (wr > w_desc) + w_desc = wr; + } + } + int mid_pad = 2; + int total_width = 1 + w_input + mid_pad + w_desc + 1; + Qmsg* qm = qmsg_push(ORCA_ARRAY_COUNTOF(items), total_width); + qmsg_set_title(qm, "Controls"); + WINDOW* w = qmsg_window(qm); + for (int i = 0; i < (int)ORCA_ARRAY_COUNTOF(items); ++i) { + if (items[i].input) { + wmove(w, i, 1 + w_input - (int)strlen(items[i].input)); + waddstr(w, items[i].input); + } + if (items[i].desc) { + wmove(w, i, 1 + w_input + mid_pad); + waddstr(w, items[i].desc); + } + } +} +static void push_opers_guide_msg(void) +{ + struct Guide_item { + char glyph; + char const* name; + char const* desc; + }; + static struct Guide_item items[] = { + { 'A', "add", "Outputs sum of inputs." }, + { 'B', "subtract", "Outputs difference of inputs." }, + { 'C', "clock", "Outputs modulo of frame." }, + { 'D', "delay", "Bangs on modulo of frame." }, + { 'E', "east", "Moves eastward, or bangs." }, + { 'F', "if", "Bangs if inputs are equal." }, + { 'G', "generator", "Writes operands with offset." }, + { 'H', "halt", "Halts southward operand." }, + { 'I', "increment", "Increments southward operand." }, + { 'J', "jumper", "Outputs northward operand." }, + { 'K', "konkat", "Reads multiple variables." }, + { 'L', "lesser", "Outputs smallest input." }, + { 'M', "multiply", "Outputs product of inputs." }, + { 'N', "north", "Moves Northward, or bangs." }, + { 'O', "read", "Reads operand with offset." }, + { 'P', "push", "Writes eastward operand." }, + { 'Q', "query", "Reads operands with offset." }, + { 'R', "random", "Outputs random value." }, + { 'S', "south", "Moves southward, or bangs." }, + { 'T', "track", "Reads eastward operand." }, + { 'U', "uclid", "Bangs on Euclidean rhythm." }, + { 'V', "variable", "Reads and writes variable." }, + { 'W', "west", "Moves westward, or bangs." }, + { 'X', "write", "Writes operand with offset." }, + { 'Y', "jymper", "Outputs westward operand." }, + { 'Z', "lerp", "Transitions operand to target." }, + { '*', "bang", "Bangs neighboring operands." }, + { '#', "comment", "Halts line." }, + // {'*', "self", "Sends ORCA command."}, + { ':', "midi", "Sends MIDI note." }, + { '!', "cc", "Sends MIDI control change." }, + { '?', "pb", "Sends MIDI pitch bend." }, + // {'%', "mono", "Sends MIDI monophonic note."}, + { '=', "osc", "Sends OSC message." }, + { ';', "udp", "Sends UDP message." }, + }; + int w_desc = 0; + for (Usz i = 0; i < ORCA_ARRAY_COUNTOF(items); ++i) { + if (items[i].desc) { + int wr = (int)strlen(items[i].desc); + if (wr > w_desc) + w_desc = wr; + } + } + int left_pad = 1, mid_pad = 1, right_pad = 1; + int total_width = left_pad + 1 + mid_pad + w_desc + right_pad; + Qmsg* qm = qmsg_push(ORCA_ARRAY_COUNTOF(items), total_width); + qmsg_set_title(qm, "Operators"); + WINDOW* w = qmsg_window(qm); + for (int i = 0; i < (int)ORCA_ARRAY_COUNTOF(items); ++i) { + wmove(w, i, left_pad); + waddch(w, (chtype)items[i].glyph | A_bold); + wmove(w, i, left_pad + 1 + mid_pad); + wattrset(w, A_normal); + waddstr(w, items[i].desc); + } +} +static void push_open_form(char const* initial) +{ + qform_single_line_input(Open_form_id, "Open", initial); +} +staticni bool try_save_with_msg(Field* field, oso const* str) +{ + if (!osolen(str)) + return false; + bool ok = hacky_try_save(field, osoc(str)); + if (ok) { + Qmsg* qm = qmsg_printf_push(NULL, "Saved to:\n%s", osoc(str)); + qmsg_set_dismiss_mode(qm, Qmsg_dismiss_mode_passthrough); + } else { + qmsg_printf_push("Error Saving File", "Unable to save file to:\n%s", + osoc(str)); + } + return ok; +} +static void push_save_as_form(char const* initial) +{ + qform_single_line_input(Save_as_form_id, "Save As", initial); +} +static void push_set_tempo_form(Usz initial) +{ + char buff[64]; + int snres = snprintf(buff, sizeof buff, "%zu", initial); + char const* inistr = snres > 0 && (Usz)snres < sizeof buff ? buff : "120"; + qform_single_line_input(Set_tempo_form_id, "Set BPM", inistr); +} +static void push_set_grid_dims_form(Usz init_height, Usz init_width) +{ + char buff[128]; + int snres = snprintf(buff, sizeof buff, "%zux%zu", init_width, + init_height); + char const* inistr = snres > 0 && (Usz)snres < sizeof buff ? buff + : "57x25"; + qform_single_line_input(Set_grid_dims_form_id, "Set Grid Size", inistr); } #ifdef FEAT_PORTMIDI -staticni void push_portmidi_output_device_menu(Midi_mode const *midi_mode) { - Qmenu *qm = qmenu_create(Portmidi_output_device_menu_id); - qmenu_set_title(qm, "PortMidi Device Selection"); - PmError e = portmidi_init_if_necessary(); - if (e) { - qmenu_destroy(qm); - qmsg_printf_push("PortMidi Error", - "PortMidi error during initialization:\n%s", - Pm_GetErrorText(e)); - return; - } - int num = Pm_CountDevices(); - int output_devices = 0; - int cur_dev_id = 0; - bool has_cur_dev_id = false; - if (midi_mode->any.type == Midi_mode_type_portmidi) { - cur_dev_id = midi_mode->portmidi.device_id; - has_cur_dev_id = true; - } - for (int i = 0; i < num; ++i) { - PmDeviceInfo const *info = Pm_GetDeviceInfo(i); - if (!info || !info->output) - continue; - bool is_cur_dev_id = has_cur_dev_id && cur_dev_id == i; - qmenu_add_printf(qm, i, "(%c) #%d - %s", is_cur_dev_id ? '*' : ' ', i, - info->name); - ++output_devices; - } - if (output_devices == 0) { - qmenu_destroy(qm); - qmsg_printf_push("No PortMidi Devices", - "No PortMidi output devices found."); - return; - } - if (has_cur_dev_id) { - qmenu_set_current_item(qm, cur_dev_id); - } - qmenu_push_to_nav(qm); +staticni void push_portmidi_output_device_menu(Midi_mode const* midi_mode) +{ + Qmenu* qm = qmenu_create(Portmidi_output_device_menu_id); + qmenu_set_title(qm, "PortMidi Device Selection"); + PmError e = portmidi_init_if_necessary(); + if (e) { + qmenu_destroy(qm); + qmsg_printf_push("PortMidi Error", + "PortMidi error during initialization:\n%s", + Pm_GetErrorText(e)); + return; + } + int num = Pm_CountDevices(); + int output_devices = 0; + int cur_dev_id = 0; + bool has_cur_dev_id = false; + if (midi_mode->any.type == Midi_mode_type_portmidi) { + cur_dev_id = midi_mode->portmidi.device_id; + has_cur_dev_id = true; + } + for (int i = 0; i < num; ++i) { + PmDeviceInfo const* info = Pm_GetDeviceInfo(i); + if (!info || !info->output) + continue; + bool is_cur_dev_id = has_cur_dev_id && cur_dev_id == i; + qmenu_add_printf(qm, i, "(%c) #%d - %s", is_cur_dev_id ? '*' : ' ', i, + info->name); + ++output_devices; + } + if (output_devices == 0) { + qmenu_destroy(qm); + qmsg_printf_push("No PortMidi Devices", + "No PortMidi output devices found."); + return; + } + if (has_cur_dev_id) { + qmenu_set_current_item(qm, cur_dev_id); + } + qmenu_push_to_nav(qm); } #endif -staticni bool read_int(char const *str, int *out) { - int a; - int res = sscanf(str, "%d", &a); - if (res != 1) - return false; - *out = a; - return true; +staticni bool read_int(char const* str, int* out) +{ + int a; + int res = sscanf(str, "%d", &a); + if (res != 1) + return false; + *out = a; + return true; } // Reads something like '5x3' or '5'. Writes the same value to both outputs if // only one is specified. Returns false on error. -staticni bool read_nxn_or_n(char const *str, int *out_a, int *out_b) { - int a, b; - int res = sscanf(str, "%dx%d", &a, &b); - if (res == EOF) +staticni bool read_nxn_or_n(char const* str, int* out_a, int* out_b) +{ + int a, b; + int res = sscanf(str, "%dx%d", &a, &b); + if (res == EOF) + return false; + if (res == 1) { + *out_a = a; + *out_b = a; + return true; + } + if (res == 2) { + *out_a = a; + *out_b = b; + return true; + } return false; - if (res == 1) { - *out_a = a; - *out_b = a; - return true; - } - if (res == 2) { - *out_a = a; - *out_b = b; - return true; - } - return false; } typedef enum { - Brackpaste_seq_none = 0, - Brackpaste_seq_begin, - Brackpaste_seq_end, + Brackpaste_seq_none = 0, + Brackpaste_seq_begin, + Brackpaste_seq_end, } Brackpaste_seq; // Try to getch to complete the sequence of a start or end of brackpet paste // escape sequence. If it doesn't turn out to be one, unwind it back into the // event queue with ungetch. Yeah this is golfed let me have fun. -staticni Brackpaste_seq brackpaste_seq_getungetch(WINDOW *win) { - int chs[5], n = 0, begorend; // clang-format off +staticni Brackpaste_seq brackpaste_seq_getungetch(WINDOW* win) +{ + int chs[5], n = 0, begorend; // clang-format off if ((chs[n++] = wgetch(win)) != '[') goto unwind; if ((chs[n++] = wgetch(win)) != '2') goto unwind; if ((chs[n++] = wgetch(win)) != '0') goto unwind; @@ -2421,10 +2539,11 @@ staticni Brackpaste_seq brackpaste_seq_getungetch(WINDOW *win) { return Brackpaste_seq_none; // clang-format on } -staticni void try_send_to_gui_clipboard(Ged const *a, - bool *io_use_gui_clipboard) { - if (!*io_use_gui_clipboard) - return; +staticni void try_send_to_gui_clipboard(Ged const* a, + bool* io_use_gui_clipboard) +{ + if (!*io_use_gui_clipboard) + return; #if 0 // If we want to use grid directly Usz curs_y, curs_x, curs_h, curs_w; if (!ged_try_selection_clipped_to_field(a, &curs_y, &curs_x, &curs_h, @@ -2434,28 +2553,28 @@ staticni void try_send_to_gui_clipboard(Ged const *a, cboard_copy(a->clipboard_field.buffer, a->clipboard_field.height, a->clipboard_field.width, curs_y, curs_x, curs_h, curs_w); #endif - Usz cb_h = a->clipboard_field.height, cb_w = a->clipboard_field.width; - if (cb_h < 1 || cb_w < 1) - return; - Cboard_error cberr = - cboard_copy(a->clipboard_field.buffer, cb_h, cb_w, 0, 0, cb_h, cb_w); - if (cberr) - *io_use_gui_clipboard = false; + Usz cb_h = a->clipboard_field.height, cb_w = a->clipboard_field.width; + if (cb_h < 1 || cb_w < 1) + return; + Cboard_error cberr = cboard_copy(a->clipboard_field.buffer, cb_h, cb_w, 0, + 0, cb_h, cb_w); + if (cberr) + *io_use_gui_clipboard = false; } -static char const *const conf_file_name = "orca.conf"; +static char const* const conf_file_name = "orca.conf"; #define CONFOPT_STRING(x) #x, #define CONFOPT_ENUM(x) Confopt_##x, -#define CONFOPTS(_) \ - _(portmidi_output_device) \ - _(osc_output_address) \ - _(osc_output_port) \ - _(osc_output_enabled) \ - _(midi_beat_clock) \ - _(margins) \ - _(grid_dot_type) \ - _(grid_ruler_type) -char const *const confopts[] = {CONFOPTS(CONFOPT_STRING)}; +#define CONFOPTS(_) \ + _(portmidi_output_device) \ + _(osc_output_address) \ + _(osc_output_port) \ + _(osc_output_enabled) \ + _(midi_beat_clock) \ + _(margins) \ + _(grid_dot_type) \ + _(grid_ruler_type) +char const* const confopts[] = { CONFOPTS(CONFOPT_STRING) }; enum { Confoptslen = ORCA_ARRAY_COUNTOF(confopts) }; enum { CONFOPTS(CONFOPT_ENUM) }; #undef CONFOPTS @@ -2468,767 +2587,812 @@ enum { CONFOPTS(CONFOPT_ENUM) }; // written back to the conf file during a conf file save. #define TOUCHFLAG(_confopt) (1u << (Usz)_confopt) -char const *const prefval_plain = "plain"; -char const *const prefval_fancy = "fancy"; +char const* const prefval_plain = "plain"; +char const* const prefval_fancy = "fancy"; -staticni bool plainorfancy(char const *val, bool *out) { - if (strcmp(val, prefval_plain) == 0) { - *out = false; - return true; - } - if (strcmp(val, prefval_fancy) == 0) { - *out = true; - return true; - } - return false; +staticni bool plainorfancy(char const* val, bool* out) +{ + if (strcmp(val, prefval_plain) == 0) { + *out = false; + return true; + } + if (strcmp(val, prefval_fancy) == 0) { + *out = true; + return true; + } + return false; } -staticni bool conf_read_boolish(char const *val, bool *out) { - static char const *const trues[] = {"1", "true", "yes"}; - static char const *const falses[] = {"0", "false", "no"}; - for (Usz i = 0; i < ORCA_ARRAY_COUNTOF(trues); i++) { - if (strcmp(val, trues[i]) != 0) - continue; - *out = true; - return true; - } - for (Usz i = 0; i < ORCA_ARRAY_COUNTOF(falses); i++) { - if (strcmp(val, falses[i]) != 0) - continue; - *out = false; - return true; - } - return false; +staticni bool conf_read_boolish(char const* val, bool* out) +{ + static char const* const trues[] = { "1", "true", "yes" }; + static char const* const falses[] = { "0", "false", "no" }; + for (Usz i = 0; i < ORCA_ARRAY_COUNTOF(trues); i++) { + if (strcmp(val, trues[i]) != 0) + continue; + *out = true; + return true; + } + for (Usz i = 0; i < ORCA_ARRAY_COUNTOF(falses); i++) { + if (strcmp(val, falses[i]) != 0) + continue; + *out = false; + return true; + } + return false; } typedef struct { - Ged ged; - oso *file_name; - oso *osc_address, *osc_port, *osc_midi_bidule_path; - int undo_history_limit; - int softmargin_y, softmargin_x; - int hardmargin_y, hardmargin_x; - U32 prefs_touched; - bool use_gui_cboard; // not bitfields due to taking address of - bool strict_timing; - bool osc_output_enabled; - bool fancy_grid_dots, fancy_grid_rulers; + Ged ged; + oso* file_name; + oso *osc_address, *osc_port, *osc_midi_bidule_path; + int undo_history_limit; + int softmargin_y, softmargin_x; + int hardmargin_y, hardmargin_x; + U32 prefs_touched; + bool use_gui_cboard; // not bitfields due to taking address of + bool strict_timing; + bool osc_output_enabled; + bool fancy_grid_dots, fancy_grid_rulers; } Tui; -ORCA_OK_IF_UNUSED staticni void print_loading_message(char const *s) { - Usz len = strlen(s); - if (len > INT_MAX) - return; - int h, w; - getmaxyx(stdscr, h, w); - int y = h / 2; - int x = (int)len < w ? (w - (int)len) / 2 : 0; - werase(stdscr); - wmove(stdscr, y, x); - waddstr(stdscr, s); - refresh(); -} - -staticni void tui_load_conf(Tui *t) { - oso *portmidi_output_device = NULL, *osc_output_address = NULL, - *osc_output_port = NULL; - U32 touched = 0; - Ezconf_r ez; - for (ezconf_r_start(&ez, conf_file_name); - ezconf_r_step(&ez, confopts, Confoptslen);) { - switch (ez.index) { - case Confopt_portmidi_output_device: - osoput(&portmidi_output_device, ez.value); - break; - case Confopt_osc_output_address: { - // Don't actually allocate heap string if string is empty - Usz len = strlen(ez.value); - if (len > 0) - osoputlen(&osc_output_address, ez.value, len); - touched |= TOUCHFLAG(Confopt_osc_output_address); - break; - } - case Confopt_osc_output_port: { - osoput(&osc_output_port, ez.value); - touched |= TOUCHFLAG(Confopt_osc_output_port); - break; - } - case Confopt_osc_output_enabled: { - bool enabled; - if (conf_read_boolish(ez.value, &enabled)) { - t->osc_output_enabled = enabled; - touched |= TOUCHFLAG(Confopt_osc_output_enabled); - } - break; - } - case Confopt_midi_beat_clock: { - bool enabled; - if (conf_read_boolish(ez.value, &enabled)) { - t->ged.midi_bclock = enabled; - touched |= TOUCHFLAG(Confopt_midi_beat_clock); - } - break; - } - case Confopt_margins: { - int softmargin_y, softmargin_x; - if (read_nxn_or_n(ez.value, &softmargin_x, &softmargin_y) && - softmargin_y >= 0 && softmargin_x >= 0) { - t->softmargin_y = softmargin_y; - t->softmargin_x = softmargin_x; - touched |= TOUCHFLAG(Confopt_margins); - } - break; - } - case Confopt_grid_dot_type: { - bool fancy; - if (plainorfancy(ez.value, &fancy)) { - t->fancy_grid_dots = fancy; - touched |= TOUCHFLAG(Confopt_grid_dot_type); - } - break; - } - case Confopt_grid_ruler_type: { - bool fancy; - if (plainorfancy(ez.value, &fancy)) { - t->fancy_grid_rulers = fancy; - touched |= TOUCHFLAG(Confopt_grid_ruler_type); - } - break; - } - } - } - - if (touched & TOUCHFLAG(Confopt_osc_output_address)) { - ososwap(&t->osc_address, &osc_output_address); - } else { - // leave null - } - if (touched & TOUCHFLAG(Confopt_osc_output_port)) { - ososwap(&t->osc_port, &osc_output_port); - } else { - osoput(&t->osc_port, "49162"); - } +ORCA_OK_IF_UNUSED staticni void print_loading_message(char const* s) +{ + Usz len = strlen(s); + if (len > INT_MAX) + return; + int h, w; + getmaxyx(stdscr, h, w); + int y = h / 2; + int x = (int)len < w ? (w - (int)len) / 2 : 0; + werase(stdscr); + wmove(stdscr, y, x); + waddstr(stdscr, s); + refresh(); +} + +staticni void tui_load_conf(Tui* t) +{ + oso *portmidi_output_device = NULL, *osc_output_address = NULL, + *osc_output_port = NULL; + U32 touched = 0; + Ezconf_r ez; + for (ezconf_r_start(&ez, conf_file_name); + ezconf_r_step(&ez, confopts, Confoptslen);) { + switch (ez.index) { + case Confopt_portmidi_output_device: + osoput(&portmidi_output_device, ez.value); + break; + case Confopt_osc_output_address: { + // Don't actually allocate heap string if string is empty + Usz len = strlen(ez.value); + if (len > 0) + osoputlen(&osc_output_address, ez.value, len); + touched |= TOUCHFLAG(Confopt_osc_output_address); + break; + } + case Confopt_osc_output_port: { + osoput(&osc_output_port, ez.value); + touched |= TOUCHFLAG(Confopt_osc_output_port); + break; + } + case Confopt_osc_output_enabled: { + bool enabled; + if (conf_read_boolish(ez.value, &enabled)) { + t->osc_output_enabled = enabled; + touched |= TOUCHFLAG(Confopt_osc_output_enabled); + } + break; + } + case Confopt_midi_beat_clock: { + bool enabled; + if (conf_read_boolish(ez.value, &enabled)) { + t->ged.midi_bclock = enabled; + touched |= TOUCHFLAG(Confopt_midi_beat_clock); + } + break; + } + case Confopt_margins: { + int softmargin_y, softmargin_x; + if (read_nxn_or_n(ez.value, &softmargin_x, &softmargin_y) + && softmargin_y >= 0 && softmargin_x >= 0) { + t->softmargin_y = softmargin_y; + t->softmargin_x = softmargin_x; + touched |= TOUCHFLAG(Confopt_margins); + } + break; + } + case Confopt_grid_dot_type: { + bool fancy; + if (plainorfancy(ez.value, &fancy)) { + t->fancy_grid_dots = fancy; + touched |= TOUCHFLAG(Confopt_grid_dot_type); + } + break; + } + case Confopt_grid_ruler_type: { + bool fancy; + if (plainorfancy(ez.value, &fancy)) { + t->fancy_grid_rulers = fancy; + touched |= TOUCHFLAG(Confopt_grid_ruler_type); + } + break; + } + } + } + + if (touched & TOUCHFLAG(Confopt_osc_output_address)) { + ososwap(&t->osc_address, &osc_output_address); + } else { + // leave null + } + if (touched & TOUCHFLAG(Confopt_osc_output_port)) { + ososwap(&t->osc_port, &osc_output_port); + } else { + osoput(&t->osc_port, "49162"); + } #ifdef FEAT_PORTMIDI - if (t->ged.midi_mode.any.type == Midi_mode_type_null && - osolen(portmidi_output_device)) { - // PortMidi can be hilariously slow to initialize. Since it will be - // initialized automatically if the user has a prefs entry for PortMidi - // devices, we should show a message to the user letting them know why - // orca is locked up/frozen. (When it's done via menu action, that's - // fine, since they can figure out why.) - print_loading_message("Waiting on PortMidi..."); - PmError pmerr; - PmDeviceID devid; - if (portmidi_find_device_id_by_name(osoc(portmidi_output_device), - osolen(portmidi_output_device), &pmerr, - &devid)) { - midi_mode_deinit(&t->ged.midi_mode); - pmerr = midi_mode_init_portmidi(&t->ged.midi_mode, devid); - if (pmerr) { - // todo stuff - } - } - } + if (t->ged.midi_mode.any.type == Midi_mode_type_null + && osolen(portmidi_output_device)) { + // PortMidi can be hilariously slow to initialize. Since it will be + // initialized automatically if the user has a prefs entry for PortMidi + // devices, we should show a message to the user letting them know why + // orca is locked up/frozen. (When it's done via menu action, that's + // fine, since they can figure out why.) + print_loading_message("Waiting on PortMidi..."); + PmError pmerr; + PmDeviceID devid; + if (portmidi_find_device_id_by_name(osoc(portmidi_output_device), + osolen(portmidi_output_device), + &pmerr, &devid)) { + midi_mode_deinit(&t->ged.midi_mode); + pmerr = midi_mode_init_portmidi(&t->ged.midi_mode, devid); + if (pmerr) { + // todo stuff + } + } + } #endif - t->prefs_touched |= touched; - osofree(portmidi_output_device); - osofree(osc_output_address); - osofree(osc_output_port); -} - -staticni void tui_save_prefs(Tui *t) { - Ezconf_opt optsbuff[Confoptslen]; - Ezconf_w ez; - ezconf_w_start(&ez, optsbuff, ORCA_ARRAY_COUNTOF(optsbuff), conf_file_name); - oso *midi_output_device_name = NULL; - switch (t->ged.midi_mode.any.type) { - case Midi_mode_type_null: - break; - case Midi_mode_type_osc_bidule: - // TODO - break; + t->prefs_touched |= touched; + osofree(portmidi_output_device); + osofree(osc_output_address); + osofree(osc_output_port); +} + +staticni void tui_save_prefs(Tui* t) +{ + Ezconf_opt optsbuff[Confoptslen]; + Ezconf_w ez; + ezconf_w_start(&ez, optsbuff, ORCA_ARRAY_COUNTOF(optsbuff), + conf_file_name); + oso* midi_output_device_name = NULL; + switch (t->ged.midi_mode.any.type) { + case Midi_mode_type_null: + break; + case Midi_mode_type_osc_bidule: + // TODO + break; #ifdef FEAT_PORTMIDI - case Midi_mode_type_portmidi: { - PmError pmerror; - if (!portmidi_find_name_of_device_id(t->ged.midi_mode.portmidi.device_id, - &pmerror, &midi_output_device_name) || - osolen(midi_output_device_name) < 1) { - osowipe(&midi_output_device_name); - break; - } - ezconf_w_addopt(&ez, confopts[Confopt_portmidi_output_device], - Confopt_portmidi_output_device); - break; - } + case Midi_mode_type_portmidi: { + PmError pmerror; + if (!portmidi_find_name_of_device_id( + t->ged.midi_mode.portmidi.device_id, &pmerror, + &midi_output_device_name) + || osolen(midi_output_device_name) < 1) { + osowipe(&midi_output_device_name); + break; + } + ezconf_w_addopt(&ez, confopts[Confopt_portmidi_output_device], + Confopt_portmidi_output_device); + break; + } #endif - } - // Add all conf items touched by user that we want to write to config file. - // "Touched" items include conf items that were present on disk when we first - // loaded the config file, plus the items that the user has modified by - // interacting with the menus. - for (int i = 0; i < Confoptslen; i++) { - if (i == Confopt_portmidi_output_device) - // This has its own special logic - continue; - if (t->prefs_touched & TOUCHFLAG(i)) - ezconf_w_addopt(&ez, confopts[i], i); - } - while (ezconf_w_step(&ez)) { - switch (ez.optid) { - case Confopt_osc_output_address: - // Fine to not write anything here - if (osolen(t->osc_address)) - fputs(osoc(t->osc_address), ez.file); - break; - case Confopt_osc_output_port: - if (osolen(t->osc_port)) - fputs(osoc(t->osc_port), ez.file); - break; - case Confopt_osc_output_enabled: - fputc(t->osc_output_enabled ? '1' : '0', ez.file); - break; + } + // Add all conf items touched by user that we want to write to config file. + // "Touched" items include conf items that were present on disk when we + // first loaded the config file, plus the items that the user has modified + // by interacting with the menus. + for (int i = 0; i < Confoptslen; i++) { + if (i == Confopt_portmidi_output_device) + // This has its own special logic + continue; + if (t->prefs_touched & TOUCHFLAG(i)) + ezconf_w_addopt(&ez, confopts[i], i); + } + while (ezconf_w_step(&ez)) { + switch (ez.optid) { + case Confopt_osc_output_address: + // Fine to not write anything here + if (osolen(t->osc_address)) + fputs(osoc(t->osc_address), ez.file); + break; + case Confopt_osc_output_port: + if (osolen(t->osc_port)) + fputs(osoc(t->osc_port), ez.file); + break; + case Confopt_osc_output_enabled: + fputc(t->osc_output_enabled ? '1' : '0', ez.file); + break; #ifdef FEAT_PORTMIDI - case Confopt_portmidi_output_device: - fputs(osoc(midi_output_device_name), ez.file); - break; + case Confopt_portmidi_output_device: + fputs(osoc(midi_output_device_name), ez.file); + break; #endif - case Confopt_midi_beat_clock: - fputc(t->ged.midi_bclock ? '1' : '0', ez.file); - break; - case Confopt_margins: - fprintf(ez.file, "%dx%d", t->softmargin_x, t->softmargin_y); - break; - case Confopt_grid_dot_type: - fputs(t->fancy_grid_dots ? prefval_fancy : prefval_plain, ez.file); - break; - case Confopt_grid_ruler_type: - fputs(t->fancy_grid_rulers ? prefval_fancy : prefval_plain, ez.file); - break; - } - } - osofree(midi_output_device_name); - if (ez.error) { - char const *msg = ezconf_w_errorstring(ez.error); - qmsg_printf_push("Config Error", - "Error when writing configuration file:\n%s", msg); - } -} - -staticni bool tui_suggest_nice_grid_size(Tui *t, int win_h, int win_w, - Usz *out_grid_h, Usz *out_grid_w) { - int softmargin_y = t->softmargin_y, softmargin_x = t->softmargin_x; - int ruler_spacing_y = (int)t->ged.ruler_spacing_y, - ruler_spacing_x = (int)t->ged.ruler_spacing_x; - if (win_h < 1 || win_w < 1 || softmargin_y < 0 || softmargin_x < 0 || - ruler_spacing_y < 1 || ruler_spacing_x < 1) - return false; - // TODO overflow checks - int h = (win_h - softmargin_y - Hud_height - 1) / ruler_spacing_y; - h *= ruler_spacing_y; - int w = (win_w - softmargin_x * 2 - 1) / ruler_spacing_x; - w *= ruler_spacing_x; - if (h < ruler_spacing_y) - h = ruler_spacing_y; - if (w < ruler_spacing_x) - w = ruler_spacing_x; - h++; - w++; - if (h >= ORCA_Y_MAX || w >= ORCA_X_MAX) - return false; - *out_grid_h = (Usz)h; - *out_grid_w = (Usz)w; - return true; + case Confopt_midi_beat_clock: + fputc(t->ged.midi_bclock ? '1' : '0', ez.file); + break; + case Confopt_margins: + fprintf(ez.file, "%dx%d", t->softmargin_x, t->softmargin_y); + break; + case Confopt_grid_dot_type: + fputs(t->fancy_grid_dots ? prefval_fancy : prefval_plain, ez.file); + break; + case Confopt_grid_ruler_type: + fputs(t->fancy_grid_rulers ? prefval_fancy : prefval_plain, + ez.file); + break; + } + } + osofree(midi_output_device_name); + if (ez.error) { + char const* msg = ezconf_w_errorstring(ez.error); + qmsg_printf_push("Config Error", + "Error when writing configuration file:\n%s", msg); + } +} + +staticni bool tui_suggest_nice_grid_size(Tui* t, int win_h, int win_w, + Usz* out_grid_h, Usz* out_grid_w) +{ + int softmargin_y = t->softmargin_y, softmargin_x = t->softmargin_x; + int ruler_spacing_y = (int)t->ged.ruler_spacing_y, + ruler_spacing_x = (int)t->ged.ruler_spacing_x; + if (win_h < 1 || win_w < 1 || softmargin_y < 0 || softmargin_x < 0 + || ruler_spacing_y < 1 || ruler_spacing_x < 1) + return false; + // TODO overflow checks + int h = (win_h - softmargin_y - Hud_height - 1) / ruler_spacing_y; + h *= ruler_spacing_y; + int w = (win_w - softmargin_x * 2 - 1) / ruler_spacing_x; + w *= ruler_spacing_x; + if (h < ruler_spacing_y) + h = ruler_spacing_y; + if (w < ruler_spacing_x) + w = ruler_spacing_x; + h++; + w++; + if (h >= ORCA_Y_MAX || w >= ORCA_X_MAX) + return false; + *out_grid_h = (Usz)h; + *out_grid_w = (Usz)w; + return true; } -staticni bool tui_suggest_tight_grid_size(Tui *t, int win_h, int win_w, - Usz *out_grid_h, Usz *out_grid_w) { - int softmargin_y = t->softmargin_y, softmargin_x = t->softmargin_x; - if (win_h < 1 || win_w < 1 || softmargin_y < 0 || softmargin_x < 0) - return false; - // TODO overflow checks - int h = win_h - softmargin_y - Hud_height; - int w = win_w - softmargin_x * 2; - if (h < 1 || w < 1 || h >= ORCA_Y_MAX || w >= ORCA_X_MAX) - return false; - *out_grid_h = (Usz)h; - *out_grid_w = (Usz)w; - return true; -} - -staticni void plainorfancy_menu_was_picked(Tui *t, int picked_id, - bool *p_is_fancy, - U32 pref_touch_flag) { - bool is_fancy = picked_id == 1; // 1 -> fancy, 2 -> plain - qnav_stack_pop(); - // ^- doesn't actually matter when we do this, with our current code - if (is_fancy == *p_is_fancy) - return; - *p_is_fancy = is_fancy; - t->prefs_touched |= pref_touch_flag; - tui_save_prefs(t); - t->ged.is_draw_dirty = true; -} - -staticni bool tui_restart_osc_udp_if_enabled_diderror(Tui *t) { - bool error = false; - if (t->osc_output_enabled && t->osc_port) { - error = !ged_set_osc_udp(&t->ged, osoc(t->osc_address) /* null ok here */, - osoc(t->osc_port)); - } else { - ged_clear_osc_udp(&t->ged); - } - return error; -} -staticni void tui_restart_osc_udp_showerror(void) { - qmsg_printf_push("OSC Networking Error", "Failed to set up OSC networking"); -} -staticni void tui_restart_osc_udp_if_enabled(Tui *t) { - bool old_inuse = ged_is_using_osc_udp(&t->ged); - bool did_error = tui_restart_osc_udp_if_enabled_diderror(t); - bool new_inuse = ged_is_using_osc_udp(&t->ged); - if (old_inuse != new_inuse) { - Qblock *qb = qnav_top_block(); - if (qb && qb->tag == Qblock_type_qmenu && - qmenu_id(qmenu_of(qb)) == Osc_menu_id) { - int itemid = qmenu_current_item(qmenu_of(qb)); - qnav_stack_pop(); - push_osc_menu(new_inuse); - qmenu_set_current_item(qmenu_of(qnav_top_block()), itemid); - } - } - if (did_error) - tui_restart_osc_udp_showerror(); -} -staticni void tui_adjust_term_size(Tui *t, WINDOW **cont_window) { - int term_h, term_w; - getmaxyx(stdscr, term_h, term_w); - assert(term_h >= 0 && term_w >= 0); - int content_y = 0, content_x = 0; - int content_h = term_h, content_w = term_w; - if (t->hardmargin_y > 0 && term_h > t->hardmargin_y * 2 + 2) { - content_y += t->hardmargin_y; - content_h -= t->hardmargin_y * 2; - } - if (t->hardmargin_x > 0 && term_w > t->hardmargin_x * 2 + 2) { - content_x += t->hardmargin_x; - content_w -= t->hardmargin_x * 2; - } - bool remake_window = true; - if (*cont_window) { - int cwin_y, cwin_x, cwin_h, cwin_w; - getbegyx(*cont_window, cwin_y, cwin_x); - getmaxyx(*cont_window, cwin_h, cwin_w); - remake_window = cwin_y != content_y || cwin_x != content_x || - cwin_h != content_h || cwin_w != content_w; - } - if (remake_window) { - if (*cont_window) - delwin(*cont_window); - wclear(stdscr); - *cont_window = derwin(stdscr, content_h, content_w, content_y, content_x); +staticni bool tui_suggest_tight_grid_size(Tui* t, int win_h, int win_w, + Usz* out_grid_h, Usz* out_grid_w) +{ + int softmargin_y = t->softmargin_y, softmargin_x = t->softmargin_x; + if (win_h < 1 || win_w < 1 || softmargin_y < 0 || softmargin_x < 0) + return false; + // TODO overflow checks + int h = win_h - softmargin_y - Hud_height; + int w = win_w - softmargin_x * 2; + if (h < 1 || w < 1 || h >= ORCA_Y_MAX || w >= ORCA_X_MAX) + return false; + *out_grid_h = (Usz)h; + *out_grid_w = (Usz)w; + return true; +} + +staticni void plainorfancy_menu_was_picked(Tui* t, int picked_id, + bool* p_is_fancy, + U32 pref_touch_flag) +{ + bool is_fancy = picked_id == 1; // 1 -> fancy, 2 -> plain + qnav_stack_pop(); + // ^- doesn't actually matter when we do this, with our current code + if (is_fancy == *p_is_fancy) + return; + *p_is_fancy = is_fancy; + t->prefs_touched |= pref_touch_flag; + tui_save_prefs(t); t->ged.is_draw_dirty = true; - } - // OK to call this unconditionally -- deriving the sub-window areas is - // more than a single comparison, and we don't want to split up or - // duplicate the math and checks for it, so this routine will calculate - // the stuff it needs to and then early-out if there's no further work. - ged_set_window_size(&t->ged, content_h, content_w, t->softmargin_y, - t->softmargin_x); } -static void tui_try_save(Tui *t) { - if (osolen(t->file_name) > 0) - try_save_with_msg(&t->ged.field, t->file_name); - else - push_save_as_form(""); +staticni bool tui_restart_osc_udp_if_enabled_diderror(Tui* t) +{ + bool error = false; + if (t->osc_output_enabled && t->osc_port) { + error = !ged_set_osc_udp(&t->ged, + osoc(t->osc_address) /* null ok here */, + osoc(t->osc_port)); + } else { + ged_clear_osc_udp(&t->ged); + } + return error; +} +staticni void tui_restart_osc_udp_showerror(void) +{ + qmsg_printf_push("OSC Networking Error", + "Failed to set up OSC networking"); +} +staticni void tui_restart_osc_udp_if_enabled(Tui* t) +{ + bool old_inuse = ged_is_using_osc_udp(&t->ged); + bool did_error = tui_restart_osc_udp_if_enabled_diderror(t); + bool new_inuse = ged_is_using_osc_udp(&t->ged); + if (old_inuse != new_inuse) { + Qblock* qb = qnav_top_block(); + if (qb && qb->tag == Qblock_type_qmenu + && qmenu_id(qmenu_of(qb)) == Osc_menu_id) { + int itemid = qmenu_current_item(qmenu_of(qb)); + qnav_stack_pop(); + push_osc_menu(new_inuse); + qmenu_set_current_item(qmenu_of(qnav_top_block()), itemid); + } + } + if (did_error) + tui_restart_osc_udp_showerror(); +} +staticni void tui_adjust_term_size(Tui* t, WINDOW** cont_window) +{ + int term_h, term_w; + getmaxyx(stdscr, term_h, term_w); + assert(term_h >= 0 && term_w >= 0); + int content_y = 0, content_x = 0; + int content_h = term_h, content_w = term_w; + if (t->hardmargin_y > 0 && term_h > t->hardmargin_y * 2 + 2) { + content_y += t->hardmargin_y; + content_h -= t->hardmargin_y * 2; + } + if (t->hardmargin_x > 0 && term_w > t->hardmargin_x * 2 + 2) { + content_x += t->hardmargin_x; + content_w -= t->hardmargin_x * 2; + } + bool remake_window = true; + if (*cont_window) { + int cwin_y, cwin_x, cwin_h, cwin_w; + getbegyx(*cont_window, cwin_y, cwin_x); + getmaxyx(*cont_window, cwin_h, cwin_w); + remake_window = cwin_y != content_y || cwin_x != content_x + || cwin_h != content_h || cwin_w != content_w; + } + if (remake_window) { + if (*cont_window) + delwin(*cont_window); + wclear(stdscr); + *cont_window = derwin(stdscr, content_h, content_w, content_y, + content_x); + t->ged.is_draw_dirty = true; + } + // OK to call this unconditionally -- deriving the sub-window areas is + // more than a single comparison, and we don't want to split up or + // duplicate the math and checks for it, so this routine will calculate + // the stuff it needs to and then early-out if there's no further work. + ged_set_window_size(&t->ged, content_h, content_w, t->softmargin_y, + t->softmargin_x); +} + +static void tui_try_save(Tui* t) +{ + if (osolen(t->file_name) > 0) + try_save_with_msg(&t->ged.field, t->file_name); + else + push_save_as_form(""); } typedef enum { - Tui_menus_nothing = 0, - Tui_menus_quit, - Tui_menus_consumed_input, + Tui_menus_nothing = 0, + Tui_menus_quit, + Tui_menus_consumed_input, } Tui_menus_result; -staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { - Qblock *qb = qnav_top_block(); - if (!qb) - return Tui_menus_nothing; - if (key == CTRL_PLUS('q')) - return Tui_menus_quit; - switch (qb->tag) { - case Qblock_type_qmsg: { - Qmsg *qm = qmsg_of(qb); - Qmsg_action act; - if (qmsg_drive(qm, key, &act)) { - if (act.dismiss) - qnav_stack_pop(); - if (act.passthrough) +staticni Tui_menus_result tui_drive_menus(Tui* t, int key) +{ + Qblock* qb = qnav_top_block(); + if (!qb) return Tui_menus_nothing; - } - break; - } - case Qblock_type_qmenu: { - Qmenu *qm = qmenu_of(qb); - Qmenu_action act; - // special case for main menu: pressing the key to open it will close - // it again. - if (qmenu_id(qm) == Main_menu_id && - (key == CTRL_PLUS('d') || key == KEY_F(1))) { - qnav_stack_pop(); - break; - } - if (!qmenu_drive(qm, key, &act)) - break; - switch (act.any.type) { - case Qmenu_action_type_canceled: - qnav_stack_pop(); - break; - case Qmenu_action_type_picked: - switch (qmenu_id(qm)) { - case Main_menu_id: - switch (act.picked.id) { - case Main_menu_quit: - return Tui_menus_quit; - case Main_menu_playback: - push_playback_menu(t->ged.midi_bclock); - break; - case Main_menu_cosmetics: - push_cosmetics_menu(); - break; - case Main_menu_osc: - push_osc_menu(ged_is_using_osc_udp(&t->ged)); - break; - case Main_menu_controls: - push_controls_msg(); - break; - case Main_menu_opers_guide: - push_opers_guide_msg(); - break; - case Main_menu_about: - push_about_msg(); - break; - case Main_menu_new: - push_confirm_new_file_menu(); - break; - case Main_menu_open: - push_open_form(osoc(t->file_name)); - break; - case Main_menu_save: - tui_try_save(t); - break; - case Main_menu_save_as: - push_save_as_form(osoc(t->file_name)); - break; - case Main_menu_set_tempo: - push_set_tempo_form(t->ged.bpm); - break; - case Main_menu_set_grid_dims: - push_set_grid_dims_form(t->ged.field.height, t->ged.field.width); - break; - case Main_menu_autofit_grid: - push_autofit_menu(); - break; -#ifdef FEAT_PORTMIDI - case Main_menu_choose_portmidi_output: - push_portmidi_output_device_menu(&t->ged.midi_mode); - break; -#endif + if (key == CTRL_PLUS('q')) + return Tui_menus_quit; + switch (qb->tag) { + case Qblock_type_qmsg: { + Qmsg* qm = qmsg_of(qb); + Qmsg_action act; + if (qmsg_drive(qm, key, &act)) { + if (act.dismiss) + qnav_stack_pop(); + if (act.passthrough) + return Tui_menus_nothing; } break; - case Autofit_menu_id: { - Usz new_field_h, new_field_w; - bool did_get_ok_size = false; - switch (act.picked.id) { - case Autofit_nicely_id: - did_get_ok_size = tui_suggest_nice_grid_size( - t, t->ged.win_h, t->ged.win_w, &new_field_h, &new_field_w); - break; - case Autofit_tightly_id: - did_get_ok_size = tui_suggest_tight_grid_size( - t, t->ged.win_h, t->ged.win_w, &new_field_h, &new_field_w); - break; - } - if (did_get_ok_size) { - ged_resize_grid(&t->ged.field, &t->ged.mbuf_r, new_field_h, - new_field_w, t->ged.tick_num, &t->ged.scratch_field, - &t->ged.undo_hist, &t->ged.ged_cursor); - ged_update_internal_geometry(&t->ged); - t->ged.needs_remarking = true; - t->ged.is_draw_dirty = true; - ged_make_cursor_visible(&t->ged); - } - qnav_stack_pop(); - pop_qnav_if_main_menu(); - break; - } - case Confirm_new_file_menu_id: - switch (act.picked.id) { - case Confirm_new_file_reject_id: - qnav_stack_pop(); - break; - case Confirm_new_file_accept_id: { - Usz new_field_h, new_field_w; - if (tui_suggest_nice_grid_size(t, t->ged.win_h, t->ged.win_w, - &new_field_h, &new_field_w)) { - undo_history_push(&t->ged.undo_hist, &t->ged.field, - t->ged.tick_num); - field_resize_raw(&t->ged.field, new_field_h, new_field_w); - memset(t->ged.field.buffer, '.', - new_field_h * new_field_w * sizeof(Glyph)); - ged_cursor_confine(&t->ged.ged_cursor, new_field_h, new_field_w); - mbuf_reusable_ensure_size(&t->ged.mbuf_r, new_field_h, new_field_w); - ged_update_internal_geometry(&t->ged); - ged_make_cursor_visible(&t->ged); - t->ged.needs_remarking = true; - t->ged.is_draw_dirty = true; - osoclear(&t->file_name); + } + case Qblock_type_qmenu: { + Qmenu* qm = qmenu_of(qb); + Qmenu_action act; + // special case for main menu: pressing the key to open it will close + // it again. + if (qmenu_id(qm) == Main_menu_id + && (key == CTRL_PLUS('d') || key == KEY_F(1))) { qnav_stack_pop(); - pop_qnav_if_main_menu(); - } - break; - } - } - break; - case Cosmetics_menu_id: - switch (act.picked.id) { - case Cosmetics_soft_margins_id: - push_soft_margins_form(t->softmargin_y, t->softmargin_x); - break; - case Cosmetics_grid_dots_id: - push_plainorfancy_menu(Set_fancy_grid_dots_menu_id, "Grid Dots", - t->fancy_grid_dots); - break; - case Cosmetics_grid_rulers_id: - push_plainorfancy_menu(Set_fancy_grid_rulers_menu_id, "Grid Rulers", - t->fancy_grid_rulers); - break; - } - break; - case Playback_menu_id: - switch (act.picked.id) { - case Playback_menu_midi_bclock: { - bool new_enabled = !t->ged.midi_bclock; - t->ged.midi_bclock = new_enabled; - if (t->ged.is_playing) { - int msgbyte = new_enabled ? 0xFA /* start */ : 0xFC /* stop */; - send_midi_byte(t->ged.oosc_dev, &t->ged.midi_mode, msgbyte); - // TODO timing judder will be experienced here, because the - // deadline calculation conditions will have been changed by - // toggling the midi_bclock flag. We would have to transfer the - // current remaining time from the reference clock point into the - // accum time, and mutiply or divide it. - } - t->prefs_touched |= TOUCHFLAG(Confopt_midi_beat_clock); - qnav_stack_pop(); - push_playback_menu(new_enabled); - tui_save_prefs(t); - break; - } - } - break; - case Set_fancy_grid_dots_menu_id: - plainorfancy_menu_was_picked(t, act.picked.id, &t->fancy_grid_dots, - TOUCHFLAG(Confopt_grid_dot_type)); - break; - case Set_fancy_grid_rulers_menu_id: - plainorfancy_menu_was_picked(t, act.picked.id, &t->fancy_grid_rulers, - TOUCHFLAG(Confopt_grid_ruler_type)); - break; - case Osc_menu_id: - switch (act.picked.id) { - case Osc_menu_output_enabledisable: { - qnav_stack_pop(); - t->osc_output_enabled = !ged_is_using_osc_udp(&t->ged); - // Funny dance to keep the qnav stack in good order - bool diderror = tui_restart_osc_udp_if_enabled_diderror(t); - push_osc_menu(ged_is_using_osc_udp(&t->ged)); - if (diderror) { - t->osc_output_enabled = false; - tui_restart_osc_udp_showerror(); - } - t->prefs_touched |= TOUCHFLAG(Confopt_osc_output_enabled); - tui_save_prefs(t); - break; - } - case Osc_menu_output_address: - push_osc_output_address_form(osoc(t->osc_address) /* null ok */); - break; - case Osc_menu_output_port: - push_osc_output_port_form(osoc(t->osc_port) /* null ok */); - break; - } - break; -#ifdef FEAT_PORTMIDI - case Portmidi_output_device_menu_id: { - ged_stop_all_sustained_notes(&t->ged); - midi_mode_deinit(&t->ged.midi_mode); - PmError pme = midi_mode_init_portmidi(&t->ged.midi_mode, act.picked.id); - qnav_stack_pop(); - if (pme) { - qmsg_printf_push("PortMidi Error", - "Error setting PortMidi output device:\n%s", - Pm_GetErrorText(pme)); - } else { - tui_save_prefs(t); - } - break; - } -#endif - } - break; - } - break; - } - case Qblock_type_qform: { - Qform *qf = qform_of(qb); - Qform_action act; - if (qform_drive(qf, key, &act)) { - switch (act.any.type) { - case Qform_action_type_canceled: - qnav_stack_pop(); - break; - case Qform_action_type_submitted: { - switch (qform_id(qf)) { - case Open_form_id: { - oso *temp_name = qform_get_nonempty_single_line_input(qf); - if (!temp_name) - break; - expand_home_tilde(&temp_name); - if (!temp_name) break; - bool added_hist = undo_history_push(&t->ged.undo_hist, &t->ged.field, - t->ged.tick_num); - Field_load_error fle = - field_load_file(osoc(temp_name), &t->ged.field); - if (fle == Field_load_error_ok) { - qnav_stack_pop(); - osoputoso(&t->file_name, temp_name); - mbuf_reusable_ensure_size(&t->ged.mbuf_r, t->ged.field.height, - t->ged.field.width); - ged_cursor_confine(&t->ged.ged_cursor, t->ged.field.height, - t->ged.field.width); - ged_update_internal_geometry(&t->ged); - ged_make_cursor_visible(&t->ged); - t->ged.needs_remarking = true; - t->ged.is_draw_dirty = true; - pop_qnav_if_main_menu(); - } else { - if (added_hist) - undo_history_pop(&t->ged.undo_hist, &t->ged.field, - &t->ged.tick_num); - qmsg_printf_push("Error Loading File", "%s:\n%s", osoc(temp_name), - field_load_error_string(fle)); - } - osofree(temp_name); - break; } - case Save_as_form_id: { - oso *temp_name = qform_get_nonempty_single_line_input(qf); - if (!temp_name) - break; - qnav_stack_pop(); - bool saved_ok = try_save_with_msg(&t->ged.field, temp_name); - if (saved_ok) - osoputoso(&t->file_name, temp_name); - osofree(temp_name); - break; - } - case Set_tempo_form_id: { - oso *tmpstr = qform_get_nonempty_single_line_input(qf); - if (!tmpstr) + if (!qmenu_drive(qm, key, &act)) break; - int newbpm = atoi(osoc(tmpstr)); - if (newbpm > 0) { - t->ged.bpm = (Usz)newbpm; + switch (act.any.type) { + case Qmenu_action_type_canceled: qnav_stack_pop(); - } - osofree(tmpstr); - break; - } - case Osc_output_address_form_id: { - oso *addr = NULL; - // Empty string is OK here - if (qform_get_single_text_line(qf, &addr)) { - if (osolen(addr)) - ososwap(&t->osc_address, &addr); - else - osowipe(&t->osc_address); - qnav_stack_pop(); - tui_restart_osc_udp_if_enabled(t); - t->prefs_touched |= TOUCHFLAG(Confopt_osc_output_address); - tui_save_prefs(t); - } - osofree(addr); - break; - } - case Osc_output_port_form_id: { - oso *portstr = qform_get_nonempty_single_line_input(qf); - if (!portstr) break; - qnav_stack_pop(); - ososwap(&t->osc_port, &portstr); - tui_restart_osc_udp_if_enabled(t); - osofree(portstr); - t->prefs_touched |= TOUCHFLAG(Confopt_osc_output_port); - tui_save_prefs(t); - break; - } - case Set_grid_dims_form_id: { - oso *tmpstr = qform_get_nonempty_single_line_input(qf); - if (!tmpstr) + case Qmenu_action_type_picked: + switch (qmenu_id(qm)) { + case Main_menu_id: + switch (act.picked.id) { + case Main_menu_quit: + return Tui_menus_quit; + case Main_menu_playback: + push_playback_menu(t->ged.midi_bclock); + break; + case Main_menu_cosmetics: + push_cosmetics_menu(); + break; + case Main_menu_osc: + push_osc_menu(ged_is_using_osc_udp(&t->ged)); + break; + case Main_menu_controls: + push_controls_msg(); + break; + case Main_menu_opers_guide: + push_opers_guide_msg(); + break; + case Main_menu_about: + push_about_msg(); + break; + case Main_menu_new: + push_confirm_new_file_menu(); + break; + case Main_menu_open: + push_open_form(osoc(t->file_name)); + break; + case Main_menu_save: + tui_try_save(t); + break; + case Main_menu_save_as: + push_save_as_form(osoc(t->file_name)); + break; + case Main_menu_set_tempo: + push_set_tempo_form(t->ged.bpm); + break; + case Main_menu_set_grid_dims: + push_set_grid_dims_form(t->ged.field.height, + t->ged.field.width); + break; + case Main_menu_autofit_grid: + push_autofit_menu(); + break; +#ifdef FEAT_PORTMIDI + case Main_menu_choose_portmidi_output: + push_portmidi_output_device_menu(&t->ged.midi_mode); + break; +#endif + } + break; + case Autofit_menu_id: { + Usz new_field_h, new_field_w; + bool did_get_ok_size = false; + switch (act.picked.id) { + case Autofit_nicely_id: + did_get_ok_size = tui_suggest_nice_grid_size( + t, t->ged.win_h, t->ged.win_w, &new_field_h, + &new_field_w); + break; + case Autofit_tightly_id: + did_get_ok_size = tui_suggest_tight_grid_size( + t, t->ged.win_h, t->ged.win_w, &new_field_h, + &new_field_w); + break; + } + if (did_get_ok_size) { + ged_resize_grid(&t->ged.field, &t->ged.mbuf_r, new_field_h, + new_field_w, t->ged.tick_num, + &t->ged.scratch_field, &t->ged.undo_hist, + &t->ged.ged_cursor); + ged_update_internal_geometry(&t->ged); + t->ged.needs_remarking = true; + t->ged.is_draw_dirty = true; + ged_make_cursor_visible(&t->ged); + } + qnav_stack_pop(); + pop_qnav_if_main_menu(); + break; + } + case Confirm_new_file_menu_id: + switch (act.picked.id) { + case Confirm_new_file_reject_id: + qnav_stack_pop(); + break; + case Confirm_new_file_accept_id: { + Usz new_field_h, new_field_w; + if (tui_suggest_nice_grid_size(t, t->ged.win_h, + t->ged.win_w, &new_field_h, + &new_field_w)) { + undo_history_push(&t->ged.undo_hist, &t->ged.field, + t->ged.tick_num); + field_resize_raw(&t->ged.field, new_field_h, + new_field_w); + memset(t->ged.field.buffer, '.', + new_field_h * new_field_w * sizeof(Glyph)); + ged_cursor_confine(&t->ged.ged_cursor, new_field_h, + new_field_w); + mbuf_reusable_ensure_size(&t->ged.mbuf_r, new_field_h, + new_field_w); + ged_update_internal_geometry(&t->ged); + ged_make_cursor_visible(&t->ged); + t->ged.needs_remarking = true; + t->ged.is_draw_dirty = true; + osoclear(&t->file_name); + qnav_stack_pop(); + pop_qnav_if_main_menu(); + } + break; + } + } + break; + case Cosmetics_menu_id: + switch (act.picked.id) { + case Cosmetics_soft_margins_id: + push_soft_margins_form(t->softmargin_y, t->softmargin_x); + break; + case Cosmetics_grid_dots_id: + push_plainorfancy_menu(Set_fancy_grid_dots_menu_id, + "Grid Dots", t->fancy_grid_dots); + break; + case Cosmetics_grid_rulers_id: + push_plainorfancy_menu(Set_fancy_grid_rulers_menu_id, + "Grid Rulers", + t->fancy_grid_rulers); + break; + } + break; + case Playback_menu_id: + switch (act.picked.id) { + case Playback_menu_midi_bclock: { + bool new_enabled = !t->ged.midi_bclock; + t->ged.midi_bclock = new_enabled; + if (t->ged.is_playing) { + int msgbyte = new_enabled ? 0xFA /* start */ + : 0xFC /* stop */; + send_midi_byte(t->ged.oosc_dev, &t->ged.midi_mode, + msgbyte); + // TODO timing judder will be experienced here, because + // the deadline calculation conditions will have been + // changed by toggling the midi_bclock flag. We would + // have to transfer the current remaining time from the + // reference clock point into the accum time, and + // mutiply or divide it. + } + t->prefs_touched |= TOUCHFLAG(Confopt_midi_beat_clock); + qnav_stack_pop(); + push_playback_menu(new_enabled); + tui_save_prefs(t); + break; + } + } + break; + case Set_fancy_grid_dots_menu_id: + plainorfancy_menu_was_picked(t, act.picked.id, + &t->fancy_grid_dots, + TOUCHFLAG(Confopt_grid_dot_type)); + break; + case Set_fancy_grid_rulers_menu_id: + plainorfancy_menu_was_picked( + t, act.picked.id, &t->fancy_grid_rulers, + TOUCHFLAG(Confopt_grid_ruler_type)); + break; + case Osc_menu_id: + switch (act.picked.id) { + case Osc_menu_output_enabledisable: { + qnav_stack_pop(); + t->osc_output_enabled = !ged_is_using_osc_udp(&t->ged); + // Funny dance to keep the qnav stack in good order + bool diderror = tui_restart_osc_udp_if_enabled_diderror(t); + push_osc_menu(ged_is_using_osc_udp(&t->ged)); + if (diderror) { + t->osc_output_enabled = false; + tui_restart_osc_udp_showerror(); + } + t->prefs_touched |= TOUCHFLAG(Confopt_osc_output_enabled); + tui_save_prefs(t); + break; + } + case Osc_menu_output_address: + push_osc_output_address_form( + osoc(t->osc_address) /* null ok */); + break; + case Osc_menu_output_port: + push_osc_output_port_form(osoc(t->osc_port) /* null ok */); + break; + } + break; +#ifdef FEAT_PORTMIDI + case Portmidi_output_device_menu_id: { + ged_stop_all_sustained_notes(&t->ged); + midi_mode_deinit(&t->ged.midi_mode); + PmError pme = midi_mode_init_portmidi(&t->ged.midi_mode, + act.picked.id); + qnav_stack_pop(); + if (pme) { + qmsg_printf_push( + "PortMidi Error", + "Error setting PortMidi output device:\n%s", + Pm_GetErrorText(pme)); + } else { + tui_save_prefs(t); + } + break; + } +#endif + } break; - int newheight, newwidth; - if (sscanf(osoc(tmpstr), "%dx%d", &newwidth, &newheight) == 2 && - newheight > 0 && newwidth > 0 && newheight < ORCA_Y_MAX && - newwidth < ORCA_X_MAX) { - if (t->ged.field.height != (Usz)newheight || - t->ged.field.width != (Usz)newwidth) { - ged_resize_grid(&t->ged.field, &t->ged.mbuf_r, (Usz)newheight, - (Usz)newwidth, t->ged.tick_num, - &t->ged.scratch_field, &t->ged.undo_hist, - &t->ged.ged_cursor); - ged_update_internal_geometry(&t->ged); - t->ged.needs_remarking = true; - t->ged.is_draw_dirty = true; - ged_make_cursor_visible(&t->ged); + } + break; + } + case Qblock_type_qform: { + Qform* qf = qform_of(qb); + Qform_action act; + if (qform_drive(qf, key, &act)) { + switch (act.any.type) { + case Qform_action_type_canceled: + qnav_stack_pop(); + break; + case Qform_action_type_submitted: { + switch (qform_id(qf)) { + case Open_form_id: { + oso* temp_name = qform_get_nonempty_single_line_input(qf); + if (!temp_name) + break; + expand_home_tilde(&temp_name); + if (!temp_name) + break; + bool added_hist = undo_history_push( + &t->ged.undo_hist, &t->ged.field, t->ged.tick_num); + Field_load_error fle = field_load_file(osoc(temp_name), + &t->ged.field); + if (fle == Field_load_error_ok) { + qnav_stack_pop(); + osoputoso(&t->file_name, temp_name); + mbuf_reusable_ensure_size(&t->ged.mbuf_r, + t->ged.field.height, + t->ged.field.width); + ged_cursor_confine(&t->ged.ged_cursor, + t->ged.field.height, + t->ged.field.width); + ged_update_internal_geometry(&t->ged); + ged_make_cursor_visible(&t->ged); + t->ged.needs_remarking = true; + t->ged.is_draw_dirty = true; + pop_qnav_if_main_menu(); + } else { + if (added_hist) + undo_history_pop(&t->ged.undo_hist, &t->ged.field, + &t->ged.tick_num); + qmsg_printf_push("Error Loading File", "%s:\n%s", + osoc(temp_name), + field_load_error_string(fle)); + } + osofree(temp_name); + break; + } + case Save_as_form_id: { + oso* temp_name = qform_get_nonempty_single_line_input(qf); + if (!temp_name) + break; + qnav_stack_pop(); + bool saved_ok = try_save_with_msg(&t->ged.field, + temp_name); + if (saved_ok) + osoputoso(&t->file_name, temp_name); + osofree(temp_name); + break; + } + case Set_tempo_form_id: { + oso* tmpstr = qform_get_nonempty_single_line_input(qf); + if (!tmpstr) + break; + int newbpm = atoi(osoc(tmpstr)); + if (newbpm > 0) { + t->ged.bpm = (Usz)newbpm; + qnav_stack_pop(); + } + osofree(tmpstr); + break; + } + case Osc_output_address_form_id: { + oso* addr = NULL; + // Empty string is OK here + if (qform_get_single_text_line(qf, &addr)) { + if (osolen(addr)) + ososwap(&t->osc_address, &addr); + else + osowipe(&t->osc_address); + qnav_stack_pop(); + tui_restart_osc_udp_if_enabled(t); + t->prefs_touched |= TOUCHFLAG( + Confopt_osc_output_address); + tui_save_prefs(t); + } + osofree(addr); + break; + } + case Osc_output_port_form_id: { + oso* portstr = qform_get_nonempty_single_line_input(qf); + if (!portstr) + break; + qnav_stack_pop(); + ososwap(&t->osc_port, &portstr); + tui_restart_osc_udp_if_enabled(t); + osofree(portstr); + t->prefs_touched |= TOUCHFLAG(Confopt_osc_output_port); + tui_save_prefs(t); + break; + } + case Set_grid_dims_form_id: { + oso* tmpstr = qform_get_nonempty_single_line_input(qf); + if (!tmpstr) + break; + int newheight, newwidth; + if (sscanf(osoc(tmpstr), "%dx%d", &newwidth, &newheight) + == 2 + && newheight > 0 && newwidth > 0 + && newheight < ORCA_Y_MAX && newwidth < ORCA_X_MAX) { + if (t->ged.field.height != (Usz)newheight + || t->ged.field.width != (Usz)newwidth) { + ged_resize_grid( + &t->ged.field, &t->ged.mbuf_r, (Usz)newheight, + (Usz)newwidth, t->ged.tick_num, + &t->ged.scratch_field, &t->ged.undo_hist, + &t->ged.ged_cursor); + ged_update_internal_geometry(&t->ged); + t->ged.needs_remarking = true; + t->ged.is_draw_dirty = true; + ged_make_cursor_visible(&t->ged); + } + qnav_stack_pop(); + } + osofree(tmpstr); + break; + } + case Set_soft_margins_form_id: { + oso* tmpstr = qform_get_nonempty_single_line_input(qf); + if (!tmpstr) + break; + bool do_save = false; + int newy, newx; + if (read_nxn_or_n(osoc(tmpstr), &newx, &newy) && newy >= 0 + && newx >= 0 + && (newy != t->softmargin_y + || newx != t->softmargin_x)) { + t->prefs_touched |= TOUCHFLAG(Confopt_margins); + t->softmargin_y = newy; + t->softmargin_x = newx; + ungetch(KEY_RESIZE); // kinda lame but whatever + do_save = true; + } + qnav_stack_pop(); + // Might push message, so gotta pop old guy first + if (do_save) + tui_save_prefs(t); + osofree(tmpstr); + break; + } + } + break; + } } - qnav_stack_pop(); - } - osofree(tmpstr); - break; } - case Set_soft_margins_form_id: { - oso *tmpstr = qform_get_nonempty_single_line_input(qf); - if (!tmpstr) - break; - bool do_save = false; - int newy, newx; - if (read_nxn_or_n(osoc(tmpstr), &newx, &newy) && newy >= 0 && - newx >= 0 && - (newy != t->softmargin_y || newx != t->softmargin_x)) { - t->prefs_touched |= TOUCHFLAG(Confopt_margins); - t->softmargin_y = newy; - t->softmargin_x = newx; - ungetch(KEY_RESIZE); // kinda lame but whatever - do_save = true; - } - qnav_stack_pop(); - // Might push message, so gotta pop old guy first - if (do_save) - tui_save_prefs(t); - osofree(tmpstr); - break; - } - } - break; - } - } - } - break; - } - } - return Tui_menus_consumed_input; + break; + } + } + return Tui_menus_consumed_input; } // @@ -3236,253 +3400,264 @@ staticni Tui_menus_result tui_drive_menus(Tui *t, int key) { // enum { - Argopt_hardmargins = UCHAR_MAX + 1, - Argopt_undo_limit, - Argopt_init_grid_size, - Argopt_osc_midi_bidule, - Argopt_strict_timing, - Argopt_bpm, - Argopt_seed, - Argopt_portmidi_deprecated, - Argopt_osc_deprecated, + Argopt_hardmargins = UCHAR_MAX + 1, + Argopt_undo_limit, + Argopt_init_grid_size, + Argopt_osc_midi_bidule, + Argopt_strict_timing, + Argopt_bpm, + Argopt_seed, + Argopt_portmidi_deprecated, + Argopt_osc_deprecated, }; -int main(int argc, char **argv) { - static struct option tui_options[] = { - {"hard-margins", required_argument, 0, Argopt_hardmargins}, - {"undo-limit", required_argument, 0, Argopt_undo_limit}, - {"initial-size", required_argument, 0, Argopt_init_grid_size}, - {"help", no_argument, 0, 'h'}, - {"osc-midi-bidule", required_argument, 0, Argopt_osc_midi_bidule}, - {"strict-timing", no_argument, 0, Argopt_strict_timing}, - {"bpm", required_argument, 0, Argopt_bpm}, - {"seed", required_argument, 0, Argopt_seed}, - {"portmidi-list-devices", no_argument, 0, Argopt_portmidi_deprecated}, - {"portmidi-output-device", required_argument, 0, - Argopt_portmidi_deprecated}, - {"osc-server", required_argument, 0, Argopt_osc_deprecated}, - {"osc-port", required_argument, 0, Argopt_osc_deprecated}, - {NULL, 0, NULL, 0}}; - int init_bpm = 120; - int init_seed = 1; - int init_grid_dim_y = 25, init_grid_dim_x = 57; - bool explicit_initial_grid_size = false; - - Tui t = {.file_name = NULL}; // Weird because of clang warning - t.undo_history_limit = 100; - t.softmargin_y = 1; - t.softmargin_x = 2; - t.use_gui_cboard = true; - t.fancy_grid_dots = true; - t.fancy_grid_rulers = true; - - int longindex = 0; - for (;;) { - int c = getopt_long(argc, argv, "h", tui_options, &longindex); - if (c == -1) - break; - switch (c) { - case 'h': - usage(); - exit(0); - case '?': - usage(); - exit(1); -#define OPTFAIL(...) \ - { \ - fprintf(stderr, "Bad %s argument: %s\n", tui_options[longindex].name, \ - optarg); \ - fprintf(stderr, __VA_ARGS__); \ - fputc('\n', stderr); \ - exit(1); \ - } - case Argopt_hardmargins: - if (read_nxn_or_n(optarg, &t.hardmargin_x, &t.hardmargin_y) && - t.hardmargin_x >= 0 && t.hardmargin_y >= 0) - break; - OPTFAIL("Must be 0 or positive integer."); - case Argopt_undo_limit: - if (read_int(optarg, &t.undo_history_limit) && t.undo_history_limit >= 0) - break; - OPTFAIL("Must be 0 or positive integer."); - case Argopt_bpm: - if (read_int(optarg, &init_bpm) && init_bpm >= 1) - break; - OPTFAIL("Must be positive integer."); - case Argopt_seed: - if (read_int(optarg, &init_seed) && init_seed >= 0) - break; - OPTFAIL("Must be 0 or positive integer."); - case Argopt_init_grid_size: - if (sscanf(optarg, "%dx%d", &init_grid_dim_x, &init_grid_dim_y) != 2) - OPTFAIL("Bad format or count. Expected something like: 40x30"); - if (init_grid_dim_x <= 0 || init_grid_dim_x > ORCA_X_MAX) - OPTFAIL("X dimension for initial-size must be 1 <= n <= %d, was %d.", - ORCA_X_MAX, init_grid_dim_x); - if (init_grid_dim_y <= 0 || init_grid_dim_y > ORCA_Y_MAX) - OPTFAIL("Y dimension for initial-size must be 1 <= n <= %d, was %d.", - ORCA_Y_MAX, init_grid_dim_y); - explicit_initial_grid_size = true; - break; - case Argopt_osc_midi_bidule: - osoput(&t.osc_midi_bidule_path, optarg); - break; - case Argopt_strict_timing: - t.strict_timing = true; - break; - case Argopt_portmidi_deprecated: - fprintf(stderr, - "Option \"--%s\" has been removed.\nInstead, choose " - "your MIDI output device from within the ORCA menu.\n" - "This new menu allows you to pick your MIDI device " - "interactively\n", - tui_options[longindex].name); - exit(1); - case Argopt_osc_deprecated: - fprintf( - stderr, - "Options --osc-server and --osc-port have been removed.\n" - "Instead, set the OSC server and port from within the ORCA menu.\n"); - exit(1); - } - } +int main(int argc, char** argv) +{ + static struct option tui_options[] = { + { "hard-margins", required_argument, 0, Argopt_hardmargins }, + { "undo-limit", required_argument, 0, Argopt_undo_limit }, + { "initial-size", required_argument, 0, Argopt_init_grid_size }, + { "help", no_argument, 0, 'h' }, + { "osc-midi-bidule", required_argument, 0, Argopt_osc_midi_bidule }, + { "strict-timing", no_argument, 0, Argopt_strict_timing }, + { "bpm", required_argument, 0, Argopt_bpm }, + { "seed", required_argument, 0, Argopt_seed }, + { "portmidi-list-devices", no_argument, 0, + Argopt_portmidi_deprecated }, + { "portmidi-output-device", required_argument, 0, + Argopt_portmidi_deprecated }, + { "osc-server", required_argument, 0, Argopt_osc_deprecated }, + { "osc-port", required_argument, 0, Argopt_osc_deprecated }, + { NULL, 0, NULL, 0 } + }; + int init_bpm = 120; + int init_seed = 1; + int init_grid_dim_y = 25, init_grid_dim_x = 57; + bool explicit_initial_grid_size = false; + + Tui t = { .file_name = NULL }; // Weird because of clang warning + t.undo_history_limit = 100; + t.softmargin_y = 1; + t.softmargin_x = 2; + t.use_gui_cboard = true; + t.fancy_grid_dots = true; + t.fancy_grid_rulers = true; + + int longindex = 0; + for (;;) { + int c = getopt_long(argc, argv, "h", tui_options, &longindex); + if (c == -1) + break; + switch (c) { + case 'h': + usage(); + exit(0); + case '?': + usage(); + exit(1); +#define OPTFAIL(...) \ + { \ + fprintf(stderr, "Bad %s argument: %s\n", tui_options[longindex].name, \ + optarg); \ + fprintf(stderr, __VA_ARGS__); \ + fputc('\n', stderr); \ + exit(1); \ + } + case Argopt_hardmargins: + if (read_nxn_or_n(optarg, &t.hardmargin_x, &t.hardmargin_y) + && t.hardmargin_x >= 0 && t.hardmargin_y >= 0) + break; + OPTFAIL("Must be 0 or positive integer."); + case Argopt_undo_limit: + if (read_int(optarg, &t.undo_history_limit) + && t.undo_history_limit >= 0) + break; + OPTFAIL("Must be 0 or positive integer."); + case Argopt_bpm: + if (read_int(optarg, &init_bpm) && init_bpm >= 1) + break; + OPTFAIL("Must be positive integer."); + case Argopt_seed: + if (read_int(optarg, &init_seed) && init_seed >= 0) + break; + OPTFAIL("Must be 0 or positive integer."); + case Argopt_init_grid_size: + if (sscanf(optarg, "%dx%d", &init_grid_dim_x, &init_grid_dim_y) + != 2) + OPTFAIL("Bad format or count. Expected something like: 40x30"); + if (init_grid_dim_x <= 0 || init_grid_dim_x > ORCA_X_MAX) + OPTFAIL("X dimension for initial-size must be 1 <= n <= %d, " + "was %d.", + ORCA_X_MAX, init_grid_dim_x); + if (init_grid_dim_y <= 0 || init_grid_dim_y > ORCA_Y_MAX) + OPTFAIL("Y dimension for initial-size must be 1 <= n <= %d, " + "was %d.", + ORCA_Y_MAX, init_grid_dim_y); + explicit_initial_grid_size = true; + break; + case Argopt_osc_midi_bidule: + osoput(&t.osc_midi_bidule_path, optarg); + break; + case Argopt_strict_timing: + t.strict_timing = true; + break; + case Argopt_portmidi_deprecated: + fprintf(stderr, + "Option \"--%s\" has been removed.\nInstead, choose " + "your MIDI output device from within the ORCA menu.\n" + "This new menu allows you to pick your MIDI device " + "interactively\n", + tui_options[longindex].name); + exit(1); + case Argopt_osc_deprecated: + fprintf(stderr, + "Options --osc-server and --osc-port have been removed.\n" + "Instead, set the OSC server and port from within the " + "ORCA menu.\n"); + exit(1); + } + } #undef OPTFAIL - if (optind == argc - 1) { - osoput(&t.file_name, argv[optind]); - } else if (optind < argc - 1) { - fprintf(stderr, "Expected only 1 file argument.\n"); - exit(1); - } - qnav_init(); // Initialize the menu/navigation global state - // Initialize the 'Grid EDitor' stuff. This sits underneath the TUI. - ged_init(&t.ged, (Usz)t.undo_history_limit, (Usz)init_bpm, (Usz)init_seed); - // This will need to be changed to work with conf/menu - if (osolen(t.osc_midi_bidule_path) > 0) { - midi_mode_deinit(&t.ged.midi_mode); - midi_mode_init_osc_bidule(&t.ged.midi_mode, osoc(t.osc_midi_bidule_path)); - } - stm_setup(); // Set up timer lib - // Enable UTF-8 by explicitly initializing our locale before initializing - // ncurses. Only needed (maybe?) if using libncursesw/wide-chars or UTF-8. - // Using it unguarded will mess up box drawing chars in Linux virtual - // consoles unless using libncursesw. - setlocale(LC_ALL, ""); - initscr(); // Initialize ncurses - // Allow ncurses to control newline translation. Fine to use with any modern - // terminal, and will let ncurses run faster. - nonl(); - // Set interrupt keys (interrupt, break, quit...) to not flush. Helps keep - // ncurses state consistent, at the cost of less responsive terminal - // interrupt. (This will rarely happen.) - intrflush(stdscr, FALSE); - // Receive keyboard input immediately without line buffering, and receive - // ctrl+z, ctrl+c etc. as input instead of having a signal generated. We need - // to do this even with wtimeout() if we don't want ctrl+z etc. to interrupt - // the program. - raw(); - noecho(); // Don't echo keyboard input - keypad(stdscr, TRUE); // Also receive arrow keys, etc. - curs_set(0); // Hide the terminal cursor - set_escdelay(1); // Short delay before triggering escape - term_util_init_colors(); // Our color init routine - mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); - if (has_mouse()) // no waiting for distinguishing click from press - mouseinterval(0); - printf("\033[?2004h\n"); // Ask terminal to use bracketed paste. - - tui_load_conf(&t); // load orca.conf (if it exists) - tui_restart_osc_udp_if_enabled(&t); // start udp if conf enabled it - - wtimeout(stdscr, 0); - int cur_timeout = 0; - Usz brackpaste_starting_x = 0, brackpaste_y = 0, brackpaste_x = 0, - brackpaste_max_y = 0, brackpaste_max_x = 0; - bool is_in_brackpaste = false; - - WINDOW *cont_window = NULL; - tui_adjust_term_size(&t, &cont_window); - - bool grid_initialized = false; - if (osolen(t.file_name)) { - Field_load_error fle = field_load_file(osoc(t.file_name), &t.ged.field); - switch (fle) { - case Field_load_error_ok: - if (t.ged.field.height < 1 || t.ged.field.width < 1) { - // Opening an empty file or attempting to open a directory can lead us - // here. - field_deinit(&t.ged.field); - qmsg_printf_push("Unusable File", "Not a usable file:\n%s", - (osoc(t.file_name))); - break; - } - grid_initialized = true; - break; - case Field_load_error_cant_open_file: { - // Probably a new file, though TODO we should add an explicit - // differentiation between "file exists and can't open it" and "file - // doesn't seem to exist." - Qmsg *qm = qmsg_printf_push(NULL, "New file: %s", osoc(t.file_name)); - qmsg_set_dismiss_mode(qm, Qmsg_dismiss_mode_passthrough); - break; + if (optind == argc - 1) { + osoput(&t.file_name, argv[optind]); + } else if (optind < argc - 1) { + fprintf(stderr, "Expected only 1 file argument.\n"); + exit(1); + } + qnav_init(); // Initialize the menu/navigation global state + // Initialize the 'Grid EDitor' stuff. This sits underneath the TUI. + ged_init(&t.ged, (Usz)t.undo_history_limit, (Usz)init_bpm, (Usz)init_seed); + // This will need to be changed to work with conf/menu + if (osolen(t.osc_midi_bidule_path) > 0) { + midi_mode_deinit(&t.ged.midi_mode); + midi_mode_init_osc_bidule(&t.ged.midi_mode, + osoc(t.osc_midi_bidule_path)); + } + stm_setup(); // Set up timer lib + // Enable UTF-8 by explicitly initializing our locale before initializing + // ncurses. Only needed (maybe?) if using libncursesw/wide-chars or UTF-8. + // Using it unguarded will mess up box drawing chars in Linux virtual + // consoles unless using libncursesw. + setlocale(LC_ALL, ""); + initscr(); // Initialize ncurses + // Allow ncurses to control newline translation. Fine to use with any + // modern terminal, and will let ncurses run faster. + nonl(); + // Set interrupt keys (interrupt, break, quit...) to not flush. Helps keep + // ncurses state consistent, at the cost of less responsive terminal + // interrupt. (This will rarely happen.) + intrflush(stdscr, FALSE); + // Receive keyboard input immediately without line buffering, and receive + // ctrl+z, ctrl+c etc. as input instead of having a signal generated. We + // need to do this even with wtimeout() if we don't want ctrl+z etc. to + // interrupt the program. + raw(); + noecho(); // Don't echo keyboard input + keypad(stdscr, TRUE); // Also receive arrow keys, etc. + curs_set(0); // Hide the terminal cursor + set_escdelay(1); // Short delay before triggering escape + term_util_init_colors(); // Our color init routine + mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); + if (has_mouse()) // no waiting for distinguishing click from press + mouseinterval(0); + printf("\033[?2004h\n"); // Ask terminal to use bracketed paste. + + tui_load_conf(&t); // load orca.conf (if it exists) + tui_restart_osc_udp_if_enabled(&t); // start udp if conf enabled it + + wtimeout(stdscr, 0); + int cur_timeout = 0; + Usz brackpaste_starting_x = 0, brackpaste_y = 0, brackpaste_x = 0, + brackpaste_max_y = 0, brackpaste_max_x = 0; + bool is_in_brackpaste = false; + + WINDOW* cont_window = NULL; + tui_adjust_term_size(&t, &cont_window); + + bool grid_initialized = false; + if (osolen(t.file_name)) { + Field_load_error fle = field_load_file(osoc(t.file_name), + &t.ged.field); + switch (fle) { + case Field_load_error_ok: + if (t.ged.field.height < 1 || t.ged.field.width < 1) { + // Opening an empty file or attempting to open a directory can + // lead us here. + field_deinit(&t.ged.field); + qmsg_printf_push("Unusable File", "Not a usable file:\n%s", + (osoc(t.file_name))); + break; + } + grid_initialized = true; + break; + case Field_load_error_cant_open_file: { + // Probably a new file, though TODO we should add an explicit + // differentiation between "file exists and can't open it" and + // "file doesn't seem to exist." + Qmsg* qm = qmsg_printf_push(NULL, "New file: %s", + osoc(t.file_name)); + qmsg_set_dismiss_mode(qm, Qmsg_dismiss_mode_passthrough); + break; + } + default: + qmsg_printf_push("File Load Error", "File load error:\n%s.", + field_load_error_string(fle)); + break; + } } - default: - qmsg_printf_push("File Load Error", "File load error:\n%s.", - field_load_error_string(fle)); - break; - } - } - // If we haven't yet initialized the grid, because we were waiting for the - // terminal size, do it now. - if (!grid_initialized) { - Usz new_field_h, new_field_w; - if (explicit_initial_grid_size || - !tui_suggest_nice_grid_size(&t, t.ged.win_h, t.ged.win_w, &new_field_h, - &new_field_w)) { - new_field_h = (Usz)init_grid_dim_y; - new_field_w = (Usz)init_grid_dim_x; - } - field_init_fill(&t.ged.field, (Usz)new_field_h, (Usz)new_field_w, '.'); - } - mbuf_reusable_ensure_size(&t.ged.mbuf_r, t.ged.field.height, - t.ged.field.width); - ged_make_cursor_visible(&t.ged); - ged_send_osc_bpm(&t.ged, (I32)t.ged.bpm); // Send initial BPM - ged_set_playing(&t.ged, true); // Auto-play - // Enter main loop. Process events as they arrive. + // If we haven't yet initialized the grid, because we were waiting for the + // terminal size, do it now. + if (!grid_initialized) { + Usz new_field_h, new_field_w; + if (explicit_initial_grid_size + || !tui_suggest_nice_grid_size(&t, t.ged.win_h, t.ged.win_w, + &new_field_h, &new_field_w)) { + new_field_h = (Usz)init_grid_dim_y; + new_field_w = (Usz)init_grid_dim_x; + } + field_init_fill(&t.ged.field, (Usz)new_field_h, (Usz)new_field_w, '.'); + } + mbuf_reusable_ensure_size(&t.ged.mbuf_r, t.ged.field.height, + t.ged.field.width); + ged_make_cursor_visible(&t.ged); + ged_send_osc_bpm(&t.ged, (I32)t.ged.bpm); // Send initial BPM + ged_set_playing(&t.ged, true); // Auto-play + // Enter main loop. Process events as they arrive. event_loop:; - int key = wgetch(stdscr); - if (cur_timeout != 0) { - wtimeout(stdscr, 0); // Until we run out, don't wait between events. - cur_timeout = 0; - } - switch (key) { - case ERR: { // ERR indicates no more events. - ged_do_stuff(&t.ged); - bool drew_any = false; - if (ged_is_draw_dirty(&t.ged) || qnav_stack.occlusion_dirty) { - werase(cont_window); - ged_draw(&t.ged, cont_window, osoc(t.file_name), t.fancy_grid_dots, - t.fancy_grid_rulers); - wnoutrefresh(cont_window); - drew_any = true; - } - drew_any |= qnav_draw(); // clears qnav_stack.occlusion_dirty - if (drew_any) - doupdate(); - double secs_to_d = ged_secs_to_deadline(&t.ged); -#define DEADTIME(_millisecs, _new_timeout) \ - else if (secs_to_d < ms_to_sec(_millisecs)) new_timeout = _new_timeout; - int new_timeout; - // These values are tuned to work OK with the normal scheduling behavior - // on Linux, Mac, and Windows. All of the usual caveats of trying to - // guess what the scheduler will do apply. - // - // Of course, there's no guarantee about how the scheduler will work, so - // if you are using a modified kernel or something, or the measurements - // here are bad, or it's some OS that behaves differently than expected, - // this won't be very good. But there's not really much we can do about - // it, and it's better than doing nothing and burning up the CPU! - if (t.strict_timing) { // clang-format off + int key = wgetch(stdscr); + if (cur_timeout != 0) { + wtimeout(stdscr, 0); // Until we run out, don't wait between events. + cur_timeout = 0; + } + switch (key) { + case ERR: { // ERR indicates no more events. + ged_do_stuff(&t.ged); + bool drew_any = false; + if (ged_is_draw_dirty(&t.ged) || qnav_stack.occlusion_dirty) { + werase(cont_window); + ged_draw(&t.ged, cont_window, osoc(t.file_name), t.fancy_grid_dots, + t.fancy_grid_rulers); + wnoutrefresh(cont_window); + drew_any = true; + } + drew_any |= qnav_draw(); // clears qnav_stack.occlusion_dirty + if (drew_any) + doupdate(); + double secs_to_d = ged_secs_to_deadline(&t.ged); +#define DEADTIME(_millisecs, _new_timeout) \ + else if (secs_to_d < ms_to_sec(_millisecs)) new_timeout = _new_timeout; + int new_timeout; + // These values are tuned to work OK with the normal scheduling + // behavior on Linux, Mac, and Windows. All of the usual caveats of + // trying to guess what the scheduler will do apply. + // + // Of course, there's no guarantee about how the scheduler will work, + // so if you are using a modified kernel or something, or the + // measurements here are bad, or it's some OS that behaves differently + // than expected, this won't be very good. But there's not really much + // we can do about it, and it's better than doing nothing and burning + // up the CPU! + if (t.strict_timing) { // clang-format off if (false) {} // "If there's less than 1.5 milliseconds to the deadline, use a curses // timeout value of 0." @@ -3510,364 +3685,371 @@ event_loop:; else new_timeout = 50; } // clang-format on #undef DEADTIME - if (new_timeout != cur_timeout) { - wtimeout(stdscr, new_timeout); - cur_timeout = new_timeout; + if (new_timeout != cur_timeout) { + wtimeout(stdscr, new_timeout); + cur_timeout = new_timeout; #if TIME_DEBUG - spin_track_timeout = cur_timeout; + spin_track_timeout = cur_timeout; #endif + } + goto event_loop; } - goto event_loop; - } - case KEY_RESIZE: - tui_adjust_term_size(&t, &cont_window); - qnav_adjust_term_size(); - goto event_loop; + case KEY_RESIZE: + tui_adjust_term_size(&t, &cont_window); + qnav_adjust_term_size(); + goto event_loop; #ifndef FEAT_NOMOUSE - case KEY_MOUSE: { - MEVENT mevent; - if (cont_window && getmouse(&mevent) == OK) { - int win_y, win_x; - int win_h, win_w; - getbegyx(cont_window, win_y, win_x); - getmaxyx(cont_window, win_h, win_w); - int inwin_y = mevent.y - win_y; - int inwin_x = mevent.x - win_x; - if (inwin_y >= win_h) - inwin_y = win_h - 1; - if (inwin_y < 0) - inwin_y = 0; - if (inwin_x >= win_w) - inwin_x = win_w - 1; - if (inwin_x < 0) - inwin_x = 0; - ged_mouse_event(&t.ged, (Usz)inwin_y, (Usz)inwin_x, mevent.bstate); + case KEY_MOUSE: { + MEVENT mevent; + if (cont_window && getmouse(&mevent) == OK) { + int win_y, win_x; + int win_h, win_w; + getbegyx(cont_window, win_y, win_x); + getmaxyx(cont_window, win_h, win_w); + int inwin_y = mevent.y - win_y; + int inwin_x = mevent.x - win_x; + if (inwin_y >= win_h) + inwin_y = win_h - 1; + if (inwin_y < 0) + inwin_y = 0; + if (inwin_x >= win_w) + inwin_x = win_w - 1; + if (inwin_x < 0) + inwin_x = 0; + ged_mouse_event(&t.ged, (Usz)inwin_y, (Usz)inwin_x, mevent.bstate); + } + goto event_loop; } - goto event_loop; - } #endif - } - - // If we have the menus open, we'll let the menus do what they want with - // the input before the regular editor (which will be displayed - // underneath.) The menus may tell us to quit, that they didn't do anything - // with the input, or that they consumed the input and therefore we - // shouldn't pass the input key to the rest of the editing system. - switch (tui_drive_menus(&t, key)) { - case Tui_menus_nothing: - break; - case Tui_menus_quit: - goto quit; - case Tui_menus_consumed_input: - goto event_loop; - } - - // If this key input is intended to reach the grid, check to see if we're - // in bracketed paste and use alternate 'filtered input for characters' - // mode. We'll ignore most control sequences here. - if (is_in_brackpaste) { - if (key == 27 /* escape */) { - if (brackpaste_seq_getungetch(stdscr) == Brackpaste_seq_end) { - is_in_brackpaste = false; - if (brackpaste_max_y > t.ged.ged_cursor.y) - t.ged.ged_cursor.h = brackpaste_max_y - t.ged.ged_cursor.y + 1; - if (brackpaste_max_x > t.ged.ged_cursor.x) - t.ged.ged_cursor.w = brackpaste_max_x - t.ged.ged_cursor.x + 1; + } + + // If we have the menus open, we'll let the menus do what they want with + // the input before the regular editor (which will be displayed + // underneath.) The menus may tell us to quit, that they didn't do anything + // with the input, or that they consumed the input and therefore we + // shouldn't pass the input key to the rest of the editing system. + switch (tui_drive_menus(&t, key)) { + case Tui_menus_nothing: + break; + case Tui_menus_quit: + goto quit; + case Tui_menus_consumed_input: + goto event_loop; + } + + // If this key input is intended to reach the grid, check to see if we're + // in bracketed paste and use alternate 'filtered input for characters' + // mode. We'll ignore most control sequences here. + if (is_in_brackpaste) { + if (key == 27 /* escape */) { + if (brackpaste_seq_getungetch(stdscr) == Brackpaste_seq_end) { + is_in_brackpaste = false; + if (brackpaste_max_y > t.ged.ged_cursor.y) + t.ged.ged_cursor.h = brackpaste_max_y - t.ged.ged_cursor.y + + 1; + if (brackpaste_max_x > t.ged.ged_cursor.x) + t.ged.ged_cursor.w = brackpaste_max_x - t.ged.ged_cursor.x + + 1; + t.ged.needs_remarking = true; + t.ged.is_draw_dirty = true; + } + goto event_loop; + } + if (key == KEY_ENTER) + key = '\r'; + if (key >= CHAR_MIN && key <= CHAR_MAX) { + if ((char)key == '\r' || (char)key == '\n') { + brackpaste_x = brackpaste_starting_x; + ++brackpaste_y; + goto event_loop; + } + if (key != ' ') { + char cleaned = (char)key; + if (!orca_is_valid_glyph((Glyph)key)) + cleaned = '.'; + if (brackpaste_y < t.ged.field.height + && brackpaste_x < t.ged.field.width) { + gbuffer_poke(t.ged.field.buffer, t.ged.field.height, + t.ged.field.width, brackpaste_y, brackpaste_x, + cleaned); + // Could move this out one level if we wanted the final + // selection size to reflect even the pasted area which + // didn't fit on the grid. + if (brackpaste_y > brackpaste_max_y) + brackpaste_max_y = brackpaste_y; + if (brackpaste_x > brackpaste_max_x) + brackpaste_max_x = brackpaste_x; + } + } + ++brackpaste_x; + } + goto event_loop; + } + + // Regular inputs when we're not in a menu and not in bracketed paste. + switch (key) { + // Checking again for 'quit' here, because it's only listened for if we're + // in the menus or *not* in bracketed paste mode. + case CTRL_PLUS('q'): + goto quit; + case CTRL_PLUS('o'): + push_open_form(osoc(t.file_name)); + break; + case 127: // backspace in terminal.app, apparently + case KEY_BACKSPACE: + if (t.ged.input_mode == Ged_input_mode_append) { + ged_dir_input(&t.ged, Ged_dir_left, 1); + ged_input_character(&t.ged, '.'); + ged_dir_input(&t.ged, Ged_dir_left, 1); + } else { + ged_input_character(&t.ged, '.'); + } + break; + case CTRL_PLUS('z'): + case CTRL_PLUS('u'): + ged_input_cmd(&t.ged, Ged_input_cmd_undo); + break; + case CTRL_PLUS('r'): + t.ged.tick_num = 0; t.ged.needs_remarking = true; t.ged.is_draw_dirty = true; - } - goto event_loop; - } - if (key == KEY_ENTER) - key = '\r'; - if (key >= CHAR_MIN && key <= CHAR_MAX) { - if ((char)key == '\r' || (char)key == '\n') { - brackpaste_x = brackpaste_starting_x; - ++brackpaste_y; - goto event_loop; - } - if (key != ' ') { - char cleaned = (char)key; - if (!orca_is_valid_glyph((Glyph)key)) - cleaned = '.'; - if (brackpaste_y < t.ged.field.height && - brackpaste_x < t.ged.field.width) { - gbuffer_poke(t.ged.field.buffer, t.ged.field.height, - t.ged.field.width, brackpaste_y, brackpaste_x, cleaned); - // Could move this out one level if we wanted the final selection - // size to reflect even the pasted area which didn't fit on the - // grid. - if (brackpaste_y > brackpaste_max_y) + break; + case '[': + ged_adjust_rulers_relative(&t.ged, 0, -1); + break; + case ']': + ged_adjust_rulers_relative(&t.ged, 0, 1); + break; + case '{': + ged_adjust_rulers_relative(&t.ged, -1, 0); + break; + case '}': + ged_adjust_rulers_relative(&t.ged, 1, 0); + break; + case '(': + ged_resize_grid_relative(&t.ged, 0, -1); + break; + case ')': + ged_resize_grid_relative(&t.ged, 0, 1); + break; + case '_': + ged_resize_grid_relative(&t.ged, -1, 0); + break; + case '+': + ged_resize_grid_relative(&t.ged, 1, 0); + break; + case '\r': + case KEY_ENTER: + break; // Currently unused. + case CTRL_PLUS('i'): + case KEY_IC: + ged_input_cmd(&t.ged, Ged_input_cmd_toggle_append_mode); + break; + case '/': + // Formerly 'piano'/trigger mode toggle. We're repurposing it here to + // input a '?' instead of a '/' because '?' opens the help guide, and + // it might be a bad idea to take that away, since orca will take over + // the TTY and may leave users confused. I know of at least 1 person + // who was saved by pressing '?' after they didn't know what to do. + // Hmm. + ged_input_character(&t.ged, '?'); + break; + case '<': + ged_adjust_bpm(&t.ged, -1); + break; + case '>': + ged_adjust_bpm(&t.ged, 1); + break; + case CTRL_PLUS('f'): + ged_input_cmd(&t.ged, Ged_input_cmd_step_forward); + break; + case CTRL_PLUS('e'): + ged_input_cmd(&t.ged, Ged_input_cmd_toggle_show_event_list); + break; + case CTRL_PLUS('x'): + ged_input_cmd(&t.ged, Ged_input_cmd_cut); + try_send_to_gui_clipboard(&t.ged, &t.use_gui_cboard); + break; + case CTRL_PLUS('c'): + ged_input_cmd(&t.ged, Ged_input_cmd_copy); + try_send_to_gui_clipboard(&t.ged, &t.use_gui_cboard); + break; + case CTRL_PLUS('v'): + if (t.use_gui_cboard) { + bool added_hist = undo_history_push(&t.ged.undo_hist, &t.ged.field, + t.ged.tick_num); + Usz pasted_h, pasted_w; + Cboard_error cberr = cboard_paste( + t.ged.field.buffer, t.ged.field.height, t.ged.field.width, + t.ged.ged_cursor.y, t.ged.ged_cursor.x, &pasted_h, &pasted_w); + if (cberr) { + if (added_hist) + undo_history_pop(&t.ged.undo_hist, &t.ged.field, + &t.ged.tick_num); + t.use_gui_cboard = false; + ged_input_cmd(&t.ged, Ged_input_cmd_paste); + } else { + if (pasted_h > 0 && pasted_w > 0) { + t.ged.ged_cursor.h = pasted_h; + t.ged.ged_cursor.w = pasted_w; + } + } + t.ged.needs_remarking = true; + t.ged.is_draw_dirty = true; + } else { + ged_input_cmd(&t.ged, Ged_input_cmd_paste); + } + break; + case '\'': + ged_input_cmd(&t.ged, Ged_input_cmd_toggle_selresize_mode); + break; + case '`': + case '~': + ged_input_cmd(&t.ged, Ged_input_cmd_toggle_slide_mode); + break; + case ' ': + if (t.ged.input_mode == Ged_input_mode_append) + ged_input_character(&t.ged, '.'); + else + ged_input_cmd(&t.ged, Ged_input_cmd_toggle_play_pause); + break; + case 27: // Escape + // Check for escape sequences we're interested in that ncurses didn't + // handle. Such as bracketed paste. + if (brackpaste_seq_getungetch(stdscr) == Brackpaste_seq_begin) { + is_in_brackpaste = true; + undo_history_push(&t.ged.undo_hist, &t.ged.field, t.ged.tick_num); + brackpaste_y = t.ged.ged_cursor.y; + brackpaste_x = t.ged.ged_cursor.x; + brackpaste_starting_x = brackpaste_x; brackpaste_max_y = brackpaste_y; - if (brackpaste_x > brackpaste_max_x) brackpaste_max_x = brackpaste_x; + break; } - } - ++brackpaste_x; - } - goto event_loop; - } - - // Regular inputs when we're not in a menu and not in bracketed paste. - switch (key) { - // Checking again for 'quit' here, because it's only listened for if we're - // in the menus or *not* in bracketed paste mode. - case CTRL_PLUS('q'): - goto quit; - case CTRL_PLUS('o'): - push_open_form(osoc(t.file_name)); - break; - case 127: // backspace in terminal.app, apparently - case KEY_BACKSPACE: - if (t.ged.input_mode == Ged_input_mode_append) { - ged_dir_input(&t.ged, Ged_dir_left, 1); - ged_input_character(&t.ged, '.'); - ged_dir_input(&t.ged, Ged_dir_left, 1); - } else { - ged_input_character(&t.ged, '.'); - } - break; - case CTRL_PLUS('z'): - case CTRL_PLUS('u'): - ged_input_cmd(&t.ged, Ged_input_cmd_undo); - break; - case CTRL_PLUS('r'): - t.ged.tick_num = 0; - t.ged.needs_remarking = true; - t.ged.is_draw_dirty = true; - break; - case '[': - ged_adjust_rulers_relative(&t.ged, 0, -1); - break; - case ']': - ged_adjust_rulers_relative(&t.ged, 0, 1); - break; - case '{': - ged_adjust_rulers_relative(&t.ged, -1, 0); - break; - case '}': - ged_adjust_rulers_relative(&t.ged, 1, 0); - break; - case '(': - ged_resize_grid_relative(&t.ged, 0, -1); - break; - case ')': - ged_resize_grid_relative(&t.ged, 0, 1); - break; - case '_': - ged_resize_grid_relative(&t.ged, -1, 0); - break; - case '+': - ged_resize_grid_relative(&t.ged, 1, 0); - break; - case '\r': - case KEY_ENTER: - break; // Currently unused. - case CTRL_PLUS('i'): - case KEY_IC: - ged_input_cmd(&t.ged, Ged_input_cmd_toggle_append_mode); - break; - case '/': - // Formerly 'piano'/trigger mode toggle. We're repurposing it here to - // input a '?' instead of a '/' because '?' opens the help guide, and it - // might be a bad idea to take that away, since orca will take over the - // TTY and may leave users confused. I know of at least 1 person who was - // saved by pressing '?' after they didn't know what to do. Hmm. - ged_input_character(&t.ged, '?'); - break; - case '<': - ged_adjust_bpm(&t.ged, -1); - break; - case '>': - ged_adjust_bpm(&t.ged, 1); - break; - case CTRL_PLUS('f'): - ged_input_cmd(&t.ged, Ged_input_cmd_step_forward); - break; - case CTRL_PLUS('e'): - ged_input_cmd(&t.ged, Ged_input_cmd_toggle_show_event_list); - break; - case CTRL_PLUS('x'): - ged_input_cmd(&t.ged, Ged_input_cmd_cut); - try_send_to_gui_clipboard(&t.ged, &t.use_gui_cboard); - break; - case CTRL_PLUS('c'): - ged_input_cmd(&t.ged, Ged_input_cmd_copy); - try_send_to_gui_clipboard(&t.ged, &t.use_gui_cboard); - break; - case CTRL_PLUS('v'): - if (t.use_gui_cboard) { - bool added_hist = - undo_history_push(&t.ged.undo_hist, &t.ged.field, t.ged.tick_num); - Usz pasted_h, pasted_w; - Cboard_error cberr = cboard_paste( - t.ged.field.buffer, t.ged.field.height, t.ged.field.width, - t.ged.ged_cursor.y, t.ged.ged_cursor.x, &pasted_h, &pasted_w); - if (cberr) { - if (added_hist) - undo_history_pop(&t.ged.undo_hist, &t.ged.field, &t.ged.tick_num); - t.use_gui_cboard = false; - ged_input_cmd(&t.ged, Ged_input_cmd_paste); - } else { - if (pasted_h > 0 && pasted_w > 0) { - t.ged.ged_cursor.h = pasted_h; - t.ged.ged_cursor.w = pasted_w; - } - } - t.ged.needs_remarking = true; - t.ged.is_draw_dirty = true; - } else { - ged_input_cmd(&t.ged, Ged_input_cmd_paste); - } - break; - case '\'': - ged_input_cmd(&t.ged, Ged_input_cmd_toggle_selresize_mode); - break; - case '`': - case '~': - ged_input_cmd(&t.ged, Ged_input_cmd_toggle_slide_mode); - break; - case ' ': - if (t.ged.input_mode == Ged_input_mode_append) - ged_input_character(&t.ged, '.'); - else - ged_input_cmd(&t.ged, Ged_input_cmd_toggle_play_pause); - break; - case 27: // Escape - // Check for escape sequences we're interested in that ncurses didn't - // handle. Such as bracketed paste. - if (brackpaste_seq_getungetch(stdscr) == Brackpaste_seq_begin) { - is_in_brackpaste = true; - undo_history_push(&t.ged.undo_hist, &t.ged.field, t.ged.tick_num); - brackpaste_y = t.ged.ged_cursor.y; - brackpaste_x = t.ged.ged_cursor.x; - brackpaste_starting_x = brackpaste_x; - brackpaste_max_y = brackpaste_y; - brackpaste_max_x = brackpaste_x; - break; - } - ged_input_cmd(&t.ged, Ged_input_cmd_escape); - break; - - case 330: // delete? - ged_input_character(&t.ged, '.'); - break; - - // Cursor movement - case KEY_UP: - case CTRL_PLUS('k'): - ged_dir_input(&t.ged, Ged_dir_up, 1); - break; - case CTRL_PLUS('j'): - case KEY_DOWN: - ged_dir_input(&t.ged, Ged_dir_down, 1); - break; - case CTRL_PLUS('h'): - case KEY_LEFT: - ged_dir_input(&t.ged, Ged_dir_left, 1); - break; - case CTRL_PLUS('l'): - case KEY_RIGHT: - ged_dir_input(&t.ged, Ged_dir_right, 1); - break; - - // Selection size modification. These may not work in all terminals. (Only - // tested in xterm so far.) - case 337: // shift-up - ged_modify_selection_size(&t.ged, -1, 0); - break; - case 336: // shift-down - ged_modify_selection_size(&t.ged, 1, 0); - break; - case 393: // shift-left - ged_modify_selection_size(&t.ged, 0, -1); - break; - case 402: // shift-right - ged_modify_selection_size(&t.ged, 0, 1); - break; - case 567: // shift-control-up - ged_modify_selection_size(&t.ged, -(int)t.ged.ruler_spacing_y, 0); - break; - case 526: // shift-control-down - ged_modify_selection_size(&t.ged, (int)t.ged.ruler_spacing_y, 0); - break; - case 546: // shift-control-left - ged_modify_selection_size(&t.ged, 0, -(int)t.ged.ruler_spacing_x); - break; - case 561: // shift-control-right - ged_modify_selection_size(&t.ged, 0, (int)t.ged.ruler_spacing_x); - break; - - // Move cursor further if control is held - case 566: // control-up - ged_dir_input(&t.ged, Ged_dir_up, (int)t.ged.ruler_spacing_y); - break; - case 525: // control-down - ged_dir_input(&t.ged, Ged_dir_down, (int)t.ged.ruler_spacing_y); - break; - case 545: // control-left - ged_dir_input(&t.ged, Ged_dir_left, (int)t.ged.ruler_spacing_x); - break; - case 560: // control-right - ged_dir_input(&t.ged, Ged_dir_right, (int)t.ged.ruler_spacing_x); - break; - - // Slide selection on alt-arrow - case 564: // alt-up - ged_slide_selection(&t.ged, -1, 0); - break; - case 523: // alt-down - ged_slide_selection(&t.ged, 1, 0); - break; - case 543: // alt-left - ged_slide_selection(&t.ged, 0, -1); - break; - case 558: // alt-right - ged_slide_selection(&t.ged, 0, 1); - break; - - case CTRL_PLUS('d'): - case KEY_F(1): - push_main_menu(); - break; - case '?': - push_controls_msg(); - break; - case CTRL_PLUS('g'): - push_opers_guide_msg(); - break; - case CTRL_PLUS('s'): - tui_try_save(&t); - break; - - default: - if (key >= CHAR_MIN && key <= CHAR_MAX && orca_is_valid_glyph((Glyph)key)) - ged_input_character(&t.ged, (char)key); + ged_input_cmd(&t.ged, Ged_input_cmd_escape); + break; + + case 330: // delete? + ged_input_character(&t.ged, '.'); + break; + + // Cursor movement + case KEY_UP: + case CTRL_PLUS('k'): + ged_dir_input(&t.ged, Ged_dir_up, 1); + break; + case CTRL_PLUS('j'): + case KEY_DOWN: + ged_dir_input(&t.ged, Ged_dir_down, 1); + break; + case CTRL_PLUS('h'): + case KEY_LEFT: + ged_dir_input(&t.ged, Ged_dir_left, 1); + break; + case CTRL_PLUS('l'): + case KEY_RIGHT: + ged_dir_input(&t.ged, Ged_dir_right, 1); + break; + + // Selection size modification. These may not work in all terminals. (Only + // tested in xterm so far.) + case 337: // shift-up + ged_modify_selection_size(&t.ged, -1, 0); + break; + case 336: // shift-down + ged_modify_selection_size(&t.ged, 1, 0); + break; + case 393: // shift-left + ged_modify_selection_size(&t.ged, 0, -1); + break; + case 402: // shift-right + ged_modify_selection_size(&t.ged, 0, 1); + break; + case 567: // shift-control-up + ged_modify_selection_size(&t.ged, -(int)t.ged.ruler_spacing_y, 0); + break; + case 526: // shift-control-down + ged_modify_selection_size(&t.ged, (int)t.ged.ruler_spacing_y, 0); + break; + case 546: // shift-control-left + ged_modify_selection_size(&t.ged, 0, -(int)t.ged.ruler_spacing_x); + break; + case 561: // shift-control-right + ged_modify_selection_size(&t.ged, 0, (int)t.ged.ruler_spacing_x); + break; + + // Move cursor further if control is held + case 566: // control-up + ged_dir_input(&t.ged, Ged_dir_up, (int)t.ged.ruler_spacing_y); + break; + case 525: // control-down + ged_dir_input(&t.ged, Ged_dir_down, (int)t.ged.ruler_spacing_y); + break; + case 545: // control-left + ged_dir_input(&t.ged, Ged_dir_left, (int)t.ged.ruler_spacing_x); + break; + case 560: // control-right + ged_dir_input(&t.ged, Ged_dir_right, (int)t.ged.ruler_spacing_x); + break; + + // Slide selection on alt-arrow + case 564: // alt-up + ged_slide_selection(&t.ged, -1, 0); + break; + case 523: // alt-down + ged_slide_selection(&t.ged, 1, 0); + break; + case 543: // alt-left + ged_slide_selection(&t.ged, 0, -1); + break; + case 558: // alt-right + ged_slide_selection(&t.ged, 0, 1); + break; + + case CTRL_PLUS('d'): + case KEY_F(1): + push_main_menu(); + break; + case '?': + push_controls_msg(); + break; + case CTRL_PLUS('g'): + push_opers_guide_msg(); + break; + case CTRL_PLUS('s'): + tui_try_save(&t); + break; + + default: + if (key >= CHAR_MIN && key <= CHAR_MAX + && orca_is_valid_glyph((Glyph)key)) + ged_input_character(&t.ged, (char)key); #if 0 else fprintf(stderr, "Unknown key number: %d\n", key); #endif - break; - } - goto event_loop; + break; + } + goto event_loop; quit: - ged_stop_all_sustained_notes(&t.ged); - qnav_deinit(); - if (cont_window) - delwin(cont_window); + ged_stop_all_sustained_notes(&t.ged); + qnav_deinit(); + if (cont_window) + delwin(cont_window); #ifndef FEAT_NOMOUSE - printf("\033[?1003l\n"); // turn off console mouse events if they were active + printf( + "\033[?1003l\n"); // turn off console mouse events if they were active #endif - printf("\033[?2004h\n"); // Tell terminal to not use bracketed paste - endwin(); - ged_deinit(&t.ged); - osofree(t.file_name); - osofree(t.osc_address); - osofree(t.osc_port); - osofree(t.osc_midi_bidule_path); + printf("\033[?2004h\n"); // Tell terminal to not use bracketed paste + endwin(); + ged_deinit(&t.ged); + osofree(t.file_name); + osofree(t.osc_address); + osofree(t.osc_port); + osofree(t.osc_midi_bidule_path); #ifdef FEAT_PORTMIDI - if (portmidi_is_initialized) - Pm_Terminate(); + if (portmidi_is_initialized) + Pm_Terminate(); #endif } diff --git a/vmio.c b/vmio.c index 95f674d..33a589b 100644 --- a/vmio.c +++ b/vmio.c @@ -1,33 +1,36 @@ #include "vmio.h" -void oevent_list_init(Oevent_list *olist) { - olist->buffer = NULL; - olist->count = 0; - olist->capacity = 0; +void oevent_list_init(Oevent_list* olist) +{ + olist->buffer = NULL; + olist->count = 0; + olist->capacity = 0; } -void oevent_list_deinit(Oevent_list *olist) { free(olist->buffer); } -void oevent_list_clear(Oevent_list *olist) { olist->count = 0; } -void oevent_list_copy(Oevent_list const *src, Oevent_list *dest) { - Usz src_count = src->count; - if (dest->capacity < src_count) { - Usz new_cap = orca_round_up_power2(src_count); - dest->buffer = realloc(dest->buffer, new_cap * sizeof(Oevent)); - dest->capacity = new_cap; - } - memcpy(dest->buffer, src->buffer, src_count * sizeof(Oevent)); - dest->count = src_count; +void oevent_list_deinit(Oevent_list* olist) { free(olist->buffer); } +void oevent_list_clear(Oevent_list* olist) { olist->count = 0; } +void oevent_list_copy(Oevent_list const* src, Oevent_list* dest) +{ + Usz src_count = src->count; + if (dest->capacity < src_count) { + Usz new_cap = orca_round_up_power2(src_count); + dest->buffer = realloc(dest->buffer, new_cap * sizeof(Oevent)); + dest->capacity = new_cap; + } + memcpy(dest->buffer, src->buffer, src_count * sizeof(Oevent)); + dest->count = src_count; } -Oevent *oevent_list_alloc_item(Oevent_list *olist) { - Usz count = olist->count; - if (olist->capacity == count) { - // Note: no overflow check, but you're probably out of memory if this - // happens anyway. Like other uses of realloc in orca, we also don't check - // for a failed allocation. - Usz capacity = count < 16 ? 16 : orca_round_up_power2(count + 1); - olist->buffer = realloc(olist->buffer, capacity * sizeof(Oevent)); - olist->capacity = capacity; - } - Oevent *result = olist->buffer + count; - olist->count = count + 1; - return result; +Oevent* oevent_list_alloc_item(Oevent_list* olist) +{ + Usz count = olist->count; + if (olist->capacity == count) { + // Note: no overflow check, but you're probably out of memory if this + // happens anyway. Like other uses of realloc in orca, we also don't + // check for a failed allocation. + Usz capacity = count < 16 ? 16 : orca_round_up_power2(count + 1); + olist->buffer = realloc(olist->buffer, capacity * sizeof(Oevent)); + olist->capacity = capacity; + } + Oevent* result = olist->buffer + count; + olist->count = count + 1; + return result; } diff --git a/vmio.h b/vmio.h index 32edd12..e6f9601 100644 --- a/vmio.h +++ b/vmio.h @@ -2,67 +2,67 @@ #include "base.h" typedef enum { - Oevent_type_midi_note, - Oevent_type_midi_cc, - Oevent_type_midi_pb, - Oevent_type_osc_ints, - Oevent_type_udp_string, + Oevent_type_midi_note, + Oevent_type_midi_cc, + Oevent_type_midi_pb, + Oevent_type_osc_ints, + Oevent_type_udp_string, } Oevent_types; typedef struct { - U8 oevent_type; + U8 oevent_type; } Oevent_any; typedef struct { - U8 oevent_type; - U8 channel, octave, note, velocity, duration : 7, mono : 1; + U8 oevent_type; + U8 channel, octave, note, velocity, duration : 7, mono : 1; } Oevent_midi_note; typedef struct { - U8 oevent_type; - U8 channel, control, value; + U8 oevent_type; + U8 channel, control, value; } Oevent_midi_cc; typedef struct { - U8 oevent_type; - U8 channel, lsb, msb; + U8 oevent_type; + U8 channel, lsb, msb; } Oevent_midi_pb; enum { Oevent_osc_int_count = 35 }; typedef struct { - U8 oevent_type; - Glyph glyph; - U8 count; - U8 numbers[Oevent_osc_int_count]; + U8 oevent_type; + Glyph glyph; + U8 count; + U8 numbers[Oevent_osc_int_count]; } Oevent_osc_ints; enum { Oevent_udp_string_count = 16 }; typedef struct { - U8 oevent_type; - U8 count; - char chars[Oevent_udp_string_count]; + U8 oevent_type; + U8 count; + char chars[Oevent_udp_string_count]; } Oevent_udp_string; typedef union { - Oevent_any any; - Oevent_midi_note midi_note; - Oevent_midi_cc midi_cc; - Oevent_midi_pb midi_pb; - Oevent_osc_ints osc_ints; - Oevent_udp_string udp_string; + Oevent_any any; + Oevent_midi_note midi_note; + Oevent_midi_cc midi_cc; + Oevent_midi_pb midi_pb; + Oevent_osc_ints osc_ints; + Oevent_udp_string udp_string; } Oevent; typedef struct { - Oevent *buffer; - Usz count, capacity; + Oevent* buffer; + Usz count, capacity; } Oevent_list; -void oevent_list_init(Oevent_list *olist); -void oevent_list_deinit(Oevent_list *olist); -void oevent_list_clear(Oevent_list *olist); +void oevent_list_init(Oevent_list* olist); +void oevent_list_deinit(Oevent_list* olist); +void oevent_list_clear(Oevent_list* olist); ORCA_NOINLINE -void oevent_list_copy(Oevent_list const *src, Oevent_list *dest); +void oevent_list_copy(Oevent_list const* src, Oevent_list* dest); ORCA_NOINLINE -Oevent *oevent_list_alloc_item(Oevent_list *olist); +Oevent* oevent_list_alloc_item(Oevent_list* olist);