├── .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 |
--------------------------------------------------------------------------------