├── .clang-format ├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── base.h ├── cli_main.c ├── examples ├── basics │ ├── _midi.orca │ ├── _osc.orca │ ├── _udp.orca │ ├── a.orca │ ├── b.orca │ ├── c.orca │ ├── d.orca │ ├── f.orca │ ├── g.orca │ ├── h.orca │ ├── i.orca │ ├── j.orca │ ├── k.orca │ ├── l.orca │ ├── u.orca │ ├── v.orca │ └── z.orca ├── benchmarks │ ├── cardinals.orca │ ├── families.orca │ ├── io.orca │ ├── logic.orca │ ├── notes.orca │ ├── rw.orca │ └── tables.orca ├── misc │ ├── arpeggio.orca │ ├── bang.orca │ ├── chord.orca │ ├── chromatic.orca │ ├── colors.orca │ ├── echoes.orca │ ├── gates.orca │ ├── if+else.orca │ ├── kombine.orca │ ├── multiplication.orca │ ├── popcorn.orca │ ├── recursion.orca │ ├── timing.orca │ ├── tower.orca │ ├── udp+loop.orca │ └── wave.orca └── setups │ ├── knobs.orca │ ├── sequencer.orca │ └── tracker.orca ├── field.c ├── field.h ├── gbuffer.c ├── gbuffer.h ├── osc_out.c ├── osc_out.h ├── sim.c ├── sim.h ├── sysmisc.c ├── sysmisc.h ├── term_util.c ├── term_util.h ├── thirdparty ├── oso.c ├── oso.h └── sokol_time.h ├── tool ├── tui_main.c ├── vmio.c └── vmio.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | ReflowComments: false 3 | 4 | MacroBlockBegin: "^BEGIN_OPERATOR" 5 | MacroBlockEnd: "^END_OPERATOR" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Default build dir 46 | build/ 47 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hundredrabbits 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: release 2 | 3 | .PHONY: debug 4 | debug: 5 | @./tool build -d --portmidi orca 6 | 7 | .PHONY: release 8 | release: 9 | @./tool build --portmidi orca 10 | @echo "Executable program saved as: build/orca" >&2 11 | @echo "To run it, simply execute it:" >&2 12 | @echo "$$ build/orca" >&2 13 | 14 | .PHONY: clean 15 | clean: 16 | @./tool clean 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ORCΛ 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | Orca is not a synthesizer, but a flexible livecoding environment capable of sending MIDI, OSC, and UDP to your audio/visual interfaces like Ableton, Renoise, VCV Rack, or SuperCollider. 8 | 9 | 10 | 11 | | Main git repo | GitHub mirror | 12 | | ------------- | ------------- | 13 | | [git.sr.ht/~rabbits/orca](https://git.sr.ht/~rabbits/orca) | [github.com/hundredrabbits/Orca-c](https://github.com/hundredrabbits/Orca-c) | 14 | 15 | ## Quick Start for Debian/Raspbian (Raspberry Pi) 16 | 17 | ```sh 18 | sudo apt-get install git libncurses5-dev libncursesw5-dev libportmidi-dev 19 | git clone https://github.com/hundredrabbits/Orca-c.git 20 | cd Orca-c 21 | make # Compile orca 22 | build/orca # Run orca 23 | ``` 24 | 25 | To choose your MIDI output device, press `F1` (or `Ctrl+D`) to open the main menu, and then select `MIDI Output...` 26 | 27 | ``` 28 | ┌ ORCA ───────────────┐┌ PortMidi Device Selection ─────┐ 29 | │ New ││ > (*) #0 - Midi Through Port-0 │ 30 | │ Open... ││ ( ) #2 - ES1371 │ 31 | │ Save │└────────────────────────────────┘ 32 | │ Save As... │ 33 | │ │ 34 | │ Set BPM... │ 35 | │ Set Grid Size... │ 36 | │ Auto-fit Grid │ 37 | │ │ 38 | │ OSC Output... │ 39 | │ > MIDI Output... │ 40 | │ │ 41 | │ Clock & Timing... │ 42 | │.....................│ 43 | ``` 44 | 45 | ## Prerequisites 46 | 47 | Core library: A C99 compiler (no VLAs required), plus enough libc for `malloc`, `realloc`, `free`, `memcpy`, `memset`, and `memmove`. (Also, `#pragma once` must be supported.) 48 | 49 | Command-line interpreter: The above, plus POSIX, and enough libc for the common string operations (`strlen`, `strcmp`, etc.) 50 | 51 | Livecoding terminal UI: The above, plus ncurses (or compatible curses library), and floating point support (for timing.) Optionally, PortMidi can be used to enable direct MIDI output. 52 | 53 | ## Build 54 | 55 | The build script, called simply `tool`, is written in POSIX `sh`. It should work with `gcc` (including the `musl-gcc` wrapper), `tcc`, and `clang`, and will automatically detect your compiler. You can manually specify a compiler with the `-c` option. 56 | 57 | Currently known to build on macOS (`gcc`, `clang`, `tcc`) and Linux (`gcc`, `musl-gcc`, `tcc`, and `clang`, optionally with `LLD`), and Windows via cygwin or WSL (`gcc` or `clang`, `tcc` untested). 58 | 59 | There is a fire-and-forget `make` wrapper around the build script. 60 | 61 | PortMidi is an optional dependency. It can be enabled by adding the option `--portmidi` when running the `tool` build script. 62 | 63 | Mouse awareness can be disabled by adding the `--no-mouse` option. 64 | 65 | ### Build using the `tool` build script 66 | 67 | Run `./tool help` to see usage info. Examples: 68 | 69 | ```sh 70 | ./tool build -c clang-7 --portmidi orca 71 | # Build the livecoding environment with a compiler 72 | # named clang-7, with optimizations enabled, and 73 | # with PortMidi enabled for MIDI output. 74 | # Binary placed at build/orca 75 | 76 | ./tool build -d orca 77 | # Debug build of the livecoding environment. 78 | # Binary placed at build/debug/orca 79 | 80 | ./tool build -d cli 81 | # Debug build of the headless CLI interpreter. 82 | # Binary placed at build/debug/cli 83 | 84 | ./tool clean 85 | # Same as make clean. Removes build/ 86 | ``` 87 | 88 | ### Build using the `make` wrapper 89 | 90 | ```sh 91 | make release # optimized build, binary placed at build/orca 92 | make debug # debugging build, binary placed at build/debug/orca 93 | make clean # removes build/ 94 | ``` 95 | 96 | The `make` wrapper will enable `--portmidi` by default. If you run the `tool` build script on its own, `--portmidi` is not enabled by default. 97 | 98 | ## `orca` Livecoding Environment Usage 99 | 100 | ``` 101 | Usage: orca [options] [file] 102 | 103 | General options: 104 | --undo-limit Set the maximum number of undo steps. 105 | If you plan to work with large files, 106 | set this to a low number. 107 | Default: 100 108 | --initial-size When creating a new grid file, use these 109 | starting dimensions. 110 | --bpm Set the tempo (beats per minute). 111 | Default: 120 112 | --seed Set the seed for the random function. 113 | Default: 1 114 | -h or --help Print this message and exit. 115 | 116 | OSC/MIDI options: 117 | --strict-timing 118 | Reduce the timing jitter of outgoing MIDI and OSC messages. 119 | Uses more CPU time. 120 | 121 | --osc-midi-bidule 122 | Set MIDI to be sent via OSC formatted for Plogue Bidule. 123 | The path argument is the path of the Plogue OSC MIDI device. 124 | Example: /OSC_MIDI_0/MIDI 125 | ``` 126 | 127 | ### Example: build and run `orca` livecoding environment with MIDI output 128 | 129 | ```sh 130 | $ ./tool build --portmidi orca # compile orca using build script 131 | $ build/orca # run orca 132 | ``` 133 | 134 | ### `orca` Livecoding Environment Controls 135 | 136 | ``` 137 | ┌ Controls ───────────────────────────────────────────┐ 138 | │ Ctrl+Q Quit │ 139 | │ Arrow Keys Move Cursor │ 140 | │ Ctrl+D or F1 Open Main Menu │ 141 | │ 0-9, A-Z, a-z, Insert Character │ 142 | │ ! : % / = # * │ 143 | │ Spacebar Play/Pause │ 144 | │ Ctrl+Z or Ctrl+U Undo │ 145 | │ Ctrl+X Cut │ 146 | │ Ctrl+C Copy │ 147 | │ Ctrl+V Paste │ 148 | │ Ctrl+S Save │ 149 | │ Ctrl+F Frame Step Forward │ 150 | │ Ctrl+R Reset Frame Number │ 151 | │ Ctrl+I or Insert Append/Overwrite Mode │ 152 | │ ' (quote) Rectangle Selection Mode │ 153 | │ Shift+Arrow Keys Adjust Rectangle Selection │ 154 | │ Alt+Arrow Keys Slide Selection │ 155 | │ ` (grave) or ~ Slide Selection Mode │ 156 | │ Escape Return to Normal Mode or Deselect │ 157 | │ ( ) _ + [ ] { } Adjust Grid Size and Rulers │ 158 | │ < and > Adjust BPM │ 159 | │ ? Controls (this message) │ 160 | └─────────────────────────────────────────────────────┘ 161 | ``` 162 | 163 | ## `cli` command-line interface interpreter 164 | 165 | The CLI (`cli` binary) reads from a file and runs the orca simulation for 1 timestep (default) or a specified number (`-t` option) and writes the resulting state of the grid to stdout. 166 | 167 | ```sh 168 | cli [-t timesteps] infile 169 | ``` 170 | 171 | You can also make `cli` read from stdin: 172 | ```sh 173 | echo -e "...\na34\n..." | cli /dev/stdin 174 | ``` 175 | 176 | ## Extras 177 | 178 | - Discuss and get help in the [forum thread](https://llllllll.co/t/orca-live-coding-tool/17689). 179 | - Support this project through [Patreon](https://patreon.com/hundredrabbits). 180 | - See the [License](LICENSE.md) (MIT) file for license rights and limitations. 181 | -------------------------------------------------------------------------------- /base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // clang or gcc or other 11 | #if defined(__clang__) 12 | #define ORCA_OPT_MINSIZE __attribute__((minsize)) 13 | #elif defined(__GNUC__) 14 | #define ORCA_OPT_MINSIZE __attribute__((optimize("Os"))) 15 | #else 16 | #define ORCA_OPT_MINSIZE 17 | #endif 18 | 19 | // (gcc / clang) or msvc or other 20 | #if defined(__GNUC__) || defined(__clang__) 21 | #define ORCA_FORCEINLINE __attribute__((always_inline)) inline 22 | #define ORCA_NOINLINE __attribute__((noinline)) 23 | #elif defined(_MSC_VER) 24 | #define ORCA_FORCEINLINE __forceinline 25 | #define ORCA_NOINLINE __declspec(noinline) 26 | #else 27 | #define ORCA_FORCEINLINE inline 28 | #define ORCA_NOINLINE 29 | #endif 30 | 31 | // (gcc / clang) or other 32 | #if defined(__GNUC__) || defined(__clang__) 33 | #define ORCA_ASSUME_ALIGNED(_ptr, _alignment) \ 34 | __builtin_assume_aligned(_ptr, _alignment) 35 | #define ORCA_PURE __attribute__((pure)) 36 | #define ORCA_LIKELY(_x) __builtin_expect(_x, 1) 37 | #define ORCA_UNLIKELY(_x) __builtin_expect(_x, 0) 38 | #define ORCA_OK_IF_UNUSED __attribute__((unused)) 39 | #define ORCA_UNREACHABLE __builtin_unreachable() 40 | #else 41 | #define ORCA_ASSUME_ALIGNED(_ptr, _alignment) (_ptr) 42 | #define ORCA_PURE 43 | #define ORCA_LIKELY(_x) (_x) 44 | #define ORCA_UNLIKELY(_x) (_x) 45 | #define ORCA_OK_IF_UNUSED 46 | #define ORCA_UNREACHABLE assert(false) 47 | #endif 48 | 49 | // array count, safer on gcc/clang 50 | #if defined(__GNUC__) || defined(__clang__) 51 | #define ORCA_ASSERT_IS_ARRAY(_array) \ 52 | (sizeof(char[1 - 2 * __builtin_types_compatible_p( \ 53 | __typeof(_array), __typeof(&(_array)[0]))]) - \ 54 | 1) 55 | #define ORCA_ARRAY_COUNTOF(_array) \ 56 | (sizeof(_array) / sizeof((_array)[0]) + ORCA_ASSERT_IS_ARRAY(_array)) 57 | #else 58 | // pray 59 | #define ORCA_ARRAY_COUNTOF(_array) (sizeof(_array) / sizeof(_array[0])) 60 | #endif 61 | 62 | #define ORCA_Y_MAX UINT16_MAX 63 | #define ORCA_X_MAX UINT16_MAX 64 | 65 | typedef uint8_t U8; 66 | typedef int8_t I8; 67 | typedef uint16_t U16; 68 | typedef int16_t I16; 69 | typedef uint32_t U32; 70 | typedef int32_t I32; 71 | typedef uint64_t U64; 72 | typedef int64_t I64; 73 | typedef size_t Usz; 74 | typedef ssize_t Isz; 75 | 76 | typedef char Glyph; 77 | typedef U8 Mark; 78 | 79 | ORCA_FORCEINLINE static Usz orca_round_up_power2(Usz x) { 80 | assert(x <= SIZE_MAX / 2 + 1); 81 | x -= 1; 82 | x |= x >> 1; 83 | x |= x >> 2; 84 | x |= x >> 4; 85 | x |= x >> 8; 86 | x |= x >> 16; 87 | #if SIZE_MAX > UINT32_MAX 88 | x |= x >> 32; 89 | #endif 90 | return x + 1; 91 | } 92 | 93 | ORCA_OK_IF_UNUSED 94 | static bool orca_is_valid_glyph(Glyph c) { 95 | if (c >= '0' && c <= '9') 96 | return true; 97 | if (c >= 'A' && c <= 'Z') 98 | return true; 99 | if (c >= 'a' && c <= 'z') 100 | return true; 101 | switch (c) { 102 | case '!': 103 | case '#': 104 | case '%': 105 | case '*': 106 | case '.': 107 | case ':': 108 | case ';': 109 | case '=': 110 | case '?': 111 | return true; 112 | } 113 | return false; 114 | } 115 | -------------------------------------------------------------------------------- /cli_main.c: -------------------------------------------------------------------------------- 1 | #include "base.h" 2 | #include "field.h" 3 | #include "gbuffer.h" 4 | #include "sim.h" 5 | #include "vmio.h" 6 | #include 7 | 8 | static ORCA_NOINLINE void usage(void) { // clang-format off 9 | fprintf(stderr, 10 | "Usage: cli [options] infile\n\n" 11 | "Options:\n" 12 | " -t Number of timesteps to simulate.\n" 13 | " Must be 0 or a positive integer.\n" 14 | " Default: 1\n" 15 | " -q or --quiet Don't print the result to stdout.\n" 16 | " -h or --help Print this message and exit.\n" 17 | );} // clang-format on 18 | 19 | int main(int argc, char **argv) { 20 | static struct option cli_options[] = {{"help", no_argument, 0, 'h'}, 21 | {"quiet", no_argument, 0, 'q'}, 22 | {NULL, 0, NULL, 0}}; 23 | 24 | char *input_file = NULL; 25 | int ticks = 1; 26 | bool print_output = true; 27 | 28 | for (;;) { 29 | int c = getopt_long(argc, argv, "t:qh", cli_options, NULL); 30 | if (c == -1) 31 | break; 32 | switch (c) { 33 | case 't': 34 | ticks = atoi(optarg); 35 | if (ticks == 0 && strcmp(optarg, "0")) { 36 | fprintf(stderr, 37 | "Bad timestep argument %s.\n" 38 | "Must be 0 or a positive integer.\n", 39 | optarg); 40 | return 1; 41 | } 42 | break; 43 | case 'q': 44 | print_output = false; 45 | break; 46 | case 'h': 47 | usage(); 48 | return 0; 49 | case '?': 50 | usage(); 51 | return 1; 52 | } 53 | } 54 | 55 | if (optind == argc - 1) { 56 | input_file = argv[optind]; 57 | } else if (optind < argc - 1) { 58 | fprintf(stderr, "Expected only 1 file argument.\n"); 59 | usage(); 60 | return 1; 61 | } 62 | 63 | if (input_file == NULL) { 64 | fprintf(stderr, "No input file.\n"); 65 | usage(); 66 | return 1; 67 | } 68 | if (ticks < 0) { 69 | fprintf(stderr, "Time must be >= 0.\n"); 70 | usage(); 71 | return 1; 72 | } 73 | 74 | Field field; 75 | field_init(&field); 76 | Field_load_error fle = field_load_file(input_file, &field); 77 | if (fle != Field_load_error_ok) { 78 | field_deinit(&field); 79 | fprintf(stderr, "File load error: %s.\n", field_load_error_string(fle)); 80 | return 1; 81 | } 82 | Mbuf_reusable mbuf_r; 83 | mbuf_reusable_init(&mbuf_r); 84 | mbuf_reusable_ensure_size(&mbuf_r, field.height, field.width); 85 | Oevent_list oevent_list; 86 | oevent_list_init(&oevent_list); 87 | Usz max_ticks = (Usz)ticks; 88 | for (Usz i = 0; i < max_ticks; ++i) { 89 | mbuffer_clear(mbuf_r.buffer, field.height, field.width); 90 | oevent_list_clear(&oevent_list); 91 | orca_run(field.buffer, mbuf_r.buffer, field.height, field.width, i, 92 | &oevent_list, 0); 93 | } 94 | mbuf_reusable_deinit(&mbuf_r); 95 | oevent_list_deinit(&oevent_list); 96 | if (print_output) 97 | field_fput(&field, stdout); 98 | field_deinit(&field); 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /examples/basics/_midi.orca: -------------------------------------------------------------------------------- 1 | .......................................... 2 | .#.MIDI.#................................. 3 | .......................................... 4 | ...wC4.................................... 5 | .gD204TCAFE..################............. 6 | ...:02C.g....#..............#............. 7 | .............#..Channel..1..#............. 8 | ...8C4.......#..Octave.234..#............. 9 | .4D234TCAFE..#..Notes.CAFE..#............. 10 | ...:13E.4....#..............#............. 11 | .............################............. 12 | ...4C4.................................... 13 | .1D424TCAFE............................... 14 | ...%24F.2................................. 15 | .......................................... 16 | .......................................... 17 | .......................................... 18 | .......................................... -------------------------------------------------------------------------------- /examples/basics/_osc.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.OSC.#................................. 3 | ..............#########################.. 4 | .#.3.VALUES.#.#.......................#.. 5 | ..............#.First.char.is.path....#.. 6 | .D8...........#.......................#.. 7 | ..=a3123......#.Second.char.is.length.#.. 8 | ..............#.......................#.. 9 | .#.2.VALUES.#.#.Remaining.chars.are...#.. 10 | ..............#.integer.values........#.. 11 | .D6...........#.......................#.. 12 | .*=b212.......#########################.. 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | -------------------------------------------------------------------------------- /examples/basics/_udp.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.UDP.#................................. 3 | ......................................... 4 | ......................................... 5 | ....Cf......Cf........................... 6 | ..0F6.....2F6............................ 7 | ..B.H.....B.H............................ 8 | ...xS......xS............................ 9 | ......................................... 10 | ......................................... 11 | ..5;HELLO.4;ORCA......................... 12 | ......................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/basics/a.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.ADD.#................................. 3 | ......................................... 4 | .#.ADD.TWO.NUMBERS.TOGETHER.#............ 5 | ......................................... 6 | .1A2..................................... 7 | ..3...................................... 8 | ......................................... 9 | .#.ADD.THREE.NUMBERS.TOGETHER.#.......... 10 | ......................................... 11 | .1A2A3................................... 12 | ..3A5.................................... 13 | ...8..................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/basics/b.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.BOUNCE.....#.......................... 3 | ......................................... 4 | ................#.OUTPUTS.DIFFERENCE.#... 5 | ..Cg............#.OF.INPUTS..........#... 6 | ..5B8.................................... 7 | ...3.X*............5B3...aB4...7B3....... 8 | ........*...........2.....6.....4........ 9 | ......................................... 10 | ......................................... 11 | ......................................... 12 | ......................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/basics/c.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.CLOCK.#............................... 3 | ......................................... 4 | .#.COUNT.TO.8.#.......................... 5 | ......................................... 6 | ..C8..................................... 7 | ..5...................................... 8 | ......................................... 9 | .#.COUNT.TO.8.SLOWLY.#................... 10 | ......................................... 11 | .2C8..................................... 12 | ..2...................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/basics/d.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.DELAY.#............................... 3 | ......................................... 4 | Cg.D3...............Cg.D4................ 5 | b.X*................b.X.................. 6 | .#..*..*..*..**..*.#.#*...*...*...*...#.. 7 | Cg2D3...............Cg2D4................ 8 | b.X.................b.X.................. 9 | .#..*.....*...*....#.#*.......*.......#.. 10 | Cg3D3...............Cg3D4................ 11 | b.X.................b.X.................. 12 | .#........*......*.#.#........*...*...#.. 13 | Cg4D3...............Cg4D4................ 14 | b.X.................b.X.................. 15 | .#........*...*....#.#*...............#.. 16 | ......................................... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/basics/f.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.IF.#.................................. 3 | ......................................... 4 | .#.COMPARE.2.VALUES.#.................... 5 | ......................................... 6 | .aFb.aFa.1F0.1F1......................... 7 | ......*.......*.......................... 8 | ......................................... 9 | .#.INVERT.BANGS.#........................ 10 | ......................................... 11 | .....D4.................................. 12 | ......F.................................. 13 | ......*.................................. 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/basics/g.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.GENERATOR.#........................... 3 | ......................................... 4 | ...C.3C2................................. 5 | ...68P0.................................. 6 | .....00011101............................ 7 | ..C..JJJJJJJJ............................ 8 | .168G00011101............................ 9 | .....01000111............................ 10 | .....00000111............................ 11 | .....00000111............................ 12 | .....00010111............................ 13 | .....00011111............................ 14 | .....00011111............................ 15 | .....00011101............................ 16 | .....11000111............................ 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/basics/h.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.HALT.#................................ 3 | ......................................... 4 | ......................................... 5 | ..............gC2.....8C2.....4C2........ 6 | .2D............0F0.....1F0.....0F0....... 7 | ...H............*...............*........ 8 | ..xE............h.......h.......h........ 9 | ..........E..............E...........E... 10 | ......................................... 11 | ......................................... 12 | ......................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/basics/i.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.INCREMENT.#........................... 3 | ......................................... 4 | .#.INCREMENT.TO.16.#..................... 5 | ......................................... 6 | .1Ig..................................... 7 | ..0...................................... 8 | ......................................... 9 | .#.DECREMENT.TO.16.WITH.CAPITALS.#....... 10 | ......................................... 11 | .fIG..................................... 12 | ..0gT.................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/basics/j.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.JUMPER.#.............................. 3 | ......................................... 4 | .2bO2bO2bO2bO2bO2bO2bO2bO2bO2bO2bO..D.... 5 | ............*.......................*.... 6 | ...J..J..J..J..J..J..J..J..J..J..J..J.... 7 | ............*.......................*.... 8 | ...J..J..J..J..J..J..J..J..J..J..J..J.... 9 | ............*.......................*.... 10 | ...J..J..J..J..J..J..J..J..J..J..J..J.... 11 | ............*.......................*.... 12 | ...J..J..J..J..J..J..J..J..J..J..J..J.... 13 | ............*.......................*.... 14 | ...J..J..J..J..J..J..J..J..J..J..J..J.... 15 | ............*.......................*.... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/basics/k.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.KONKAT.#.............................. 3 | ......................................... 4 | .#.ASSIGN.VARIABLES.#.................... 5 | ......................................... 6 | .aV1.bV2.cV3.dV4.eV5.fV5................. 7 | ......................................... 8 | ......................................... 9 | .#.COMBINE.THEM.TOGETHER.#............... 10 | ......................................... 11 | .7Kabc.def............................... 12 | ...123.455............................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/basics/l.orca: -------------------------------------------------------------------------------- 1 | ......................... 2 | .#.LESS...#.............. 3 | ......................... 4 | ...5L3...aL4...7L3....... 5 | ....3.....4.....3........ 6 | ......................... 7 | ......................... 8 | ......................... 9 | ......................... 10 | -------------------------------------------------------------------------------- /examples/basics/u.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.UCLID.#............................... 3 | ......................................... 4 | .Cg.U8..............Cg5U8................ 5 | .4.X................4.X*................. 6 | ..#*.......*.......#.#*.*.**.**.*.**.*#.. 7 | .Cg.U8..............Cg6U8................ 8 | .4.X................4.X*................. 9 | ..#*.......*.......#.#*.***.***.***.**#.. 10 | .Cg.U8..............Cg7U8................ 11 | .4.X................4.X*................. 12 | ..#*.......*.......#.#*.*******.******#.. 13 | .Cg.U8..............Cg8U8................ 14 | .4.X................4.X*................. 15 | ..#*.......*.......#.#****************#.. 16 | ......................................... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/basics/v.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.VARIABLE.#............................ 3 | ......................................... 4 | .#.WRITE.A.VARIABLE.#.................... 5 | ......................................... 6 | .aV3..................................... 7 | ......................................... 8 | ......................................... 9 | .#.READ.A.VARIABLE.#..................... 10 | ......................................... 11 | ..Va..................................... 12 | ......................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/basics/z.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.LERP.#................................ 3 | ......................................... 4 | ...R8....R5........D....D................ 5 | .xV7...yV0............................... 6 | ...................vx...vy............... 7 | ..................Z5...Z1................ 8 | ................xV5..yV1................. 9 | ...2Kxy.................................. 10 | .....51X................................. 11 | ......................................... 12 | ......................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/benchmarks/cardinals.orca: -------------------------------------------------------------------------------- 1 | .......................................... 2 | .#.CLOCKWISE.#...#.COUNTER.#.............. 3 | .......................................... 4 | ...2D4.....D4......2D4....D4.............. 5 | .32X.............32X...................... 6 | .......H...............H.................. 7 | .......E...H...........S.................. 8 | .......j...S...........j.................. 9 | ...........j................0............. 10 | .......................................... 11 | ..........................H............... 12 | ...........S..........H...Ny.............. 13 | ............H.........Ey..E.0............. 14 | ...........xW............................. 15 | .......0.................................. 16 | .......................................... 17 | .......................................... 18 | .......................................... -------------------------------------------------------------------------------- /examples/benchmarks/families.orca: -------------------------------------------------------------------------------- 1 | ................................................. 2 | .#.READING.OUTPUT.#....#.READERS.#............... 3 | ................................................. 4 | ...I....Z....H....F......O....G....T............. 5 | ...4....0.........*.............................. 6 | ................................................. 7 | .#.CLOCKS.#............#.WRITERS..#.............. 8 | ................................................. 9 | ...D....U....C....R......X....Q....P............. 10 | .............7....g.............................. 11 | ................................................. 12 | .#.MATHS.#.............#.VARIABLES.#............. 13 | ................................................. 14 | ...A....B....L....M......V....K.................. 15 | ...0....0.........0.............................. 16 | ................................................. 17 | .#.JUMPERS.#...........#.CARDINAL.#.............. 18 | ................................................. 19 | ...J....Y..............#.NESW.#.................. 20 | ................................................. 21 | ................................................. 22 | ................................................. 23 | ................................................. 24 | ................................................. 25 | ................................................. 26 | -------------------------------------------------------------------------------- /examples/benchmarks/io.orca: -------------------------------------------------------------------------------- 1 | ............................................. 2 | .#.TEST.IO.#................................. 3 | ............................................. 4 | .C9..........2C9...........3C9............... 5 | .29T01aAgGZz..59T01aAgGZz...39T01aAgGZz...... 6 | .aVa..........bVG...........cVA.............. 7 | ............................................. 8 | .#.TEST.#.................................... 9 | ............................................. 10 | .#.MIDI..#..#.CC..#..#.UDP.#..#.OSC.#........ 11 | ............................................. 12 | .H.3Kabc....H.3Kabc..H.3Kabc..H.3Kabc........ 13 | .*Y*:aGA....*Y*!aGA..*Y*;aGA..*Y*=aGA........ 14 | ............................................. 15 | ............H.3Kabc.......................... 16 | ............*Y*?aGA.......................... 17 | ............................................. 18 | ............H....4Kabca...................... 19 | ............*Y*$pg:aGAa...................... 20 | ............................................. -------------------------------------------------------------------------------- /examples/benchmarks/logic.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.BENCHMARK.#........................... 3 | ......................................... 4 | .8C8.............C8...................... 5 | ..78T012AGag.....68T012AGag.............. 6 | ..aV.............bVg..................... 7 | ......................................... 8 | .3Ka.b.3Ka.b.3Ka.b.3Ka.b.3Ka.b.3Ka.b..... 9 | ....Ag....Bg....Cg....Rg....Mg....Vg..... 10 | ....g.....e.....e.....5.....0............ 11 | ......................................... 12 | .3Ka.b.3Ka.b.3Ka.b.3Ka.b.......3K..a..... 13 | ....Ig....Dg....Fg....Lg..........V...... 14 | ....5.................*.................. 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/benchmarks/notes.orca: -------------------------------------------------------------------------------- 1 | ......................................................... 2 | .2Cq..................................................... 3 | ..dqTAaBbCcDdEeFfGgHhIiJjKkLlMm.......................... 4 | ..0Vg.................................................... 5 | ......................................................... 6 | .2Cq..................................................... 7 | ..dqTNnOoPpQqRrSsTtUuVvWwXxYyZz.......................... 8 | ..1Vt.................................................... 9 | ......................................................... 10 | .2Cq..................................................... 11 | ..dqTABCDEFGHIJKLMNOPQRSTUVWXYZ.......................... 12 | ..2VN.................................................... 13 | ......................................................... 14 | .2Cq..................................................... 15 | ..dqTabcdefghijklmnopqrstuvwxyz.......................... 16 | ..3Vn.................................................... 17 | ......................................................... 18 | ..D2..V2................................................. 19 | ...:01N.................................................. 20 | ......................................................... -------------------------------------------------------------------------------- /examples/benchmarks/rw.orca: -------------------------------------------------------------------------------- 1 | ..................................2C4..... 2 | .#.READ.#........................2M1...... 3 | ................................lV2....... 4 | .C8...........Cg...........Vl............. 5 | .30O01234567..b8T01234567..202Q01234567... 6 | ...3............3............23........... 7 | .......................................... 8 | .#.WRITE.#................................ 9 | .......................................... 10 | .C8.C8........Cg.C8........Vl............. 11 | .30X3.........b8P3.........202G01......... 12 | ...01234567.....01234567......0101.101.... 13 | .......................................... 14 | .......................................... 15 | .......................................... 16 | .......................................... 17 | .......................................... -------------------------------------------------------------------------------- /examples/benchmarks/tables.orca: -------------------------------------------------------------------------------- 1 | ................................................................................. 2 | ...Cf..fCf....................................................................... 3 | .xV9..yV5........................................................................ 4 | ................................................................................. 5 | ...3Kx.y..............3Kx.y..............3Kx.y..............3Kx.y................ 6 | .2Kxy9M5............2Kxy9L5............2Kxy9B5............2Kxy9A5................ 7 | ...95X9...............95X5...............95X4...............95Xe................. 8 | .....000000000000000....000000000000000....0123456789abcde....0123456789abcde.... 9 | .....0123456789abcde....011111111111111....10123456789abcd....123456789abcdef.... 10 | .....02468acegikmoqs....012222222222222....210123456789abc....23456789abcdefg.... 11 | .....0369cfilorux036....012333333333333....3210123456789ab....3456789abcdefgh.... 12 | .....048cgkosw048cgk....012344444444444....43210123456789a....456789abcdefghi.... 13 | .....05afkpuz49ejoty....012345555555555....543210123456789....56789abcdefghij.... 14 | .....06ciou06ciou06c....012345666666666....654321012345678....6789abcdefghijk.... 15 | .....07elsz6dkry5cjq....012345677777777....765432101234567....789abcdefghijkl.... 16 | .....08gow4cks08gow4....012345678888888....876543210123456....89abcdefghijklm.... 17 | .....09ir09ir09ir09i....012345678999999....987654321012345....9abcdefghijklmn.... 18 | .....0aku4eoy8is2cmw....0123456789aaaaa....a98765432101234....abcdefghijklmno.... 19 | .....0bmx8ju5gr2doza....0123456789abbbb....ba9876543210123....bcdefghijklmnop.... 20 | .....0co0co0co0co0co....0123456789abccc....cba987654321012....cdefghijklmnopq.... 21 | .....0dq3gt6jw9mzcp2....0123456789abcdd....dcba98765432101....defghijklmnopqr.... 22 | .....0es6kycq4iwao2g....0123456789abcde....edcba9876543210....efghijklmnopqrs.... 23 | ................................................................................. 24 | ................................................................................. 25 | ................................................................................. -------------------------------------------------------------------------------- /examples/misc/arpeggio.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.ARPEGGIO.#............................ 3 | ......................................... 4 | .gC4......4C4............................ 5 | ..14T1324..14TCDEF....................... 6 | ..aV3......bVD........................... 7 | ......................................... 8 | .#.NOTE.STEP.#........................... 9 | ......................................... 10 | ..04O.D4................................. 11 | .31XG.................................... 12 | ..Va..vb................................. 13 | .H3Y3AG.................................. 14 | .*:02J................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/bang.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.BANG.#................................ 3 | ......................................... 4 | ......................................... 5 | ......................................... 6 | ......................................... 7 | ......................................... 8 | ..............2D.H....................... 9 | ................xE....................... 10 | .....................E..#.BANG.#......... 11 | ......................................... 12 | ......................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... -------------------------------------------------------------------------------- /examples/misc/chord.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.CHORD.#............................... 3 | ......................................... 4 | ......8C8................................ 5 | ..2D4..68TCEGACEFB....................... 6 | .bV....2VF............................... 7 | ......................................... 8 | .#.DISTANCE.#............................ 9 | ......................................... 10 | .3V2.4V4.5V6.6V8......................... 11 | ......................................... 12 | .#.PLAY.HAND.#........................... 13 | ......................................... 14 | ..3K2.3...3K2.4...3K2.5...3K2.6.......... 15 | .Vb.FA2..Vb.FA4..Vb.FA6..Vb.FA8.......... 16 | ..:03H88..:03J88..:03L88..:03N88......... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/chromatic.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.CHROMATIC.#........................... 3 | ......................................... 4 | ......................................... 5 | ......................................... 6 | ......................................... 7 | ......................................... 8 | ......................................... 9 | ..4Cc.................................... 10 | .D46cTCcDdEFfGgAaB....................... 11 | .*:03f84................................. 12 | ......................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/colors.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.COLORS.#.............................. 3 | ......................................... 4 | ...Bg.2Bg.4Bg............................ 5 | .rV2.gV1.cVf............................. 6 | ......................................... 7 | .C3...................................... 8 | .23T048.................................. 9 | ...8..................................... 10 | ...J.3Krgb............................... 11 | .D18.3G213............................... 12 | .*$co:003;103;213........................ 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/echoes.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.ECHOES.#.............................. 3 | ......................................... 4 | .22O..................................... 5 | .aV.22O.................................. 6 | .......22O............................... 7 | ..........22O............................ 8 | .............22O......................... 9 | ................22O...................... 10 | ...................22O................... 11 | ......................22O................ 12 | .........................22O............. 13 | ............................22O.......... 14 | .................................Va...... 15 | .................................1....... 16 | ......................................... -------------------------------------------------------------------------------- /examples/misc/gates.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.LOGIC.GATES.#......................... 3 | ......................................... 4 | .2C4.......2C4........................... 5 | ..04T.*.*...04T..**...................... 6 | ..aV........bV........................... 7 | ......................................... 8 | ...and......xor.....or................... 9 | ......................................... 10 | ..3Ka.b...3Ka.b...Va..Vb................. 11 | .....L.......F.....F.F................... 12 | ......F0....F*.....*L*................... 13 | ....................0F................... 14 | ......................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/misc/if+else.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.IF.ELSE.#............................. 3 | ......................................... 4 | ..4C4..2C4............................... 5 | .aV1..bV2..cV0.#.ASSIGN.#................ 6 | ......................................... 7 | .3Ka.b................................... 8 | ...1F2................................... 9 | ...............#.IF.#.................... 10 | ...cv1................................... 11 | ......................................... 12 | ....Vc................................... 13 | ..0F0..........#.ELSE.#.................. 14 | ...*..................................... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/kombine.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.KOMBINE.#............................. 3 | ......................................... 4 | .4C4.#OCTA#.2C4.#NOTE#..1C4.#VELO#....... 5 | ..34T3454....24TCDEF.....04T0123......... 6 | ..oV5........nVD.........vV3............. 7 | ......................................... 8 | ......................................... 9 | .#.KONKAT.#.............................. 10 | ......................................... 11 | .3Konv................................... 12 | ...5D3................................... 13 | ......................................... 14 | ......................................... 15 | ......................................... 16 | ......................................... -------------------------------------------------------------------------------- /examples/misc/multiplication.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.MULTIPLICATION.#...................... 3 | ......................................... 4 | .aV4.bV3................................. 5 | ......................................... 6 | .2Kab.................................... 7 | ...43O................................... 8 | .....c#123456#........................... 9 | ......#2468ac#........................... 10 | ......#369cfi#........................... 11 | ......#48cgko#........................... 12 | ......#5afkpu#........................... 13 | ......#6ciou.#........................... 14 | ......................................... 15 | ......................................... 16 | ......................................... -------------------------------------------------------------------------------- /examples/misc/popcorn.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.POPCORN.#............................. 3 | ......................................... 4 | ..2C8.........gC4........................ 5 | .2H38T13579bdf.16T2345................... 6 | ...xV7.........yV3....................... 7 | ......................................... 8 | ..2Kxy................................... 9 | .D2.732Q................................. 10 | ..Y.:04G................................. 11 | ........#5C4a5C4G4d4G4C..#............... 12 | ........#5C4a5C4G4d4G4C..#............... 13 | ........#5C5D5d5D5d5C5D5C#............... 14 | ........#5D4a5C4a5C4g5C..#............... 15 | ......................................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/recursion.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.RECURSION.#........................... 3 | ......................................... 4 | .#.BY.1.#.......#.BY.3.#................. 5 | ......................................... 6 | ..03O............03O..................... 7 | .30XG...........30XV..................... 8 | ....1AG............3AV................... 9 | .....H..............Y.................... 10 | ......................................... 11 | .#.BY.2.#.......#.BY.4.#................. 12 | ......................................... 13 | ..03O............03O..................... 14 | .30XU...........30X4..................... 15 | ....2AU............4A4................... 16 | .....W..............8.................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/timing.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.TIMING.#.............................. 3 | ......................................... 4 | .C8.C2...............C8.D2............... 5 | .3.X1................3.X*................ 6 | ..#10101010#..........#*.*.*.*.#......... 7 | .C9.C3...............C9.D3............... 8 | .6.X0................6.X................. 9 | ..#120120120#.........#*..*..*..#........ 10 | .Cc.C4...............Cc.D4............... 11 | .3.X3................3.X................. 12 | ..#123012301230#......#*...*...*...#..... 13 | .Cf.C5...............Cf.D5............... 14 | .0.X0................0.X................. 15 | ..#123401234012340#...#*....*....*....#.. 16 | ......................................... -------------------------------------------------------------------------------- /examples/misc/tower.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.TOWER.#............................... 3 | ......................................... 4 | ...................C..................... 5 | ..................C1C.................... 6 | .................C0A1C................... 7 | ................C3A1A1C.................. 8 | ...............C2A4A2A1C................. 9 | ..............C1A6A6A3A1C................ 10 | ..............0A7AcA9A4A1................ 11 | ...............7AjAlAdA5................. 12 | ................qA4AyAi.................. 13 | .................uA2Ag................... 14 | ..................wAi.................... 15 | ...................e..................... 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/udp+loop.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.UDP.LOOP.#............................ 3 | ......................................... 4 | .#.SET.UDP.TO.49160.#.................... 5 | .#.SEND.WRITE.CMD.VIA.UDP.#.............. 6 | ......................................... 7 | .#.TARGET:.#.2........................... 8 | ......................................... 9 | .D4.4C................................... 10 | ..;w:313;6............................... 11 | ......................................... 12 | .#.SET.COLOR.#........................... 13 | ......................................... 14 | .D2....R.g............................... 15 | .*;c:8f3................................. 16 | ......................................... 17 | ......................................... -------------------------------------------------------------------------------- /examples/misc/wave.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .#.WAVE.#...8C6...C5..................... 3 | ...........aV5..bV1...................... 4 | 3Ka.b.................................... 5 | ..5A1.................................... 6 | ...6XE................................... 7 | ....E...........................:04E..... 8 | ................................:04D..... 9 | ..............................E.:04C..... 10 | ....................E....E...E.*:03B..... 11 | ...............E...E....E...E...:03A..... 12 | .....E....E...E........E...E...*:03G..... 13 | ....E....E...E....E...E........E:03F..... 14 | ........E...E....E...E....E.....:03E..... 15 | .......E...E....E...............:03D..... 16 | ......E.........................:03C..... 17 | ......................................... -------------------------------------------------------------------------------- /examples/setups/knobs.orca: -------------------------------------------------------------------------------- 1 | ......................................... 2 | .............20eQ..03..00..00..0i........ 3 | ...03..00..00..0i..0f..09..36..6i........ 4 | ...JJ..JJ..JJ..JJ..44..25..0f..fi........ 5 | .0V032V004V006V0i..ff..09..36..69........ 6 | ....J...J...J...J..f4..15..gf..fl........ 7 | ..1V3.3V0.5V0.7Vi..ff..09..36..6i........ 8 | ......................................... 9 | ......................................... 10 | .......V0.......V1.......V2.......V3..... 11 | ..D1..Z0...D1..Z3...D1..Z0...D1..Z0...... 12 | ..*!010....*!023....*!030....*!040....... 13 | ......................................... 14 | .......V4.......V5.......V6.......V7..... 15 | ..D1..Z0...D1..Z0...D1..Z0...D1..Zi...... 16 | ..*!050....*!060....*!070....*!08i....... 17 | ......................................... 18 | -------------------------------------------------------------------------------- /examples/setups/sequencer.orca: -------------------------------------------------------------------------------- 1 | .......................................... 2 | .#.SEQUENCER.#....................Cw...Cw. 3 | ................................4Aa..1Aa.. 4 | ...............................aVe..bVb... 5 | .......................................... 6 | .Va.Vb..0.......1.......2.......3......... 7 | .e1ObxT#.................................. 8 | .2V.1V.#................................#. 9 | .Va.Vb..0................................. 10 | .e1ObxT#.................................. 11 | .4V.3V.#................................#. 12 | .Va.Vb..0................................. 13 | .e1ObxT#.................................. 14 | .6V.5V.#................................#. 15 | .Va.Vb..0................................. 16 | .e1ObxT#.................................. 17 | .8V.7V.#................................#. 18 | .Va.Vb..0................................. 19 | .e1ObxT#.................................. 20 | .aV.9V.#................................#. 21 | .......................................... 22 | .H...V1..H...V3..H...V5..H...V7..H...V9... 23 | .*:03....*:23....*:43....*:63....*:83..... 24 | .H...V2..H...V4..H...V6..H...V8..H...Va... 25 | .*:13....*:33....*:53....*:73....*:a3..... -------------------------------------------------------------------------------- /examples/setups/tracker.orca: -------------------------------------------------------------------------------- 1 | .............................................. 2 | .#.TRACKER.#.......Cg..................Cg..... 3 | ..............Cg.5Ae..............Cg.5Ae...... 4 | .........Cg.4Ae.H.j2Q........Cg.4Ae.H.j2Q..... 5 | ....Cg.3Ae.H.i2Q*:3.....Cg.3Ae.H.i2Q*:7....... 6 | ..2Ae.H.h2Q*:2........2Ae.H.h2Q*:6............ 7 | .H.g2Q*:1............H.g2Q*:5................. 8 | .*:0..................:4...................... 9 | .....#..#.#3C#.#..#.#..#.#..#.#..#.#..#.#..#.. 10 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 11 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 12 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 13 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 14 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 15 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 16 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 17 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 18 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 19 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 20 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 21 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 22 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 23 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. 24 | .....#..#.#..#.#..#.#..#.#..#.#..#.#..#.#..#.. -------------------------------------------------------------------------------- /field.c: -------------------------------------------------------------------------------- 1 | #include "field.h" 2 | #include "gbuffer.h" 3 | #include 4 | 5 | void field_init(Field *f) { 6 | f->buffer = NULL; 7 | f->height = 0; 8 | f->width = 0; 9 | } 10 | 11 | void field_init_fill(Field *f, Usz height, Usz width, Glyph fill_char) { 12 | assert(height <= ORCA_Y_MAX && width <= ORCA_X_MAX); 13 | Usz num_cells = height * width; 14 | f->buffer = malloc(num_cells * sizeof(Glyph)); 15 | memset(f->buffer, fill_char, num_cells); 16 | f->height = (U16)height; 17 | f->width = (U16)width; 18 | } 19 | 20 | void field_deinit(Field *f) { free(f->buffer); } 21 | 22 | void field_resize_raw(Field *f, Usz height, Usz width) { 23 | assert(height <= ORCA_Y_MAX && width <= ORCA_X_MAX); 24 | Usz cells = height * width; 25 | f->buffer = realloc(f->buffer, cells * sizeof(Glyph)); 26 | f->height = (U16)height; 27 | f->width = (U16)width; 28 | } 29 | 30 | void field_resize_raw_if_necessary(Field *field, Usz height, Usz width) { 31 | if (field->height != height || field->width != width) { 32 | field_resize_raw(field, height, width); 33 | } 34 | } 35 | 36 | void field_copy(Field *src, Field *dest) { 37 | field_resize_raw_if_necessary(dest, src->height, src->width); 38 | gbuffer_copy_subrect(src->buffer, dest->buffer, src->height, src->width, 39 | dest->height, dest->width, 0, 0, 0, 0, src->height, 40 | src->width); 41 | } 42 | 43 | static inline bool glyph_char_is_valid(char c) { return c >= '!' && c <= '~'; } 44 | 45 | void field_fput(Field *f, FILE *stream) { 46 | enum { Column_buffer_count = 4096 }; 47 | char out_buffer[Column_buffer_count]; 48 | Usz f_height = f->height; 49 | Usz f_width = f->width; 50 | Glyph *f_buffer = f->buffer; 51 | if (f_width > Column_buffer_count - 2) 52 | return; 53 | for (Usz iy = 0; iy < f_height; ++iy) { 54 | Glyph *row_p = f_buffer + f_width * iy; 55 | for (Usz ix = 0; ix < f_width; ++ix) { 56 | char c = row_p[ix]; 57 | out_buffer[ix] = glyph_char_is_valid(c) ? c : '?'; 58 | } 59 | out_buffer[f_width] = '\n'; 60 | out_buffer[f_width + 1] = '\0'; 61 | fputs(out_buffer, stream); 62 | } 63 | } 64 | 65 | Field_load_error field_load_file(char const *filepath, Field *field) { 66 | FILE *file = fopen(filepath, "r"); 67 | if (file == NULL) { 68 | return Field_load_error_cant_open_file; 69 | } 70 | enum { Bufsize = 4096 }; 71 | char buf[Bufsize]; 72 | Usz first_row_columns = 0; 73 | Usz rows = 0; 74 | for (;;) { 75 | char *s = fgets(buf, Bufsize, file); 76 | if (s == NULL) 77 | break; 78 | if (rows == ORCA_Y_MAX) { 79 | fclose(file); 80 | return Field_load_error_too_many_rows; 81 | } 82 | Usz len = strlen(buf); 83 | if (len == Bufsize - 1 && buf[len - 1] != '\n' && !feof(file)) { 84 | fclose(file); 85 | return Field_load_error_too_many_columns; 86 | } 87 | for (;;) { 88 | if (len == 0) 89 | break; 90 | if (!isspace(buf[len - 1])) 91 | break; 92 | --len; 93 | } 94 | if (len == 0) 95 | continue; 96 | if (len >= ORCA_X_MAX) { 97 | fclose(file); 98 | return Field_load_error_too_many_columns; 99 | } 100 | // quick hack until we use a proper scanner 101 | if (rows == 0) { 102 | first_row_columns = len; 103 | } else if (len != first_row_columns) { 104 | fclose(file); 105 | return Field_load_error_not_a_rectangle; 106 | } 107 | field_resize_raw(field, rows + 1, first_row_columns); 108 | Glyph *rowbuff = field->buffer + first_row_columns * rows; 109 | for (Usz i = 0; i < len; ++i) { 110 | char c = buf[i]; 111 | rowbuff[i] = glyph_char_is_valid(c) ? c : '.'; 112 | } 113 | ++rows; 114 | } 115 | fclose(file); 116 | return Field_load_error_ok; 117 | } 118 | 119 | char const *field_load_error_string(Field_load_error fle) { 120 | char const *errstr = "Unknown"; 121 | switch (fle) { 122 | case Field_load_error_ok: 123 | errstr = "OK"; 124 | break; 125 | case Field_load_error_cant_open_file: 126 | errstr = "Unable to open file"; 127 | break; 128 | case Field_load_error_too_many_columns: 129 | errstr = "Grid file has too many columns"; 130 | break; 131 | case Field_load_error_too_many_rows: 132 | errstr = "Grid file has too many rows"; 133 | break; 134 | case Field_load_error_no_rows_read: 135 | errstr = "Grid file has no rows"; 136 | break; 137 | case Field_load_error_not_a_rectangle: 138 | errstr = "Grid file is not a rectangle"; 139 | break; 140 | } 141 | return errstr; 142 | } 143 | 144 | void mbuf_reusable_init(Mbuf_reusable *mbr) { 145 | mbr->buffer = NULL; 146 | mbr->capacity = 0; 147 | } 148 | 149 | void mbuf_reusable_ensure_size(Mbuf_reusable *mbr, Usz height, Usz width) { 150 | Usz capacity = height * width; 151 | if (mbr->capacity < capacity) { 152 | mbr->buffer = realloc(mbr->buffer, capacity); 153 | mbr->capacity = capacity; 154 | } 155 | } 156 | 157 | void mbuf_reusable_deinit(Mbuf_reusable *mbr) { free(mbr->buffer); } 158 | -------------------------------------------------------------------------------- /field.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base.h" 3 | #include // FILE cannot be forward declared 4 | 5 | // A reusable buffer for glyphs, stored with its dimensions. Also some helpers 6 | // for loading/saving from files and doing common operations that a UI layer 7 | // might want to do. Not used by the VM. 8 | 9 | typedef struct { 10 | Glyph *buffer; 11 | U16 width, height; 12 | } Field; 13 | 14 | void field_init(Field *field); 15 | void field_init_fill(Field *field, Usz height, Usz width, Glyph fill_char); 16 | void field_deinit(Field *field); 17 | void field_resize_raw(Field *field, Usz height, Usz width); 18 | void field_resize_raw_if_necessary(Field *field, Usz height, Usz width); 19 | void field_copy(Field *src, Field *dest); 20 | void field_fput(Field *field, FILE *stream); 21 | 22 | typedef enum { 23 | Field_load_error_ok = 0, 24 | Field_load_error_cant_open_file = 1, 25 | Field_load_error_too_many_columns = 2, 26 | Field_load_error_too_many_rows = 3, 27 | Field_load_error_no_rows_read = 4, 28 | Field_load_error_not_a_rectangle = 5, 29 | } Field_load_error; 30 | 31 | Field_load_error field_load_file(char const *filepath, Field *field); 32 | 33 | char const *field_load_error_string(Field_load_error fle); 34 | 35 | // A reusable buffer for the per-grid-cell flags. Similar to how Field is a 36 | // reusable buffer for Glyph, Mbuf_reusable is for Mark. The naming isn't so 37 | // great. Also like Field, the VM doesn't have to care about the buffer being 38 | // reusable -- it only cares about a 'Mark*' type. (With the same dimensions of 39 | // the 'Field*' buffer, since it uses them together.) There are no procedures 40 | // for saving/loading Mark* buffers to/from disk, since we currently don't need 41 | // that functionality. 42 | 43 | typedef struct Mbuf_reusable { 44 | Mark *buffer; 45 | Usz capacity; 46 | } Mbuf_reusable; 47 | 48 | void mbuf_reusable_init(Mbuf_reusable *mbr); 49 | void mbuf_reusable_ensure_size(Mbuf_reusable *mbr, Usz height, Usz width); 50 | void mbuf_reusable_deinit(Mbuf_reusable *mbr); 51 | -------------------------------------------------------------------------------- /gbuffer.c: -------------------------------------------------------------------------------- 1 | #include "gbuffer.h" 2 | 3 | void gbuffer_copy_subrect(Glyph *src, Glyph *dest, Usz src_height, 4 | Usz src_width, Usz dest_height, Usz dest_width, 5 | Usz src_y, Usz src_x, Usz dest_y, Usz dest_x, 6 | Usz height, Usz width) { 7 | if (src_height <= src_y || src_width <= src_x || dest_height <= dest_y || 8 | dest_width <= dest_x) 9 | return; 10 | Usz ny_0 = src_height - src_y; 11 | Usz ny_1 = dest_height - dest_y; 12 | Usz ny = height; 13 | if (ny_0 < ny) 14 | ny = ny_0; 15 | if (ny_1 < ny) 16 | ny = ny_1; 17 | if (ny == 0) 18 | return; 19 | Usz row_copy_0 = src_width - src_x; 20 | Usz row_copy_1 = dest_width - dest_x; 21 | Usz row_copy = width; 22 | if (row_copy_0 < row_copy) 23 | row_copy = row_copy_0; 24 | if (row_copy_1 < row_copy) 25 | row_copy = row_copy_1; 26 | Usz copy_bytes = row_copy * sizeof(Glyph); 27 | Glyph *src_p = src + src_y * src_width + src_x; 28 | Glyph *dest_p = dest + dest_y * dest_width + dest_x; 29 | Isz src_stride; 30 | Isz dest_stride; 31 | if (src_y >= dest_y) { 32 | src_stride = (Isz)src_width; 33 | dest_stride = (Isz)dest_width; 34 | } else { 35 | src_p += (ny - 1) * src_width; 36 | dest_p += (ny - 1) * dest_width; 37 | src_stride = -(Isz)src_width; 38 | dest_stride = -(Isz)dest_width; 39 | } 40 | Usz iy = 0; 41 | for (;;) { 42 | memmove(dest_p, src_p, copy_bytes); 43 | ++iy; 44 | if (iy == ny) 45 | break; 46 | src_p += src_stride; 47 | dest_p += dest_stride; 48 | } 49 | } 50 | 51 | void gbuffer_fill_subrect(Glyph *gbuffer, Usz f_height, Usz f_width, Usz y, 52 | Usz x, Usz height, Usz width, Glyph fill_char) { 53 | if (y >= f_height || x >= f_width) 54 | return; 55 | Usz rows_0 = f_height - y; 56 | Usz rows = height; 57 | if (rows_0 < rows) 58 | rows = rows_0; 59 | if (rows == 0) 60 | return; 61 | Usz columns_0 = f_width - x; 62 | Usz columns = width; 63 | if (columns_0 < columns) 64 | columns = columns_0; 65 | Usz fill_bytes = columns * sizeof(Glyph); 66 | Glyph *p = gbuffer + y * f_width + x; 67 | Usz iy = 0; 68 | for (;;) { 69 | memset(p, fill_char, fill_bytes); 70 | ++iy; 71 | if (iy == rows) 72 | break; 73 | p += f_width; 74 | } 75 | } 76 | 77 | void mbuffer_clear(Mark *mbuf, Usz height, Usz width) { 78 | Usz cleared_size = height * width; 79 | memset(mbuf, 0, cleared_size); 80 | } 81 | -------------------------------------------------------------------------------- /gbuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base.h" 3 | 4 | ORCA_PURE static inline Glyph gbuffer_peek_relative(Glyph *gbuf, Usz height, 5 | Usz width, Usz y, Usz x, 6 | Isz delta_y, Isz delta_x) { 7 | Isz y0 = (Isz)y + delta_y; 8 | Isz x0 = (Isz)x + delta_x; 9 | if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) 10 | return '.'; 11 | return gbuf[(Usz)y0 * width + (Usz)x0]; 12 | } 13 | 14 | static inline void gbuffer_poke(Glyph *gbuf, Usz height, Usz width, Usz y, 15 | Usz x, Glyph g) { 16 | assert(y < height && x < width); 17 | (void)height; 18 | gbuf[y * width + x] = g; 19 | } 20 | 21 | static inline void gbuffer_poke_relative(Glyph *gbuf, Usz height, Usz width, 22 | Usz y, Usz x, Isz delta_y, Isz delta_x, 23 | Glyph g) { 24 | Isz y0 = (Isz)y + delta_y; 25 | Isz x0 = (Isz)x + delta_x; 26 | if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) 27 | return; 28 | gbuf[(Usz)y0 * width + (Usz)x0] = g; 29 | } 30 | 31 | ORCA_NOINLINE 32 | void gbuffer_copy_subrect(Glyph *src, Glyph *dest, Usz src_grid_h, 33 | Usz src_grid_w, Usz dest_grid_h, Usz dest_grid_w, 34 | Usz src_y, Usz src_x, Usz dest_y, Usz dest_x, 35 | Usz height, Usz width); 36 | 37 | ORCA_NOINLINE 38 | void gbuffer_fill_subrect(Glyph *gbuf, Usz grid_h, Usz grid_w, Usz y, Usz x, 39 | Usz height, Usz width, Glyph fill_char); 40 | 41 | typedef enum { 42 | Mark_flag_none = 0, 43 | Mark_flag_input = 1 << 0, 44 | Mark_flag_output = 1 << 1, 45 | Mark_flag_haste_input = 1 << 2, 46 | Mark_flag_lock = 1 << 3, 47 | Mark_flag_sleep = 1 << 4, 48 | } Mark_flags; 49 | 50 | ORCA_OK_IF_UNUSED 51 | static Mark_flags mbuffer_peek(Mark *mbuf, Usz height, Usz width, Usz y, 52 | Usz x) { 53 | (void)height; 54 | return mbuf[y * width + x]; 55 | } 56 | 57 | ORCA_OK_IF_UNUSED 58 | static Mark_flags mbuffer_peek_relative(Mark *mbuf, Usz height, Usz width, 59 | Usz y, Usz x, Isz offs_y, Isz offs_x) { 60 | Isz y0 = (Isz)y + offs_y; 61 | Isz x0 = (Isz)x + offs_x; 62 | if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) 63 | return Mark_flag_none; 64 | return mbuf[(Usz)y0 * width + (Usz)x0]; 65 | } 66 | 67 | ORCA_OK_IF_UNUSED 68 | static void mbuffer_poke_flags_or(Mark *mbuf, Usz height, Usz width, Usz y, 69 | Usz x, Mark_flags flags) { 70 | (void)height; 71 | mbuf[y * width + x] |= (Mark)flags; 72 | } 73 | 74 | ORCA_OK_IF_UNUSED 75 | static void mbuffer_poke_relative_flags_or(Mark *mbuf, Usz height, Usz width, 76 | Usz y, Usz x, Isz offs_y, Isz offs_x, 77 | Mark_flags flags) { 78 | Isz y0 = (Isz)y + offs_y; 79 | Isz x0 = (Isz)x + offs_x; 80 | if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) 81 | return; 82 | mbuf[(Usz)y0 * width + (Usz)x0] |= (Mark)flags; 83 | } 84 | 85 | void mbuffer_clear(Mark *mbuf, Usz height, Usz width); 86 | -------------------------------------------------------------------------------- /osc_out.c: -------------------------------------------------------------------------------- 1 | #include "osc_out.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct Oosc_dev { 11 | int fd; 12 | // Just keep the whole list around, since juggling the strict-aliasing 13 | // problems with sockaddr_storage is not worth it. 14 | struct addrinfo *chosen; 15 | struct addrinfo *head; 16 | }; 17 | 18 | Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev **out_ptr, 19 | char const *dest_addr, 20 | char const *dest_port) { 21 | struct addrinfo hints = {0}; 22 | hints.ai_family = AF_UNSPEC; 23 | hints.ai_socktype = SOCK_DGRAM; 24 | hints.ai_protocol = 0; 25 | hints.ai_flags = AI_ADDRCONFIG; 26 | struct addrinfo *chosen = NULL; 27 | struct addrinfo *head = NULL; 28 | int err = getaddrinfo(dest_addr, dest_port, &hints, &head); 29 | if (err != 0) { 30 | #if 0 31 | fprintf(stderr, "Failed to get address info, error: %d\n", errno); 32 | #endif 33 | return Oosc_udp_create_error_getaddrinfo_failed; 34 | } 35 | // Special behavior: if no hostname was provided, we'll get loopback(s) from 36 | // getaddrinfo. Which is good. But on systems with both an ipv4 and ipv6 37 | // address, we might get the ipv6 address listed first. And the OSC server, 38 | // for example Plogue Bidule, might not support ipv6. And defaulting to the 39 | // ipv6 loopback wouldn't work, in that case. So if there's no hostname, and 40 | // we find an ipv4 address in the results, prefer to use that. 41 | // 42 | // Actually let's just prefer ipv4 completely for now 43 | #if 0 44 | if (!dest_addr) { 45 | #endif 46 | { 47 | for (struct addrinfo *a = head; a; a = a->ai_next) { 48 | if (a->ai_family != AF_INET) 49 | continue; 50 | chosen = a; 51 | break; 52 | } 53 | } 54 | if (!chosen) 55 | chosen = head; 56 | #if 0 57 | for (struct addrinfo* a = head; a; a = a->ai_next) { 58 | char buff[INET6_ADDRSTRLEN]; 59 | char const* str = NULL; 60 | if (a->ai_family == AF_INET) { 61 | str = inet_ntop(AF_INET, &((struct sockaddr_in*)a->ai_addr)->sin_addr, 62 | buff, sizeof(buff)); 63 | } else if (a->ai_family == AF_INET6) { 64 | str = inet_ntop(AF_INET6, &((struct sockaddr_in6*)a->ai_addr)->sin6_addr, 65 | buff, sizeof(buff)); 66 | } 67 | if (str) { 68 | fprintf(stderr, "ntop name: %s\n", str); 69 | } else { 70 | fprintf(stderr, "inet_ntop error\n"); 71 | } 72 | } 73 | #endif 74 | int udpfd = 75 | socket(chosen->ai_family, chosen->ai_socktype, chosen->ai_protocol); 76 | if (udpfd < 0) { 77 | #if 0 78 | fprintf(stderr, "Failed to open UDP socket, error number: %d\n", errno); 79 | #endif 80 | freeaddrinfo(head); 81 | return Oosc_udp_create_error_couldnt_open_socket; 82 | } 83 | Oosc_dev *dev = malloc(sizeof(Oosc_dev)); 84 | dev->fd = udpfd; 85 | dev->chosen = chosen; 86 | dev->head = head; 87 | *out_ptr = dev; 88 | return Oosc_udp_create_error_ok; 89 | } 90 | 91 | void oosc_dev_destroy(Oosc_dev *dev) { 92 | close(dev->fd); 93 | freeaddrinfo(dev->head); 94 | free(dev); 95 | } 96 | 97 | void oosc_send_datagram(Oosc_dev *dev, char const *data, Usz size) { 98 | ssize_t res = sendto(dev->fd, data, size, 0, dev->chosen->ai_addr, 99 | dev->chosen->ai_addrlen); 100 | (void)res; 101 | // TODO handle this in UI somehow 102 | #if 0 103 | if (res < 0) { 104 | fprintf(stderr, "UDP message send failed\n"); 105 | exit(1); 106 | } 107 | #endif 108 | } 109 | 110 | static bool oosc_write_strn(char *restrict buffer, Usz buffer_size, 111 | Usz *buffer_pos, char const *restrict in_str, 112 | Usz in_str_len) { 113 | // no overflow check, should be fine 114 | Usz in_plus_null = in_str_len + 1; 115 | Usz null_pad = (4 - in_plus_null % 4) % 4; 116 | Usz needed = in_plus_null + null_pad; 117 | Usz cur_pos = *buffer_pos; 118 | if (cur_pos + needed >= buffer_size) 119 | return false; 120 | for (Usz i = 0; i < in_str_len; ++i) { 121 | buffer[cur_pos + i] = in_str[i]; 122 | } 123 | buffer[cur_pos + in_str_len] = 0; 124 | cur_pos += in_plus_null; 125 | for (Usz i = 0; i < null_pad; ++i) { 126 | buffer[cur_pos + i] = 0; 127 | } 128 | *buffer_pos = cur_pos + null_pad; 129 | return true; 130 | } 131 | 132 | void oosc_send_int32s(Oosc_dev *dev, char const *osc_address, I32 const *vals, 133 | Usz count) { 134 | char buffer[2048]; 135 | Usz buf_pos = 0; 136 | if (!oosc_write_strn(buffer, sizeof(buffer), &buf_pos, osc_address, 137 | strlen(osc_address))) 138 | return; 139 | Usz typetag_str_size = 1 + count + 1; // comma, 'i'... , null 140 | Usz typetag_str_null_pad = (4 - typetag_str_size % 4) % 4; 141 | if (buf_pos + typetag_str_size + typetag_str_null_pad > sizeof(buffer)) 142 | return; 143 | buffer[buf_pos] = ','; 144 | ++buf_pos; 145 | for (Usz i = 0; i < count; ++i) { 146 | buffer[buf_pos + i] = 'i'; 147 | } 148 | buffer[buf_pos + count] = 0; 149 | buf_pos += count + 1; 150 | for (Usz i = 0; i < typetag_str_null_pad; ++i) { 151 | buffer[buf_pos + i] = 0; 152 | } 153 | buf_pos += typetag_str_null_pad; 154 | Usz ints_size = count * sizeof(I32); 155 | if (buf_pos + ints_size > sizeof(buffer)) 156 | return; 157 | for (Usz i = 0; i < count; ++i) { 158 | union { 159 | I32 i; 160 | U32 u; 161 | } pun; 162 | pun.i = vals[i]; 163 | U32 u_ne = htonl(pun.u); 164 | memcpy(buffer + buf_pos, &u_ne, sizeof(u_ne)); 165 | buf_pos += sizeof(u_ne); 166 | } 167 | oosc_send_datagram(dev, buffer, buf_pos); 168 | } 169 | 170 | void susnote_list_init(Susnote_list *sl) { 171 | sl->buffer = NULL; 172 | sl->count = 0; 173 | sl->capacity = 0; 174 | } 175 | 176 | void susnote_list_deinit(Susnote_list *sl) { free(sl->buffer); } 177 | 178 | void susnote_list_clear(Susnote_list *sl) { sl->count = 0; } 179 | 180 | void susnote_list_add_notes(Susnote_list *sl, Susnote const *restrict notes, 181 | Usz added_count, Usz *restrict start_removed, 182 | Usz *restrict end_removed) { 183 | Susnote *buffer = sl->buffer; 184 | Usz count = sl->count; 185 | Usz cap = sl->capacity; 186 | Usz rem = count + added_count; 187 | Usz needed_cap = rem + added_count; 188 | if (cap < needed_cap) { 189 | cap = needed_cap < 16 ? 16 : orca_round_up_power2(needed_cap); 190 | buffer = realloc(buffer, cap * sizeof(Susnote)); 191 | sl->capacity = cap; 192 | sl->buffer = buffer; 193 | } 194 | *start_removed = rem; 195 | Usz i_in = 0; 196 | for (; i_in < added_count; ++i_in) { 197 | Susnote this_in = notes[i_in]; 198 | for (Usz i_old = 0; i_old < count; ++i_old) { 199 | Susnote this_old = buffer[i_old]; 200 | if (this_old.chan_note == this_in.chan_note) { 201 | buffer[i_old] = this_in; 202 | buffer[rem] = this_old; 203 | ++rem; 204 | goto next_in; 205 | } 206 | } 207 | buffer[count] = this_in; 208 | ++count; 209 | next_in:; 210 | } 211 | sl->count = count; 212 | *end_removed = rem; 213 | } 214 | 215 | void susnote_list_advance_time(Susnote_list *sl, double delta_time, 216 | Usz *restrict start_removed, 217 | Usz *restrict end_removed, 218 | double *soonest_deadline) { 219 | Susnote *restrict buffer = sl->buffer; 220 | Usz count = sl->count; 221 | *end_removed = count; 222 | float delta_float = (float)delta_time; 223 | float soonest = 1.0f; 224 | for (Usz i = 0; i < count;) { 225 | Susnote sn = buffer[i]; 226 | sn.remaining -= delta_float; 227 | if (sn.remaining > 0.001) { 228 | if (sn.remaining < soonest) 229 | soonest = sn.remaining; 230 | buffer[i].remaining = sn.remaining; 231 | ++i; 232 | } else { 233 | --count; 234 | buffer[i] = buffer[count]; 235 | buffer[count] = sn; 236 | } 237 | } 238 | *start_removed = count; 239 | *soonest_deadline = (double)soonest; 240 | sl->count = count; 241 | } 242 | 243 | void susnote_list_remove_by_chan_mask(Susnote_list *sl, Usz chan_mask, 244 | Usz *restrict start_removed, 245 | Usz *restrict end_removed) { 246 | Susnote *restrict buffer = sl->buffer; 247 | Usz count = sl->count; 248 | *end_removed = count; 249 | for (Usz i = 0; i < count;) { 250 | Susnote sn = buffer[i]; 251 | Usz chan = sn.chan_note >> 8; 252 | if (chan_mask & 1u << chan) { 253 | --count; 254 | buffer[i] = buffer[count]; 255 | buffer[count] = sn; 256 | } else { 257 | ++i; 258 | } 259 | } 260 | *start_removed = count; 261 | sl->count = count; 262 | } 263 | 264 | double susnote_list_soonest_deadline(Susnote_list const *sl) { 265 | float soonest = 1.0f; 266 | Susnote const *buffer = sl->buffer; 267 | for (Usz i = 0, n = sl->count; i < n; ++i) { 268 | float rem = buffer[i].remaining; 269 | if (rem < soonest) 270 | soonest = rem; 271 | } 272 | return (double)soonest; 273 | } 274 | -------------------------------------------------------------------------------- /osc_out.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base.h" 3 | 4 | typedef struct Oosc_dev Oosc_dev; 5 | 6 | typedef enum { 7 | Oosc_udp_create_error_ok = 0, 8 | Oosc_udp_create_error_getaddrinfo_failed = 1, 9 | Oosc_udp_create_error_couldnt_open_socket = 2, 10 | } Oosc_udp_create_error; 11 | 12 | Oosc_udp_create_error oosc_dev_create_udp(Oosc_dev **out_ptr, 13 | char const *dest_addr, 14 | char const *dest_port); 15 | void oosc_dev_destroy(Oosc_dev *dev); 16 | 17 | // Send a raw UDP datagram. 18 | void oosc_send_datagram(Oosc_dev *dev, char const *data, Usz size); 19 | 20 | // Send a list/array of 32-bit integers in OSC format to the specified "osc 21 | // address" (a path like /foo) as a UDP datagram. 22 | void oosc_send_int32s(Oosc_dev *dev, char const *osc_address, I32 const *vals, 23 | Usz count); 24 | 25 | // Susnote is for handling MIDI note sustains -- each MIDI on event should be 26 | // matched with a MIDI note-off event. The duration/sustain length of a MIDI 27 | // note is specified when it is first triggered, so the orca VM itself is not 28 | // responsible for sending the note-off event. We keep a list of currently 'on' 29 | // notes so that they can have a matching 'off' sent at the correct time. 30 | typedef struct { 31 | float remaining; 32 | U16 chan_note; 33 | } Susnote; 34 | 35 | typedef struct { 36 | Susnote *buffer; 37 | Usz count, capacity; 38 | } Susnote_list; 39 | 40 | void susnote_list_init(Susnote_list *sl); 41 | void susnote_list_deinit(Susnote_list *sl); 42 | void susnote_list_clear(Susnote_list *sl); 43 | void susnote_list_add_notes(Susnote_list *sl, Susnote const *restrict notes, 44 | Usz count, Usz *restrict start_removed, 45 | Usz *restrict end_removed); 46 | void susnote_list_advance_time( 47 | Susnote_list *sl, double delta_time, Usz *restrict start_removed, 48 | Usz *restrict end_removed, 49 | // 1.0 if no notes remain or none are shorter than 1.0 50 | double *soonest_deadline); 51 | void susnote_list_remove_by_chan_mask(Susnote_list *sl, Usz chan_mask, 52 | Usz *restrict start_removed, 53 | Usz *restrict end_removed); 54 | 55 | // Returns 1.0 if no notes remain or none are shorter than 1.0 56 | double susnote_list_soonest_deadline(Susnote_list const *sl); 57 | -------------------------------------------------------------------------------- /sim.c: -------------------------------------------------------------------------------- 1 | #include "sim.h" 2 | #include "gbuffer.h" 3 | 4 | //////// Utilities 5 | 6 | static Glyph const glyph_table[36] = { 7 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', // 0-11 8 | 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 12-23 9 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', // 24-35 10 | }; 11 | enum { Glyphs_index_count = sizeof glyph_table }; 12 | static inline Glyph glyph_of(Usz index) { 13 | assert(index < Glyphs_index_count); 14 | return glyph_table[index]; 15 | } 16 | 17 | static U8 const index_table[128] = { 18 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 20 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32-47 21 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // 48-63 22 | 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 64-79 23 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, // 80-95 24 | 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 96-111 25 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0}; // 112-127 26 | static ORCA_FORCEINLINE Usz index_of(Glyph c) { return index_table[c & 0x7f]; } 27 | 28 | // Reference implementation: 29 | // static Usz index_of(Glyph c) { 30 | // if (c >= '0' && c <= '9') return (Usz)(c - '0'); 31 | // if (c >= 'A' && c <= 'Z') return (Usz)(c - 'A' + 10); 32 | // if (c >= 'a' && c <= 'z') return (Usz)(c - 'a' + 10); 33 | // return 0; 34 | // } 35 | 36 | static ORCA_FORCEINLINE bool glyph_is_lowercase(Glyph g) { return g & 1 << 5; } 37 | static ORCA_FORCEINLINE Glyph glyph_lowered_unsafe(Glyph g) { 38 | return (Glyph)(g | 1 << 5); 39 | } 40 | static inline Glyph glyph_with_case(Glyph g, Glyph caser) { 41 | enum { Case_bit = 1 << 5, Alpha_bit = 1 << 6 }; 42 | return (Glyph)((g & ~Case_bit) | ((~g & Alpha_bit) >> 1) | 43 | (caser & Case_bit)); 44 | } 45 | 46 | static ORCA_PURE bool oper_has_neighboring_bang(Glyph const *gbuf, Usz h, Usz w, 47 | Usz y, Usz x) { 48 | Glyph const *gp = gbuf + w * y + x; 49 | if (x < w - 1 && gp[1] == '*') 50 | return true; 51 | if (x > 0 && *(gp - 1) == '*') 52 | return true; 53 | if (y < h - 1 && gp[w] == '*') 54 | return true; 55 | // note: negative array subscript on rhs of short-circuit, may cause ub if 56 | // the arithmetic under/overflows, even if guarded the guard on lhs is false 57 | if (y > 0 && *(gp - w) == '*') 58 | return true; 59 | return false; 60 | } 61 | 62 | // Returns UINT8_MAX if not a valid note. 63 | static U8 midi_note_number_of(Glyph g) { 64 | int sharp = (g & 1 << 5) >> 5; // sharp=1 if lowercase 65 | g &= (Glyph) ~(1 << 5); // make uppercase 66 | if (g < 'A' || g > 'Z') // A through Z only 67 | return UINT8_MAX; 68 | // We want C=0, D=1, E=2, etc. A and B are equivalent to H and I. 69 | int deg = g <= 'B' ? 'G' - 'B' + g - 'A' : g - 'C'; 70 | return (U8)(deg / 7 * 12 + (I8[]){0, 2, 4, 5, 7, 9, 11}[deg % 7] + sharp); 71 | } 72 | 73 | typedef struct { 74 | Glyph *vars_slots; 75 | Oevent_list *oevent_list; 76 | Usz random_seed; 77 | } Oper_extra_params; 78 | 79 | static void oper_poke_and_stun(Glyph *restrict gbuffer, Mark *restrict mbuffer, 80 | Usz height, Usz width, Usz y, Usz x, Isz delta_y, 81 | Isz delta_x, Glyph g) { 82 | Isz y0 = (Isz)y + delta_y; 83 | Isz x0 = (Isz)x + delta_x; 84 | if (y0 < 0 || x0 < 0 || (Usz)y0 >= height || (Usz)x0 >= width) 85 | return; 86 | Usz offs = (Usz)y0 * width + (Usz)x0; 87 | gbuffer[offs] = g; 88 | mbuffer[offs] |= Mark_flag_sleep; 89 | } 90 | 91 | // For anyone editing this in the future: the "no inline" here is deliberate. 92 | // You may think that inlining is always faster. Or even just letting the 93 | // compiler decide. You would be wrong. Try it. If you really want this VM to 94 | // run faster, you will need to use computed goto or assembly. 95 | #define OPER_FUNCTION_ATTRIBS ORCA_NOINLINE static void 96 | 97 | #define BEGIN_OPERATOR(_oper_name) \ 98 | OPER_FUNCTION_ATTRIBS oper_behavior_##_oper_name( \ 99 | Glyph *const restrict gbuffer, Mark *const restrict mbuffer, \ 100 | Usz const height, Usz const width, Usz const y, Usz const x, \ 101 | Usz Tick_number, Oper_extra_params *const extra_params, \ 102 | Mark const cell_flags, Glyph const This_oper_char) { \ 103 | (void)gbuffer; \ 104 | (void)mbuffer; \ 105 | (void)height; \ 106 | (void)width; \ 107 | (void)y; \ 108 | (void)x; \ 109 | (void)Tick_number; \ 110 | (void)extra_params; \ 111 | (void)cell_flags; \ 112 | (void)This_oper_char; 113 | 114 | #define END_OPERATOR } 115 | 116 | #define PEEK(_delta_y, _delta_x) \ 117 | gbuffer_peek_relative(gbuffer, height, width, y, x, _delta_y, _delta_x) 118 | #define POKE(_delta_y, _delta_x, _glyph) \ 119 | gbuffer_poke_relative(gbuffer, height, width, y, x, _delta_y, _delta_x, \ 120 | _glyph) 121 | #define STUN(_delta_y, _delta_x) \ 122 | mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ 123 | _delta_x, Mark_flag_sleep) 124 | #define POKE_STUNNED(_delta_y, _delta_x, _glyph) \ 125 | oper_poke_and_stun(gbuffer, mbuffer, height, width, y, x, _delta_y, \ 126 | _delta_x, _glyph) 127 | #define LOCK(_delta_y, _delta_x) \ 128 | mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ 129 | _delta_x, Mark_flag_lock) 130 | 131 | #define IN Mark_flag_input 132 | #define OUT Mark_flag_output 133 | #define NONLOCKING Mark_flag_lock 134 | #define PARAM Mark_flag_haste_input 135 | 136 | #define LOWERCASE_REQUIRES_BANG \ 137 | if (glyph_is_lowercase(This_oper_char) && \ 138 | !oper_has_neighboring_bang(gbuffer, height, width, y, x)) \ 139 | return 140 | 141 | #define STOP_IF_NOT_BANGED \ 142 | if (!oper_has_neighboring_bang(gbuffer, height, width, y, x)) \ 143 | return 144 | 145 | #define PORT(_delta_y, _delta_x, _flags) \ 146 | mbuffer_poke_relative_flags_or(mbuffer, height, width, y, x, _delta_y, \ 147 | _delta_x, (_flags) ^ Mark_flag_lock) 148 | //////// Operators 149 | 150 | #define UNIQUE_OPERATORS(_) \ 151 | _('!', midicc) \ 152 | _('#', comment) \ 153 | _('%', midi) \ 154 | _('*', bang) \ 155 | _(':', midi) \ 156 | _(';', udp) \ 157 | _('=', osc) \ 158 | _('?', midipb) 159 | 160 | #define ALPHA_OPERATORS(_) \ 161 | _('A', add) \ 162 | _('B', subtract) \ 163 | _('C', clock) \ 164 | _('D', delay) \ 165 | _('E', movement) \ 166 | _('F', if) \ 167 | _('G', generator) \ 168 | _('H', halt) \ 169 | _('I', increment) \ 170 | _('J', jump) \ 171 | _('K', konkat) \ 172 | _('L', lesser) \ 173 | _('M', multiply) \ 174 | _('N', movement) \ 175 | _('O', offset) \ 176 | _('P', push) \ 177 | _('Q', query) \ 178 | _('R', random) \ 179 | _('S', movement) \ 180 | _('T', track) \ 181 | _('U', uclid) \ 182 | _('V', variable) \ 183 | _('W', movement) \ 184 | _('X', teleport) \ 185 | _('Y', yump) \ 186 | _('Z', lerp) 187 | 188 | BEGIN_OPERATOR(movement) 189 | if (glyph_is_lowercase(This_oper_char) && 190 | !oper_has_neighboring_bang(gbuffer, height, width, y, x)) 191 | return; 192 | Isz delta_y, delta_x; 193 | switch (glyph_lowered_unsafe(This_oper_char)) { 194 | case 'n': 195 | delta_y = -1; 196 | delta_x = 0; 197 | break; 198 | case 'e': 199 | delta_y = 0; 200 | delta_x = 1; 201 | break; 202 | case 's': 203 | delta_y = 1; 204 | delta_x = 0; 205 | break; 206 | case 'w': 207 | delta_y = 0; 208 | delta_x = -1; 209 | break; 210 | default: 211 | // could cause strict aliasing problem, maybe 212 | delta_y = 0; 213 | delta_x = 0; 214 | break; 215 | } 216 | Isz y0 = (Isz)y + delta_y; 217 | Isz x0 = (Isz)x + delta_x; 218 | if (y0 >= (Isz)height || x0 >= (Isz)width || y0 < 0 || x0 < 0) { 219 | gbuffer[y * width + x] = '*'; 220 | return; 221 | } 222 | Glyph *restrict g_at_dest = gbuffer + (Usz)y0 * width + (Usz)x0; 223 | if (*g_at_dest == '.') { 224 | *g_at_dest = This_oper_char; 225 | gbuffer[y * width + x] = '.'; 226 | mbuffer[(Usz)y0 * width + (Usz)x0] |= Mark_flag_sleep; 227 | } else { 228 | gbuffer[y * width + x] = '*'; 229 | } 230 | END_OPERATOR 231 | 232 | BEGIN_OPERATOR(midicc) 233 | for (Usz i = 1; i < 4; ++i) { 234 | PORT(0, (Isz)i, IN); 235 | } 236 | STOP_IF_NOT_BANGED; 237 | Glyph channel_g = PEEK(0, 1); 238 | Glyph control_g = PEEK(0, 2); 239 | Glyph value_g = PEEK(0, 3); 240 | if (channel_g == '.' || control_g == '.') 241 | return; 242 | Usz channel = index_of(channel_g); 243 | if (channel > 15) 244 | return; 245 | PORT(0, 0, OUT); 246 | Oevent_midi_cc *oe = 247 | (Oevent_midi_cc *)oevent_list_alloc_item(extra_params->oevent_list); 248 | oe->oevent_type = Oevent_type_midi_cc; 249 | oe->channel = (U8)channel; 250 | oe->control = (U8)index_of(control_g); 251 | oe->value = (U8)(index_of(value_g) * 127 / 35); // 0~35 -> 0~127 252 | END_OPERATOR 253 | 254 | BEGIN_OPERATOR(comment) 255 | // restrict probably ok here... 256 | Glyph const *restrict gline = gbuffer + y * width; 257 | Mark *restrict mline = mbuffer + y * width; 258 | Usz max_x = x + 255; 259 | if (width < max_x) 260 | max_x = width; 261 | for (Usz x0 = x + 1; x0 < max_x; ++x0) { 262 | Glyph g = gline[x0]; 263 | mline[x0] |= (Mark)Mark_flag_lock; 264 | if (g == '#') 265 | break; 266 | } 267 | END_OPERATOR 268 | 269 | BEGIN_OPERATOR(bang) 270 | gbuffer_poke(gbuffer, height, width, y, x, '.'); 271 | END_OPERATOR 272 | 273 | BEGIN_OPERATOR(midi) 274 | for (Usz i = 1; i < 6; ++i) { 275 | PORT(0, (Isz)i, IN); 276 | } 277 | STOP_IF_NOT_BANGED; 278 | Glyph channel_g = PEEK(0, 1); 279 | Glyph octave_g = PEEK(0, 2); 280 | Glyph note_g = PEEK(0, 3); 281 | Glyph velocity_g = PEEK(0, 4); 282 | Glyph length_g = PEEK(0, 5); 283 | U8 octave_num = (U8)index_of(octave_g); 284 | if (octave_g == '.') 285 | return; 286 | if (octave_num > 9) 287 | octave_num = 9; 288 | U8 note_num = midi_note_number_of(note_g); 289 | if (note_num == UINT8_MAX) 290 | return; 291 | Usz channel_num = index_of(channel_g); 292 | if (channel_num > 15) 293 | channel_num = 15; 294 | Usz vel_num; 295 | if (velocity_g == '.') { 296 | // If no velocity is specified, set it to full. 297 | vel_num = 127; 298 | } else { 299 | vel_num = index_of(velocity_g); 300 | // MIDI notes with velocity zero are actually note-offs. (MIDI has two ways 301 | // to send note offs. Zero-velocity is the alternate way.) If there is a zero 302 | // velocity, we'll just not do anything. 303 | if (vel_num == 0) 304 | return; 305 | vel_num = vel_num * 8 - 1; // 1~16 -> 7~127 306 | if (vel_num > 127) 307 | vel_num = 127; 308 | } 309 | PORT(0, 0, OUT); 310 | Oevent_midi_note *oe = 311 | (Oevent_midi_note *)oevent_list_alloc_item(extra_params->oevent_list); 312 | oe->oevent_type = (U8)Oevent_type_midi_note; 313 | oe->channel = (U8)channel_num; 314 | oe->octave = octave_num; 315 | oe->note = note_num; 316 | oe->velocity = (U8)vel_num; 317 | // Mask used here to suppress bad GCC Wconversion for bitfield. This is bad 318 | // -- we should do something smarter than this. 319 | oe->duration = (U8)(index_of(length_g) & 0x7Fu); 320 | oe->mono = This_oper_char == '%' ? 1 : 0; 321 | END_OPERATOR 322 | 323 | BEGIN_OPERATOR(udp) 324 | Usz n = width - x - 1; 325 | if (n > 16) 326 | n = 16; 327 | Glyph const *restrict gline = gbuffer + y * width + x + 1; 328 | Mark *restrict mline = mbuffer + y * width + x + 1; 329 | Glyph cpy[Oevent_udp_string_count]; 330 | Usz i; 331 | for (i = 0; i < n; ++i) { 332 | Glyph g = gline[i]; 333 | if (g == '.') 334 | break; 335 | cpy[i] = g; 336 | mline[i] |= Mark_flag_lock; 337 | } 338 | n = i; 339 | STOP_IF_NOT_BANGED; 340 | PORT(0, 0, OUT); 341 | Oevent_udp_string *oe = 342 | (Oevent_udp_string *)oevent_list_alloc_item(extra_params->oevent_list); 343 | oe->oevent_type = (U8)Oevent_type_udp_string; 344 | oe->count = (U8)n; 345 | for (i = 0; i < n; ++i) { 346 | oe->chars[i] = cpy[i]; 347 | } 348 | END_OPERATOR 349 | 350 | BEGIN_OPERATOR(osc) 351 | PORT(0, 1, IN | PARAM); 352 | PORT(0, 2, IN | PARAM); 353 | Usz len = index_of(PEEK(0, 2)); 354 | if (len > Oevent_osc_int_count) 355 | len = Oevent_osc_int_count; 356 | for (Usz i = 0; i < len; ++i) { 357 | PORT(0, (Isz)i + 3, IN); 358 | } 359 | STOP_IF_NOT_BANGED; 360 | Glyph g = PEEK(0, 1); 361 | if (g != '.') { 362 | PORT(0, 0, OUT); 363 | U8 buff[Oevent_osc_int_count]; 364 | for (Usz i = 0; i < len; ++i) { 365 | buff[i] = (U8)index_of(PEEK(0, (Isz)i + 3)); 366 | } 367 | Oevent_osc_ints *oe = 368 | &oevent_list_alloc_item(extra_params->oevent_list)->osc_ints; 369 | oe->oevent_type = (U8)Oevent_type_osc_ints; 370 | oe->glyph = g; 371 | oe->count = (U8)len; 372 | for (Usz i = 0; i < len; ++i) { 373 | oe->numbers[i] = buff[i]; 374 | } 375 | } 376 | END_OPERATOR 377 | 378 | BEGIN_OPERATOR(midipb) 379 | for (Usz i = 1; i < 4; ++i) { 380 | PORT(0, (Isz)i, IN); 381 | } 382 | STOP_IF_NOT_BANGED; 383 | Glyph channel_g = PEEK(0, 1); 384 | Glyph msb_g = PEEK(0, 2); 385 | Glyph lsb_g = PEEK(0, 3); 386 | if (channel_g == '.') 387 | return; 388 | Usz channel = index_of(channel_g); 389 | if (channel > 15) 390 | return; 391 | PORT(0, 0, OUT); 392 | Oevent_midi_pb *oe = 393 | (Oevent_midi_pb *)oevent_list_alloc_item(extra_params->oevent_list); 394 | oe->oevent_type = Oevent_type_midi_pb; 395 | oe->channel = (U8)channel; 396 | oe->msb = (U8)(index_of(msb_g) * 127 / 35); // 0~35 -> 0~127 397 | oe->lsb = (U8)(index_of(lsb_g) * 127 / 35); 398 | END_OPERATOR 399 | 400 | BEGIN_OPERATOR(add) 401 | LOWERCASE_REQUIRES_BANG; 402 | PORT(0, -1, IN | PARAM); 403 | PORT(0, 1, IN); 404 | PORT(1, 0, OUT); 405 | Glyph a = PEEK(0, -1); 406 | Glyph b = PEEK(0, 1); 407 | Glyph g = glyph_table[(index_of(a) + index_of(b)) % Glyphs_index_count]; 408 | POKE(1, 0, glyph_with_case(g, b)); 409 | END_OPERATOR 410 | 411 | BEGIN_OPERATOR(subtract) 412 | LOWERCASE_REQUIRES_BANG; 413 | PORT(0, -1, IN | PARAM); 414 | PORT(0, 1, IN); 415 | PORT(1, 0, OUT); 416 | Glyph a = PEEK(0, -1); 417 | Glyph b = PEEK(0, 1); 418 | Isz val = (Isz)index_of(b) - (Isz)index_of(a); 419 | if (val < 0) 420 | val = -val; 421 | POKE(1, 0, glyph_with_case(glyph_of((Usz)val), b)); 422 | END_OPERATOR 423 | 424 | BEGIN_OPERATOR(clock) 425 | LOWERCASE_REQUIRES_BANG; 426 | PORT(0, -1, IN | PARAM); 427 | PORT(0, 1, IN); 428 | PORT(1, 0, OUT); 429 | Glyph b = PEEK(0, 1); 430 | Usz rate = index_of(PEEK(0, -1)); 431 | Usz mod_num = index_of(b); 432 | if (rate == 0) 433 | rate = 1; 434 | if (mod_num == 0) 435 | mod_num = 8; 436 | Glyph g = glyph_of(Tick_number / rate % mod_num); 437 | POKE(1, 0, glyph_with_case(g, b)); 438 | END_OPERATOR 439 | 440 | BEGIN_OPERATOR(delay) 441 | LOWERCASE_REQUIRES_BANG; 442 | PORT(0, -1, IN | PARAM); 443 | PORT(0, 1, IN); 444 | PORT(1, 0, OUT); 445 | Usz rate = index_of(PEEK(0, -1)); 446 | Usz mod_num = index_of(PEEK(0, 1)); 447 | if (rate == 0) 448 | rate = 1; 449 | if (mod_num == 0) 450 | mod_num = 8; 451 | Glyph g = Tick_number % (rate * mod_num) == 0 ? '*' : '.'; 452 | POKE(1, 0, g); 453 | END_OPERATOR 454 | 455 | BEGIN_OPERATOR(if) 456 | LOWERCASE_REQUIRES_BANG; 457 | PORT(0, -1, IN | PARAM); 458 | PORT(0, 1, IN); 459 | PORT(1, 0, OUT); 460 | Glyph g0 = PEEK(0, -1); 461 | Glyph g1 = PEEK(0, 1); 462 | POKE(1, 0, g0 == g1 ? '*' : '.'); 463 | END_OPERATOR 464 | 465 | BEGIN_OPERATOR(generator) 466 | LOWERCASE_REQUIRES_BANG; 467 | Isz out_x = (Isz)index_of(PEEK(0, -3)); 468 | Isz out_y = (Isz)index_of(PEEK(0, -2)) + 1; 469 | Isz len = (Isz)index_of(PEEK(0, -1)); 470 | PORT(0, -3, IN | PARAM); // x 471 | PORT(0, -2, IN | PARAM); // y 472 | PORT(0, -1, IN | PARAM); // len 473 | for (Isz i = 0; i < len; ++i) { 474 | PORT(0, i + 1, IN); 475 | PORT(out_y, out_x + i, OUT | NONLOCKING); 476 | Glyph g = PEEK(0, i + 1); 477 | POKE_STUNNED(out_y, out_x + i, g); 478 | } 479 | END_OPERATOR 480 | 481 | BEGIN_OPERATOR(halt) 482 | LOWERCASE_REQUIRES_BANG; 483 | PORT(1, 0, IN | PARAM); 484 | END_OPERATOR 485 | 486 | BEGIN_OPERATOR(increment) 487 | LOWERCASE_REQUIRES_BANG; 488 | PORT(0, -1, IN | PARAM); 489 | PORT(0, 1, IN); 490 | PORT(1, 0, IN | OUT); 491 | Glyph ga = PEEK(0, -1); 492 | Glyph gb = PEEK(0, 1); 493 | Usz rate = 1; 494 | if (ga != '.' && ga != '*') 495 | rate = index_of(ga); 496 | Usz max = index_of(gb); 497 | Usz val = index_of(PEEK(1, 0)); 498 | if (max == 0) 499 | max = 36; 500 | val = val + rate; 501 | val = val % max; 502 | POKE(1, 0, glyph_with_case(glyph_of(val), gb)); 503 | END_OPERATOR 504 | 505 | BEGIN_OPERATOR(jump) 506 | LOWERCASE_REQUIRES_BANG; 507 | Glyph g = PEEK(-1, 0); 508 | if (g == 'J') 509 | return; 510 | PORT(-1, 0, IN); 511 | for (Isz i = 1; i <= 256; ++i) { 512 | if (PEEK(i, 0) != This_oper_char) { 513 | PORT(i, 0, OUT); 514 | POKE(i, 0, g); 515 | break; 516 | } 517 | STUN(i, 0); 518 | } 519 | END_OPERATOR 520 | 521 | // Note: this is merged from a pull request without being fully tested or 522 | // optimized 523 | BEGIN_OPERATOR(konkat) 524 | LOWERCASE_REQUIRES_BANG; 525 | Isz len = (Isz)index_of(PEEK(0, -1)); 526 | if (len == 0) 527 | len = 1; 528 | PORT(0, -1, IN | PARAM); 529 | for (Isz i = 0; i < len; ++i) { 530 | PORT(0, i + 1, IN); 531 | Glyph var = PEEK(0, i + 1); 532 | if (var != '.') { 533 | Usz var_idx = index_of(var); 534 | Glyph result = extra_params->vars_slots[var_idx]; 535 | PORT(1, i + 1, OUT); 536 | POKE(1, i + 1, result); 537 | } 538 | } 539 | END_OPERATOR 540 | 541 | BEGIN_OPERATOR(lesser) 542 | LOWERCASE_REQUIRES_BANG; 543 | PORT(0, -1, IN | PARAM); 544 | PORT(0, 1, IN); 545 | PORT(1, 0, OUT); 546 | Glyph ga = PEEK(0, -1); 547 | Glyph gb = PEEK(0, 1); 548 | if (ga == '.' || gb == '.') { 549 | POKE(1, 0, '.'); 550 | } else { 551 | Usz ia = index_of(ga); 552 | Usz ib = index_of(gb); 553 | Usz out = ia < ib ? ia : ib; 554 | POKE(1, 0, glyph_with_case(glyph_of(out), gb)); 555 | } 556 | END_OPERATOR 557 | 558 | BEGIN_OPERATOR(multiply) 559 | LOWERCASE_REQUIRES_BANG; 560 | PORT(0, -1, IN | PARAM); 561 | PORT(0, 1, IN); 562 | PORT(1, 0, OUT); 563 | Glyph a = PEEK(0, -1); 564 | Glyph b = PEEK(0, 1); 565 | Glyph g = glyph_table[(index_of(a) * index_of(b)) % Glyphs_index_count]; 566 | POKE(1, 0, glyph_with_case(g, b)); 567 | END_OPERATOR 568 | 569 | BEGIN_OPERATOR(offset) 570 | LOWERCASE_REQUIRES_BANG; 571 | Isz in_x = (Isz)index_of(PEEK(0, -2)) + 1; 572 | Isz in_y = (Isz)index_of(PEEK(0, -1)); 573 | PORT(0, -1, IN | PARAM); 574 | PORT(0, -2, IN | PARAM); 575 | PORT(in_y, in_x, IN); 576 | PORT(1, 0, OUT); 577 | POKE(1, 0, PEEK(in_y, in_x)); 578 | END_OPERATOR 579 | 580 | BEGIN_OPERATOR(push) 581 | LOWERCASE_REQUIRES_BANG; 582 | Usz key = index_of(PEEK(0, -2)); 583 | Usz len = index_of(PEEK(0, -1)); 584 | PORT(0, -1, IN | PARAM); 585 | PORT(0, -2, IN | PARAM); 586 | PORT(0, 1, IN); 587 | if (len == 0) 588 | return; 589 | Isz out_x = (Isz)(key % len); 590 | for (Usz i = 0; i < len; ++i) { 591 | LOCK(1, (Isz)i); 592 | } 593 | PORT(1, out_x, OUT); 594 | POKE(1, out_x, PEEK(0, 1)); 595 | END_OPERATOR 596 | 597 | BEGIN_OPERATOR(query) 598 | LOWERCASE_REQUIRES_BANG; 599 | Isz in_x = (Isz)index_of(PEEK(0, -3)) + 1; 600 | Isz in_y = (Isz)index_of(PEEK(0, -2)); 601 | Isz len = (Isz)index_of(PEEK(0, -1)); 602 | Isz out_x = 1 - len; 603 | PORT(0, -3, IN | PARAM); // x 604 | PORT(0, -2, IN | PARAM); // y 605 | PORT(0, -1, IN | PARAM); // len 606 | // todo direct buffer manip 607 | for (Isz i = 0; i < len; ++i) { 608 | PORT(in_y, in_x + i, IN); 609 | PORT(1, out_x + i, OUT); 610 | Glyph g = PEEK(in_y, in_x + i); 611 | POKE(1, out_x + i, g); 612 | } 613 | END_OPERATOR 614 | 615 | BEGIN_OPERATOR(random) 616 | LOWERCASE_REQUIRES_BANG; 617 | PORT(0, -1, IN | PARAM); 618 | PORT(0, 1, IN); 619 | PORT(1, 0, OUT); 620 | Glyph gb = PEEK(0, 1); 621 | Usz a = index_of(PEEK(0, -1)); 622 | Usz b = index_of(gb); 623 | if (b == 0) 624 | b = 36; 625 | Usz min, max; 626 | if (a == b) { 627 | POKE(1, 0, glyph_of(a)); 628 | return; 629 | } else if (a < b) { 630 | min = a; 631 | max = b; 632 | } else { 633 | min = b; 634 | max = a; 635 | } 636 | // Initial input params for the hash 637 | Usz key = (extra_params->random_seed + y * width + x) ^ 638 | (Tick_number << UINT32_C(16)); 639 | // 32-bit shift_mult hash to evenly distribute bits 640 | key = (key ^ UINT32_C(61)) ^ (key >> UINT32_C(16)); 641 | key = key + (key << UINT32_C(3)); 642 | key = key ^ (key >> UINT32_C(4)); 643 | key = key * UINT32_C(0x27d4eb2d); 644 | key = key ^ (key >> UINT32_C(15)); 645 | // Hash finished. Restrict to desired range of numbers. 646 | Usz val = key % (max - min) + min; 647 | POKE(1, 0, glyph_with_case(glyph_of(val), gb)); 648 | END_OPERATOR 649 | 650 | BEGIN_OPERATOR(track) 651 | LOWERCASE_REQUIRES_BANG; 652 | Usz key = index_of(PEEK(0, -2)); 653 | Usz len = index_of(PEEK(0, -1)); 654 | PORT(0, -2, IN | PARAM); 655 | PORT(0, -1, IN | PARAM); 656 | if (len == 0) 657 | return; 658 | Isz read_val_x = (Isz)(key % len) + 1; 659 | for (Usz i = 0; i < len; ++i) { 660 | LOCK(0, (Isz)(i + 1)); 661 | } 662 | PORT(0, (Isz)read_val_x, IN); 663 | PORT(1, 0, OUT); 664 | POKE(1, 0, PEEK(0, read_val_x)); 665 | END_OPERATOR 666 | 667 | // https://www.computermusicdesign.com/ 668 | // simplest-euclidean-rhythm-algorithm-explained/ 669 | BEGIN_OPERATOR(uclid) 670 | LOWERCASE_REQUIRES_BANG; 671 | PORT(0, -1, IN | PARAM); 672 | PORT(0, 1, IN); 673 | PORT(1, 0, OUT); 674 | Glyph left = PEEK(0, -1); 675 | Usz steps = 1; 676 | if (left != '.' && left != '*') 677 | steps = index_of(left); 678 | Usz max = index_of(PEEK(0, 1)); 679 | if (max == 0) 680 | max = 8; 681 | Usz bucket = (steps * (Tick_number + max - 1)) % max + steps; 682 | Glyph g = (bucket >= max) ? '*' : '.'; 683 | POKE(1, 0, g); 684 | END_OPERATOR 685 | 686 | BEGIN_OPERATOR(variable) 687 | LOWERCASE_REQUIRES_BANG; 688 | PORT(0, -1, IN | PARAM); 689 | PORT(0, 1, IN); 690 | Glyph left = PEEK(0, -1); 691 | Glyph right = PEEK(0, 1); 692 | if (left != '.') { 693 | // Write 694 | Usz var_idx = index_of(left); 695 | extra_params->vars_slots[var_idx] = right; 696 | } else if (right != '.') { 697 | // Read 698 | PORT(1, 0, OUT); 699 | Usz var_idx = index_of(right); 700 | Glyph result = extra_params->vars_slots[var_idx]; 701 | POKE(1, 0, result); 702 | } 703 | END_OPERATOR 704 | 705 | BEGIN_OPERATOR(teleport) 706 | LOWERCASE_REQUIRES_BANG; 707 | Isz out_x = (Isz)index_of(PEEK(0, -2)); 708 | Isz out_y = (Isz)index_of(PEEK(0, -1)) + 1; 709 | PORT(0, -2, IN | PARAM); // x 710 | PORT(0, -1, IN | PARAM); // y 711 | PORT(0, 1, IN); 712 | PORT(out_y, out_x, OUT | NONLOCKING); 713 | POKE_STUNNED(out_y, out_x, PEEK(0, 1)); 714 | END_OPERATOR 715 | 716 | BEGIN_OPERATOR(yump) 717 | LOWERCASE_REQUIRES_BANG; 718 | Glyph g = PEEK(0, -1); 719 | if (g == 'Y') 720 | return; 721 | PORT(0, -1, IN); 722 | for (Isz i = 1; i <= 256; ++i) { 723 | if (PEEK(0, i) != This_oper_char) { 724 | PORT(0, i, OUT); 725 | POKE(0, i, g); 726 | break; 727 | } 728 | STUN(0, i); 729 | } 730 | END_OPERATOR 731 | 732 | BEGIN_OPERATOR(lerp) 733 | LOWERCASE_REQUIRES_BANG; 734 | PORT(0, -1, IN | PARAM); 735 | PORT(0, 1, IN); 736 | PORT(1, 0, IN | OUT); 737 | Glyph g = PEEK(0, -1); 738 | Glyph b = PEEK(0, 1); 739 | Isz rate = g == '.' || g == '*' ? 1 : (Isz)index_of(g); 740 | Isz goal = (Isz)index_of(b); 741 | Isz val = (Isz)index_of(PEEK(1, 0)); 742 | Isz mod = val <= goal - rate ? rate : val >= goal + rate ? -rate : goal - val; 743 | POKE(1, 0, glyph_with_case(glyph_of((Usz)(val + mod)), b)); 744 | END_OPERATOR 745 | 746 | //////// Run simulation 747 | 748 | void orca_run(Glyph *restrict gbuf, Mark *restrict mbuf, Usz height, Usz width, 749 | Usz tick_number, Oevent_list *oevent_list, Usz random_seed) { 750 | Glyph vars_slots[Glyphs_index_count]; 751 | memset(vars_slots, '.', sizeof(vars_slots)); 752 | Oper_extra_params extras; 753 | extras.vars_slots = &vars_slots[0]; 754 | extras.oevent_list = oevent_list; 755 | extras.random_seed = random_seed; 756 | 757 | for (Usz iy = 0; iy < height; ++iy) { 758 | Glyph const *glyph_row = gbuf + iy * width; 759 | Mark const *mark_row = mbuf + iy * width; 760 | for (Usz ix = 0; ix < width; ++ix) { 761 | Glyph glyph_char = glyph_row[ix]; 762 | if (ORCA_LIKELY(glyph_char == '.')) 763 | continue; 764 | Mark cell_flags = mark_row[ix] & (Mark_flag_lock | Mark_flag_sleep); 765 | if (cell_flags & (Mark_flag_lock | Mark_flag_sleep)) 766 | continue; 767 | switch (glyph_char) { 768 | #define UNIQUE_CASE(_oper_char, _oper_name) \ 769 | case _oper_char: \ 770 | oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, tick_number, \ 771 | &extras, cell_flags, glyph_char); \ 772 | break; 773 | 774 | #define ALPHA_CASE(_upper_oper_char, _oper_name) \ 775 | case _upper_oper_char: \ 776 | case (char)(_upper_oper_char | 1 << 5): \ 777 | oper_behavior_##_oper_name(gbuf, mbuf, height, width, iy, ix, tick_number, \ 778 | &extras, cell_flags, glyph_char); \ 779 | break; 780 | UNIQUE_OPERATORS(UNIQUE_CASE) 781 | ALPHA_OPERATORS(ALPHA_CASE) 782 | #undef UNIQUE_CASE 783 | #undef ALPHA_CASE 784 | } 785 | } 786 | } 787 | } 788 | -------------------------------------------------------------------------------- /sim.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base.h" 3 | #include "vmio.h" 4 | 5 | void orca_run(Glyph *restrict gbuffer, Mark *restrict mbuffer, Usz height, 6 | Usz width, Usz tick_number, Oevent_list *oevent_list, 7 | Usz random_seed); 8 | -------------------------------------------------------------------------------- /sysmisc.c: -------------------------------------------------------------------------------- 1 | #include "sysmisc.h" 2 | #include "gbuffer.h" 3 | #include "oso.h" 4 | #include 5 | #include 6 | #include 7 | 8 | static char const *const xdg_config_home_env = "XDG_CONFIG_HOME"; 9 | static char const *const home_env = "HOME"; 10 | 11 | void expand_home_tilde(oso **path) { 12 | oso *s = *path; 13 | size_t n = osolen(s); 14 | if (n < 2) 15 | return; 16 | if (osoc(s)[0] != '~' || osoc(s)[1] != '/') 17 | return; 18 | char const *homedir = getenv(home_env); 19 | if (!homedir) 20 | return; 21 | size_t hlen = strlen(homedir); 22 | osoensurecap(&s, n + hlen - 1); 23 | if (!s) 24 | goto done; 25 | memmove((char *)s + hlen, (char *)s + 1, n); // includes '\0' 26 | memcpy((char *)s, homedir, hlen); 27 | osopokelen(s, n + hlen - 1); 28 | done: 29 | *path = s; 30 | } 31 | 32 | ORCA_NOINLINE 33 | Cboard_error cboard_copy(Glyph const *gbuffer, Usz field_height, 34 | Usz field_width, Usz rect_y, Usz rect_x, Usz rect_h, 35 | Usz rect_w) { 36 | (void)field_height; 37 | FILE *fp = 38 | #ifdef ORCA_OS_MAC 39 | popen("pbcopy -pboard general 2>/dev/null", "w"); 40 | #else 41 | popen("xclip -i -selection clipboard 2>/dev/null", "w"); 42 | #endif 43 | if (!fp) 44 | return Cboard_error_popen_failed; 45 | for (Usz iy = 0; iy < rect_h; iy++) { 46 | Glyph const *row = gbuffer + (rect_y + iy) * field_width + rect_x; 47 | fwrite(row, sizeof(Glyph), rect_w, fp); 48 | if (iy + 1 < rect_h) 49 | fputc('\n', fp); 50 | } 51 | int status = pclose(fp); 52 | return status ? Cboard_error_process_exit_error : Cboard_error_none; 53 | } 54 | 55 | ORCA_NOINLINE 56 | Cboard_error cboard_paste(Glyph *gbuffer, Usz height, Usz width, Usz y, Usz x, 57 | Usz *out_h, Usz *out_w) { 58 | FILE *fp = 59 | #ifdef ORCA_OS_MAC 60 | popen("pbpaste -pboard general -Prefer txt 2>/dev/null", "r"); 61 | #else 62 | popen("xclip -o -selection clipboard 2>/dev/null", "r"); 63 | #endif 64 | Usz start_y = y, start_x = x, max_y = y, max_x = x; 65 | if (!fp) 66 | return Cboard_error_popen_failed; 67 | char inbuff[512]; 68 | for (;;) { 69 | size_t n = fread(inbuff, 1, sizeof inbuff, fp); 70 | for (size_t i = 0; i < n; i++) { 71 | char c = inbuff[i]; 72 | if (c == '\r' || c == '\n') { 73 | y++; 74 | x = start_x; 75 | continue; 76 | } 77 | if (c != ' ' && y < height && x < width) { 78 | Glyph g = orca_is_valid_glyph(c) ? (Glyph)c : '.'; 79 | gbuffer_poke(gbuffer, height, width, y, x, g); 80 | if (x > max_x) 81 | max_x = x; 82 | if (y > max_y) 83 | max_y = y; 84 | } 85 | x++; 86 | } 87 | if (n < sizeof inbuff) 88 | break; 89 | } 90 | int status = pclose(fp); 91 | *out_h = max_y - start_y + 1; 92 | *out_w = max_x - start_x + 1; 93 | return status ? Cboard_error_process_exit_error : Cboard_error_none; 94 | } 95 | 96 | ORCA_NOINLINE 97 | Conf_read_result conf_read_line(FILE *file, char *buf, Usz bufsize, 98 | char **out_left, Usz *out_leftsize, 99 | char **out_right, Usz *out_rightsize) { 100 | // a0 and a1 are the start and end positions of the left side of an "foo=bar" 101 | // pair. b0 and b1 are the positions right side. Leading and trailing spaces 102 | // will be removed. 103 | Usz len, a0, a1, b0, b1; 104 | char *s; 105 | if (bufsize < 2) 106 | goto insufficient_buffer; 107 | #if SIZE_MAX > INT_MAX 108 | if (bufsize > (Usz)INT_MAX) 109 | exit(1); // he boot too big 110 | #endif 111 | s = fgets(buf, (int)bufsize, file); 112 | if (!s) { 113 | if (feof(file)) 114 | goto eof; 115 | goto ioerror; 116 | } 117 | len = strlen(buf); 118 | if (len == bufsize - 1 && buf[len - 1] != '\n' && !feof(file)) 119 | goto insufficient_buffer; 120 | a0 = 0; 121 | for (;;) { // scan for first non-space in " foo=bar" 122 | if (a0 == len) 123 | goto ignore; 124 | char c = s[a0]; 125 | if (c == ';' || c == '#') // comment line, ignore 126 | goto ignore; 127 | if (c == '=') // '=' before any other char, bad 128 | goto ignore; 129 | if (!isspace(c)) 130 | break; 131 | a0++; 132 | } 133 | a1 = a0; 134 | for (;;) { // scan for '=' 135 | a1++; 136 | if (a1 == len) 137 | goto ignore; 138 | char c = s[a1]; 139 | Usz x = a1; // don't include any whitespace preceeding the '=' 140 | while (isspace(c)) { 141 | x++; 142 | if (x == len) 143 | goto ignore; 144 | c = s[x]; 145 | } 146 | if (c == '=') { 147 | b0 = x; 148 | break; 149 | } 150 | a1 = x; 151 | } 152 | for (;;) { // scan for first non-whitespace after '=' 153 | b0++; 154 | if (b0 == len) { // empty right side, but still valid pair 155 | b1 = b0; 156 | goto ok; 157 | } 158 | char c = s[b0]; 159 | if (!isspace(c)) 160 | break; 161 | } 162 | b1 = b0; 163 | for (;;) { // scan for end of useful stuff for right-side value 164 | b1++; 165 | if (b1 == len) 166 | goto ok; 167 | char c = s[b1]; 168 | Usz x = b1; // don't include any whitespace preceeding the EOL 169 | while (isspace(c)) { 170 | x++; 171 | if (x == len) 172 | goto ok; 173 | c = s[x]; 174 | } 175 | b1 = x; 176 | } 177 | Conf_read_result err; 178 | insufficient_buffer: 179 | err = Conf_read_buffer_too_small; 180 | goto fail; 181 | eof: 182 | err = Conf_read_eof; 183 | goto fail; 184 | ioerror: 185 | err = Conf_read_io_error; 186 | goto fail; 187 | fail: 188 | *out_left = NULL; 189 | *out_leftsize = 0; 190 | goto null_right; 191 | ignore: 192 | s[len - 1] = '\0'; 193 | *out_left = s; 194 | *out_leftsize = len; 195 | err = Conf_read_irrelevant; 196 | goto null_right; 197 | null_right: 198 | *out_right = NULL; 199 | *out_rightsize = 0; 200 | return err; 201 | ok: 202 | s[a1] = '\0'; 203 | s[b1] = '\0'; 204 | *out_left = s + a0; 205 | *out_leftsize = a1 - a0; 206 | *out_right = s + b0; 207 | *out_rightsize = b1 - b0; 208 | return Conf_read_left_and_right; 209 | } 210 | 211 | bool conf_read_match(FILE **pfile, char const *const *names, Usz nameslen, 212 | char *buf, Usz bufsize, Usz *out_index, char **out_value) { 213 | FILE *file = *pfile; 214 | if (!file) 215 | return false; 216 | char *left; 217 | Usz leftsz, rightsz; 218 | next_line:; 219 | Conf_read_result res = 220 | conf_read_line(file, buf, bufsize, &left, &leftsz, out_value, &rightsz); 221 | switch (res) { 222 | case Conf_read_left_and_right: 223 | for (Usz i = 0; i < nameslen; i++) { 224 | if (strcmp(names[i], left) == 0) { 225 | *out_index = i; 226 | return true; 227 | } 228 | } 229 | goto next_line; 230 | case Conf_read_irrelevant: 231 | goto next_line; 232 | case Conf_read_buffer_too_small: 233 | case Conf_read_eof: 234 | case Conf_read_io_error: 235 | break; 236 | } 237 | fclose(file); 238 | *pfile = NULL; 239 | return false; 240 | } 241 | 242 | typedef enum { 243 | Conf_dir_ok = 0, 244 | Conf_dir_no_home, 245 | } Conf_dir_error; 246 | 247 | static Conf_dir_error try_get_conf_dir(oso **out) { 248 | char const *xdgcfgdir = getenv(xdg_config_home_env); 249 | if (xdgcfgdir) { 250 | Usz xdgcfgdirlen = strlen(xdgcfgdir); 251 | if (xdgcfgdirlen > 0) { 252 | osoputlen(out, xdgcfgdir, xdgcfgdirlen); 253 | return Conf_dir_ok; 254 | } 255 | } 256 | char const *homedir = getenv(home_env); 257 | if (homedir) { 258 | Usz homedirlen = strlen(homedir); 259 | if (homedirlen > 0) { 260 | osoputprintf(out, "%s/.config", homedir); 261 | return Conf_dir_ok; 262 | } 263 | } 264 | return Conf_dir_no_home; 265 | } 266 | 267 | static void conf_impl_catconfpath(oso **p, char const *conf_file_name, 268 | size_t conflen) { 269 | oso *path = *p; 270 | size_t n = osolen(path); 271 | osoensurecap(&path, n + 1 + conflen); 272 | if (!path) 273 | goto done; 274 | ((char *)path)[n] = '/'; 275 | memcpy((char *)path + n + 1, conf_file_name, conflen); 276 | ((char *)path)[n + 1 + conflen] = '\0'; 277 | osopokelen(path, n + 1 + conflen); 278 | done: 279 | *p = path; 280 | } 281 | 282 | FILE *conf_file_open_for_reading(char const *conf_file_name) { 283 | if (!conf_file_name) 284 | return NULL; 285 | oso *path = NULL; 286 | if (try_get_conf_dir(&path)) 287 | return NULL; 288 | size_t conflen = strlen(conf_file_name); 289 | if (conflen == 0) 290 | return NULL; 291 | conf_impl_catconfpath(&path, conf_file_name, conflen); 292 | if (!path) 293 | return NULL; 294 | FILE *file = fopen(osoc(path), "r"); 295 | osofree(path); 296 | return file; 297 | } 298 | 299 | Conf_save_start_error conf_save_start(Conf_save *p, 300 | char const *conf_file_name) { 301 | *p = (Conf_save){0}; 302 | oso *dir = NULL; 303 | Conf_save_start_error err; 304 | if (!conf_file_name) { 305 | err = Conf_save_start_bad_conf_name; 306 | goto cleanup; 307 | } 308 | if (try_get_conf_dir(&dir)) { 309 | err = Conf_save_start_no_home; 310 | goto cleanup; 311 | } 312 | if (!dir) 313 | goto allocfail; 314 | osoputoso(&p->canonpath, dir); 315 | if (!p->canonpath) 316 | goto allocfail; 317 | size_t namelen = strlen(conf_file_name); 318 | if (namelen == 0) { 319 | err = Conf_save_start_bad_conf_name; 320 | goto cleanup; 321 | } 322 | conf_impl_catconfpath(&p->canonpath, conf_file_name, namelen); 323 | if (!p->canonpath) 324 | goto allocfail; 325 | osoputoso(&p->temppath, p->canonpath); 326 | if (!p->temppath) 327 | goto allocfail; 328 | osocat(&p->temppath, ".tmp"); 329 | if (!p->temppath) 330 | goto allocfail; 331 | // Remove old temp file if it exists. If it exists and we can't remove it, 332 | // error. 333 | if (unlink(osoc(p->temppath)) == -1 && errno != ENOENT) { 334 | switch (errno) { 335 | case ENOTDIR: 336 | err = Conf_save_start_conf_dir_not_dir; 337 | break; 338 | case EACCES: 339 | err = Conf_save_start_temp_file_perm_denied; 340 | break; 341 | default: 342 | err = Conf_save_start_old_temp_file_stuck; 343 | break; 344 | } 345 | goto cleanup; 346 | } 347 | p->tempfile = fopen(osoc(p->temppath), "w"); 348 | if (!p->tempfile) { 349 | // Try to create config dir, in case it doesn't exist. (XDG says we should 350 | // do this, and use mode 0700.) 351 | mkdir(osoc(dir), 0700); 352 | p->tempfile = fopen(osoc(p->temppath), "w"); 353 | } 354 | if (!p->tempfile) { 355 | err = Conf_save_start_temp_file_open_failed; 356 | goto cleanup; 357 | } 358 | // This may be left as NULL. 359 | p->origfile = fopen(osoc(p->canonpath), "r"); 360 | // We did it, boys. 361 | osofree(dir); 362 | return Conf_save_start_ok; 363 | 364 | allocfail: 365 | err = Conf_save_start_alloc_failed; 366 | cleanup: 367 | osofree(dir); 368 | conf_save_cancel(p); 369 | return err; 370 | } 371 | 372 | void conf_save_cancel(Conf_save *p) { 373 | osofree(p->canonpath); 374 | osofree(p->temppath); 375 | if (p->origfile) 376 | fclose(p->origfile); 377 | if (p->tempfile) 378 | fclose(p->tempfile); 379 | *p = (Conf_save){0}; 380 | } 381 | 382 | Conf_save_commit_error conf_save_commit(Conf_save *p) { 383 | Conf_save_commit_error err; 384 | fclose(p->tempfile); 385 | p->tempfile = NULL; 386 | if (p->origfile) { 387 | fclose(p->origfile); 388 | p->origfile = NULL; 389 | } 390 | // This isn't really atomic. But if we want to close and move a file 391 | // simultaneously, I think we have to use OS-specific facilities. So I guess 392 | // this is the best we can do for now. I could be wrong, though. But I 393 | // couldn't find any good information about it. 394 | if (rename(osoc(p->temppath), osoc(p->canonpath)) == -1) { 395 | err = Conf_save_commit_rename_failed; 396 | goto cleanup; 397 | } 398 | err = Conf_save_commit_ok; 399 | cleanup: 400 | conf_save_cancel(p); 401 | return err; 402 | } 403 | 404 | char const *ezconf_w_errorstring(Ezconf_w_error error) { 405 | switch (error) { 406 | case Ezconf_w_ok: 407 | return "No error"; 408 | case Ezconf_w_bad_conf_name: 409 | return "Bad config file name"; 410 | case Ezconf_w_oom: 411 | return "Out of memory"; 412 | case Ezconf_w_no_home: 413 | return "Unable to resolve $XDG_CONFIG_HOME or $HOME"; 414 | case Ezconf_w_mkdir_failed: 415 | return "Unable to create $XDG_CONFIG_HOME or $HOME/.config directory"; 416 | case Ezconf_w_conf_dir_not_dir: 417 | return "Config directory path is not a directory"; 418 | case Ezconf_w_old_temp_file_stuck: 419 | return "Unable to remove old .conf.tmp file"; 420 | case Ezconf_w_temp_file_perm_denied: 421 | return "Permission denied for config directory"; 422 | case Ezconf_w_temp_open_failed: 423 | return "Unable to open .conf.tmp for writing"; 424 | case Ezconf_w_temp_fsync_failed: 425 | return "fsync() reported an when writing temp file.\n" 426 | "Refusing to continue."; 427 | case Ezconf_w_temp_close_failed: 428 | return "Unable to close temp file"; 429 | case Ezconf_w_rename_failed: 430 | return "Unable to rename .conf.tmp to .conf"; 431 | case Ezconf_w_line_too_long: 432 | return "Line in file is too long"; 433 | case Ezconf_w_existing_read_error: 434 | return "Error when reading existing configuration file"; 435 | case Ezconf_w_unknown_error: 436 | break; 437 | } 438 | return "Unknown"; 439 | } 440 | 441 | void ezconf_r_start(Ezconf_r *ezcr, char const *conf_file_name) { 442 | ezcr->file = conf_file_open_for_reading(conf_file_name); 443 | ezcr->index = 0; 444 | ezcr->value = NULL; 445 | } 446 | 447 | bool ezconf_r_step(Ezconf_r *ezcr, char const *const *names, size_t nameslen) { 448 | return conf_read_match(&ezcr->file, names, nameslen, ezcr->buffer, 449 | sizeof ezcr->buffer, &ezcr->index, &ezcr->value); 450 | } 451 | 452 | enum { 453 | Confwflag_add_newline = 1 << 0, 454 | Ezconf_opt_written = 1 << 0, 455 | }; 456 | 457 | void ezconf_w_start(Ezconf_w *ezcw, Ezconf_opt *optsbuffer, size_t buffercap, 458 | char const *conf_file_name) { 459 | *ezcw = (Ezconf_w){.save = {0}}; // Weird to silence clang warning 460 | ezcw->opts = optsbuffer; 461 | ezcw->optscap = buffercap; 462 | Ezconf_w_error error = Ezconf_w_unknown_error; 463 | switch (conf_save_start(&ezcw->save, conf_file_name)) { 464 | case Conf_save_start_ok: 465 | error = Ezconf_w_ok; 466 | ezcw->file = ezcw->save.tempfile; 467 | break; 468 | case Conf_save_start_bad_conf_name: 469 | error = Ezconf_w_bad_conf_name; 470 | break; 471 | case Conf_save_start_alloc_failed: 472 | error = Ezconf_w_oom; 473 | break; 474 | case Conf_save_start_no_home: 475 | error = Ezconf_w_no_home; 476 | break; 477 | case Conf_save_start_mkdir_failed: 478 | error = Ezconf_w_mkdir_failed; 479 | break; 480 | case Conf_save_start_conf_dir_not_dir: 481 | error = Ezconf_w_conf_dir_not_dir; 482 | break; 483 | case Conf_save_start_old_temp_file_stuck: 484 | error = Ezconf_w_old_temp_file_stuck; 485 | break; 486 | case Conf_save_start_temp_file_perm_denied: 487 | error = Ezconf_w_temp_file_perm_denied; 488 | break; 489 | case Conf_save_start_temp_file_open_failed: 490 | error = Ezconf_w_temp_open_failed; 491 | break; 492 | } 493 | ezcw->error = error; 494 | } 495 | void ezconf_w_addopt(Ezconf_w *ezcw, char const *key, intptr_t id) { 496 | size_t count = ezcw->optscount, cap = ezcw->optscap; 497 | if (count == cap) 498 | return; 499 | ezcw->opts[count] = (Ezconf_opt){.name = key, .id = id, .flags = 0}; 500 | ezcw->optscount = count + 1; 501 | } 502 | bool ezconf_w_step(Ezconf_w *ezcw) { 503 | uint32_t stateflags = ezcw->stateflags; 504 | FILE *origfile = ezcw->save.origfile, *tempfile = ezcw->save.tempfile; 505 | Ezconf_opt *opts = ezcw->opts, *chosen = NULL; 506 | size_t optscount = ezcw->optscount; 507 | if (ezcw->error || !tempfile) // Already errored or finished ok 508 | return false; 509 | // If we set a flag to write a closing newline the last time we were called, 510 | // write it now. 511 | if (stateflags & Confwflag_add_newline) { 512 | fputs("\n", tempfile); 513 | stateflags &= ~(uint32_t)Confwflag_add_newline; 514 | } 515 | if (!optscount) 516 | goto commit; 517 | if (!origfile) 518 | goto write_leftovers; 519 | for (;;) { // Scan through file looking for known keys in key=value lines 520 | char linebuff[1024]; 521 | char *left, *right; 522 | size_t leftsz, rightsz; 523 | Conf_read_result res = conf_read_line(origfile, linebuff, sizeof linebuff, 524 | &left, &leftsz, &right, &rightsz); 525 | switch (res) { 526 | case Conf_read_left_and_right: { 527 | for (size_t i = 0; i < optscount; i++) { 528 | char const *name = opts[i].name; 529 | if (!name) 530 | continue; 531 | if (strcmp(name, left) != 0) 532 | continue; 533 | // If we already wrote this one, comment out the line instead, and move 534 | // on to the next line. 535 | if (opts[i].flags & (uint8_t)Ezconf_opt_written) { 536 | fputs("# ", tempfile); 537 | goto write_landr; 538 | } 539 | chosen = opts + i; 540 | goto return_for_writing; 541 | } 542 | write_landr: 543 | fputs(left, tempfile); 544 | fputs(" = ", tempfile); 545 | fputs(right, tempfile); 546 | fputs("\n", tempfile); 547 | continue; 548 | } 549 | case Conf_read_irrelevant: 550 | fputs(left, tempfile); 551 | fputs("\n", tempfile); 552 | continue; 553 | case Conf_read_eof: 554 | goto end_original; 555 | case Conf_read_buffer_too_small: 556 | ezcw->error = Ezconf_w_line_too_long; 557 | goto cancel; 558 | case Conf_read_io_error: 559 | ezcw->error = Ezconf_w_existing_read_error; 560 | goto cancel; 561 | } 562 | } 563 | end_original: // Don't need original file anymore 564 | fclose(origfile); 565 | ezcw->save.origfile = origfile = NULL; 566 | write_leftovers: // Write out any guys that weren't in original file. 567 | for (;;) { // Find the first guy that wasn't already written. 568 | if (!optscount) 569 | goto commit; 570 | chosen = opts; 571 | // Drop the guy from the front of the list. This is to reduce super-linear 572 | // complexity growth as the number of conf key-value pairs are increased. 573 | // (Otherwise, we iterate the full set of guys on each call during the 574 | // "write the leftovers" phase.) 575 | opts++; 576 | optscount--; 577 | if (!(chosen->flags & (uint8_t)Ezconf_opt_written)) 578 | break; 579 | } 580 | // Once control has reached here, we're going to return true to the caller. 581 | // Which means we expect to be called at least one more time. So update the 582 | // pointers stored in the persistent state, so that we don't have to scan 583 | // through as much of this list next time. (This might even end up finishing 584 | // it off, making it empty.) 585 | ezcw->opts = opts; 586 | ezcw->optscount = optscount; 587 | return_for_writing: 588 | chosen->flags |= (uint8_t)Ezconf_opt_written; 589 | fputs(chosen->name, tempfile); 590 | fputs(" = ", tempfile); 591 | ezcw->optid = chosen->id; 592 | stateflags |= (uint32_t)Confwflag_add_newline; 593 | ezcw->stateflags = stateflags; 594 | return true; 595 | cancel: 596 | conf_save_cancel(&ezcw->save); 597 | // ^- Sets tempfile to null, which we use as a guard at the top of this 598 | // function. 599 | ezcw->file = NULL; 600 | ezcw->stateflags = 0; 601 | return false; 602 | commit:; 603 | Ezconf_w_error error = Ezconf_w_unknown_error; 604 | switch (conf_save_commit(&ezcw->save)) { 605 | case Conf_save_commit_ok: 606 | error = Ezconf_w_ok; 607 | break; 608 | case Conf_save_commit_temp_fsync_failed: 609 | error = Ezconf_w_temp_fsync_failed; 610 | break; 611 | case Conf_save_commit_temp_close_failed: 612 | error = Ezconf_w_temp_close_failed; 613 | break; 614 | case Conf_save_commit_rename_failed: 615 | error = Ezconf_w_rename_failed; 616 | break; 617 | } 618 | ezcw->file = NULL; 619 | ezcw->error = error; 620 | ezcw->stateflags = 0; 621 | return false; 622 | } 623 | -------------------------------------------------------------------------------- /sysmisc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base.h" 3 | #include // FILE cannot be forward declared 4 | struct oso; 5 | 6 | void expand_home_tilde(struct oso **path); 7 | 8 | typedef enum { 9 | Cboard_error_none = 0, 10 | Cboard_error_unavailable, 11 | Cboard_error_popen_failed, 12 | Cboard_error_process_exit_error, 13 | } Cboard_error; 14 | 15 | Cboard_error cboard_copy(Glyph const *gbuffer, Usz field_height, 16 | Usz field_width, Usz rect_y, Usz rect_x, Usz rect_h, 17 | Usz rect_w); 18 | 19 | Cboard_error cboard_paste(Glyph *gbuffer, Usz height, Usz width, Usz y, Usz x, 20 | Usz *out_h, Usz *out_w); 21 | 22 | typedef enum { 23 | Conf_read_left_and_right = 0, // left and right will be set 24 | Conf_read_irrelevant, // only left will be set 25 | Conf_read_buffer_too_small, // neither will be set 26 | Conf_read_eof, // " 27 | Conf_read_io_error, // " 28 | } Conf_read_result; 29 | 30 | Conf_read_result conf_read_line(FILE *file, char *buf, Usz bufsize, 31 | char **out_left, Usz *out_leftlen, 32 | char **out_right, Usz *out_rightlen); 33 | 34 | bool conf_read_match(FILE **pfile, char const *const *names, Usz nameslen, 35 | char *buf, Usz bufsize, Usz *out_index, char **out_value); 36 | 37 | FILE *conf_file_open_for_reading(char const *conf_file_name); 38 | 39 | typedef struct { 40 | FILE *origfile, *tempfile; 41 | struct oso *canonpath, *temppath; 42 | } Conf_save; 43 | 44 | typedef enum { 45 | Conf_save_start_ok = 0, 46 | Conf_save_start_bad_conf_name, 47 | Conf_save_start_alloc_failed, 48 | Conf_save_start_no_home, 49 | Conf_save_start_mkdir_failed, 50 | Conf_save_start_conf_dir_not_dir, 51 | Conf_save_start_temp_file_perm_denied, 52 | Conf_save_start_old_temp_file_stuck, 53 | Conf_save_start_temp_file_open_failed, 54 | } Conf_save_start_error; 55 | 56 | typedef enum { 57 | Conf_save_commit_ok = 0, 58 | Conf_save_commit_temp_fsync_failed, 59 | Conf_save_commit_temp_close_failed, 60 | Conf_save_commit_rename_failed, 61 | } Conf_save_commit_error; 62 | 63 | Conf_save_start_error conf_save_start(Conf_save *p, char const *conf_file_name); 64 | // `*p` may be passed in uninitialized or zeroed -- either is fine. If the 65 | // return value is `Conf_save_start_ok`, then you must call either 66 | // `conf_save_cancel()` or `conf_save_commit()`, otherwise file handles and 67 | // strings will be leaked. If the return value is not `Conf_save_start_ok`, 68 | // then the contents of `*p` are zeroed, and nothing further has to be called. 69 | // 70 | // `conf_file_name` should be a C string like "myprogram.conf" 71 | // 72 | // Note that `origfile` in the `struct Conf_save` may be null even if the call 73 | // succeeded and didn't return an error. This is because it's possible for 74 | // there to be no existing config file. It might be the first time a config 75 | // file is being written. 76 | 77 | void conf_save_cancel(Conf_save *p); 78 | // Cancels a config save. Closes any file handles and frees any necessary 79 | // strings. Calling with a zeroed `*p` is fine, but don't call it with 80 | // uninitialized data. Afterwards, `*p` will be zeroed. 81 | 82 | Conf_save_commit_error conf_save_commit(Conf_save *p); 83 | // Finishes. Do not call this with a zeroed `*p`. Afterwards, `*p` will be 84 | // zeroed. 85 | 86 | // Just playing around with this design 87 | typedef struct { 88 | FILE *file; 89 | Usz index; 90 | char *value; 91 | char buffer[1024]; 92 | } Ezconf_r; 93 | 94 | void ezconf_r_start(Ezconf_r *ezcr, char const *conf_file_name); 95 | bool ezconf_r_step(Ezconf_r *ezcr, char const *const *names, Usz nameslen); 96 | 97 | typedef enum { 98 | Ezconf_w_ok = 0, 99 | Ezconf_w_bad_conf_name, 100 | Ezconf_w_oom, 101 | Ezconf_w_no_home, 102 | Ezconf_w_mkdir_failed, 103 | Ezconf_w_conf_dir_not_dir, 104 | Ezconf_w_old_temp_file_stuck, 105 | Ezconf_w_temp_file_perm_denied, 106 | Ezconf_w_temp_open_failed, 107 | Ezconf_w_temp_fsync_failed, 108 | Ezconf_w_temp_close_failed, 109 | Ezconf_w_rename_failed, 110 | Ezconf_w_line_too_long, 111 | Ezconf_w_existing_read_error, 112 | Ezconf_w_unknown_error, 113 | } Ezconf_w_error; 114 | 115 | char const *ezconf_w_errorstring(Ezconf_w_error error); 116 | 117 | typedef struct { 118 | char const *name; 119 | intptr_t id; 120 | uint8_t flags; 121 | } Ezconf_opt; 122 | 123 | typedef struct { 124 | Conf_save save; 125 | Ezconf_opt *opts; 126 | size_t optscount, optscap; 127 | intptr_t optid; 128 | FILE *file; 129 | Ezconf_w_error error; 130 | uint32_t stateflags; 131 | } Ezconf_w; 132 | 133 | void ezconf_w_start(Ezconf_w *ezcw, Ezconf_opt *optsbuffer, size_t buffercap, 134 | char const *conf_file_name); 135 | void ezconf_w_addopt(Ezconf_w *ezcw, char const *key, intptr_t id); 136 | bool ezconf_w_step(Ezconf_w *ezcw); 137 | -------------------------------------------------------------------------------- /term_util.c: -------------------------------------------------------------------------------- 1 | #include "term_util.h" 2 | #include "oso.h" 3 | #include 4 | #include 5 | 6 | void term_util_init_colors() { 7 | if (has_colors()) { 8 | // Enable color 9 | start_color(); 10 | use_default_colors(); 11 | for (int ifg = 0; ifg < Colors_count; ++ifg) { 12 | for (int ibg = 0; ibg < Colors_count; ++ibg) { 13 | int res = init_pair((short int)(1 + ifg * Colors_count + ibg), 14 | (short int)(ifg - 1), (short int)(ibg - 1)); 15 | (void)res; 16 | // Might fail on Linux virtual console/terminal for a couple of colors. 17 | // Just ignore. 18 | #if 0 19 | if (res == ERR) { 20 | endwin(); 21 | fprintf(stderr, "Error initializing color pair: %d %d\n", ifg - 1, 22 | ibg - 1); 23 | exit(1); 24 | } 25 | #endif 26 | } 27 | } 28 | } 29 | } 30 | 31 | #define ORCA_CONTAINER_OF(ptr, type, member) \ 32 | ((type *)((char *)(1 ? (ptr) : &((type *)0)->member) - \ 33 | offsetof(type, member))) 34 | 35 | struct Qmsg { 36 | Qblock qblock; 37 | Qmsg_dismiss_mode dismiss_mode; 38 | }; 39 | 40 | typedef struct Qmenu_item { 41 | char const *text; 42 | int id; 43 | U8 owns_string : 1, is_spacer : 1; 44 | } Qmenu_item; 45 | 46 | struct Qmenu { 47 | Qblock qblock; 48 | Qmenu_item *items; 49 | Usz items_count, items_cap; 50 | int current_item, id; 51 | U8 needs_reprint : 1, is_frontmost : 1; 52 | }; 53 | 54 | struct Qform { 55 | Qblock qblock; 56 | FORM *ncurses_form; 57 | FIELD *ncurses_fields[32]; 58 | Usz fields_count; 59 | int id; 60 | }; 61 | 62 | static void qmenu_free(Qmenu *qm); 63 | static void qform_free(Qform *qf); 64 | ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm); 65 | 66 | Qnav_stack qnav_stack; 67 | 68 | void qnav_init() { qnav_stack = (Qnav_stack){0}; } 69 | void qnav_deinit() { 70 | while (qnav_stack.top) 71 | qnav_stack_pop(); 72 | } 73 | // Set new y and x coordinates for the top and left of a Qblock based on the 74 | // position of the Qblock "below" it in the stack. (Below meaning its order in 75 | // the stack, not vertical position on a Y axis.) The target Qblock should 76 | // already be inserted into the stack somewhere, so don't call this before 77 | // you've finished doing the rest of the setup on the Qblock. The y and x 78 | // fields can be junk, though, since this function writes to them without 79 | // reading them. 80 | static ORCA_NOINLINE void qnav_reposition_block(Qblock *qb) { 81 | int top = 0, left = 0; 82 | Qblock *prev = qb->down; 83 | if (!prev) 84 | goto done; 85 | int total_h, total_w; 86 | getmaxyx(qb->outer_window, total_h, total_w); 87 | WINDOW *w = prev->outer_window; 88 | int prev_y = prev->y, prev_x = prev->x, prev_h, prev_w; 89 | getmaxyx(w, prev_h, prev_w); 90 | // Start by trying to position the item to the right of the previous item. 91 | left = prev_x + prev_w + 0; 92 | int term_h, term_w; 93 | getmaxyx(stdscr, term_h, term_w); 94 | // Check if we'll run out of room if we position the new item to the right 95 | // of the existing item (with the same Y position.) 96 | if (left + total_w > term_w) { 97 | // If we have enough room if we position just below the previous item in 98 | // the stack, do that instead of positioning to the right of it. 99 | if (prev_x + total_w <= term_w && total_h < term_h - (prev_y + prev_h)) { 100 | top = prev_y + prev_h; 101 | left = prev_x; 102 | } 103 | // If the item doesn't fit there, but it's less wide than the terminal, 104 | // right-align it to the edge of the terminal. 105 | else if (total_w < term_w) { 106 | left = term_w - total_w; 107 | } 108 | // Otherwise, just start the layout over at Y=0,X=0 109 | else { 110 | left = 0; 111 | } 112 | } 113 | done: 114 | qb->y = top; 115 | qb->x = left; 116 | } 117 | static ORCA_NOINLINE void qnav_stack_push(Qblock *qb, int height, int width) { 118 | #ifndef NDEBUG 119 | for (Qblock *i = qnav_stack.top; i; i = i->down) { 120 | assert(i != qb); 121 | } 122 | #endif 123 | int total_h = height + 2, total_w = width + 2; 124 | if (qnav_stack.top) 125 | qnav_stack.top->up = qb; 126 | else 127 | qnav_stack.bottom = qb; 128 | qb->down = qnav_stack.top; 129 | qnav_stack.top = qb; 130 | qb->outer_window = newpad(total_h, total_w); 131 | qb->content_window = subpad(qb->outer_window, height, width, 1, 1); 132 | qnav_reposition_block(qb); 133 | qnav_stack.occlusion_dirty = true; 134 | } 135 | 136 | Qblock *qnav_top_block() { return qnav_stack.top; } 137 | 138 | void qblock_init(Qblock *qb, Qblock_type_tag tag) { 139 | *qb = (Qblock){0}; 140 | qb->tag = tag; 141 | } 142 | 143 | void qnav_free_block(Qblock *qb) { 144 | switch (qb->tag) { 145 | case Qblock_type_qmsg: { 146 | Qmsg *qm = qmsg_of(qb); 147 | free(qm); 148 | break; 149 | } 150 | case Qblock_type_qmenu: 151 | qmenu_free(qmenu_of(qb)); 152 | break; 153 | case Qblock_type_qform: 154 | qform_free(qform_of(qb)); 155 | break; 156 | } 157 | } 158 | 159 | void qnav_stack_pop(void) { 160 | assert(qnav_stack.top); 161 | if (!qnav_stack.top) 162 | return; 163 | Qblock *qb = qnav_stack.top; 164 | qnav_stack.top = qb->down; 165 | if (qnav_stack.top) 166 | qnav_stack.top->up = NULL; 167 | else 168 | qnav_stack.bottom = NULL; 169 | qnav_stack.occlusion_dirty = true; 170 | WINDOW *content_window = qb->content_window; 171 | WINDOW *outer_window = qb->outer_window; 172 | // erase any stuff underneath where this window is, in case it's outside of 173 | // the grid in an area that isn't actively redraw 174 | werase(outer_window); 175 | wnoutrefresh(outer_window); 176 | qnav_free_block(qb); 177 | delwin(content_window); 178 | delwin(outer_window); 179 | } 180 | 181 | bool qnav_draw(void) { 182 | bool drew_any = false; 183 | if (!qnav_stack.bottom) 184 | goto done; 185 | int term_h, term_w; 186 | getmaxyx(stdscr, term_h, term_w); 187 | for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) { 188 | bool is_frontmost = qb == qnav_stack.top; 189 | if (qnav_stack.occlusion_dirty) 190 | qblock_print_frame(qb, is_frontmost); 191 | switch (qb->tag) { 192 | case Qblock_type_qmsg: 193 | break; 194 | case Qblock_type_qmenu: { 195 | Qmenu *qm = qmenu_of(qb); 196 | if (qm->is_frontmost != is_frontmost) { 197 | qm->is_frontmost = is_frontmost; 198 | qm->needs_reprint = 1; 199 | } 200 | if (qm->needs_reprint) { 201 | qmenu_reprint(qm); 202 | qm->needs_reprint = 0; 203 | } 204 | break; 205 | } 206 | case Qblock_type_qform: 207 | break; 208 | } 209 | touchwin(qb->outer_window); // here? or after continue? 210 | if (term_h < 1 || term_w < 1) 211 | continue; 212 | int qbwin_h, qbwin_w; 213 | getmaxyx(qb->outer_window, qbwin_h, qbwin_w); 214 | int qbwin_endy = qb->y + qbwin_h; 215 | int qbwin_endx = qb->x + qbwin_w; 216 | if (qbwin_endy >= term_h) 217 | qbwin_endy = term_h - 1; 218 | if (qbwin_endx >= term_w) 219 | qbwin_endx = term_w - 1; 220 | if (qb->y >= qbwin_endy || qb->x >= qbwin_endx) 221 | continue; 222 | pnoutrefresh(qb->outer_window, 0, 0, qb->y, qb->x, qbwin_endy, qbwin_endx); 223 | drew_any = true; 224 | } 225 | done: 226 | qnav_stack.occlusion_dirty = false; 227 | return drew_any; 228 | } 229 | 230 | void qnav_adjust_term_size(void) { 231 | if (!qnav_stack.bottom) 232 | return; 233 | for (Qblock *qb = qnav_stack.bottom; qb; qb = qb->up) 234 | qnav_reposition_block(qb); 235 | qnav_stack.occlusion_dirty = true; 236 | } 237 | 238 | void qblock_print_border(Qblock *qb, unsigned int attr) { 239 | wborder(qb->outer_window, ACS_VLINE | attr, ACS_VLINE | attr, 240 | ACS_HLINE | attr, ACS_HLINE | attr, ACS_ULCORNER | attr, 241 | ACS_URCORNER | attr, ACS_LLCORNER | attr, ACS_LRCORNER | attr); 242 | } 243 | 244 | void qblock_print_title(Qblock *qb, char const *title, int attr) { 245 | wmove(qb->outer_window, 0, 1); 246 | attr_t attrs = A_NORMAL; 247 | short pair = 0; 248 | wattr_get(qb->outer_window, &attrs, &pair, NULL); 249 | wattrset(qb->outer_window, attr); 250 | waddch(qb->outer_window, ' '); 251 | waddstr(qb->outer_window, title); 252 | waddch(qb->outer_window, ' '); 253 | wattr_set(qb->outer_window, attrs, pair, NULL); 254 | } 255 | 256 | void qblock_set_title(Qblock *qb, char const *title) { qb->title = title; } 257 | 258 | void qblock_print_frame(Qblock *qb, bool active) { 259 | qblock_print_border(qb, active ? A_NORMAL : A_DIM); 260 | if (qb->title) { 261 | qblock_print_title(qb, qb->title, active ? A_NORMAL : A_DIM); 262 | } 263 | if (qb->tag == Qblock_type_qform) { 264 | Qform *qf = qform_of(qb); 265 | if (qf->ncurses_form) { 266 | pos_form_cursor(qf->ncurses_form); 267 | } 268 | } 269 | } 270 | 271 | WINDOW *qmsg_window(Qmsg *qm) { return qm->qblock.content_window; } 272 | 273 | void qmsg_set_title(Qmsg *qm, char const *title) { 274 | qblock_set_title(&qm->qblock, title); 275 | } 276 | 277 | void qmsg_set_dismiss_mode(Qmsg *qm, Qmsg_dismiss_mode mode) { 278 | if (qm->dismiss_mode == mode) 279 | return; 280 | qm->dismiss_mode = mode; 281 | } 282 | 283 | Qmsg *qmsg_push(int height, int width) { 284 | Qmsg *qm = malloc(sizeof(Qmsg)); 285 | qblock_init(&qm->qblock, Qblock_type_qmsg); 286 | qm->dismiss_mode = Qmsg_dismiss_mode_explicitly; 287 | qnav_stack_push(&qm->qblock, height, width); 288 | return qm; 289 | } 290 | 291 | Qmsg *qmsg_printf_push(char const *title, char const *fmt, ...) { 292 | int titlewidth = title ? (int)strlen(title) : 0; 293 | va_list ap; 294 | va_start(ap, fmt); 295 | int msgbytes = vsnprintf(NULL, 0, fmt, ap); 296 | va_end(ap); 297 | char *buffer = malloc((Usz)msgbytes + 1); 298 | if (!buffer) 299 | exit(1); 300 | va_start(ap, fmt); 301 | int printedbytes = vsnprintf(buffer, (Usz)msgbytes + 1, fmt, ap); 302 | va_end(ap); 303 | if (printedbytes != msgbytes) 304 | exit(1); // todo better handling? 305 | int lines = 1; 306 | int curlinewidth = 0; 307 | int maxlinewidth = 0; 308 | for (int i = 0; i < msgbytes; i++) { 309 | if (buffer[i] == '\n') { 310 | buffer[i] = '\0'; // This is terrifying :) 311 | lines++; 312 | if (curlinewidth > maxlinewidth) 313 | maxlinewidth = curlinewidth; 314 | curlinewidth = 0; 315 | } else { 316 | curlinewidth++; 317 | } 318 | } 319 | if (curlinewidth > maxlinewidth) 320 | maxlinewidth = curlinewidth; 321 | int width = titlewidth > maxlinewidth ? titlewidth : maxlinewidth; 322 | width += 2; // 1 padding on left and right each 323 | Qmsg *msg = qmsg_push(lines, width); // no wrapping yet, no real wcwidth, etc 324 | WINDOW *msgw = qmsg_window(msg); 325 | int i = 0; 326 | int offset = 0; 327 | for (;;) { 328 | if (offset == msgbytes + 1) 329 | break; 330 | int numbytes = (int)strlen(buffer + offset); 331 | wmove(msgw, i, 1); 332 | waddstr(msgw, buffer + offset); 333 | offset += numbytes + 1; 334 | i++; 335 | } 336 | free(buffer); 337 | if (title) 338 | qmsg_set_title(msg, title); 339 | return msg; 340 | } 341 | 342 | bool qmsg_drive(Qmsg *qm, int key, Qmsg_action *out_action) { 343 | *out_action = (Qmsg_action){0}; 344 | Qmsg_dismiss_mode dm = qm->dismiss_mode; 345 | switch (dm) { 346 | case Qmsg_dismiss_mode_explicitly: 347 | break; 348 | case Qmsg_dismiss_mode_easily: 349 | out_action->dismiss = true; 350 | return true; 351 | case Qmsg_dismiss_mode_passthrough: 352 | out_action->dismiss = true; 353 | out_action->passthrough = true; 354 | return true; 355 | } 356 | switch (key) { 357 | case ' ': 358 | case 27: 359 | case '\r': 360 | case KEY_ENTER: 361 | out_action->dismiss = true; 362 | return true; 363 | } 364 | return false; 365 | } 366 | 367 | Qmsg *qmsg_of(Qblock *qb) { return ORCA_CONTAINER_OF(qb, Qmsg, qblock); } 368 | 369 | Qmenu *qmenu_create(int id) { 370 | Qmenu *qm = (Qmenu *)malloc(sizeof(Qmenu)); 371 | qblock_init(&qm->qblock, Qblock_type_qmenu); 372 | qm->items = NULL; 373 | qm->items_count = 0; 374 | qm->items_cap = 0; 375 | qm->current_item = 0; 376 | qm->id = id; 377 | qm->needs_reprint = 1; 378 | qm->is_frontmost = 0; 379 | return qm; 380 | } 381 | void qmenu_destroy(Qmenu *qm) { qmenu_free(qm); } 382 | int qmenu_id(Qmenu const *qm) { return qm->id; } 383 | static ORCA_NOINLINE Qmenu_item *qmenu_allocitems(Qmenu *qm, Usz count) { 384 | Usz old_count = qm->items_count; 385 | if (old_count > SIZE_MAX - count) // overflow 386 | exit(1); 387 | Usz new_count = old_count + count; 388 | Usz items_cap = qm->items_cap; 389 | Qmenu_item *items = qm->items; 390 | if (new_count > items_cap) { 391 | // todo overflow check, realloc fail check 392 | Usz new_cap = new_count < 32 ? 32 : orca_round_up_power2(new_count); 393 | Usz new_size = new_cap * sizeof(Qmenu_item); 394 | Qmenu_item *new_items = (Qmenu_item *)realloc(items, new_size); 395 | if (!new_items) 396 | exit(1); 397 | items = new_items; 398 | items_cap = new_cap; 399 | qm->items = new_items; 400 | qm->items_cap = new_cap; 401 | } 402 | qm->items_count = new_count; 403 | return items + old_count; 404 | } 405 | ORCA_NOINLINE static void qmenu_reprint(Qmenu *qm) { 406 | WINDOW *win = qm->qblock.content_window; 407 | Qmenu_item *items = qm->items; 408 | bool isfront = qm->is_frontmost; 409 | werase(win); 410 | for (Usz i = 0, n = qm->items_count; i < n; ++i) { 411 | bool iscur = items[i].id == qm->current_item; 412 | wattrset(win, isfront ? iscur ? A_BOLD : A_NORMAL : A_DIM); 413 | wmove(win, (int)i, iscur ? 1 : 3); 414 | if (iscur) 415 | waddstr(win, "> "); 416 | waddstr(win, items[i].text); 417 | } 418 | } 419 | void qmenu_set_title(Qmenu *qm, char const *title) { 420 | qblock_set_title(&qm->qblock, title); 421 | } 422 | void qmenu_add_choice(Qmenu *qm, int id, char const *text) { 423 | assert(id != 0); 424 | Qmenu_item *item = qmenu_allocitems(qm, 1); 425 | item->text = text; 426 | item->id = id; 427 | item->owns_string = false; 428 | item->is_spacer = false; 429 | if (!qm->current_item) 430 | qm->current_item = id; 431 | } 432 | void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) { 433 | va_list ap; 434 | va_start(ap, fmt); 435 | int textsize = vsnprintf(NULL, 0, fmt, ap); 436 | va_end(ap); 437 | char *buffer = malloc((Usz)textsize + 1); 438 | if (!buffer) 439 | exit(1); 440 | va_start(ap, fmt); 441 | int printedsize = vsnprintf(buffer, (Usz)textsize + 1, fmt, ap); 442 | va_end(ap); 443 | if (printedsize != textsize) 444 | exit(1); // todo better handling? 445 | Qmenu_item *item = qmenu_allocitems(qm, 1); 446 | item->text = buffer; 447 | item->id = id; 448 | item->owns_string = true; 449 | item->is_spacer = false; 450 | if (!qm->current_item) 451 | qm->current_item = id; 452 | } 453 | void qmenu_add_spacer(Qmenu *qm) { 454 | Qmenu_item *item = qmenu_allocitems(qm, 1); 455 | item->text = " "; 456 | item->id = 0; 457 | item->owns_string = false; 458 | item->is_spacer = true; 459 | } 460 | void qmenu_set_current_item(Qmenu *qm, int id) { 461 | if (qm->current_item == id) 462 | return; 463 | qm->current_item = id; 464 | qm->needs_reprint = 1; 465 | } 466 | int qmenu_current_item(Qmenu *qm) { return qm->current_item; } 467 | void qmenu_push_to_nav(Qmenu *qm) { 468 | // Probably a programming error if there are no items. Make the menu visible 469 | // so the programmer knows something went wrong. 470 | if (qm->items_count == 0) 471 | qmenu_add_spacer(qm); 472 | Usz n = qm->items_count; 473 | Qmenu_item *items = qm->items; 474 | int menu_min_h = (int)n, menu_min_w = 0; 475 | for (Usz i = 0; i < n; ++i) { 476 | int item_w = (int)strlen(items[i].text); 477 | if (item_w > menu_min_w) 478 | menu_min_w = item_w; 479 | } 480 | menu_min_w += 3 + 1; // left " > " plus 1 empty space on right 481 | if (qm->qblock.title) { 482 | // Stupid lack of wcswidth() means we can't know how wide this string is 483 | // actually displayed. Just fake it for now, until we have Unicode strings 484 | // in the UI. Then we get sad. 485 | int title_w = (int)strlen(qm->qblock.title) + 2; 486 | if (title_w > menu_min_w) 487 | menu_min_w = title_w; 488 | } 489 | qnav_stack_push(&qm->qblock, menu_min_h, menu_min_w); 490 | } 491 | 492 | static void qmenu_free(Qmenu *qm) { 493 | Qmenu_item *items = qm->items; 494 | for (Usz i = 0, n = qm->items_count; i < n; ++i) { 495 | if (items[i].owns_string) 496 | free((void *)items[i].text); 497 | } 498 | free(qm->items); 499 | free(qm); 500 | } 501 | 502 | ORCA_NOINLINE static void qmenu_drive_upordown(Qmenu *qm, bool downwards) { 503 | Qmenu_item *items = qm->items; 504 | Usz n = qm->items_count; 505 | if (n <= 1) 506 | return; 507 | int cur_id = qm->current_item; 508 | Usz starting = 0; 509 | for (; starting < n; ++starting) { 510 | if (items[starting].id == cur_id) 511 | goto found; 512 | } 513 | return; 514 | found:; 515 | Usz current = starting; 516 | for (;;) { 517 | if (downwards && current < n - 1) 518 | current++; 519 | else if (!downwards && current > 0) 520 | current--; 521 | if (current == starting) 522 | break; 523 | if (!items[current].is_spacer) 524 | break; 525 | } 526 | if (current != starting) { 527 | qm->current_item = items[current].id; 528 | qm->needs_reprint = 1; 529 | } 530 | } 531 | 532 | bool qmenu_drive(Qmenu *qm, int key, Qmenu_action *out_action) { 533 | switch (key) { 534 | case 27: { 535 | out_action->any.type = Qmenu_action_type_canceled; 536 | return true; 537 | } 538 | case ' ': 539 | case '\r': 540 | case KEY_ENTER: 541 | out_action->picked.type = Qmenu_action_type_picked; 542 | out_action->picked.id = qm->current_item; 543 | return true; 544 | case KEY_UP: 545 | qmenu_drive_upordown(qm, false); 546 | return false; 547 | case KEY_DOWN: 548 | qmenu_drive_upordown(qm, true); 549 | return false; 550 | } 551 | return false; 552 | } 553 | 554 | Qmenu *qmenu_of(Qblock *qb) { return ORCA_CONTAINER_OF(qb, Qmenu, qblock); } 555 | 556 | bool qmenu_top_is_menu(int id) { 557 | Qblock *qb = qnav_top_block(); 558 | if (!qb) 559 | return false; 560 | if (qb->tag != Qblock_type_qmenu) 561 | return false; 562 | Qmenu *qm = qmenu_of(qb); 563 | return qm->id == id; 564 | } 565 | 566 | Qform *qform_create(int id) { 567 | Qform *qf = (Qform *)malloc(sizeof(Qform)); 568 | qblock_init(&qf->qblock, Qblock_type_qform); 569 | qf->ncurses_form = NULL; 570 | qf->ncurses_fields[0] = NULL; 571 | qf->fields_count = 0; 572 | qf->id = id; 573 | return qf; 574 | } 575 | static void qform_free(Qform *qf) { 576 | curs_set(0); 577 | unpost_form(qf->ncurses_form); 578 | free_form(qf->ncurses_form); 579 | for (Usz i = 0; i < qf->fields_count; ++i) { 580 | free_field(qf->ncurses_fields[i]); 581 | } 582 | free(qf); 583 | } 584 | int qform_id(Qform const *qf) { return qf->id; } 585 | Qform *qform_of(Qblock *qb) { return ORCA_CONTAINER_OF(qb, Qform, qblock); } 586 | void qform_set_title(Qform *qf, char const *title) { 587 | qblock_set_title(&qf->qblock, title); 588 | } 589 | void qform_add_line_input(Qform *qf, int id, char const *initial) { 590 | FIELD *f = new_field(1, 30, 0, 0, 0, 0); 591 | if (initial) 592 | set_field_buffer(f, 0, initial); 593 | set_field_userptr(f, (void *)(intptr_t)(id)); 594 | field_opts_off(f, O_WRAP | O_BLANK | O_STATIC); 595 | qf->ncurses_fields[qf->fields_count] = f; 596 | ++qf->fields_count; 597 | qf->ncurses_fields[qf->fields_count] = NULL; 598 | } 599 | void qform_push_to_nav(Qform *qf) { 600 | qf->ncurses_form = new_form(qf->ncurses_fields); 601 | int form_min_h, form_min_w; 602 | scale_form(qf->ncurses_form, &form_min_h, &form_min_w); 603 | qnav_stack_push(&qf->qblock, form_min_h, form_min_w); 604 | set_form_win(qf->ncurses_form, qf->qblock.outer_window); 605 | set_form_sub(qf->ncurses_form, qf->qblock.content_window); 606 | post_form(qf->ncurses_form); 607 | // quick'n'dirty cursor change for now 608 | curs_set(1); 609 | form_driver(qf->ncurses_form, REQ_END_LINE); 610 | } 611 | void qform_single_line_input(int id, char const *title, char const *initial) { 612 | Qform *qf = qform_create(id); 613 | qform_set_title(qf, title); 614 | qform_add_line_input(qf, 1, initial); 615 | qform_push_to_nav(qf); 616 | } 617 | bool qform_drive(Qform *qf, int key, Qform_action *out_action) { 618 | switch (key) { 619 | case 27: 620 | out_action->any.type = Qform_action_type_canceled; 621 | return true; 622 | case CTRL_PLUS('a'): 623 | form_driver(qf->ncurses_form, REQ_BEG_LINE); 624 | return false; 625 | case CTRL_PLUS('e'): 626 | form_driver(qf->ncurses_form, REQ_END_LINE); 627 | return false; 628 | case CTRL_PLUS('b'): 629 | form_driver(qf->ncurses_form, REQ_PREV_CHAR); 630 | return false; 631 | case CTRL_PLUS('f'): 632 | form_driver(qf->ncurses_form, REQ_NEXT_CHAR); 633 | return false; 634 | case CTRL_PLUS('k'): 635 | form_driver(qf->ncurses_form, REQ_CLR_EOL); 636 | return false; 637 | case KEY_RIGHT: 638 | form_driver(qf->ncurses_form, REQ_RIGHT_CHAR); 639 | return false; 640 | case KEY_LEFT: 641 | form_driver(qf->ncurses_form, REQ_LEFT_CHAR); 642 | return false; 643 | case 127: // backspace in terminal.app, apparently 644 | case KEY_BACKSPACE: 645 | case CTRL_PLUS('h'): 646 | form_driver(qf->ncurses_form, REQ_DEL_PREV); 647 | return false; 648 | case '\r': 649 | case KEY_ENTER: 650 | out_action->any.type = Qform_action_type_submitted; 651 | return true; 652 | } 653 | form_driver(qf->ncurses_form, key); 654 | return false; 655 | } 656 | static Usz size_without_trailing_spaces(char const *str) { 657 | Usz size = strlen(str); 658 | for (;;) { 659 | if (size == 0) 660 | break; 661 | if (!isspace(str[size - 1])) 662 | break; 663 | --size; 664 | } 665 | return size; 666 | } 667 | static FIELD *qform_find_field(Qform const *qf, int id) { 668 | Usz count = qf->fields_count; 669 | for (Usz i = 0; i < count; ++i) { 670 | FIELD *f = qf->ncurses_fields[i]; 671 | if ((int)(intptr_t)field_userptr(f) == id) 672 | return f; 673 | } 674 | return NULL; 675 | } 676 | bool qform_get_text_line(Qform const *qf, int id, oso **out) { 677 | FIELD *f = qform_find_field(qf, id); 678 | if (!f) 679 | return false; 680 | form_driver(qf->ncurses_form, REQ_VALIDATION); 681 | char *buf = field_buffer(f, 0); 682 | if (!buf) 683 | return false; 684 | Usz trimmed = size_without_trailing_spaces(buf); 685 | osoputlen(out, buf, trimmed); 686 | return true; 687 | } 688 | bool qform_get_single_text_line(Qform const *qf, struct oso **out) { 689 | return qform_get_text_line(qf, 1, out); 690 | } 691 | oso *qform_get_nonempty_single_line_input(Qform *qf) { 692 | oso *s = NULL; 693 | if (qform_get_text_line(qf, 1, &s) && osolen(s) > 0) 694 | return s; 695 | osofree(s); 696 | return NULL; 697 | } 698 | -------------------------------------------------------------------------------- /term_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base.h" 3 | #include 4 | 5 | #if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute) 6 | #if __has_attribute(format) 7 | #define ORCA_TERM_UTIL_PRINTF(...) __attribute__((format(printf, __VA_ARGS__))) 8 | #endif 9 | #endif 10 | #ifndef ORCA_TERM_UTIL_PRINTF 11 | #define ORCA_TERM_UTIL_PRINTF(...) 12 | #endif 13 | 14 | #define CTRL_PLUS(c) ((c)&037) 15 | 16 | struct oso; 17 | 18 | typedef enum { 19 | C_natural, 20 | C_black, 21 | C_red, 22 | C_green, 23 | C_yellow, 24 | C_blue, 25 | C_magenta, 26 | C_cyan, 27 | C_white, 28 | } Color_name; 29 | 30 | enum { 31 | Colors_count = C_white + 1, 32 | }; 33 | 34 | enum { 35 | Cdef_normal = COLOR_PAIR(1), 36 | }; 37 | 38 | typedef enum { 39 | A_normal = A_NORMAL, 40 | A_bold = A_BOLD, 41 | A_dim = A_DIM, 42 | A_standout = A_STANDOUT, 43 | A_reverse = A_REVERSE, 44 | } Term_attr; 45 | 46 | static ORCA_FORCEINLINE ORCA_OK_IF_UNUSED attr_t fg_bg(Color_name fg, 47 | Color_name bg) { 48 | return COLOR_PAIR(1 + fg * Colors_count + bg); 49 | } 50 | 51 | void term_util_init_colors(void); 52 | 53 | typedef enum { 54 | Qblock_type_qmsg, 55 | Qblock_type_qmenu, 56 | Qblock_type_qform, 57 | } Qblock_type_tag; 58 | 59 | typedef struct Qblock { 60 | Qblock_type_tag tag; 61 | WINDOW *outer_window, *content_window; 62 | char const *title; 63 | struct Qblock *down, *up; 64 | int y, x; 65 | } Qblock; 66 | 67 | typedef struct { 68 | Qblock *top, *bottom; 69 | bool occlusion_dirty; 70 | } Qnav_stack; 71 | 72 | typedef struct Qmsg Qmsg; 73 | 74 | typedef struct Qmenu Qmenu; 75 | 76 | typedef enum { 77 | Qmenu_action_type_canceled, 78 | Qmenu_action_type_picked, 79 | } Qmenu_action_type; 80 | 81 | typedef struct { 82 | Qmenu_action_type type; 83 | } Qmenu_action_any; 84 | 85 | typedef struct { 86 | Qmenu_action_type type; 87 | int id; 88 | } Qmenu_action_picked; 89 | 90 | typedef union { 91 | Qmenu_action_any any; 92 | Qmenu_action_picked picked; 93 | } Qmenu_action; 94 | 95 | typedef struct Qform Qform; 96 | 97 | typedef enum { 98 | Qform_action_type_canceled, 99 | Qform_action_type_submitted, 100 | } Qform_action_type; 101 | typedef struct { 102 | Qform_action_type type; 103 | } Qform_action_any; 104 | typedef union { 105 | Qform_action_any any; 106 | } Qform_action; 107 | 108 | typedef enum { 109 | Qmsg_dismiss_mode_explicitly, // Space, return, escape dismisses. Default. 110 | Qmsg_dismiss_mode_easily, // Any key dismisses. 111 | Qmsg_dismiss_mode_passthrough, // Easily, and pass through key event. 112 | } Qmsg_dismiss_mode; 113 | 114 | typedef struct { 115 | bool dismiss : 1, passthrough : 1; 116 | } Qmsg_action; 117 | 118 | void qnav_init(void); 119 | void qnav_deinit(void); 120 | Qblock *qnav_top_block(void); 121 | void qnav_stack_pop(void); 122 | bool qnav_draw(void); // also clear qnav_stack.occlusion_dirty 123 | void qnav_adjust_term_size(void); 124 | 125 | void qblock_print_frame(Qblock *qb, bool active); 126 | void qblock_set_title(Qblock *qb, char const *title); 127 | 128 | Qmsg *qmsg_push(int height, int width); 129 | Qmsg *qmsg_printf_push(char const *title, char const *fmt, ...) 130 | ORCA_TERM_UTIL_PRINTF(2, 3); 131 | WINDOW *qmsg_window(Qmsg *qm); 132 | void qmsg_set_title(Qmsg *qm, char const *title); 133 | void qmsg_set_dismiss_mode(Qmsg *qm, Qmsg_dismiss_mode mode); 134 | bool qmsg_drive(Qmsg *qm, int key, Qmsg_action *out_action); 135 | Qmsg *qmsg_of(Qblock *qb); 136 | 137 | Qmenu *qmenu_create(int id); 138 | // Useful if menu creation needs to be aborted part-way. Otherwise, no need to 139 | // call -- pushing the qmenu to the qnav stack transfers ownership. (Still 140 | // working on this design, not sure yet.) 141 | void qmenu_destroy(Qmenu *qm); 142 | int qmenu_id(Qmenu const *qm); 143 | void qmenu_set_title(Qmenu *qm, char const *title); 144 | void qmenu_add_choice(Qmenu *qm, int id, char const *text); 145 | void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) 146 | ORCA_TERM_UTIL_PRINTF(3, 4); 147 | void qmenu_add_spacer(Qmenu *qm); 148 | void qmenu_set_current_item(Qmenu *qm, int id); 149 | void qmenu_push_to_nav(Qmenu *qm); 150 | int qmenu_current_item(Qmenu *qm); 151 | bool qmenu_drive(Qmenu *qm, int key, Qmenu_action *out_action); 152 | Qmenu *qmenu_of(Qblock *qb); 153 | bool qmenu_top_is_menu(int id); 154 | 155 | Qform *qform_create(int id); 156 | int qform_id(Qform const *qf); 157 | Qform *qform_of(Qblock *qb); 158 | void qform_set_title(Qform *qf, char const *title); 159 | void qform_add_line_input(Qform *qf, int id, char const *initial); 160 | void qform_push_to_nav(Qform *qf); 161 | void qform_single_line_input(int id, char const *title, char const* initial); 162 | bool qform_drive(Qform *qf, int key, Qform_action *out_action); 163 | bool qform_get_text_line(Qform const *qf, int id, struct oso **out); 164 | bool qform_get_single_text_line(Qform const *qf, struct oso **out); 165 | struct oso *qform_get_nonempty_single_line_input(Qform *qf); 166 | 167 | extern Qnav_stack qnav_stack; 168 | 169 | #undef ORCA_TERM_UTIL_PRINTF 170 | -------------------------------------------------------------------------------- /thirdparty/oso.c: -------------------------------------------------------------------------------- 1 | #include "oso.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute) 8 | #if __has_attribute(noinline) && __has_attribute(noclone) 9 | #define OSO_NOINLINE __attribute__((noinline, noclone)) 10 | #elif __has_attribute(noinline) 11 | #define OSO_NOINLINE __attribute__((noinline)) 12 | #endif 13 | #elif defined(_MSC_VER) 14 | #define OSO_NOINLINE __declspec(noinline) 15 | #endif 16 | #ifndef OSO_NOINLINE 17 | #define OSO_NOINLINE 18 | #endif 19 | 20 | #define OSO_INTERNAL OSO_NOINLINE static 21 | #define OSO_HDR(s) ((oso_header *)s - 1) 22 | #define OSO_CAP_MAX (SIZE_MAX - (sizeof(oso_header) + 1)) 23 | 24 | typedef struct oso { 25 | size_t len, cap; 26 | } oso_header; 27 | 28 | OSO_INTERNAL oso *oso_impl_reallochdr(oso_header *hdr, size_t new_cap) { 29 | if (hdr) { 30 | oso_header *new_hdr = realloc(hdr, sizeof(oso_header) + new_cap + 1); 31 | if (!new_hdr) { 32 | free(hdr); 33 | return NULL; 34 | } 35 | new_hdr->cap = new_cap; 36 | return new_hdr + 1; 37 | } 38 | hdr = malloc(sizeof(oso_header) + new_cap + 1); 39 | if (!hdr) 40 | return NULL; 41 | hdr->len = 0; 42 | hdr->cap = new_cap; 43 | ((char *)(hdr + 1))[0] = '\0'; 44 | return hdr + 1; 45 | } 46 | OSO_INTERNAL oso *oso_impl_catvprintf(oso *s, char const *fmt, va_list ap) { 47 | size_t old_len; 48 | int required; 49 | va_list cpy; 50 | va_copy(cpy, ap); 51 | required = vsnprintf(NULL, 0, fmt, cpy); 52 | va_end(cpy); 53 | osomakeroomfor(&s, (size_t)required); 54 | if (!s) 55 | return NULL; 56 | old_len = OSO_HDR(s)->len; 57 | vsnprintf((char *)s + old_len, (size_t)required + 1, fmt, ap); 58 | OSO_HDR(s)->len = old_len + (size_t)required; 59 | return s; 60 | } 61 | 62 | OSO_NOINLINE 63 | void osoensurecap(oso **p, size_t new_cap) { 64 | oso *s = *p; 65 | if (new_cap > OSO_CAP_MAX) { 66 | if (s) { 67 | free(OSO_HDR(s)); 68 | *p = NULL; 69 | } 70 | return; 71 | } 72 | oso_header *hdr = NULL; 73 | if (s) { 74 | hdr = OSO_HDR(s); 75 | if (hdr->cap >= new_cap) 76 | return; 77 | } 78 | *p = oso_impl_reallochdr(hdr, new_cap); 79 | } 80 | 81 | OSO_NOINLINE 82 | void osomakeroomfor(oso **p, size_t add_len) { 83 | oso *s = *p; 84 | oso_header *hdr = NULL; 85 | size_t new_cap; 86 | if (s) { 87 | hdr = OSO_HDR(s); 88 | size_t len = hdr->len, cap = hdr->cap; 89 | if (len > OSO_CAP_MAX - add_len) { // overflow, goodnight 90 | free(hdr); 91 | *p = NULL; 92 | return; 93 | } 94 | new_cap = len + add_len; 95 | if (cap >= new_cap) 96 | return; 97 | } else { 98 | if (add_len > OSO_CAP_MAX) 99 | return; 100 | new_cap = add_len; 101 | } 102 | *p = oso_impl_reallochdr(hdr, new_cap); 103 | } 104 | 105 | void osoput(oso **p, char const *restrict cstr) { 106 | osoputlen(p, cstr, strlen(cstr)); 107 | } 108 | 109 | OSO_NOINLINE 110 | void osoputlen(oso **p, char const *restrict cstr, size_t len) { 111 | oso *s = *p; 112 | osoensurecap(&s, len); 113 | if (s) { 114 | OSO_HDR(s)->len = len; 115 | memcpy((char *)s, cstr, len); 116 | ((char *)s)[len] = '\0'; 117 | } 118 | *p = s; 119 | } 120 | void osoputoso(oso **p, oso const *other) { 121 | if (!other) 122 | return; 123 | osoputlen(p, (char const *)other, OSO_HDR(other)->len); 124 | } 125 | void osoputvprintf(oso **p, char const *fmt, va_list ap) { 126 | oso *s = *p; 127 | if (s) { 128 | OSO_HDR(s)->len = 0; 129 | ((char *)s)[0] = '\0'; 130 | } 131 | *p = oso_impl_catvprintf(s, fmt, ap); 132 | } 133 | void osoputprintf(oso **p, char const *fmt, ...) { 134 | oso *s = *p; 135 | if (s) { 136 | OSO_HDR(s)->len = 0; 137 | ((char *)s)[0] = '\0'; 138 | } 139 | va_list ap; 140 | va_start(ap, fmt); 141 | *p = oso_impl_catvprintf(s, fmt, ap); 142 | va_end(ap); 143 | } 144 | void osocat(oso **p, char const *cstr) { osocatlen(p, cstr, strlen(cstr)); } 145 | OSO_NOINLINE 146 | void osocatlen(oso **p, char const *cstr, size_t len) { 147 | oso *s = *p; 148 | osomakeroomfor(&s, len); 149 | if (s) { 150 | oso_header *hdr = OSO_HDR(s); 151 | size_t curr_len = hdr->len; 152 | memcpy((char *)s + curr_len, cstr, len); 153 | ((char *)s)[curr_len + len] = '\0'; 154 | hdr->len = curr_len + len; 155 | } 156 | *p = s; 157 | } 158 | void osocatoso(oso **p, oso const *other) { 159 | if (!other) 160 | return; 161 | osocatlen(p, (char const *)other, OSO_HDR(other)->len); 162 | } 163 | void osocatvprintf(oso **p, char const *fmt, va_list ap) { 164 | *p = oso_impl_catvprintf(*p, fmt, ap); 165 | } 166 | void osocatprintf(oso **p, char const *fmt, ...) { 167 | va_list ap; 168 | va_start(ap, fmt); 169 | *p = oso_impl_catvprintf(*p, fmt, ap); 170 | va_end(ap); 171 | } 172 | void osoclear(oso **p) { 173 | oso *s = *p; 174 | if (!s) 175 | return; 176 | OSO_HDR(s)->len = 0; 177 | ((char *)s)[0] = '\0'; 178 | } 179 | void osofree(oso *s) { 180 | if (s) 181 | free(OSO_HDR(s)); 182 | } 183 | void osowipe(oso **p) { 184 | osofree(*p); 185 | *p = NULL; 186 | } 187 | void ososwap(oso **a, oso **b) { 188 | oso *tmp = *a; 189 | *a = *b; 190 | *b = tmp; 191 | } 192 | void osopokelen(oso *s, size_t len) { OSO_HDR(s)->len = len; } 193 | size_t osolen(oso const *s) { return s ? OSO_HDR(s)->len : 0; } 194 | size_t osocap(oso const *s) { return s ? OSO_HDR(s)->cap : 0; } 195 | void osolencap(oso const *s, size_t *out_len, size_t *out_cap) { 196 | if (!s) { 197 | *out_len = 0; 198 | *out_cap = 0; 199 | return; 200 | } 201 | oso_header *hdr = OSO_HDR(s); 202 | *out_len = hdr->len; 203 | *out_cap = hdr->cap; 204 | } 205 | size_t osoavail(oso const *s) { 206 | if (!s) 207 | return 0; 208 | oso_header *h = OSO_HDR(s); 209 | return h->cap - h->len; 210 | } 211 | 212 | void osotrim(oso *restrict s, char const *restrict cut_set) { 213 | if (!s) 214 | return; 215 | char *str, *end, *start_pos, *end_pos; 216 | start_pos = str = (char *)s; 217 | end_pos = end = str + OSO_HDR(s)->len - 1; 218 | while (start_pos <= end && strchr(cut_set, *start_pos)) 219 | start_pos++; 220 | while (end_pos > start_pos && strchr(cut_set, *end_pos)) 221 | end_pos--; 222 | size_t len = (start_pos > end_pos) ? 0 : ((size_t)(end_pos - start_pos) + 1); 223 | OSO_HDR(s)->len = len; 224 | if (str != start_pos) 225 | memmove(str, start_pos, len); 226 | str[len] = '\0'; 227 | } 228 | 229 | #undef OSO_HDR 230 | #undef OSO_NOINLINE 231 | #undef OSO_CAP_MAX 232 | #undef OSO_INTERNAL 233 | -------------------------------------------------------------------------------- /thirdparty/oso.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Heap-allocated string handling. 3 | // Inspired by antirez's sds and gingerBill's gb_string.h. 4 | // 5 | // "I need null-terminated strings to interact with libc and/or POSIX, and my 6 | // software isn't serious enough to warrant using arena or page allocation 7 | // strategies. Manually fussing with null-terminated strings with libc sucks, 8 | // even though we're allocating everything individually on the heap! Why can't 9 | // we at least get a nicer interface for the trade-off we've already made?" 10 | // 11 | // EXAMPLE 12 | // --------- 13 | // oso *mystring = NULL; 14 | // osoput(&mystring, "Hello World"); 15 | // printf((char *)mystring); 16 | // osoput(&mystring, "How about some pancakes?"); 17 | // printf((char *)mystring); 18 | // osocat(&mstring, " Sure!"); 19 | // printf((char *)mystring); 20 | // osofree(mystring); 21 | // 22 | // > Hello World! 23 | // > How about some pancakes? 24 | // > How about some pancakes? Sure! 25 | // 26 | // RULES 27 | // ------- 28 | // 1. `oso *` can always be cast to `char *`, but it's your job to check if 29 | // it's null before passing it to on something that doesn't tolerate null 30 | // pointers. 31 | // 32 | // 2. The functions defined in this header tolerate null pointers like this: 33 | // 34 | // `oso *` -> OK to be null. 35 | // `char const *` -> Must not be null. 36 | // `oso **` -> Must not be null, but the `oso *` pointed to 37 | // can be null. The pointed-to `oso *` may be 38 | // modified during the call. 39 | // 40 | // 3. `oso *` and `char const *` as arguments to the functions here must not 41 | // overlap in memory. During the call, the buffer pointed to by a `oso *` 42 | // might need to be reallocated in memory to make room for the `char const 43 | // *` contents, and if the `char const *` contents end up being moved by 44 | // that operation, the pointer will no longer be pointing at valid memory. 45 | // (This also applies to functions which accept two `oso *` as inputs.) 46 | // 47 | // Parameters with the type `oso *` are safe to pass as a null pointer. But 48 | // `oso **` parameters shouldn't be null. The `oso *` pointed to by the `oso 49 | // **` can be null, though. In other words, you need to do `&mystring` to pass 50 | // a `oso **` argument. 51 | // 52 | // During the function call, if the `oso *` pointed to by the `oso **` needs to 53 | // become non-null, or if the existing buffer needs to be moved to grow larger, 54 | // the `oso *` will be set to a new value. 55 | // 56 | // oso *mystring = NULL; 57 | // osolen(mystring); // Gives 0 58 | // osocat(&mystring, "waffles"); 59 | // osolen(mystring); // Gives 7 60 | // osofree(mystring); 61 | // 62 | // NAMES 63 | // ------- 64 | // osoput______ -> Copy a string into an oso string. 65 | // osocat______ -> Append a string onto the end of an oso string. 66 | // ______len -> Do it with an explicit length argument, so the C-string 67 | // doesn't have to be '\0'-terminated. 68 | // ______oso -> Do it with a second oso string. 69 | // ______printf -> Do it by using printf. 70 | // 71 | // ALLOC FAILURE 72 | // --------------- 73 | // If an allocation fails (including failing to reallocate) the `oso *` will be 74 | // set to null. If you decide to handle memory allocation failures, you'll need 75 | // to check for that. 76 | // 77 | // In the oso code, if a reallocation of an existing buffer fails (`realloc()` 78 | // returns null) then the old, still-valid buffer is immediately freed. 79 | // Therefore, in an out-of-memory situation, it's possible that you will *lose* 80 | // your strings without getting a chance to do something with the old buffers. 81 | // 82 | // Variations of the oso functions may be added in the future, with a _c suffix 83 | // or something, which preserve the old buffer and return an error code in the 84 | // event of a reallocation failure. I'm not sure how important this is, since 85 | // most existing libc-based software doesn't handle out-of-memory conditions 86 | // for small strings without imploding. 87 | // 88 | // (sds, for example, will lose your string *and* leak the old buffer if a 89 | // reallocation fails.) 90 | 91 | #include 92 | #include 93 | 94 | #if (defined(__GNUC__) || defined(__clang__)) && defined(__has_attribute) 95 | #if __has_attribute(format) 96 | #define OSO_PRINTF(...) __attribute__((format(printf, __VA_ARGS__))) 97 | #endif 98 | #if __has_attribute(nonnull) 99 | #define OSO_NONNULL(...) __attribute__((nonnull(__VA_ARGS__))) 100 | #endif 101 | #endif 102 | #ifndef OSO_PRINTF 103 | #define OSO_PRINTF(...) 104 | #endif 105 | #ifndef OSO_NONNULL 106 | #define OSO_NONNULL(...) 107 | #endif 108 | 109 | typedef struct oso oso; 110 | 111 | #define osoc(s) ((char const *)s) 112 | 113 | void osoput(oso **p, char const *cstr) OSO_NONNULL(); 114 | // ^- Copies the '\0'-terminated string into the `oso *` string located at 115 | // `*p`. If `*p` is null or there isn't enough capacity to hold `cstr`, it 116 | // will be reallocated. The pointer value at `*p` will be updated if 117 | // necessary. `*p` and `cstr` must not point to overlapping memory. 118 | void osoputlen(oso **p, char const *cstr, size_t len) OSO_NONNULL(); 119 | // ^- Same as above, but uses an additional parameter that specifies the length 120 | // of `cstr, and `cstr` does not have to be '\0'-terminated. 121 | // `*p` and `cstr` must not point to overlapping memory. 122 | void osoputoso(oso **p, oso const *other) OSO_NONNULL(1); 123 | // ^- Same as above, but using another `oso`. `*p` and `other` must not point 124 | // to overlapping memory. 125 | void osoputvprintf(oso **p, char const *fmt, va_list ap) OSO_NONNULL(1, 2) 126 | OSO_PRINTF(2, 0); 127 | void osoputprintf(oso **p, char const *fmt, ...) OSO_NONNULL(1, 2) 128 | OSO_PRINTF(2, 3); 129 | // ^- Same as above, but do it by using printf. 130 | 131 | void osocat(oso **p, char const *cstr) OSO_NONNULL(); 132 | void osocatlen(oso **p, char const *cstr, size_t len) OSO_NONNULL(); 133 | void osocatoso(oso **p, oso const *other) OSO_NONNULL(1); 134 | void osocatvprintf(oso **p, char const *fmt, va_list ap) OSO_NONNULL(1, 2) 135 | OSO_PRINTF(2, 0); 136 | void osocatprintf(oso **p, char const *fmt, ...) OSO_NONNULL(1, 2) 137 | OSO_PRINTF(2, 3); 138 | // ^- Append string to oso string. Same rules as `osoput` family. 139 | 140 | void osoensurecap(oso **p, size_t cap) OSO_NONNULL(); 141 | // ^- Ensure that s has at least `cap` memory allocated for it. This does not 142 | // care about the strlen of the characters or the prefixed length count -- 143 | // only the backing memory allocation. 144 | void osomakeroomfor(oso **p, size_t len) OSO_NONNULL(); 145 | // ^- Ensure that s has enough allocated space after the '\0'-terminnation 146 | // character to hold an additional add_len characters. It does not adjust 147 | // the `length` number value, only the capacity, if necessary. 148 | // 149 | // Both `osoensurecap()` and `osomakeroomfor()` can be used to avoid making 150 | // multiple smaller reallactions as the string grows in the future. You can 151 | // also use them if you're going to modify the string buffer manually in 152 | // your own code, and need to create some space in the buffer. 153 | 154 | void osoclear(oso **p) OSO_NONNULL(); 155 | // ^- Set len to 0, write '\0' at pos 0. Leaves allocated memory in place. 156 | void osofree(oso *s); 157 | // ^- You know. And calling with null is allowed. 158 | void osowipe(oso **p) OSO_NONNULL(); 159 | // ^- It's like `osofree()`, except you give it a ptr-to-ptr, and it also sets 160 | // `*p` to null for you when it's done freeing the memory. 161 | void ososwap(oso **a, oso **b) OSO_NONNULL(); 162 | // ^- Swaps the two pointers. Yeah, that's all it does. Why? Because it's 163 | // common when dealing memory management for individually allocated strings 164 | // and changing between old and new string values. 165 | void osopokelen(oso *s, size_t len) OSO_NONNULL(); 166 | // ^- Manually updates length field. Doesn't do anything else for you. 167 | 168 | size_t osolen(oso const *s); 169 | // ^- Bytes in use by the string (not including the '\0' character.) 170 | size_t osocap(oso const *s); 171 | // ^- Bytes allocated on heap (not including the '\0' terminator.) 172 | void osolencap(oso const *s, size_t *out_len, size_t *out_cap) 173 | OSO_NONNULL(2, 3); 174 | // ^- Get both the len and the cap in one call. 175 | size_t osoavail(oso const *s); 176 | // ^- osocap(s) - osolen(s) 177 | 178 | void osotrim(oso *restrict s, char const *restrict cut_set) OSO_NONNULL(2); 179 | // ^- Remove the characters in `cut_set` from the beginning and ending of `s`. 180 | 181 | #undef OSO_PRINTF 182 | #undef OSO_NONNULL 183 | -------------------------------------------------------------------------------- /thirdparty/sokol_time.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* 3 | sokol_time.h -- simple cross-platform time measurement 4 | 5 | Do this: 6 | #define SOKOL_IMPL 7 | before you include this file in *one* C or C++ file to create the 8 | implementation. 9 | 10 | Optionally provide the following defines with your own implementations: 11 | SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) 12 | SOKOL_API_DECL - public function declaration prefix (default: extern) 13 | SOKOL_API_IMPL - public function implementation prefix (default: -) 14 | 15 | void stm_setup(); 16 | Call once before any other functions to initialize sokol_time 17 | (this calls for instance QueryPerformanceFrequency on Windows) 18 | 19 | uint64_t stm_now(); 20 | Get current point in time in unspecified 'ticks'. The value that 21 | is returned has no relation to the 'wall-clock' time and is 22 | not in a specific time unit, it is only useful to compute 23 | time differences. 24 | 25 | uint64_t stm_diff(uint64_t new, uint64_t old); 26 | Computes the time difference between new and old. This will always 27 | return a positive, non-zero value. 28 | 29 | uint64_t stm_since(uint64_t start); 30 | Takes the current time, and returns the elapsed time since start 31 | (this is a shortcut for "stm_diff(stm_now(), start)") 32 | 33 | uint64_t stm_laptime(uint64_t* last_time); 34 | This is useful for measuring frame time and other recurring 35 | events. It takes the current time, returns the time difference 36 | to the value in last_time, and stores the current time in 37 | last_time for the next call. If the value in last_time is 0, 38 | the return value will be zero (this usually happens on the 39 | very first call). 40 | 41 | Use the following functions to convert a duration in ticks into 42 | useful time units: 43 | 44 | double stm_sec(uint64_t ticks); 45 | double stm_ms(uint64_t ticks); 46 | double stm_us(uint64_t ticks); 47 | double stm_ns(uint64_t ticks); 48 | Converts a tick value into seconds, milliseconds, microseconds 49 | or nanoseconds. Note that not all platforms will have nanosecond 50 | or even microsecond precision. 51 | 52 | Uses the following time measurement functions under the hood: 53 | 54 | Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() 55 | MacOS/iOS: mach_absolute_time() 56 | emscripten: clock_gettime(CLOCK_MONOTONIC) 57 | Linux+others: clock_gettime(CLOCK_MONITONIC) 58 | 59 | zlib/libpng license 60 | 61 | Copyright (c) 2018 Andre Weissflog 62 | 63 | This software is provided 'as-is', without any express or implied warranty. 64 | In no event will the authors be held liable for any damages arising from the 65 | use of this software. 66 | 67 | Permission is granted to anyone to use this software for any purpose, 68 | including commercial applications, and to alter it and redistribute it 69 | freely, subject to the following restrictions: 70 | 71 | 1. The origin of this software must not be misrepresented; you must not 72 | claim that you wrote the original software. If you use this software in a 73 | product, an acknowledgment in the product documentation would be 74 | appreciated but is not required. 75 | 76 | 2. Altered source versions must be plainly marked as such, and must not 77 | be misrepresented as being the original software. 78 | 79 | 3. This notice may not be removed or altered from any source 80 | distribution. 81 | */ 82 | #include 83 | 84 | #ifndef SOKOL_API_DECL 85 | #define SOKOL_API_DECL extern 86 | #endif 87 | 88 | #ifdef __cplusplus 89 | extern "C" { 90 | #endif 91 | 92 | SOKOL_API_DECL void stm_setup(void); 93 | SOKOL_API_DECL uint64_t stm_now(void); 94 | SOKOL_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); 95 | SOKOL_API_DECL uint64_t stm_since(uint64_t start_ticks); 96 | SOKOL_API_DECL uint64_t stm_laptime(uint64_t* last_time); 97 | SOKOL_API_DECL double stm_sec(uint64_t ticks); 98 | SOKOL_API_DECL double stm_ms(uint64_t ticks); 99 | SOKOL_API_DECL double stm_us(uint64_t ticks); 100 | SOKOL_API_DECL double stm_ns(uint64_t ticks); 101 | 102 | #ifdef __cplusplus 103 | } /* extern "C" */ 104 | #endif 105 | 106 | /*-- IMPLEMENTATION ----------------------------------------------------------*/ 107 | #ifdef SOKOL_IMPL 108 | 109 | #ifndef SOKOL_API_IMPL 110 | #define SOKOL_API_IMPL 111 | #endif 112 | #ifndef SOKOL_ASSERT 113 | #include 114 | #define SOKOL_ASSERT(c) assert(c) 115 | #endif 116 | #ifndef _SOKOL_PRIVATE 117 | #if defined(__GNUC__) 118 | #define _SOKOL_PRIVATE __attribute__((unused)) static 119 | #else 120 | #define _SOKOL_PRIVATE static 121 | #endif 122 | #endif 123 | 124 | static int _stm_initialized; 125 | #if defined(_WIN32) 126 | #ifndef WIN32_LEAN_AND_MEAN 127 | #define WIN32_LEAN_AND_MEAN 128 | #endif 129 | #include 130 | static LARGE_INTEGER _stm_win_freq; 131 | static LARGE_INTEGER _stm_win_start; 132 | #elif defined(__APPLE__) && defined(__MACH__) 133 | #include 134 | static mach_timebase_info_data_t _stm_osx_timebase; 135 | static uint64_t _stm_osx_start; 136 | #else /* anything else, this will need more care for non-Linux platforms */ 137 | #include 138 | static uint64_t _stm_posix_start; 139 | #endif 140 | 141 | /* prevent 64-bit overflow when computing relative timestamp 142 | see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 143 | */ 144 | #if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) 145 | _SOKOL_PRIVATE int64_t int64_muldiv(int64_t value, int64_t numer, int64_t denom) { 146 | int64_t q = value / denom; 147 | int64_t r = value % denom; 148 | return q * numer + r * numer / denom; 149 | } 150 | #endif 151 | 152 | 153 | SOKOL_API_IMPL void stm_setup(void) { 154 | SOKOL_ASSERT(0 == _stm_initialized); 155 | _stm_initialized = 1; 156 | #if defined(_WIN32) 157 | QueryPerformanceFrequency(&_stm_win_freq); 158 | QueryPerformanceCounter(&_stm_win_start); 159 | #elif defined(__APPLE__) && defined(__MACH__) 160 | mach_timebase_info(&_stm_osx_timebase); 161 | _stm_osx_start = mach_absolute_time(); 162 | #else 163 | struct timespec ts; 164 | clock_gettime(CLOCK_MONOTONIC, &ts); 165 | _stm_posix_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; 166 | #endif 167 | } 168 | 169 | SOKOL_API_IMPL uint64_t stm_now(void) { 170 | SOKOL_ASSERT(_stm_initialized); 171 | uint64_t now; 172 | #if defined(_WIN32) 173 | LARGE_INTEGER qpc_t; 174 | QueryPerformanceCounter(&qpc_t); 175 | now = int64_muldiv(qpc_t.QuadPart - _stm_win_start.QuadPart, 1000000000, _stm_win_freq.QuadPart); 176 | #elif defined(__APPLE__) && defined(__MACH__) 177 | const uint64_t mach_now = mach_absolute_time() - _stm_osx_start; 178 | now = int64_muldiv(mach_now, _stm_osx_timebase.numer, _stm_osx_timebase.denom); 179 | #else 180 | struct timespec ts; 181 | clock_gettime(CLOCK_MONOTONIC, &ts); 182 | now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm_posix_start; 183 | #endif 184 | return now; 185 | } 186 | 187 | SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { 188 | if (new_ticks > old_ticks) { 189 | return new_ticks - old_ticks; 190 | } 191 | else { 192 | /* FIXME: this should be a value that converts to a non-null double */ 193 | return 1; 194 | } 195 | } 196 | 197 | SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { 198 | return stm_diff(stm_now(), start_ticks); 199 | } 200 | 201 | SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { 202 | SOKOL_ASSERT(last_time); 203 | uint64_t dt = 0; 204 | uint64_t now = stm_now(); 205 | if (0 != *last_time) { 206 | dt = stm_diff(now, *last_time); 207 | } 208 | *last_time = now; 209 | return dt; 210 | } 211 | 212 | SOKOL_API_IMPL double stm_sec(uint64_t ticks) { 213 | return (double)ticks / 1000000000.0; 214 | } 215 | 216 | SOKOL_API_IMPL double stm_ms(uint64_t ticks) { 217 | return (double)ticks / 1000000.0; 218 | } 219 | 220 | SOKOL_API_IMPL double stm_us(uint64_t ticks) { 221 | return (double)ticks / 1000.0; 222 | } 223 | 224 | SOKOL_API_IMPL double stm_ns(uint64_t ticks) { 225 | return (double)ticks; 226 | } 227 | #endif /* SOKOL_IMPL */ 228 | 229 | -------------------------------------------------------------------------------- /tool: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -euf 3 | 4 | print_usage() { 5 | cat < [options] [arguments] 7 | Example: 8 | tool build --portmidi orca 9 | Commands: 10 | build 11 | Compiles the livecoding environment or the CLI tool. 12 | Targets: orca, cli 13 | Output: build/ 14 | clean 15 | Removes build/ 16 | info 17 | Prints information about the detected build environment. 18 | help 19 | Prints this message and exits. 20 | Options: 21 | -c Use a specific compiler binary. Default: \$CC, or cc 22 | -d Build with debug features. Output changed to: 23 | build/debug/ 24 | --harden Enable compiler safeguards like -fstack-protector. 25 | You should probably do this if you plan to give the 26 | compiled binary to other people. 27 | --static Build static binary. 28 | --pie Enable PIE (ASLR). 29 | Note: --pie and --static cannot be mixed. 30 | -s Print statistics about compile time and binary size. 31 | -v Print important commands as they're executed. 32 | -h or --help Print this message and exit. 33 | Optional Features: 34 | --portmidi Enable or disable hardware MIDI output support with 35 | --no-portmidi PortMidi. Note: PortMidi has memory leaks and bugs. 36 | Default: disabled. 37 | --mouse Enable or disable mouse features in the livecoding 38 | --no-mouse environment. 39 | Default: enabled. 40 | EOF 41 | } 42 | 43 | warn() { printf 'Warning: %s\n' "$*" >&2; } 44 | fatal() { printf 'Error: %s\n' "$*" >&2; exit 1; } 45 | script_error() { printf 'Script error: %s\n' "$*" >&2; exit 1; } 46 | 47 | if [ -z "${1:-}" ]; then 48 | printf 'Error: Command required\n' >&2 49 | print_usage >&2 50 | exit 1 51 | fi 52 | 53 | cmd=$1 54 | shift 55 | 56 | case $(uname -s | awk '{print tolower($0)}') in 57 | linux*) os=linux;; 58 | darwin*) os=mac;; 59 | cygwin*) os=cygwin;; 60 | *bsd*) os=bsd;; 61 | *) os=unknown; warn "Build script not tested on this platform";; 62 | esac 63 | 64 | cc_exe="${CC:-cc}" 65 | 66 | if [ $os = cygwin ]; then 67 | # Under cygwin, specifically ignore the mingw compilers if they're set as the 68 | # CC environment variable. This may be the default from the cygwin installer. 69 | # But we want to use 'gcc' from the cygwin gcc-core package (probably aliased 70 | # to cc), *not* the mingw compiler, because otherwise lots of POSIX stuff 71 | # will break. (Note that the 'cli' target might be fine, because it doesn't 72 | # uses curses or networking, but the 'orca' target almost certainly won't 73 | # be.) 74 | # 75 | # I'm worried about ambiguity with 'cc' being still aliased to mingw if the 76 | # user doesn't have gcc-core installed. I have no idea if that actually 77 | # happens. So we'll just explicitly set it to gcc. This might mess up people 78 | # who have clang installed but not gcc, I guess? Is that even possible? 79 | case $cc_exe in 80 | i686-w64-mingw32-gcc.exe|x86_64-w64-mingw32-gcc.exe) 81 | cc_exe=gcc;; 82 | esac 83 | fi 84 | 85 | verbose=0 86 | protections_enabled=0 87 | stats_enabled=0 88 | pie_enabled=0 89 | static_enabled=0 90 | portmidi_enabled=0 91 | mouse_disabled=0 92 | config_mode=release 93 | 94 | while getopts c:dhsv-: opt_val; do 95 | case $opt_val in 96 | -) case $OPTARG in 97 | harden) protections_enabled=1;; 98 | help) print_usage; exit 0;; 99 | static) static_enabled=1;; 100 | pie) pie_enabled=1;; 101 | portmidi) portmidi_enabled=1;; 102 | no-portmidi|noportmidi) portmidi_enabled=0;; 103 | mouse) mouse_disabled=0;; 104 | no-mouse|nomouse) mouse_disabled=1;; 105 | *) printf 'Unknown option --%s\n' "$OPTARG" >&2; exit 1;; 106 | esac;; 107 | c) cc_exe=$OPTARG;; 108 | d) config_mode=debug;; 109 | h) print_usage; exit 0;; 110 | s) stats_enabled=1;; 111 | v) verbose=1;; 112 | \?) print_usage >&2; exit 1;; 113 | esac 114 | done 115 | 116 | case $(uname -m) in 117 | x86_64) arch=x86_64;; 118 | *) arch=unknown;; 119 | esac 120 | 121 | verbose_echo() { 122 | # Don't print 'timed_stats' if it's the first part of the command 123 | if [ $verbose = 1 ] && [ $# -gt 1 ]; then 124 | printf '%s ' "$@" | sed -E -e 's/^timed_stats[[:space:]]+//' -e 's/ $//' \ 125 | | tr -d '\n' 126 | printf '\n' 127 | fi 128 | "$@" 129 | } 130 | 131 | file_size() { 132 | wc -c < "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' 133 | } 134 | 135 | timed_stats_result= 136 | timed_stats() { 137 | if [ $stats_enabled = 1 ] && command -v time >/dev/null 2>&1; then 138 | TIMEFORMAT='%3R' 139 | { timed_stats_result=$( { time "$@" 1>&3- 2>&4-; } 2>&1 ); } 3>&1 4>&2 140 | else 141 | "$@" 142 | fi 143 | } 144 | 145 | normalized_version() { 146 | printf '%s\n' "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; 147 | } 148 | 149 | cc_id= 150 | cc_vers= 151 | lld_detected=0 152 | lld_name=lld 153 | if preproc_result=$( \ 154 | ("$cc_exe" -E -xc - 2>/dev/null | tail -n 2 | tr -d '\040') </dev/null 2>&1; then lld_detected=1; fi 190 | ;; 191 | esac 192 | fi 193 | 194 | test -z "$cc_id" && warn "Failed to detect compiler type" 195 | test -z "$cc_vers" && warn "Failed to detect compiler version" 196 | 197 | cc_vers_normalized=$(normalized_version "$cc_vers") 198 | 199 | cc_vers_is_gte() { 200 | test "$cc_vers_normalized" -ge "$(normalized_version "$1")" 201 | } 202 | 203 | cc_id_and_vers_gte() { 204 | test "$cc_id" = "$1" && cc_vers_is_gte "$2" 205 | } 206 | 207 | # Append arguments to a string, separated by newlines. Like a bad array. 208 | add() { 209 | if [ -z "${1:-}" ]; then 210 | script_error "At least one argument required for add" 211 | fi 212 | _add_name=${1} 213 | shift 214 | while [ -n "${1+x}" ]; do 215 | # shellcheck disable=SC2034 216 | _add_hidden=$1 217 | eval "$_add_name"'=$(printf '"'"'%s\n%s.'"' "'"$'"$_add_name"'" "$_add_hidden")' 218 | eval "$_add_name"'=${'"$_add_name"'%.}' 219 | shift 220 | done 221 | } 222 | 223 | try_make_dir() { 224 | if ! [ -e "$1" ]; then 225 | verbose_echo mkdir "$1" 226 | elif ! [ -d "$1" ]; then 227 | fatal "File $1 already exists but is not a directory" 228 | fi 229 | } 230 | 231 | build_dir=build 232 | 233 | build_target() { 234 | cc_flags= 235 | libraries= 236 | source_files= 237 | out_exe= 238 | add cc_flags -std=c99 -pipe -finput-charset=UTF-8 -Wall -Wpedantic -Wextra \ 239 | -Wwrite-strings 240 | if cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 3.9.0; then 241 | add cc_flags -Wconversion -Wshadow -Wstrict-prototypes \ 242 | -Werror=implicit-function-declaration -Werror=implicit-int \ 243 | -Werror=incompatible-pointer-types -Werror=int-conversion 244 | fi 245 | if [ "$cc_id" = tcc ]; then 246 | add cc_flags -Wunsupported 247 | fi 248 | if [ $os = mac ] && [ "$cc_id" = clang ]; then 249 | # The clang that's shipped with Mac 10.12 has bad behavior for issuing 250 | # warnings for structs initialed with {0} in C99. We have to disable this 251 | # warning, or it will issue a bunch of useless warnings. It might be fixed 252 | # in later versions, but Apple makes the version of clang/LLVM 253 | # indecipherable, so we'll just always turn it off. 254 | add cc_flags -Wno-missing-field-initializers 255 | fi 256 | if [ $lld_detected = 1 ]; then 257 | add cc_flags "-fuse-ld=$lld_name" 258 | fi 259 | if [ $protections_enabled = 1 ]; then 260 | add cc_flags -D_FORTIFY_SOURCE=2 -fstack-protector-strong 261 | fi 262 | if [ $pie_enabled = 1 ]; then 263 | add cc_flags -pie -fpie -Wl,-pie 264 | # Only explicitly specify no-pie if cc version is new enough 265 | elif cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 6.0.0; then 266 | add cc_flags -no-pie -fno-pie 267 | fi 268 | if [ $static_enabled = 1 ]; then 269 | add cc_flags -static 270 | fi 271 | case $config_mode in 272 | debug) 273 | add cc_flags -DDEBUG -ggdb 274 | # cygwin gcc doesn't seem to have this stuff, so just elide for now 275 | if [ $os != cygwin ]; then 276 | if cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 3.9.0; then 277 | add cc_flags -fsanitize=address -fsanitize=undefined \ 278 | -fsanitize=float-divide-by-zero 279 | fi 280 | if cc_id_and_vers_gte clang 7.0.0; then 281 | add cc_flags -fsanitize=implicit-conversion \ 282 | -fsanitize=unsigned-integer-overflow 283 | fi 284 | fi 285 | case $os in 286 | mac) add cc_flags -O1;; # Our Mac clang does not have -Og 287 | *) add cc_flags -Og;; 288 | esac 289 | case $cc_id in 290 | tcc) add cc_flags -g -bt10;; 291 | esac 292 | ;; 293 | release) 294 | add cc_flags -DNDEBUG -O2 -g0 295 | if [ $protections_enabled != 1 ]; then 296 | add cc_flags -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 297 | case $cc_id in 298 | gcc|clang) add cc_flags -fno-stack-protector 299 | esac 300 | fi 301 | # -flto is good on both clang and gcc on Linux and Cygwin. Not supported 302 | # on BSD, and no improvement on Mac. -s gives an obsolescence warning on 303 | # Mac. For tcc, -flto gives and unsupported warning, and -s is ignored. 304 | case $cc_id in gcc|clang) case $os in 305 | linux|cygwin) add cc_flags -flto -s;; 306 | bsd) add cc_flags -s;; 307 | esac esac 308 | ;; 309 | *) fatal "Unknown build config \"$config_mode\"";; 310 | esac 311 | 312 | case $arch in 313 | x86_64) 314 | case $cc_id in 315 | # 'nehalem' tuning actually produces faster code for orca than later 316 | # archs, for both gcc and clang, even if it's running on a later arch 317 | # CPU. This is likely due to smaller emitted code size. gcc earlier 318 | # than 4.9 does not recognize the arch flag for it it, though, and I 319 | # haven't tested a compiler that old, so I don't know what optimization 320 | # behavior we get with it is. Just leave it at default, in that case. 321 | gcc) 322 | if cc_vers_is_gte 4.9; then 323 | add cc_flags -march=nehalem 324 | fi 325 | ;; 326 | clang) add cc_flags -march=nehalem;; 327 | esac 328 | ;; 329 | esac 330 | 331 | add source_files gbuffer.c field.c vmio.c sim.c 332 | case $1 in 333 | cli) 334 | add source_files cli_main.c 335 | out_exe=cli 336 | ;; 337 | orca|tui) 338 | add source_files osc_out.c term_util.c sysmisc.c thirdparty/oso.c tui_main.c 339 | add cc_flags -D_XOPEN_SOURCE_EXTENDED=1 340 | # thirdparty headers (like sokol_time.h) should get -isystem for their 341 | # include dir so that any warnings they generate with our warning flags 342 | # are ignored. (sokol_time.h may generate sign conversion warning on 343 | # mac.) 344 | add cc_flags -isystem thirdparty 345 | out_exe=orca 346 | case $os in 347 | mac) 348 | if ! brew_prefix=$(printenv HOMEBREW_PREFIX); then 349 | brew_prefix=/usr/local 350 | fi 351 | ncurses_dir="$brew_prefix/opt/ncurses" 352 | if ! [ -d "$ncurses_dir" ]; then 353 | printf 'Error: ncurses directory not found at %s\n' \ 354 | "$ncurses_dir" >&2 355 | printf 'Install with: brew install ncurses\n' >&2 356 | exit 1 357 | fi 358 | # prefer homebrew version of ncurses if installed. Will give us 359 | # better terminfo, so we can use A_DIM in Terminal.app, etc. 360 | add libraries "-L$ncurses_dir/lib" 361 | add cc_flags "-I$ncurses_dir/include" 362 | # todo mach time stuff for mac? 363 | if [ $portmidi_enabled = 1 ]; then 364 | portmidi_dir="$brew_prefix/opt/portmidi" 365 | if ! [ -d "$portmidi_dir" ]; then 366 | printf 'Error: PortMidi directory not found at %s\n' \ 367 | "$portmidi_dir" >&2 368 | printf 'Install with: brew install portmidi\n' >&2 369 | exit 1 370 | fi 371 | add libraries "-L$portmidi_dir/lib" 372 | add cc_flags "-I$portmidi_dir/include" 373 | fi 374 | # needed for using pbpaste instead of xclip 375 | add cc_flags -DORCA_OS_MAC 376 | ;; 377 | bsd) 378 | if [ $portmidi_enabled = 1 ]; then 379 | add libraries "-L/usr/local/lib" 380 | add cc_flags "-I/usr/local/include" 381 | fi 382 | ;; 383 | *) 384 | # librt and high-res posix timers on Linux 385 | add libraries -lrt 386 | add cc_flags -D_POSIX_C_SOURCE=200809L 387 | ;; 388 | esac 389 | # Depending on the Linux distro, ncurses might have been built with tinfo 390 | # as a separate library that explicitly needs to be linked, or it might 391 | # not. And if it does, it might need to be either -ltinfo or -ltinfow. 392 | # Yikes. If this is Linux, let's try asking pkg-config what it thinks. 393 | curses_flags=0 394 | if [ $os = linux ]; then 395 | if curses_flags=$(pkg-config --libs ncursesw formw 2>/dev/null); then 396 | # Split by spaces intentionall 397 | # shellcheck disable=SC2086 398 | IFS=' ' add libraries $curses_flags 399 | curses_flags=1 400 | else 401 | curses_flags=0 402 | fi 403 | fi 404 | # If we didn't get the flags by pkg-config, just guess. (This will work 405 | # most of the time, including on Mac with Homebrew, and cygwin.) 406 | if [ $curses_flags = 0 ]; then 407 | add libraries -lncursesw -lformw 408 | fi 409 | if [ $portmidi_enabled = 1 ]; then 410 | add libraries -lportmidi 411 | add cc_flags -DFEAT_PORTMIDI 412 | if [ $config_mode = debug ]; then 413 | cat >&2 <&2 426 | exit 1 427 | ;; 428 | esac 429 | try_make_dir "$build_dir" 430 | if [ $config_mode = debug ]; then 431 | build_dir=$build_dir/debug 432 | try_make_dir "$build_dir" 433 | fi 434 | out_path=$build_dir/$out_exe 435 | IFS=' 436 | ' 437 | # shellcheck disable=SC2086 438 | verbose_echo timed_stats "$cc_exe" $cc_flags -o "$out_path" $source_files $libraries 439 | compile_ok=$? 440 | if [ $stats_enabled = 1 ]; then 441 | if [ -n "$timed_stats_result" ]; then 442 | printf '%s\n' "time: $timed_stats_result" 443 | else 444 | printf '%s\n' "time: unavailable (missing 'time' command)" 445 | fi 446 | if [ $compile_ok = 0 ]; then 447 | printf '%s\n' "size: $(file_size "$out_path")" 448 | fi 449 | fi 450 | } 451 | 452 | print_info() { 453 | if [ $lld_detected = 1 ]; then 454 | linker_name=LLD 455 | # Not sure if we should always print the specific LLD name or not. Or never 456 | # print it. 457 | if [ "$lld_name" != lld ]; then 458 | linker_name="$linker_name ($lld_name)" 459 | fi 460 | else 461 | linker_name=default 462 | fi 463 | cat <&2 <&2 <buffer = NULL; 5 | olist->count = 0; 6 | olist->capacity = 0; 7 | } 8 | void oevent_list_deinit(Oevent_list *olist) { free(olist->buffer); } 9 | void oevent_list_clear(Oevent_list *olist) { olist->count = 0; } 10 | void oevent_list_copy(Oevent_list const *src, Oevent_list *dest) { 11 | Usz src_count = src->count; 12 | if (dest->capacity < src_count) { 13 | Usz new_cap = orca_round_up_power2(src_count); 14 | dest->buffer = realloc(dest->buffer, new_cap * sizeof(Oevent)); 15 | dest->capacity = new_cap; 16 | } 17 | memcpy(dest->buffer, src->buffer, src_count * sizeof(Oevent)); 18 | dest->count = src_count; 19 | } 20 | Oevent *oevent_list_alloc_item(Oevent_list *olist) { 21 | Usz count = olist->count; 22 | if (olist->capacity == count) { 23 | // Note: no overflow check, but you're probably out of memory if this 24 | // happens anyway. Like other uses of realloc in orca, we also don't check 25 | // for a failed allocation. 26 | Usz capacity = count < 16 ? 16 : orca_round_up_power2(count + 1); 27 | olist->buffer = realloc(olist->buffer, capacity * sizeof(Oevent)); 28 | olist->capacity = capacity; 29 | } 30 | Oevent *result = olist->buffer + count; 31 | olist->count = count + 1; 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /vmio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base.h" 3 | 4 | typedef enum { 5 | Oevent_type_midi_note, 6 | Oevent_type_midi_cc, 7 | Oevent_type_midi_pb, 8 | Oevent_type_osc_ints, 9 | Oevent_type_udp_string, 10 | } Oevent_types; 11 | 12 | typedef struct { 13 | U8 oevent_type; 14 | } Oevent_any; 15 | 16 | typedef struct { 17 | U8 oevent_type; 18 | U8 channel, octave, note, velocity, duration : 7, mono : 1; 19 | } Oevent_midi_note; 20 | 21 | typedef struct { 22 | U8 oevent_type; 23 | U8 channel, control, value; 24 | } Oevent_midi_cc; 25 | 26 | typedef struct { 27 | U8 oevent_type; 28 | U8 channel, lsb, msb; 29 | } Oevent_midi_pb; 30 | 31 | enum { Oevent_osc_int_count = 35 }; 32 | 33 | typedef struct { 34 | U8 oevent_type; 35 | Glyph glyph; 36 | U8 count; 37 | U8 numbers[Oevent_osc_int_count]; 38 | } Oevent_osc_ints; 39 | 40 | enum { Oevent_udp_string_count = 16 }; 41 | 42 | typedef struct { 43 | U8 oevent_type; 44 | U8 count; 45 | char chars[Oevent_udp_string_count]; 46 | } Oevent_udp_string; 47 | 48 | typedef union { 49 | Oevent_any any; 50 | Oevent_midi_note midi_note; 51 | Oevent_midi_cc midi_cc; 52 | Oevent_midi_pb midi_pb; 53 | Oevent_osc_ints osc_ints; 54 | Oevent_udp_string udp_string; 55 | } Oevent; 56 | 57 | typedef struct { 58 | Oevent *buffer; 59 | Usz count, capacity; 60 | } Oevent_list; 61 | 62 | void oevent_list_init(Oevent_list *olist); 63 | void oevent_list_deinit(Oevent_list *olist); 64 | void oevent_list_clear(Oevent_list *olist); 65 | ORCA_NOINLINE 66 | void oevent_list_copy(Oevent_list const *src, Oevent_list *dest); 67 | ORCA_NOINLINE 68 | Oevent *oevent_list_alloc_item(Oevent_list *olist); 69 | --------------------------------------------------------------------------------