├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── README.md
├── build-html.zig
├── build.zig
└── src
├── adsr.zig
├── host
├── browser.zig
├── browser
│ ├── index.html
│ ├── index.js
│ └── worklet.js
├── macos.zig
└── macos
│ ├── core_audio.zig
│ └── objc.zig
├── main.zig
├── midi.zig
├── synth.zig
├── util.zig
├── util
├── buffer_math.zig
└── parameters.zig
└── wavetable.zig
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | jobs:
4 | test:
5 | strategy:
6 | matrix:
7 | os: [macos-latest]
8 | runs-on: ${{matrix.os}}
9 | steps:
10 | - uses: actions/checkout@v2
11 | - name: Setup Zig
12 | uses: goto-bus-stop/setup-zig@v1.2.2
13 | with:
14 | version: master
15 | - name: Run tests
16 | run: zig build test
17 |
18 | build:
19 | runs-on: macos-latest
20 | needs: test
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Setup Zig
24 | uses: goto-bus-stop/setup-zig@v1.2.2
25 | with:
26 | version: master
27 | - name: Build in Debug mode
28 | run: zig build
29 | - name: Upload macOS build
30 | uses: actions/upload-artifact@v1.0.0
31 | with:
32 | name: zig-synth-macos
33 | path: zig-cache/bin/zig-synth
34 | - name: Upload browser build
35 | uses: actions/upload-artifact@v1.0.0
36 | with:
37 | name: zig-synth-browser.html
38 | path: zig-cache/bin/zig-synth.html
39 |
40 | publish-html:
41 | runs-on: macos-latest
42 | needs: test
43 | steps:
44 | - uses: actions/checkout@v2
45 | - name: Setup Zig
46 | uses: goto-bus-stop/setup-zig@v1.2.2
47 | with:
48 | version: master
49 | - name: Build
50 | run: "zig build -Drelease-fast && mkdir public && cp zig-cache/bin/zig-synth.html public/index.html"
51 | - name: Deploy to GitHub Pages
52 | if: success()
53 | uses: crazy-max/ghaction-github-pages@v1
54 | with:
55 | target_branch: gh-pages
56 | build_dir: public
57 | allow_empty_commit: false
58 | committer_name: github-actions
59 | committer_email: ""
60 | commit_message: "Deploy browser build to GitHub Pages"
61 | keep_history: true
62 | env:
63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | zig-cache/
2 | tags
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # zig-synth
2 | An audio synthesizer in Zig
3 |
4 | ### Roadmap
5 | - Sound
6 | - [ ] Polyphony
7 | - [x] Basic polyphony
8 | - [x] Voice stealing
9 | - [ ] Apply parameter updates across all voices
10 | - [ ] Unison
11 | - An efficient way to implement Unison could be to have multiple scanners per oscillator,
12 | where each one has a slightly offset step size. However, I need to look into that first.
13 | - [ ] Modulation
14 | - Modulation Matrix (?)
15 | - With Zigs comptime abilities it seems like there is a performant, but still very
16 | dynamic implementation for this.
17 | - Because you cannot have threads in WebAssembly, I will probably have to create a separate instance
18 | on the UI thread, where I calculate the modulated values and then send those to the audio worklet.
19 | - [ ] Filter
20 |
21 | - Tech
22 | - [ ] Windows (WASAPI) Support
23 | - [ ] Linux Support (I will need to look into possible APIs first)
24 | - [ ] Build as VST. I don't really know how VSTs work, but from what I read it seems like it's just a shared library
25 | with a set of defined exports.
26 | - [ ] User Interface
27 | - I plan on having multiple render backends, like:
28 | - [ ] Metal on macOS. When this is finished I don't see a problem with getting the whole thing to run on iOS either.
29 | - [ ] DirectX / OpenGL on Windows
30 | - [ ] WebGL in the browser. This is what I will target first, because it's the most approachable one for me..
31 | I will need to workout a proper API anyways. Maybe even a DOM-backed UI will work for that.
32 |
33 | ### Building
34 | First of all `git clone https://github.com/schroffl/zig-synth` this repository and run `zig build` in it.
35 |
36 | - The standalone version only runs on macOS for now. To start it execute `zig build run` in the cloned git repo.
37 | If you have a MIDI input device, you can go into `src/host/macos.zig` and set the proper `midi_hint` value.
38 |
39 | - The single-file browser build can be found in `zig-cache/bin/zig-synth.html`.
40 | Sadly, this only works in Chrome at the time of writing. Firefox et al. haven't implemented the
41 | Audio Worklet API yet. Ironically, since Microsoft Edge now uses Chromium it probably runs there too.
42 |
43 | ### Useful Resources
44 | A list of useful resources that I find during development:
45 |
46 | * [EarLevel Engineering by Nigel Redmon](https://www.earlevel.com/main/), specifically the [wavetable oscillator series](https://www.earlevel.com/main/2012/05/03/a-wavetable-oscillator%e2%80%94introduction/)
47 | * [Using Objective-C from js-ctypes](https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes/Examples/Using_Objective-C_from_js-ctypes)
48 | * [Bandlimited oscillators](https://hackaday.io/project/157580-hgsynth/log/145886-bandlimited-oscillators)
49 | * [Sound Synthesis Theory/Oscillators and Wavetables](https://en.wikibooks.org/wiki/Sound_Synthesis_Theory/Oscillators_and_Wavetables)
50 | * [The New Wave: An In-Depth Look At Live 10's Wavetable](https://www.ableton.com/en/blog/new-wave-depth-look-wavetable/)
51 | * Basically every article on Aliasing, Sample Rates, Band-Limiting, Fourier Transforms and related math (Take a look at 3Blue1Brown on YouTube)
52 |
--------------------------------------------------------------------------------
/build-html.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const fs = std.fs;
3 |
4 | pub fn main() !void {
5 | var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
6 | var allocator = &arena_allocator.allocator;
7 | defer arena_allocator.deinit();
8 |
9 | const args = try std.process.argsAlloc(allocator);
10 | defer std.process.argsFree(allocator, args);
11 |
12 | if (args.len < 3) return error.MissingArguments;
13 |
14 | const cwd = fs.cwd();
15 | const browser_dir = try cwd.openDir("src/host/browser", .{});
16 |
17 | const wasm_path = args[1];
18 | const wasm_file = try cwd.openFile(wasm_path, .{});
19 | const wasm_content = try readFileAlloc(allocator, wasm_file);
20 | defer wasm_file.close();
21 | defer allocator.free(wasm_content);
22 |
23 | const encoded_wasm = try encodeBase64(allocator, wasm_content);
24 | defer allocator.free(encoded_wasm);
25 |
26 | var sources = try Sources.init(allocator, browser_dir);
27 | defer sources.deinit();
28 |
29 | const worklet_base64 = try encodeBase64(allocator, sources.worklet_js);
30 | defer allocator.free(worklet_base64);
31 |
32 | const with_worklet = try replace(allocator, sources.html, "WORKLET_SOURCE", worklet_base64);
33 | defer allocator.free(with_worklet);
34 | const with_wasm = try replace(allocator, with_worklet, "WASM_BINARY", encoded_wasm);
35 | defer allocator.free(with_wasm);
36 | const with_main = try replace(allocator, with_wasm, "MAIN_SOURCE", sources.index_js);
37 | defer allocator.free(with_main);
38 |
39 | const hash = std.hash.Murmur2_64.hash(with_main);
40 | const hash_hex = try std.fmt.allocPrint(allocator, "{x}", .{hash});
41 | defer allocator.free(hash_hex);
42 |
43 | const with_hash = try replace(allocator, with_main, "VERSION_HASH", hash_hex);
44 | defer allocator.free(with_hash);
45 |
46 | const out_path = args[2];
47 | var out = try cwd.createFile(out_path, .{});
48 | _ = try out.write(with_hash);
49 | }
50 |
51 | const Sources = struct {
52 | allocator: *std.mem.Allocator,
53 | html: []const u8,
54 | index_js: []const u8,
55 | worklet_js: []const u8,
56 |
57 | fn init(allocator: *std.mem.Allocator, dir: fs.Dir) !Sources {
58 | var self = Sources{
59 | .allocator = allocator,
60 | .html = undefined,
61 | .index_js = undefined,
62 | .worklet_js = undefined,
63 | };
64 |
65 | const html_file = try dir.openFile("index.html", .{});
66 | defer html_file.close();
67 |
68 | const index_js = try dir.openFile("index.js", .{});
69 | defer index_js.close();
70 |
71 | const worklet_js = try dir.openFile("worklet.js", .{});
72 | defer worklet_js.close();
73 |
74 | self.html = try readFileAlloc(allocator, html_file);
75 | self.index_js = try readFileAlloc(allocator, index_js);
76 | self.worklet_js = try readFileAlloc(allocator, worklet_js);
77 |
78 | return self;
79 | }
80 |
81 | fn deinit(self: *Sources) void {
82 | self.allocator.free(self.html);
83 | self.allocator.free(self.index_js);
84 | self.allocator.free(self.worklet_js);
85 | }
86 | };
87 |
88 | fn readFileAlloc(allocator: *std.mem.Allocator, file: fs.File) ![]u8 {
89 | const size = (try file.stat()).size;
90 | var buffer = try allocator.alloc(u8, size);
91 |
92 | _ = try file.readAll(buffer);
93 |
94 | return buffer;
95 | }
96 |
97 | fn encodeBase64(allocator: *std.mem.Allocator, src: []const u8) ![]u8 {
98 | const size = std.base64.Base64Encoder.calcSize(src.len);
99 | var buf = try allocator.alloc(u8, size);
100 |
101 | std.base64.standard_encoder.encode(buf, src);
102 |
103 | return buf;
104 | }
105 |
106 | fn replace(
107 | allocator: *std.mem.Allocator,
108 | src: []const u8,
109 | needle: []const u8,
110 | replacement: []const u8,
111 | ) ![]u8 {
112 | const dest = try allocator.alloc(u8, src.len - needle.len + replacement.len);
113 | errdefer allocator.free(dest);
114 |
115 | const index = std.mem.indexOf(u8, src, needle) orelse return error.NotFound;
116 |
117 | std.mem.copy(u8, dest[0..index], src[0..index]);
118 | std.mem.copy(u8, dest[index..], replacement);
119 | std.mem.copy(u8, dest[index + replacement.len ..], src[index + needle.len ..]);
120 |
121 | return dest;
122 | }
123 |
--------------------------------------------------------------------------------
/build.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Builder = std.build.Builder;
3 |
4 | pub fn build(b: *Builder) !void {
5 | // Standalone build
6 | {
7 | const mode = b.standardReleaseOptions();
8 | const target = b.standardTargetOptions(.{});
9 | const exe = b.addExecutable("zig-synth", "src/main.zig");
10 | exe.setTarget(target);
11 | exe.setBuildMode(mode);
12 | exe.install();
13 |
14 | switch (target.os_tag orelse std.builtin.os.tag) {
15 | .macosx => {
16 | const sdk_path_raw = try b.exec(&[_][]const u8{
17 | "xcrun",
18 | "-show-sdk-path",
19 | });
20 |
21 | const sdk_path = std.mem.trimRight(u8, sdk_path_raw, "\n");
22 | const framework_path = try std.mem.concat(b.allocator, u8, &[_][]const u8{
23 | sdk_path,
24 | "/System/Library/Frameworks",
25 | });
26 |
27 | exe.addFrameworkDir(framework_path);
28 |
29 | exe.linkFramework("CoreFoundation");
30 | exe.linkFramework("AudioToolbox");
31 | exe.linkFramework("CoreAudio");
32 | exe.linkFramework("CoreMIDI");
33 | },
34 | else => {},
35 | }
36 |
37 | const run_cmd = exe.run();
38 | run_cmd.step.dependOn(b.getInstallStep());
39 |
40 | const run_step = b.step("run", "Run the app");
41 | run_step.dependOn(&run_cmd.step);
42 | }
43 |
44 | // WASM build
45 | {
46 | const mode = b.standardReleaseOptions();
47 | const lib = b.addStaticLibrary("zig-synth", "src/main.zig");
48 |
49 | lib.setTarget(std.zig.CrossTarget{
50 | .cpu_arch = .wasm32,
51 | .os_tag = .freestanding,
52 | });
53 |
54 | lib.setBuildMode(mode);
55 | lib.install();
56 |
57 | const build_html = b.addExecutable("build-html", "build-html.zig");
58 | build_html.setBuildMode(mode);
59 |
60 | var run_build_cmd = build_html.run();
61 | run_build_cmd.step.dependOn(&lib.step);
62 |
63 | const cwd = try std.process.getCwdAlloc(b.allocator);
64 | defer b.allocator.free(cwd);
65 |
66 | const wasm_path = try std.fs.path.join(b.allocator, &[_][]const u8{
67 | cwd,
68 | b.lib_dir,
69 | lib.out_filename,
70 | });
71 | defer b.allocator.free(wasm_path);
72 |
73 | const out_html_path = try std.fs.path.join(b.allocator, &[_][]const u8{
74 | cwd,
75 | b.exe_dir,
76 | "zig-synth.html",
77 | });
78 |
79 | run_build_cmd.addArg(wasm_path);
80 | run_build_cmd.addArg(out_html_path);
81 |
82 | b.default_step.dependOn(&run_build_cmd.step);
83 | }
84 |
85 | // Tests
86 | {
87 | const mode = b.standardReleaseOptions();
88 | var main_tests = b.addTest("src/main.zig");
89 | main_tests.setBuildMode(mode);
90 |
91 | const test_step = b.step("test", "Run library tests");
92 | test_step.dependOn(&main_tests.step);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/adsr.zig:
--------------------------------------------------------------------------------
1 | const util = @import("util.zig");
2 |
3 | pub const ADSR = struct {
4 | pub const Stage = enum {
5 | Idle,
6 | Attack,
7 | Decay,
8 | Sustain,
9 | Release,
10 | };
11 |
12 | pub const Params = util.Parameters(struct {
13 | format: util.Format,
14 |
15 | /// The multiplier for when a tone is held after its decay,
16 | /// but before being released.
17 | sustain: f32,
18 |
19 | /// Time to attack in seconds
20 | attack: f64,
21 |
22 | /// Time to decay in seconds
23 | decay: f64,
24 |
25 | /// Time to release in seconds
26 | release: f64,
27 | });
28 |
29 | params: Params,
30 |
31 | samples_attack: f32,
32 | samples_decay: f32,
33 | samples_release: f32,
34 |
35 | stage: Stage,
36 | frames_passed: f32,
37 | release_from: f32,
38 |
39 | const Self = @This();
40 |
41 | pub fn init(initial_params: Params.Child) Self {
42 | var self = Self{
43 | .params = Params.init(initial_params),
44 | .samples_attack = 0,
45 | .samples_decay = 0,
46 | .samples_release = 0,
47 | .stage = .Idle,
48 | .frames_passed = 0,
49 | .release_from = 0,
50 | };
51 |
52 | self.updateParams(initial_params);
53 |
54 | return self;
55 | }
56 |
57 | pub fn updateParams(self: *Self, params: Params.Child) void {
58 | const rate = params.format.sample_rate;
59 |
60 | self.params.write(params);
61 |
62 | self.samples_attack = @floatCast(f32, params.attack * rate);
63 | self.samples_decay = @floatCast(f32, params.decay * rate);
64 | self.samples_release = @floatCast(f32, params.release * rate);
65 | }
66 |
67 | pub fn gate(self: *Self, state: bool) void {
68 | if (state) {
69 | switch (self.stage) {
70 | .Attack => {},
71 | .Decay => {},
72 | .Sustain => {},
73 | .Idle => {
74 | self.stage = .Attack;
75 | self.frames_passed = 0;
76 | },
77 | .Release => {
78 | // TODO Is this the correct thing to do? How do other implementations
79 | // handle the case "Release -> Attack".
80 | self.frames_passed = self.getMultiplier(0) * self.samples_attack;
81 | self.stage = .Attack;
82 | },
83 | }
84 | } else if (self.stage != .Idle and self.stage != .Release) {
85 | self.stage = .Release;
86 | self.frames_passed = 0;
87 | }
88 | }
89 |
90 | pub fn getMultiplier(self: *Self, forFrames: u32) f32 {
91 | return switch (self.stage) {
92 | .Attack => {
93 | const value = self.frames_passed / self.samples_attack;
94 |
95 | self.frames_passed += @intToFloat(f32, forFrames);
96 |
97 | if (self.frames_passed > self.samples_attack) {
98 | self.frames_passed = 0;
99 | self.release_from = 1;
100 | self.stage = .Decay;
101 |
102 | return 1;
103 | } else {
104 | self.release_from = value;
105 | return value;
106 | }
107 | },
108 | .Decay => {
109 | const params = self.params.read();
110 | const progress = self.frames_passed / self.samples_decay;
111 | const value_range = 1 - params.sustain;
112 | const value = 1 - value_range * progress;
113 |
114 | self.frames_passed += @intToFloat(f32, forFrames);
115 |
116 | if (self.frames_passed > self.samples_decay) {
117 | self.frames_passed = 0;
118 | self.release_from = params.sustain;
119 | self.stage = .Sustain;
120 |
121 | return params.sustain;
122 | } else {
123 | self.release_from = value;
124 | return value;
125 | }
126 | },
127 | .Sustain => {
128 | const params = self.params.read();
129 | return params.sustain;
130 | },
131 | .Release => {
132 | const value = self.frames_passed / self.samples_release * self.release_from;
133 |
134 | self.frames_passed += @intToFloat(f32, forFrames);
135 |
136 | if (self.frames_passed > self.samples_release) {
137 | self.frames_passed = 0;
138 | self.release_from = 0;
139 | self.stage = .Idle;
140 |
141 | return 0;
142 | } else {
143 | return self.release_from - value;
144 | }
145 | },
146 | else => 0,
147 | };
148 | }
149 | };
150 |
--------------------------------------------------------------------------------
/src/host/browser.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const util = @import("../util.zig");
3 | const midi = @import("../midi.zig");
4 | const synth = @import("../synth.zig");
5 |
6 | extern "debug" fn js_err(ptr: [*]const u8, len: usize) void;
7 | extern "debug" fn js_warn(ptr: [*]const u8, len: usize) void;
8 |
9 | const allocator = std.heap.page_allocator;
10 | var global_synth: synth.Synth = undefined;
11 |
12 | pub const Host = struct {
13 | pub fn warn(comptime fmt: []const u8, args: var) void {
14 | const buf = std.fmt.allocPrint(allocator, fmt, args) catch unreachable;
15 | defer allocator.free(buf);
16 | js_warn(buf.ptr, buf.len);
17 | }
18 | };
19 |
20 | export fn js_alloc(byte_length: usize) [*]u8 {
21 | const buffer = allocator.alloc(u8, byte_length) catch unreachable;
22 | return buffer.ptr;
23 | }
24 |
25 | export fn js_free(ptr: [*]u8, byte_length: usize) void {
26 | const buf = ptr[0..byte_length];
27 | allocator.free(buf);
28 | }
29 |
30 | export fn init_synth(sample_rate: f32) void {
31 | const format = util.Format{
32 | .sample_rate = sample_rate,
33 | .channel_count = 1,
34 | };
35 |
36 | global_synth = synth.Synth.init(allocator, format) catch unreachable;
37 | }
38 |
39 | export fn generate(c_buf: [*]f32, frames: u32) void {
40 | var buf = c_buf[0..frames];
41 | global_synth.sampleInto(buf);
42 | }
43 |
44 | export fn process_midi(ptr: [*]u8, len: usize) void {
45 | const buf = ptr[0..len];
46 | const msg = midi.Message.parse(buf);
47 |
48 | global_synth.handleMIDIMessage(msg);
49 | }
50 |
51 | pub fn panic(err: []const u8, maybe_trace: ?*std.builtin.StackTrace) noreturn {
52 | js_err(err.ptr, err.len);
53 |
54 | while (true) {
55 | @breakpoint();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/host/browser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Zig Synthesizer in WebAssembly
6 |
7 |
86 |
87 |
88 |
104 |
105 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/src/host/browser/index.js:
--------------------------------------------------------------------------------
1 | function clear(ctx) {
2 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3 | }
4 |
5 | function numMap(x, min, max, new_min, new_max) {
6 | return (x - min) * (new_max - new_min) / (max - min) + new_min;
7 | }
8 |
9 | function drawFrequencies(ctx, audio_ctx, data) {
10 | const dbToY = db => numMap(db, -120, 6, ctx.canvas.height, 0);
11 | const freqToX = freq => {
12 | if (freq === -1) {
13 | return 0;
14 | }
15 |
16 | return Math.log10(freq + 1) / Math.log10(audio_ctx.sampleRate / 2) * ctx.canvas.width
17 | };
18 |
19 | const db_lines = [0, -20, -40, -60, -80, -100, -120];
20 | db_lines.forEach(db => {
21 | const y = dbToY(db);
22 |
23 | ctx.beginPath();
24 | ctx.moveTo(0, y);
25 | ctx.lineTo(ctx.canvas.width, y);
26 | ctx.lineWidth = 1;
27 | ctx.strokeStyle = 'gray';
28 | ctx.stroke();
29 |
30 | ctx.beginPath();
31 | ctx.strokeStyle = 'orange';
32 | ctx.strokeText(`${db} dB`, 4, y - 4);
33 | });
34 |
35 |
36 | const calcX = x => Math.log10(x) / Math.log10(data.length) * ctx.canvas.width;
37 | const freq_lines = [1, 10, 100, 1000, 10000];
38 |
39 | freq_lines.forEach(freq => {
40 | const x = freqToX(freq);
41 |
42 | ctx.beginPath();
43 | ctx.lineWidth = 1;
44 | ctx.strokeStyle = 'black';
45 | ctx.moveTo(x, 0);
46 | ctx.lineTo(x, ctx.canvas.height);
47 | ctx.stroke();
48 |
49 | for (let i = 1; i < 10; i++) {
50 | const thin_x = freqToX(freq + i * freq);
51 |
52 | ctx.beginPath();
53 | ctx.moveTo(thin_x, 0);
54 | ctx.lineTo(thin_x, ctx.canvas.height);
55 | ctx.lineWidth = 1;
56 | ctx.strokeStyle = 'gray';
57 | ctx.stroke();
58 | }
59 |
60 | ctx.beginPath();
61 | ctx.strokeStyle = 'orange';
62 | ctx.strokeText(`${freq} Hz`, x + 4, dbToY(0) - 4);
63 | });
64 |
65 | ctx.beginPath();
66 |
67 | const minp = 0;
68 | const maxp = audio_ctx.sampleRate / 2;
69 |
70 | const minv = Math.log(0);
71 | const maxv = Math.log(audio_ctx.sampleRate / 2);
72 | const scale = (maxv - minv) / (maxp - minp);
73 |
74 | data.forEach((value, i) => {
75 | const frequency = i * (audio_ctx.sampleRate / 2) / data.length;
76 | const x = freqToX(frequency);
77 | const y = numMap(value, -140, 6, ctx.canvas.height, 0);
78 |
79 | if (i == 0) {
80 | ctx.moveTo(x, y);
81 | return;
82 | }
83 |
84 | ctx.lineTo(x, y);
85 | });
86 |
87 | ctx.strokeStyle = 'blue';
88 | ctx.lineWidth = 2;
89 | ctx.stroke();
90 | }
91 |
92 | function drawOscilloscope(ctx, audio_ctx, data) {
93 |
94 | ctx.beginPath();
95 | ctx.moveTo(0, ctx.canvas.height / 2);
96 | ctx.lineTo(ctx.canvas.width, ctx.canvas.height / 2);
97 | ctx.strokeStyle = 'black';
98 | ctx.lineWidth = 3;
99 | ctx.stroke();
100 |
101 | ctx.beginPath();
102 | data.forEach((value, i) => {
103 | const x = (data.length - i) / data.length * ctx.canvas.width;
104 | const y = ctx.canvas.height / 2 - value * (ctx.canvas.height / 2);
105 |
106 | if (i == 0) {
107 | ctx.moveTo(x, y);
108 | return;
109 | }
110 |
111 | ctx.lineTo(x, y);
112 | });
113 |
114 | ctx.strokeStyle = 'blue';
115 | ctx.lineWidth = 2;
116 | ctx.stroke();
117 | }
118 |
119 | const wasm_binary_promise = fetch(window.wasmSource).then(res => res.arrayBuffer());
120 | const play_button = document.getElementById('play-button');
121 |
122 | let running = false;
123 |
124 | play_button.addEventListener('click', e => start());
125 |
126 | window.addEventListener('keydown', onKeyDown);
127 |
128 | function onKeyDown(e) {
129 | window.removeEventListener('keydown', onKeyDown);
130 |
131 | if (e.code === 'Space') {
132 | start();
133 | }
134 | }
135 |
136 | function start() {
137 | if (running) {
138 | return;
139 | }
140 |
141 | running = true;
142 |
143 | const wrapper = play_button.parentElement;
144 | wrapper.parentElement.removeChild(wrapper);
145 |
146 | const canvas_spectrum = document.createElement('canvas');
147 | const canvas_oscilloscope = document.createElement('canvas');
148 | const ctx_spectrum = canvas_spectrum.getContext('2d');
149 | const ctx_oscilloscope = canvas_oscilloscope.getContext('2d');
150 |
151 | canvas_spectrum.height = canvas_oscilloscope.height = window.innerHeight / 2;
152 | canvas_spectrum.width = canvas_oscilloscope.width = window.innerWidth;
153 |
154 | document.body.appendChild(canvas_spectrum);
155 | document.body.appendChild(canvas_oscilloscope);
156 |
157 | const audio_ctx = new AudioContext();
158 |
159 | audio_ctx.audioWorklet.addModule(window.workletSource).then(() => {
160 | const analyser = audio_ctx.createAnalyser();
161 |
162 | analyser.fftSize = 512;
163 | let frequencies = new Float32Array(analyser.frequencyBinCount);
164 | let samples = new Float32Array(512);
165 |
166 | analyser.smoothingTimeConstant = 0.5;
167 |
168 | function render(t) {
169 |
170 | clear(ctx_spectrum);
171 | analyser.getFloatFrequencyData(frequencies);
172 | drawFrequencies(ctx_spectrum, audio_ctx, frequencies);
173 |
174 | clear(ctx_oscilloscope);
175 | analyser.getFloatTimeDomainData(samples);
176 | drawOscilloscope(ctx_oscilloscope, audio_ctx, samples);
177 |
178 | requestAnimationFrame(render);
179 | }
180 |
181 | requestAnimationFrame(render);
182 |
183 | const synth_node = new AudioWorkletNode(audio_ctx, 'zig-synth', {});
184 | synth_node.connect(analyser).connect(audio_ctx.destination);
185 |
186 | wasm_binary_promise .then(binary => {
187 | synth_node.port.postMessage({
188 | type: 'wasm-binary',
189 | data: binary,
190 | sample_rate: audio_ctx.sampleRate,
191 | });
192 | }).catch(console.error);
193 |
194 | navigator.requestMIDIAccess().then(access => {
195 | const inputs = access.inputs.values();
196 |
197 | for (const entry of inputs) {
198 | entry.addEventListener('midimessage', e => {
199 | if (synth_node) {
200 | synth_node.port.postMessage({
201 | type: 'midi-message',
202 | data: e.data,
203 | });
204 | }
205 | });
206 | }
207 | }).catch(console.error);
208 | });
209 | }
210 |
211 |
--------------------------------------------------------------------------------
/src/host/browser/worklet.js:
--------------------------------------------------------------------------------
1 | function getString(inst, ptr, len) {
2 | const slice = derefBuffer(Uint8Array, inst, ptr, len);
3 | const arr = [];
4 |
5 | for (let i = 0; i < slice.length; i++) {
6 | const char = String.fromCharCode(slice[i]);
7 | arr.push(char);
8 | }
9 |
10 | return arr.join('');
11 | }
12 |
13 | function derefBuffer(T, inst, ptr, len) {
14 | return new T(inst.exports.memory.buffer, ptr, len);
15 | }
16 |
17 | function allocBuffer(T, inst, len) {
18 | const ptr = inst.exports.js_alloc(T.BYTES_PER_ELEMENT * len);
19 |
20 | return {
21 | ptr: ptr,
22 | data: derefBuffer(T, inst, ptr, len),
23 | };
24 | }
25 |
26 | function freeBuffer(inst, buffer) {
27 | inst.exports.js_free(buffer.ptr, buffer.data.byteLength);
28 | }
29 |
30 | class ZigSynthProcessor extends AudioWorkletProcessor {
31 |
32 | constructor() {
33 | super();
34 |
35 | this.port.onmessage = this.onMessage.bind(this);
36 | }
37 |
38 | initWasm(binary, sample_rate) {
39 | let instance_closure = undefined;
40 |
41 | WebAssembly.instantiate(binary, {
42 | debug: {
43 | js_err: (ptr, len) => {
44 | const msg = getString(instance_closure, ptr, len);
45 | console.error(msg);
46 | },
47 | js_warn: (ptr, len) => {
48 | const msg = getString(instance_closure, ptr, len);
49 | console.log(msg);
50 | },
51 | },
52 | }).then(inst => {
53 | instance_closure = inst.instance;
54 | inst.instance.exports.init_synth(sample_rate);
55 | this.wasm = inst.instance;
56 | });
57 | }
58 |
59 | onMessage(event) {
60 | switch (event.data.type) {
61 | case 'wasm-binary': {
62 | this.initWasm(event.data.data, event.data.sample_rate);
63 | break;
64 | }
65 |
66 | case 'midi-message': {
67 | this.processMIDI(event.data.data);
68 | break;
69 | }
70 | }
71 | }
72 |
73 | processMIDI(data) {
74 | const buffer = allocBuffer(Uint8Array, this.wasm, data.byteLength);
75 |
76 | buffer.data.set(data);
77 | this.wasm.exports.process_midi(buffer.ptr, data.byteLength);
78 |
79 | freeBuffer(this.wasm, buffer);
80 | }
81 |
82 | process(inputs, outputs, params) {
83 | if (!this.wasm) {
84 | return true;
85 | }
86 |
87 | const channel = outputs[0][0];
88 |
89 | // TODO Ideally we want to allocate this buffer only once.
90 | // However, I need to figure how to prevent the Zig allocator
91 | // from growing the WebAssembly.Memory. When that happens
92 | // the underlying ArrayBuffer gets detached and the workmem is
93 | // invalidated. Maybe we need to detect this and reallocate?
94 | // Although I think it makes more sense to allocate a single slice
95 | // at startup and use std.heap.FixedBufferAllocator.
96 | //
97 | // Sadly, we cannot make use of the Performance API in an
98 | // Audio Worklet. So I'm unable to measure the performance impact that way.
99 | // So far, though, it seems like the dev tools in chrome handle
100 | // WebAssembly profiling as well as JavaScript. The accuracy
101 | // should be better than that of the Performance API, too.
102 | const buffer = allocBuffer(Float32Array, this.wasm, channel.length);
103 |
104 | this.wasm.exports.generate(buffer.ptr, buffer.data.length);
105 | channel.set(buffer.data);
106 |
107 | freeBuffer(this.wasm, buffer);
108 |
109 | return true;
110 | }
111 |
112 | }
113 |
114 | registerProcessor('zig-synth', ZigSynthProcessor);
115 |
--------------------------------------------------------------------------------
/src/host/macos.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Synth = @import("../synth.zig").Synth;
3 | const util = @import("../util.zig");
4 | const midi = @import("../midi.zig");
5 |
6 | const objc = @import("macos/objc.zig");
7 | usingnamespace @import("macos/core_audio.zig");
8 |
9 | pub const Host = struct {
10 | pub const warn = std.debug.warn;
11 | };
12 |
13 | /// Replace this with the name of your desired MIDI input.
14 | /// It doesn't have to be the _exact_ name, but just something that
15 | /// uniquely identifies it.
16 | /// In my case, it's an Arturia KeyStep
17 | const midi_hint = "KeyStep";
18 |
19 | pub fn main() !void {
20 | var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
21 | const allocator = &arena_allocator.allocator;
22 | defer arena_allocator.deinit();
23 |
24 | const format = util.Format{
25 | .sample_rate = 44100,
26 | .channel_count = 1,
27 | };
28 |
29 | var instance = Instance{
30 | .ref = .{
31 | .synth = try Synth.init(allocator, format),
32 | .timer = try std.time.Timer.start(),
33 | },
34 | .audio_unit = undefined,
35 | .midi_client_ref = undefined,
36 | .midi_port_ref = undefined,
37 | };
38 |
39 | // Core Audio Setup
40 | {
41 | const stream_description = formatToAudioStreamBasicDescription(f32, format);
42 | const component_description = AudioComponentDescription{
43 | .componentType = kAudioUnitType_Output,
44 | .componentSubType = kAudioUnitSubType_HALOutput,
45 | .componentManufacturer = 0,
46 | .componentFlags = 0,
47 | .componentFlagsMask = 0,
48 | };
49 |
50 | const output_device = (try findAudioDevice(allocator, "Out", true)) orelse return error.AudioDeviceNotFound;
51 | const component = (try findAudioComponent(allocator, component_description, "Output")) orelse return error.AudioComponentNotFound;
52 |
53 | checkStatus(
54 | "AudioComponentInstanceNew",
55 | AudioComponentInstanceNew(component, &instance.audio_unit),
56 | );
57 |
58 | const kOutputBus: UInt32 = 0;
59 | var trueFlag: UInt32 = 1;
60 |
61 | checkStatus(
62 | "AudioUnitSetProperty CurrentDevice",
63 | AudioUnitSetProperty(
64 | instance.audio_unit,
65 | kAudioOutputUnitProperty_CurrentDevice,
66 | kAudioUnitScope_Output,
67 | kOutputBus,
68 | &output_device,
69 | @sizeOf(@TypeOf(output_device)),
70 | ),
71 | );
72 |
73 | checkStatus(
74 | "AudioUnitSetProperty StreamFormat",
75 | AudioUnitSetProperty(
76 | instance.audio_unit,
77 | kAudioUnitProperty_StreamFormat,
78 | kAudioUnitScope_Input,
79 | kOutputBus,
80 | &stream_description,
81 | @sizeOf(@TypeOf(stream_description)),
82 | ),
83 | );
84 |
85 | var callback_struct = AURenderCallbackStruct{
86 | .inputProc = renderCallback,
87 | .inputProcRefCon = &instance.ref,
88 | };
89 |
90 | checkStatus(
91 | "AudioUnitSetProperty SetRenderCallback",
92 | AudioUnitSetProperty(
93 | instance.audio_unit,
94 | kAudioUnitProperty_SetRenderCallback,
95 | kAudioUnitScope_Global,
96 | kOutputBus,
97 | &callback_struct,
98 | @sizeOf(AURenderCallbackStruct),
99 | ),
100 | );
101 |
102 | checkStatus(
103 | "AudioUnitInitialize",
104 | AudioUnitInitialize(instance.audio_unit),
105 | );
106 |
107 | checkStatus(
108 | "AudioOutputUnitStart",
109 | AudioOutputUnitStart(instance.audio_unit),
110 | );
111 | }
112 |
113 | const maybe_midi_source = try findMIDISource(allocator, midi_hint);
114 |
115 | // Core MIDI Setup
116 | if (maybe_midi_source) |midi_source| {
117 | const client_name = createCFString("test");
118 |
119 | checkStatus(
120 | "MIDIClientCreate",
121 | MIDIClientCreate(
122 | client_name,
123 | null,
124 | null,
125 | &instance.midi_client_ref,
126 | ),
127 | );
128 |
129 | const port_name = createCFString("test-port");
130 |
131 | checkStatus(
132 | "MIDIInputPortCreate",
133 | MIDIInputPortCreate(
134 | instance.midi_client_ref,
135 | port_name,
136 | midiCallback,
137 | &instance.ref.synth,
138 | &instance.midi_port_ref,
139 | ),
140 | );
141 |
142 | checkStatus(
143 | "MIDIPortConnectSource",
144 | MIDIPortConnectSource(
145 | instance.midi_port_ref,
146 | midi_source,
147 | null,
148 | ),
149 | );
150 | }
151 |
152 | startRunLoop();
153 | }
154 |
155 | const Instance = struct {
156 | ref: struct {
157 | synth: Synth,
158 | timer: std.time.Timer,
159 | },
160 |
161 | // Audio
162 | audio_unit: AudioUnit,
163 |
164 | // MIDI
165 | midi_client_ref: MIDIClientRef,
166 | midi_port_ref: MIDIPortRef,
167 | };
168 |
169 | fn renderCallback(
170 | input: ?*c_void,
171 | flags: [*c]AudioUnitRenderActionFlags,
172 | timestamp: [*c]const AudioTimeStamp,
173 | bus: UInt32,
174 | frames: UInt32,
175 | raw_buffers: [*c]AudioBufferList,
176 | ) callconv(.C) OSStatus {
177 | const RefT = comptime std.meta.fieldInfo(Instance, "ref").field_type;
178 | const aligned_ptr = @alignCast(@alignOf(RefT), input);
179 | var ref = @ptrCast(*RefT, aligned_ptr);
180 |
181 | var audio_buffer_list = @ptrCast(*AudioBufferList, raw_buffers);
182 | var audio_buffer = audio_buffer_list.mBuffers[0];
183 | var buffer_data = @ptrCast([*c]f32, @alignCast(@alignOf(f32), audio_buffer.mData));
184 | var buffer_slice = buffer_data[0..frames];
185 |
186 | ref.timer.reset();
187 | ref.synth.sampleInto(buffer_slice);
188 | const took = ref.timer.read();
189 |
190 | std.debug.warn("{} frames took {}ns\n", .{ frames, took });
191 |
192 | return noErr;
193 | }
194 | fn midiCallback(
195 | pktlist_c: [*c]const MIDIPacketList,
196 | input: ?*c_void,
197 | src: ?*c_void,
198 | ) callconv(.C) void {
199 | const aligned_ptr = @alignCast(@alignOf(Synth), input);
200 | var synth = @ptrCast(*Synth, aligned_ptr);
201 |
202 | const pktlist = pktlist_c[0];
203 | var packet = pktlist.packet[0];
204 | var packet_i: @TypeOf(pktlist.numPackets) = 0;
205 |
206 | while (packet_i < pktlist.numPackets) : ({
207 | packet_i += 1;
208 | packet = MIDIPacketNext(&packet)[0];
209 | }) {
210 | const data = packet.data[0..packet.length];
211 | const msg = midi.Message.parse(data);
212 |
213 | synth.handleMIDIMessage(msg);
214 | }
215 | }
216 |
217 | fn startRunLoop() void {
218 | const NSRunLoop = objc.objc_getClass("NSRunLoop");
219 | const current = objc.objc_msgSend(@ptrCast(objc.id, NSRunLoop), objc.sel_getUid("currentRunLoop"));
220 | _ = objc.objc_msgSend(current, objc.sel_getUid("run"));
221 | }
222 |
223 | fn formatToAudioStreamBasicDescription(comptime ValueT: type, format: util.Format) AudioStreamBasicDescription {
224 | return .{
225 | .mSampleRate = format.sample_rate,
226 | .mFormatID = kAudioFormatLinearPCM,
227 | .mFormatFlags = switch (ValueT) {
228 | f32, f64 => kLinearPCMFormatFlagIsFloat,
229 | else => @compileError("Unsupported format type"),
230 | },
231 | .mBitsPerChannel = @sizeOf(ValueT) * 8,
232 | .mChannelsPerFrame = format.channel_count,
233 | .mFramesPerPacket = 1,
234 | .mBytesPerPacket = @sizeOf(ValueT) * format.channel_count,
235 | .mBytesPerFrame = @sizeOf(ValueT) * format.channel_count,
236 | .mReserved = 0,
237 | };
238 | }
239 |
240 | test "formatToAudioStreamBasicDescription" {
241 | const mono_44100 = util.Format{
242 | .sample_rate = 44100,
243 | .channel_count = 1,
244 | };
245 |
246 | const absd = formatToAudioStreamBasicDescription(f32, mono_44100);
247 |
248 | std.testing.expectEqual(absd.mSampleRate, mono_44100.sample_rate);
249 | std.testing.expect(@intCast(c_int, absd.mFormatFlags) & kLinearPCMFormatFlagIsFloat != 0);
250 | std.testing.expectEqual(@as(c_uint, 32), absd.mBitsPerChannel);
251 | std.testing.expectEqual(@as(c_uint, 1), absd.mChannelsPerFrame);
252 | std.testing.expectEqual(@as(c_uint, 4), absd.mBytesPerPacket);
253 | std.testing.expectEqual(@as(c_uint, 4), absd.mBytesPerFrame);
254 |
255 | std.testing.expectEqual(absd.mFormatID, kAudioFormatLinearPCM);
256 | std.testing.expectEqual(@as(c_uint, 1), absd.mFramesPerPacket);
257 | }
258 |
259 | fn convertCFString(allocator: *std.mem.Allocator, str: CFStringRef) ![]const u8 {
260 | const len = @intCast(usize, CFStringGetLength(str)) + 1;
261 | var buffer = try allocator.alloc(u8, len);
262 | errdefer allocator.free(buffer);
263 |
264 | const result = CFStringGetCString(str, &buffer[0], @intCast(CFIndex, buffer.len), kCFStringEncodingUTF8);
265 |
266 | if (result != @"true") {
267 | return error.CFStringConversionFailed;
268 | }
269 |
270 | return buffer[0 .. buffer.len - 1];
271 | }
272 |
273 | fn createCFString(str: []const u8) CFStringRef {
274 | return CFStringCreateWithCString(
275 | kCFAllocatorDefault,
276 | &str[0],
277 | kCFStringEncodingUTF8,
278 | );
279 | }
280 |
281 | fn checkStatus(hint: []const u8, status: OSStatus) void {
282 | if (status != noErr) {
283 | std.debug.warn("checkStatus(\"{}\") failed with error: {}\n", .{ hint, status });
284 | exit(1);
285 | }
286 | }
287 |
288 | fn findAudioDevice(
289 | allocator: *std.mem.Allocator,
290 | hint: []const u8,
291 | ignore_input_devices: bool,
292 | ) !?AudioDeviceID {
293 | var property_size: c_uint = undefined;
294 | var device_property_address = AudioObjectPropertyAddress{
295 | .mSelector = kAudioHardwarePropertyDevices,
296 | .mScope = kAudioObjectPropertyScopeGlobal,
297 | .mElement = kAudioObjectPropertyElementMaster,
298 | };
299 |
300 | checkStatus(
301 | "AudioObjectGetPropertyDataSize",
302 | AudioObjectGetPropertyDataSize(
303 | kAudioObjectSystemObject,
304 | &device_property_address,
305 | 0,
306 | null,
307 | &property_size,
308 | ),
309 | );
310 |
311 | const device_count = @intCast(usize, property_size) / @sizeOf(AudioDeviceID);
312 | var devices = try allocator.alloc(AudioDeviceID, device_count);
313 | defer allocator.free(devices);
314 |
315 | checkStatus(
316 | "AudioObjectGetPropertyData",
317 | AudioObjectGetPropertyData(
318 | kAudioObjectSystemObject,
319 | &device_property_address,
320 | 0,
321 | null,
322 | &property_size,
323 | &devices[0],
324 | ),
325 | );
326 |
327 | var device_name: CFStringRef = undefined;
328 | var device_name_size: c_uint = @sizeOf(CFStringRef);
329 | var device_name_property_address = AudioObjectPropertyAddress{
330 | .mSelector = kAudioDevicePropertyDeviceNameCFString,
331 | .mScope = kAudioObjectPropertyScopeGlobal,
332 | .mElement = kAudioObjectPropertyElementMaster,
333 | };
334 |
335 | var device_stream_config_size: c_uint = undefined;
336 | var device_stream_config_property_address = AudioObjectPropertyAddress{
337 | .mSelector = kAudioDevicePropertyStreamConfiguration,
338 | .mScope = kAudioDevicePropertyScopeInput,
339 | .mElement = 0,
340 | };
341 |
342 | for (devices) |device_id| {
343 | var input_count: ?UInt32 = null;
344 |
345 | if (!ignore_input_devices) {
346 | checkStatus(
347 | "AudioObjectGetPropertyDataSize StreamConfiguration",
348 | AudioObjectGetPropertyDataSize(
349 | device_id,
350 | &device_stream_config_property_address,
351 | 0,
352 | null,
353 | &device_stream_config_size,
354 | ),
355 | );
356 |
357 | input_count = 0;
358 |
359 | // The size of an empty AudioBufferList is smaller than that of an AudioBuffer.
360 | if (device_stream_config_size >= @sizeOf(AudioBuffer)) {
361 | var stream_config_bytes = try allocator.alignedAlloc(u8, @alignOf(AudioBufferList), device_stream_config_size);
362 | var audio_buffer_list = @ptrCast(*AudioBufferList, &stream_config_bytes[0]);
363 | defer allocator.free(stream_config_bytes);
364 |
365 | checkStatus(
366 | "AudioObjectGetPropertyData StreamConfiguration",
367 | AudioObjectGetPropertyData(
368 | device_id,
369 | &device_stream_config_property_address,
370 | 0,
371 | null,
372 | &device_stream_config_size,
373 | audio_buffer_list,
374 | ),
375 | );
376 |
377 | input_count = audio_buffer_list.mNumberBuffers;
378 | }
379 |
380 | if (input_count.? > 0) {
381 | continue;
382 | }
383 | }
384 |
385 | checkStatus(
386 | "AudioObjectGetPropertyData DeviceNameCFString",
387 | AudioObjectGetPropertyData(
388 | device_id,
389 | &device_name_property_address,
390 | 0,
391 | null,
392 | &device_name_size,
393 | &device_name,
394 | ),
395 | );
396 |
397 | const zig_name = try convertCFString(allocator, device_name);
398 | defer allocator.free(zig_name);
399 |
400 | if (std.mem.indexOf(u8, zig_name, hint) != null) {
401 | std.debug.warn("Found Audio Device: \"{}\" ({} inputs)\n", .{ zig_name, input_count });
402 | return device_id;
403 | }
404 | }
405 |
406 | return null;
407 | }
408 |
409 | fn findAudioComponent(
410 | allocator: *std.mem.Allocator,
411 | desc: AudioComponentDescription,
412 | hint: []const u8,
413 | ) !?AudioComponent {
414 | var current: AudioComponent = null;
415 |
416 | while (AudioComponentFindNext(current, &desc)) |found| {
417 | var name: CFStringRef = undefined;
418 |
419 | checkStatus(
420 | "AudioComponentCopyName",
421 | AudioComponentCopyName(found, &name),
422 | );
423 |
424 | const zig_name = try convertCFString(allocator, name);
425 | defer allocator.free(zig_name);
426 |
427 | if (std.mem.indexOf(u8, zig_name, hint) != null) {
428 | std.debug.warn("Selected Audio Component: \"{}\"\n", .{zig_name});
429 | return found;
430 | }
431 |
432 | current = found;
433 | }
434 |
435 | return null;
436 | }
437 |
438 | fn findMIDISource(allocator: *std.mem.Allocator, hint: []const u8) !?MIDIEndpointRef {
439 | const sources = MIDIGetNumberOfSources();
440 | var source_i: @TypeOf(sources) = 0;
441 |
442 | while (source_i < sources) : (source_i += 1) {
443 | const source = MIDIGetSource(source_i);
444 | var source_name: CFStringRef = undefined;
445 |
446 | checkStatus(
447 | "MIDIObjectGetStringProperty name",
448 | MIDIObjectGetStringProperty(
449 | source,
450 | kMIDIPropertyName,
451 | &source_name,
452 | ),
453 | );
454 |
455 | const zig_name = try convertCFString(allocator, source_name);
456 | defer allocator.free(zig_name);
457 |
458 | if (std.mem.indexOf(u8, zig_name, hint) != null) {
459 | std.debug.warn("Selected MIDI Source: {}\n", .{zig_name});
460 | return source;
461 | }
462 | }
463 |
464 | return null;
465 | }
466 |
--------------------------------------------------------------------------------
/src/host/macos/objc.zig:
--------------------------------------------------------------------------------
1 | pub const __int8_t = i8;
2 | pub const __uint8_t = u8;
3 | pub const __int16_t = c_short;
4 | pub const __uint16_t = c_ushort;
5 | pub const __int32_t = c_int;
6 | pub const __uint32_t = c_uint;
7 | pub const __int64_t = c_longlong;
8 | pub const __uint64_t = c_ulonglong;
9 | pub const __darwin_intptr_t = c_long;
10 | pub const __darwin_natural_t = c_uint;
11 | pub const __darwin_ct_rune_t = c_int;
12 | const union_unnamed_1 = extern union {
13 | __mbstate8: [128]u8,
14 | _mbstateL: c_longlong,
15 | };
16 | pub const __mbstate_t = union_unnamed_1;
17 | pub const __darwin_mbstate_t = __mbstate_t;
18 | pub const __darwin_ptrdiff_t = c_long;
19 | pub const __darwin_size_t = c_ulong;
20 | pub const __darwin_va_list = __builtin_va_list;
21 | pub const __darwin_wchar_t = c_int;
22 | pub const __darwin_rune_t = __darwin_wchar_t;
23 | pub const __darwin_wint_t = c_int;
24 | pub const __darwin_clock_t = c_ulong;
25 | pub const __darwin_socklen_t = __uint32_t;
26 | pub const __darwin_ssize_t = c_long;
27 | pub const __darwin_time_t = c_long;
28 | pub const u_int8_t = u8;
29 | pub const u_int16_t = c_ushort;
30 | pub const u_int32_t = c_uint;
31 | pub const u_int64_t = c_ulonglong;
32 | pub const register_t = i64;
33 | pub const user_addr_t = u_int64_t;
34 | pub const user_size_t = u_int64_t;
35 | pub const user_ssize_t = i64;
36 | pub const user_long_t = i64;
37 | pub const user_ulong_t = u_int64_t;
38 | pub const user_time_t = i64;
39 | pub const user_off_t = i64;
40 | pub const syscall_arg_t = u_int64_t;
41 | pub const __darwin_blkcnt_t = __int64_t;
42 | pub const __darwin_blksize_t = __int32_t;
43 | pub const __darwin_dev_t = __int32_t;
44 | pub const __darwin_fsblkcnt_t = c_uint;
45 | pub const __darwin_fsfilcnt_t = c_uint;
46 | pub const __darwin_gid_t = __uint32_t;
47 | pub const __darwin_id_t = __uint32_t;
48 | pub const __darwin_ino64_t = __uint64_t;
49 | pub const __darwin_ino_t = __darwin_ino64_t;
50 | pub const __darwin_mach_port_name_t = __darwin_natural_t;
51 | pub const __darwin_mach_port_t = __darwin_mach_port_name_t;
52 | pub const __darwin_mode_t = __uint16_t;
53 | pub const __darwin_off_t = __int64_t;
54 | pub const __darwin_pid_t = __int32_t;
55 | pub const __darwin_sigset_t = __uint32_t;
56 | pub const __darwin_suseconds_t = __int32_t;
57 | pub const __darwin_uid_t = __uint32_t;
58 | pub const __darwin_useconds_t = __uint32_t;
59 | pub const __darwin_uuid_t = [16]u8;
60 | pub const __darwin_uuid_string_t = [37]u8;
61 | pub const struct___darwin_pthread_handler_rec = extern struct {
62 | __routine: ?fn (?*c_void) callconv(.C) void,
63 | __arg: ?*c_void,
64 | __next: [*c]struct___darwin_pthread_handler_rec,
65 | };
66 | pub const struct__opaque_pthread_attr_t = extern struct {
67 | __sig: c_long,
68 | __opaque: [56]u8,
69 | };
70 | pub const struct__opaque_pthread_cond_t = extern struct {
71 | __sig: c_long,
72 | __opaque: [40]u8,
73 | };
74 | pub const struct__opaque_pthread_condattr_t = extern struct {
75 | __sig: c_long,
76 | __opaque: [8]u8,
77 | };
78 | pub const struct__opaque_pthread_mutex_t = extern struct {
79 | __sig: c_long,
80 | __opaque: [56]u8,
81 | };
82 | pub const struct__opaque_pthread_mutexattr_t = extern struct {
83 | __sig: c_long,
84 | __opaque: [8]u8,
85 | };
86 | pub const struct__opaque_pthread_once_t = extern struct {
87 | __sig: c_long,
88 | __opaque: [8]u8,
89 | };
90 | pub const struct__opaque_pthread_rwlock_t = extern struct {
91 | __sig: c_long,
92 | __opaque: [192]u8,
93 | };
94 | pub const struct__opaque_pthread_rwlockattr_t = extern struct {
95 | __sig: c_long,
96 | __opaque: [16]u8,
97 | };
98 | pub const struct__opaque_pthread_t = extern struct {
99 | __sig: c_long,
100 | __cleanup_stack: [*c]struct___darwin_pthread_handler_rec,
101 | __opaque: [8176]u8,
102 | };
103 | pub const __darwin_pthread_attr_t = struct__opaque_pthread_attr_t;
104 | pub const __darwin_pthread_cond_t = struct__opaque_pthread_cond_t;
105 | pub const __darwin_pthread_condattr_t = struct__opaque_pthread_condattr_t;
106 | pub const __darwin_pthread_key_t = c_ulong;
107 | pub const __darwin_pthread_mutex_t = struct__opaque_pthread_mutex_t;
108 | pub const __darwin_pthread_mutexattr_t = struct__opaque_pthread_mutexattr_t;
109 | pub const __darwin_pthread_once_t = struct__opaque_pthread_once_t;
110 | pub const __darwin_pthread_rwlock_t = struct__opaque_pthread_rwlock_t;
111 | pub const __darwin_pthread_rwlockattr_t = struct__opaque_pthread_rwlockattr_t;
112 | pub const __darwin_pthread_t = [*c]struct__opaque_pthread_t;
113 | pub fn _OSSwapInt16(arg__data: __uint16_t) callconv(.C) __uint16_t {
114 | var _data = arg__data;
115 | return @bitCast(__uint16_t, @truncate(c_short, ((@bitCast(c_int, @as(c_uint, _data)) << @intCast(@import("std").math.Log2Int(c_int), 8)) | (@bitCast(c_int, @as(c_uint, _data)) >> @intCast(@import("std").math.Log2Int(c_int), 8)))));
116 | } // objc_pre_processed.h:302:9: warning: TODO implement translation of CastKind BuiltinFnToFnPtr
117 | pub const _OSSwapInt32 = @compileError("unable to translate function"); // objc_pre_processed.h:316:9: warning: TODO implement translation of CastKind BuiltinFnToFnPtr
118 | pub const _OSSwapInt64 = @compileError("unable to translate function");
119 | pub const u_char = u8;
120 | pub const u_short = c_ushort;
121 | pub const u_int = c_uint;
122 | pub const u_long = c_ulong;
123 | pub const ushort = c_ushort;
124 | pub const uint = c_uint;
125 | pub const u_quad_t = u_int64_t;
126 | pub const quad_t = i64;
127 | pub const qaddr_t = [*c]quad_t;
128 | pub const caddr_t = [*c]u8;
129 | pub const daddr_t = i32;
130 | pub const dev_t = __darwin_dev_t;
131 | pub const fixpt_t = u_int32_t;
132 | pub const blkcnt_t = __darwin_blkcnt_t;
133 | pub const blksize_t = __darwin_blksize_t;
134 | pub const gid_t = __darwin_gid_t;
135 | pub const in_addr_t = __uint32_t;
136 | pub const in_port_t = __uint16_t;
137 | pub const ino_t = __darwin_ino_t;
138 | pub const ino64_t = __darwin_ino64_t;
139 | pub const key_t = __int32_t;
140 | pub const mode_t = __darwin_mode_t;
141 | pub const nlink_t = __uint16_t;
142 | pub const id_t = __darwin_id_t;
143 | pub const pid_t = __darwin_pid_t;
144 | pub const off_t = __darwin_off_t;
145 | pub const segsz_t = i32;
146 | pub const swblk_t = i32;
147 | pub const uid_t = __darwin_uid_t;
148 | pub const clock_t = __darwin_clock_t;
149 | pub const time_t = __darwin_time_t;
150 | pub const useconds_t = __darwin_useconds_t;
151 | pub const suseconds_t = __darwin_suseconds_t;
152 | pub const rsize_t = __darwin_size_t;
153 | pub const errno_t = c_int;
154 | pub const struct_fd_set = extern struct {
155 | fds_bits: [32]__int32_t,
156 | };
157 | pub const fd_set = struct_fd_set;
158 | pub fn __darwin_fd_isset(arg__n: c_int, arg__p: [*c]const struct_fd_set) callconv(.C) c_int {
159 | var _n = arg__n;
160 | var _p = arg__p;
161 | return (_p.*.fds_bits[(@bitCast(c_ulong, @as(c_long, _n)) / (@sizeOf(__int32_t) *% @bitCast(c_ulong, @as(c_long, @as(c_int, 8)))))] & (@bitCast(__int32_t, @truncate(c_uint, ((@bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) << @intCast(@import("std").math.Log2Int(c_ulong), (@bitCast(c_ulong, @as(c_long, _n)) % (@sizeOf(__int32_t) *% @bitCast(c_ulong, @as(c_long, @as(c_int, 8)))))))))));
162 | }
163 | pub const fd_mask = __int32_t;
164 | pub const pthread_attr_t = __darwin_pthread_attr_t;
165 | pub const pthread_cond_t = __darwin_pthread_cond_t;
166 | pub const pthread_condattr_t = __darwin_pthread_condattr_t;
167 | pub const pthread_mutex_t = __darwin_pthread_mutex_t;
168 | pub const pthread_mutexattr_t = __darwin_pthread_mutexattr_t;
169 | pub const pthread_once_t = __darwin_pthread_once_t;
170 | pub const pthread_rwlock_t = __darwin_pthread_rwlock_t;
171 | pub const pthread_rwlockattr_t = __darwin_pthread_rwlockattr_t;
172 | pub const pthread_t = __darwin_pthread_t;
173 | pub const pthread_key_t = __darwin_pthread_key_t;
174 | pub const fsblkcnt_t = __darwin_fsblkcnt_t;
175 | pub const fsfilcnt_t = __darwin_fsfilcnt_t;
176 | pub const struct_objc_ivar = extern struct {
177 | ivar_name: [*c]u8,
178 | ivar_type: [*c]u8,
179 | ivar_offset: c_int,
180 | space: c_int,
181 | };
182 | pub const struct_objc_ivar_list = extern struct {
183 | ivar_count: c_int,
184 | space: c_int,
185 | ivar_list: [1]struct_objc_ivar,
186 | };
187 | pub const struct_objc_method = extern struct {
188 | method_name: SEL,
189 | method_types: [*c]u8,
190 | method_imp: IMP,
191 | };
192 | pub const struct_objc_method_list = extern struct {
193 | obsolete: [*c]struct_objc_method_list,
194 | method_count: c_int,
195 | space: c_int,
196 | method_list: [1]struct_objc_method,
197 | };
198 | pub const struct_objc_cache = extern struct {
199 | mask: c_uint,
200 | occupied: c_uint,
201 | buckets: [1]Method,
202 | };
203 | pub const struct_objc_protocol_list = extern struct {
204 | next: [*c]struct_objc_protocol_list,
205 | count: c_long,
206 | list: [1][*c]Protocol,
207 | };
208 | pub const struct_objc_class = extern struct {
209 | isa: Class,
210 | super_class: Class,
211 | name: [*c]const u8,
212 | version: c_long,
213 | info: c_long,
214 | instance_size: c_long,
215 | ivars: [*c]struct_objc_ivar_list,
216 | methodLists: [*c][*c]struct_objc_method_list,
217 | cache: [*c]struct_objc_cache,
218 | protocols: [*c]struct_objc_protocol_list,
219 | };
220 | pub const Class = [*c]struct_objc_class;
221 | pub const struct_objc_object = extern struct {
222 | isa: Class,
223 | };
224 | pub const id = [*c]struct_objc_object;
225 | pub const struct_objc_selector = @OpaqueType();
226 | pub const SEL = ?*struct_objc_selector;
227 | pub const IMP = ?fn () callconv(.C) void;
228 | pub const BOOL = i8;
229 | pub extern fn sel_getName(sel: SEL) [*c]const u8;
230 | pub extern fn sel_registerName(str: [*c]const u8) SEL;
231 | pub extern fn object_getClassName(obj: id) [*c]const u8;
232 | pub extern fn object_getIndexedIvars(obj: id) ?*c_void;
233 | pub extern fn sel_isMapped(sel: SEL) BOOL;
234 | pub extern fn sel_getUid(str: [*c]const u8) SEL;
235 | pub const objc_objectptr_t = ?*const c_void;
236 | pub extern fn objc_retainedObject(obj: objc_objectptr_t) id;
237 | pub extern fn objc_unretainedObject(obj: objc_objectptr_t) id;
238 | pub extern fn objc_unretainedPointer(obj: id) objc_objectptr_t;
239 | pub const arith_t = c_long;
240 | pub const uarith_t = c_ulong;
241 | pub const STR = [*c]u8;
242 | pub const va_list = __builtin_va_list;
243 | pub const __gnuc_va_list = __builtin_va_list;
244 | pub const int_least8_t = i8;
245 | pub const int_least16_t = i16;
246 | pub const int_least32_t = i32;
247 | pub const int_least64_t = i64;
248 | pub const uint_least8_t = u8;
249 | pub const uint_least16_t = u16;
250 | pub const uint_least32_t = u32;
251 | pub const uint_least64_t = u64;
252 | pub const int_fast8_t = i8;
253 | pub const int_fast16_t = i16;
254 | pub const int_fast32_t = i32;
255 | pub const int_fast64_t = i64;
256 | pub const uint_fast8_t = u8;
257 | pub const uint_fast16_t = u16;
258 | pub const uint_fast32_t = u32;
259 | pub const uint_fast64_t = u64;
260 | pub const intmax_t = c_long;
261 | pub const uintmax_t = c_ulong;
262 | pub const ptrdiff_t = c_long;
263 | pub const wchar_t = c_int;
264 | pub const max_align_t = c_longdouble;
265 | pub const Method = [*c]struct_objc_method;
266 | pub const Ivar = [*c]struct_objc_ivar;
267 | pub const struct_objc_category = extern struct {
268 | category_name: [*c]u8,
269 | class_name: [*c]u8,
270 | instance_methods: [*c]struct_objc_method_list,
271 | class_methods: [*c]struct_objc_method_list,
272 | protocols: [*c]struct_objc_protocol_list,
273 | };
274 | pub const Category = [*c]struct_objc_category;
275 | pub const struct_objc_property = @OpaqueType();
276 | pub const objc_property_t = ?*struct_objc_property;
277 | pub const Protocol = struct_objc_object;
278 | pub const struct_objc_method_description = extern struct {
279 | name: SEL,
280 | types: [*c]u8,
281 | };
282 | const struct_unnamed_2 = extern struct {
283 | name: [*c]const u8,
284 | value: [*c]const u8,
285 | };
286 | pub const objc_property_attribute_t = struct_unnamed_2;
287 | pub extern fn object_copy(obj: id, size: usize) id;
288 | pub extern fn object_dispose(obj: id) id;
289 | pub extern fn object_getClass(obj: id) Class;
290 | pub extern fn object_setClass(obj: id, cls: Class) Class;
291 | pub extern fn object_isClass(obj: id) BOOL;
292 | pub extern fn object_getIvar(obj: id, ivar: Ivar) id;
293 | pub extern fn object_setIvar(obj: id, ivar: Ivar, value: id) void;
294 | pub extern fn object_setIvarWithStrongDefault(obj: id, ivar: Ivar, value: id) void;
295 | pub extern fn object_setInstanceVariable(obj: id, name: [*c]const u8, value: ?*c_void) Ivar;
296 | pub extern fn object_setInstanceVariableWithStrongDefault(obj: id, name: [*c]const u8, value: ?*c_void) Ivar;
297 | pub extern fn object_getInstanceVariable(obj: id, name: [*c]const u8, outValue: [*c]?*c_void) Ivar;
298 | pub extern fn objc_getClass(name: [*c]const u8) Class;
299 | pub extern fn objc_getMetaClass(name: [*c]const u8) Class;
300 | pub extern fn objc_lookUpClass(name: [*c]const u8) Class;
301 | pub extern fn objc_getRequiredClass(name: [*c]const u8) Class;
302 | pub extern fn objc_getClassList(buffer: [*c]Class, bufferCount: c_int) c_int;
303 | pub extern fn objc_copyClassList(outCount: [*c]c_uint) [*c]Class;
304 | pub extern fn class_getName(cls: Class) [*c]const u8;
305 | pub extern fn class_isMetaClass(cls: Class) BOOL;
306 | pub extern fn class_getSuperclass(cls: Class) Class;
307 | pub extern fn class_setSuperclass(cls: Class, newSuper: Class) Class;
308 | pub extern fn class_getVersion(cls: Class) c_int;
309 | pub extern fn class_setVersion(cls: Class, version: c_int) void;
310 | pub extern fn class_getInstanceSize(cls: Class) usize;
311 | pub extern fn class_getInstanceVariable(cls: Class, name: [*c]const u8) Ivar;
312 | pub extern fn class_getClassVariable(cls: Class, name: [*c]const u8) Ivar;
313 | pub extern fn class_copyIvarList(cls: Class, outCount: [*c]c_uint) [*c]Ivar;
314 | pub extern fn class_getInstanceMethod(cls: Class, name: SEL) Method;
315 | pub extern fn class_getClassMethod(cls: Class, name: SEL) Method;
316 | pub extern fn class_getMethodImplementation(cls: Class, name: SEL) IMP;
317 | pub extern fn class_getMethodImplementation_stret(cls: Class, name: SEL) IMP;
318 | pub extern fn class_respondsToSelector(cls: Class, sel: SEL) BOOL;
319 | pub extern fn class_copyMethodList(cls: Class, outCount: [*c]c_uint) [*c]Method;
320 | pub extern fn class_conformsToProtocol(cls: Class, protocol: [*c]Protocol) BOOL;
321 | pub extern fn class_copyProtocolList(cls: Class, outCount: [*c]c_uint) [*c][*c]Protocol;
322 | pub extern fn class_getProperty(cls: Class, name: [*c]const u8) objc_property_t;
323 | pub extern fn class_copyPropertyList(cls: Class, outCount: [*c]c_uint) [*c]objc_property_t;
324 | pub extern fn class_getIvarLayout(cls: Class) [*c]const u8;
325 | pub extern fn class_getWeakIvarLayout(cls: Class) [*c]const u8;
326 | pub extern fn class_addMethod(cls: Class, name: SEL, imp: IMP, types: [*c]const u8) BOOL;
327 | pub extern fn class_replaceMethod(cls: Class, name: SEL, imp: IMP, types: [*c]const u8) IMP;
328 | pub extern fn class_addIvar(cls: Class, name: [*c]const u8, size: usize, alignment: u8, types: [*c]const u8) BOOL;
329 | pub extern fn class_addProtocol(cls: Class, protocol: [*c]Protocol) BOOL;
330 | pub extern fn class_addProperty(cls: Class, name: [*c]const u8, attributes: [*c]const objc_property_attribute_t, attributeCount: c_uint) BOOL;
331 | pub extern fn class_replaceProperty(cls: Class, name: [*c]const u8, attributes: [*c]const objc_property_attribute_t, attributeCount: c_uint) void;
332 | pub extern fn class_setIvarLayout(cls: Class, layout: [*c]const u8) void;
333 | pub extern fn class_setWeakIvarLayout(cls: Class, layout: [*c]const u8) void;
334 | pub extern fn objc_getFutureClass(name: [*c]const u8) Class;
335 | pub extern fn class_createInstance(cls: Class, extraBytes: usize) id;
336 | pub extern fn objc_constructInstance(cls: Class, bytes: ?*c_void) id;
337 | pub extern fn objc_destructInstance(obj: id) ?*c_void;
338 | pub extern fn objc_allocateClassPair(superclass: Class, name: [*c]const u8, extraBytes: usize) Class;
339 | pub extern fn objc_registerClassPair(cls: Class) void;
340 | pub extern fn objc_duplicateClass(original: Class, name: [*c]const u8, extraBytes: usize) Class;
341 | pub extern fn objc_disposeClassPair(cls: Class) void;
342 | pub extern fn method_getName(m: Method) SEL;
343 | pub extern fn method_getImplementation(m: Method) IMP;
344 | pub extern fn method_getTypeEncoding(m: Method) [*c]const u8;
345 | pub extern fn method_getNumberOfArguments(m: Method) c_uint;
346 | pub extern fn method_copyReturnType(m: Method) [*c]u8;
347 | pub extern fn method_copyArgumentType(m: Method, index: c_uint) [*c]u8;
348 | pub extern fn method_getReturnType(m: Method, dst: [*c]u8, dst_len: usize) void;
349 | pub extern fn method_getArgumentType(m: Method, index: c_uint, dst: [*c]u8, dst_len: usize) void;
350 | pub extern fn method_getDescription(m: Method) [*c]struct_objc_method_description;
351 | pub extern fn method_setImplementation(m: Method, imp: IMP) IMP;
352 | pub extern fn method_exchangeImplementations(m1: Method, m2: Method) void;
353 | pub extern fn ivar_getName(v: Ivar) [*c]const u8;
354 | pub extern fn ivar_getTypeEncoding(v: Ivar) [*c]const u8;
355 | pub extern fn ivar_getOffset(v: Ivar) ptrdiff_t;
356 | pub extern fn property_getName(property: objc_property_t) [*c]const u8;
357 | pub extern fn property_getAttributes(property: objc_property_t) [*c]const u8;
358 | pub extern fn property_copyAttributeList(property: objc_property_t, outCount: [*c]c_uint) [*c]objc_property_attribute_t;
359 | pub extern fn property_copyAttributeValue(property: objc_property_t, attributeName: [*c]const u8) [*c]u8;
360 | pub extern fn objc_getProtocol(name: [*c]const u8) [*c]Protocol;
361 | pub extern fn objc_copyProtocolList(outCount: [*c]c_uint) [*c][*c]Protocol;
362 | pub extern fn protocol_conformsToProtocol(proto: [*c]Protocol, other: [*c]Protocol) BOOL;
363 | pub extern fn protocol_isEqual(proto: [*c]Protocol, other: [*c]Protocol) BOOL;
364 | pub extern fn protocol_getName(proto: [*c]Protocol) [*c]const u8;
365 | pub extern fn protocol_getMethodDescription(proto: [*c]Protocol, aSel: SEL, isRequiredMethod: BOOL, isInstanceMethod: BOOL) struct_objc_method_description;
366 | pub extern fn protocol_copyMethodDescriptionList(proto: [*c]Protocol, isRequiredMethod: BOOL, isInstanceMethod: BOOL, outCount: [*c]c_uint) [*c]struct_objc_method_description;
367 | pub extern fn protocol_getProperty(proto: [*c]Protocol, name: [*c]const u8, isRequiredProperty: BOOL, isInstanceProperty: BOOL) objc_property_t;
368 | pub extern fn protocol_copyPropertyList(proto: [*c]Protocol, outCount: [*c]c_uint) [*c]objc_property_t;
369 | pub extern fn protocol_copyPropertyList2(proto: [*c]Protocol, outCount: [*c]c_uint, isRequiredProperty: BOOL, isInstanceProperty: BOOL) [*c]objc_property_t;
370 | pub extern fn protocol_copyProtocolList(proto: [*c]Protocol, outCount: [*c]c_uint) [*c][*c]Protocol;
371 | pub extern fn objc_allocateProtocol(name: [*c]const u8) [*c]Protocol;
372 | pub extern fn objc_registerProtocol(proto: [*c]Protocol) void;
373 | pub extern fn protocol_addMethodDescription(proto: [*c]Protocol, name: SEL, types: [*c]const u8, isRequiredMethod: BOOL, isInstanceMethod: BOOL) void;
374 | pub extern fn protocol_addProtocol(proto: [*c]Protocol, addition: [*c]Protocol) void;
375 | pub extern fn protocol_addProperty(proto: [*c]Protocol, name: [*c]const u8, attributes: [*c]const objc_property_attribute_t, attributeCount: c_uint, isRequiredProperty: BOOL, isInstanceProperty: BOOL) void;
376 | pub extern fn objc_copyImageNames(outCount: [*c]c_uint) [*c][*c]const u8;
377 | pub extern fn class_getImageName(cls: Class) [*c]const u8;
378 | pub extern fn objc_copyClassNamesForImage(image: [*c]const u8, outCount: [*c]c_uint) [*c][*c]const u8;
379 | pub extern fn sel_isEqual(lhs: SEL, rhs: SEL) BOOL;
380 | pub extern fn objc_enumerationMutation(obj: id) void;
381 | pub extern fn objc_setEnumerationMutationHandler(handler: ?fn (id) callconv(.C) void) void;
382 | pub extern fn objc_setForwardHandler(fwd: ?*c_void, fwd_stret: ?*c_void) void;
383 | pub extern fn imp_implementationWithBlock(block: id) IMP;
384 | pub extern fn imp_getBlock(anImp: IMP) id;
385 | pub extern fn imp_removeBlock(anImp: IMP) BOOL;
386 | pub extern fn objc_loadWeak(location: [*c]id) id;
387 | pub extern fn objc_storeWeak(location: [*c]id, obj: id) id;
388 | pub const objc_AssociationPolicy = usize;
389 | pub const OBJC_ASSOCIATION_ASSIGN = @enumToInt(enum_unnamed_3.OBJC_ASSOCIATION_ASSIGN);
390 | pub const OBJC_ASSOCIATION_RETAIN_NONATOMIC = @enumToInt(enum_unnamed_3.OBJC_ASSOCIATION_RETAIN_NONATOMIC);
391 | pub const OBJC_ASSOCIATION_COPY_NONATOMIC = @enumToInt(enum_unnamed_3.OBJC_ASSOCIATION_COPY_NONATOMIC);
392 | pub const OBJC_ASSOCIATION_RETAIN = @enumToInt(enum_unnamed_3.OBJC_ASSOCIATION_RETAIN);
393 | pub const OBJC_ASSOCIATION_COPY = @enumToInt(enum_unnamed_3.OBJC_ASSOCIATION_COPY);
394 | const enum_unnamed_3 = extern enum(c_int) {
395 | OBJC_ASSOCIATION_ASSIGN = 0,
396 | OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
397 | OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
398 | OBJC_ASSOCIATION_RETAIN = 769,
399 | OBJC_ASSOCIATION_COPY = 771,
400 | _,
401 | };
402 | pub extern fn objc_setAssociatedObject(object: id, key: ?*const c_void, value: id, policy: objc_AssociationPolicy) void;
403 | pub extern fn objc_getAssociatedObject(object: id, key: ?*const c_void) id;
404 | pub extern fn objc_removeAssociatedObjects(object: id) void;
405 | pub const objc_hook_getImageName = ?fn (Class, [*c][*c]const u8) callconv(.C) BOOL;
406 | pub extern fn objc_setHook_getImageName(newValue: objc_hook_getImageName, outOldValue: [*c]objc_hook_getImageName) void;
407 | pub const objc_hook_getClass = ?fn ([*c]const u8, [*c]Class) callconv(.C) BOOL;
408 | pub extern fn objc_setHook_getClass(newValue: objc_hook_getClass, outOldValue: [*c]objc_hook_getClass) void;
409 | pub const objc_hook_setAssociatedObject = ?fn (id, ?*const c_void, id, objc_AssociationPolicy) callconv(.C) void;
410 | pub extern fn objc_setHook_setAssociatedObject(newValue: objc_hook_setAssociatedObject, outOldValue: [*c]objc_hook_setAssociatedObject) void;
411 | pub const struct_mach_header = @OpaqueType();
412 | pub const objc_func_loadImage = ?fn (?*const struct_mach_header) callconv(.C) void;
413 | pub extern fn objc_addLoadImageFunc(func: objc_func_loadImage) void;
414 | pub const _objc_swiftMetadataInitializer = ?fn (Class, ?*c_void) callconv(.C) Class;
415 | pub extern fn _objc_realizeClassFromSwift(cls: Class, previously: ?*c_void) Class;
416 | pub const struct_objc_method_description_list = extern struct {
417 | count: c_int,
418 | list: [1]struct_objc_method_description,
419 | };
420 | pub const struct_objc_symtab = extern struct {
421 | sel_ref_cnt: c_ulong,
422 | refs: [*c]SEL,
423 | cls_def_cnt: c_ushort,
424 | cat_def_cnt: c_ushort,
425 | defs: [1]?*c_void,
426 | };
427 | pub const Symtab = [*c]struct_objc_symtab;
428 | pub const Cache = [*c]struct_objc_cache;
429 | pub const struct_objc_module = extern struct {
430 | version: c_ulong,
431 | size: c_ulong,
432 | name: [*c]const u8,
433 | symtab: Symtab,
434 | };
435 | pub const Module = [*c]struct_objc_module;
436 | pub extern fn class_lookupMethod(cls: Class, sel: SEL) IMP;
437 | pub extern fn class_respondsToMethod(cls: Class, sel: SEL) BOOL;
438 | pub extern fn _objc_flush_caches(cls: Class) void;
439 | pub extern fn object_copyFromZone(anObject: id, nBytes: usize, z: ?*c_void) id;
440 | pub extern fn object_realloc(anObject: id, nBytes: usize) id;
441 | pub extern fn object_reallocFromZone(anObject: id, nBytes: usize, z: ?*c_void) id;
442 | pub extern fn objc_getClasses() ?*c_void;
443 | pub extern fn objc_addClass(myClass: Class) void;
444 | pub extern fn objc_setClassHandler(?fn ([*c]const u8) callconv(.C) c_int) void;
445 | pub extern fn objc_setMultithreaded(flag: BOOL) void;
446 | pub extern fn class_createInstanceFromZone(Class, idxIvars: usize, z: ?*c_void) id;
447 | pub extern fn class_addMethods(Class, [*c]struct_objc_method_list) void;
448 | pub extern fn class_removeMethods(Class, [*c]struct_objc_method_list) void;
449 | pub extern fn _objc_resolve_categories_for_class(cls: Class) void;
450 | pub extern fn class_poseAs(imposter: Class, original: Class) Class;
451 | pub extern fn method_getSizeOfArguments(m: Method) c_uint;
452 | pub extern fn method_getArgumentInfo(m: [*c]struct_objc_method, arg: c_int, type: [*c][*c]const u8, offset: [*c]c_int) c_uint;
453 | pub extern fn objc_getOrigClass(name: [*c]const u8) Class;
454 | pub extern fn class_nextMethodList(Class, [*c]?*c_void) [*c]struct_objc_method_list;
455 | pub extern var _alloc: ?fn (Class, usize) callconv(.C) id;
456 | pub extern var _copy: ?fn (id, usize) callconv(.C) id;
457 | pub extern var _realloc: ?fn (id, usize) callconv(.C) id;
458 | pub extern var _dealloc: ?fn (id) callconv(.C) id;
459 | pub extern var _zoneAlloc: ?fn (Class, usize, ?*c_void) callconv(.C) id;
460 | pub extern var _zoneRealloc: ?fn (id, usize, ?*c_void) callconv(.C) id;
461 | pub extern var _zoneCopy: ?fn (id, usize, ?*c_void) callconv(.C) id;
462 | pub const struct___va_list_tag = extern struct {
463 | gp_offset: c_uint,
464 | fp_offset: c_uint,
465 | overflow_arg_area: ?*c_void,
466 | reg_save_area: ?*c_void,
467 | };
468 | pub extern var _error: ?fn (id, [*c]const u8, [*c]struct___va_list_tag) callconv(.C) void;
469 | pub const struct_objc_super = extern struct {
470 | receiver: id,
471 | class: Class,
472 | };
473 | pub extern fn objc_msgSend(self: id, op: SEL, ...) id;
474 | pub extern fn objc_msgSendSuper() void;
475 | pub extern fn objc_msgSend_stret() void;
476 | pub extern fn objc_msgSendSuper_stret() void;
477 | pub extern fn objc_msgSend_fpret() void;
478 | pub extern fn objc_msgSend_fp2ret() void;
479 | pub extern fn method_invoke() void;
480 | pub extern fn method_invoke_stret() void;
481 | pub extern fn _objc_msgForward() void;
482 | pub extern fn _objc_msgForward_stret() void;
483 | pub const marg_list = ?*c_void;
484 | pub extern fn objc_msgSendv(self: id, op: SEL, arg_size: usize, arg_frame: marg_list) id;
485 | pub extern fn objc_msgSendv_stret(stretAddr: ?*c_void, self: id, op: SEL, arg_size: usize, arg_frame: marg_list) void;
486 | pub const __INTMAX_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
487 | pub const __UINTMAX_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
488 | pub const __PTRDIFF_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
489 | pub const __INTPTR_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
490 | pub const __SIZE_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
491 | pub const __CHAR16_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
492 | pub const __CHAR32_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
493 | pub const __UINTPTR_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
494 | pub const __INT8_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
495 | pub const __INT64_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
496 | pub const __UINT8_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
497 | pub const __UINT16_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
498 | pub const __UINT32_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
499 | pub const __UINT64_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
500 | pub const __INT_LEAST8_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
501 | pub const __UINT_LEAST8_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
502 | pub const __UINT_LEAST16_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
503 | pub const __UINT_LEAST32_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
504 | pub const __INT_LEAST64_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
505 | pub const __UINT_LEAST64_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
506 | pub const __INT_FAST8_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
507 | pub const __UINT_FAST8_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
508 | pub const __UINT_FAST16_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
509 | pub const __UINT_FAST32_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
510 | pub const __INT_FAST64_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
511 | pub const __UINT_FAST64_TYPE__ = @compileError("unable to translate C expr: unexpected token Id.Identifier");
512 | pub const __AVX__ = 1;
513 | pub const __UINT64_MAX__ = @as(c_ulonglong, 18446744073709551615);
514 | pub const __FINITE_MATH_ONLY__ = 0;
515 | pub const __SIZEOF_FLOAT__ = 4;
516 | pub const __SEG_GS = 1;
517 | pub const __UINT_LEAST64_FMTX__ = "llX";
518 | pub const __INT_FAST8_MAX__ = 127;
519 | pub const __tune_corei7__ = 1;
520 | pub const __OBJC_BOOL_IS_BOOL = 0;
521 | pub const __INT_LEAST8_FMTi__ = "hhi";
522 | pub const __UINT64_FMTX__ = "llX";
523 | pub const __SSE4_2__ = 1;
524 | pub const __SIG_ATOMIC_MAX__ = 2147483647;
525 | pub const __SSE__ = 1;
526 | pub const __BYTE_ORDER__ = __ORDER_LITTLE_ENDIAN__;
527 | pub const __NO_MATH_INLINES = 1;
528 | pub const __INT_FAST32_FMTd__ = "d";
529 | pub const __STDC_UTF_16__ = 1;
530 | pub const __UINT_FAST16_MAX__ = 65535;
531 | pub const __ATOMIC_ACQUIRE = 2;
532 | pub const __LDBL_HAS_DENORM__ = 1;
533 | pub const __INTMAX_FMTi__ = "li";
534 | pub const __FLT_EPSILON__ = @as(f32, 1.19209290e-7);
535 | pub const __FMA__ = 1;
536 | pub const __APPLE__ = 1;
537 | pub const __UINT_FAST32_FMTo__ = "o";
538 | pub const __UINT32_MAX__ = @as(c_uint, 4294967295);
539 | pub const __INT_MAX__ = 2147483647;
540 | pub const __INT_LEAST64_MAX__ = @as(c_longlong, 9223372036854775807);
541 | pub const __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 = 1;
542 | pub const __SIZEOF_INT128__ = 16;
543 | pub const __INT64_MAX__ = @as(c_longlong, 9223372036854775807);
544 | pub const __DBL_MIN_10_EXP__ = -307;
545 | pub const __INT_LEAST32_MAX__ = 2147483647;
546 | pub const __INT_FAST16_FMTd__ = "hd";
547 | pub const __UINT_LEAST64_FMTu__ = "llu";
548 | pub const __DBL_DENORM_MIN__ = 4.9406564584124654e-324;
549 | pub const __UINT8_FMTu__ = "hhu";
550 | pub const __INT_FAST16_MAX__ = 32767;
551 | pub const __INVPCID__ = 1;
552 | pub const __LP64__ = 1;
553 | pub const __SIZE_FMTx__ = "lx";
554 | pub const __ORDER_PDP_ENDIAN__ = 3412;
555 | pub const __UINT8_FMTX__ = "hhX";
556 | pub const __LDBL_MIN_10_EXP__ = -4931;
557 | pub const __LDBL_MAX_10_EXP__ = 4932;
558 | pub const __DBL_MAX_10_EXP__ = 308;
559 | pub const __PTRDIFF_FMTi__ = "li";
560 | pub const __CLFLUSHOPT__ = 1;
561 | pub const __FLT_MIN_EXP__ = -125;
562 | pub const __SIZEOF_LONG__ = 8;
563 | pub const __FLT_MIN__ = @as(f32, 1.17549435e-38);
564 | pub const __FLT_EVAL_METHOD__ = 0;
565 | pub const __UINTMAX_FMTx__ = "lx";
566 | pub const __UINT_LEAST8_FMTo__ = "hho";
567 | pub const __code_model_small_ = 1;
568 | pub const __UINT_FAST8_FMTu__ = "hhu";
569 | pub const _LP64 = 1;
570 | pub const __FLT_MAX_EXP__ = 128;
571 | pub const __DBL_HAS_DENORM__ = 1;
572 | pub const __INT_LEAST64_FMTd__ = "lld";
573 | pub const __SSSE3__ = 1;
574 | pub const __UINT_LEAST8_FMTu__ = "hhu";
575 | pub const __FLT_MAX__ = @as(f32, 3.40282347e+38);
576 | pub const __STDC_NO_THREADS__ = 1;
577 | pub const __UINT_FAST8_FMTX__ = "hhX";
578 | pub const __UINT_FAST16_FMTu__ = "hu";
579 | pub const __UINT_FAST32_FMTX__ = "X";
580 | pub const __DECIMAL_DIG__ = __LDBL_DECIMAL_DIG__;
581 | pub const __LZCNT__ = 1;
582 | pub const __SSP_STRONG__ = 2;
583 | pub const __clang_patchlevel__ = 1;
584 | pub const __UINT64_FMTu__ = "llu";
585 | pub const __SIZEOF_SHORT__ = 2;
586 | pub const __LDBL_DIG__ = 18;
587 | pub const __MPX__ = 1;
588 | pub const __OPENCL_MEMORY_SCOPE_DEVICE = 2;
589 | pub const __INT_FAST8_FMTd__ = "hhd";
590 | pub const __FLT_DENORM_MIN__ = @as(f32, 1.40129846e-45);
591 | pub const __MMX__ = 1;
592 | pub const __SIZEOF_WINT_T__ = 4;
593 | pub const __NO_INLINE__ = 1;
594 | pub const __CLANG_ATOMIC_POINTER_LOCK_FREE = 2;
595 | pub const __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 = 1;
596 | pub const __INTMAX_C_SUFFIX__ = L;
597 | pub const __UINT_LEAST32_FMTu__ = "u";
598 | pub const __INT_LEAST16_FMTi__ = "hi";
599 | pub const __LITTLE_ENDIAN__ = 1;
600 | pub const __UINTMAX_C_SUFFIX__ = UL;
601 | pub const __OPENCL_MEMORY_SCOPE_WORK_ITEM = 0;
602 | pub const __VERSION__ = "Clang 9.0.1 (https://github.com/llvm/llvm-project.git 432bf48c08da748e2542cf40e3ab9aee53a744b0)";
603 | pub const __DBL_HAS_INFINITY__ = 1;
604 | pub const __INT_LEAST16_MAX__ = 32767;
605 | pub const __SCHAR_MAX__ = 127;
606 | pub const __GNUC_MINOR__ = 2;
607 | pub const __UINT32_FMTx__ = "x";
608 | pub const __corei7 = 1;
609 | pub const __LDBL_HAS_QUIET_NAN__ = 1;
610 | pub const __UINT_FAST32_FMTu__ = "u";
611 | pub const __UINT8_FMTx__ = "hhx";
612 | pub const __UINT_LEAST8_FMTX__ = "hhX";
613 | pub const __UINT_LEAST64_FMTx__ = "llx";
614 | pub const __UINT_LEAST64_MAX__ = @as(c_ulonglong, 18446744073709551615);
615 | pub const __GCC_ATOMIC_CHAR16_T_LOCK_FREE = 2;
616 | pub const __pic__ = 2;
617 | pub const __RTM__ = 1;
618 | pub const __clang__ = 1;
619 | pub const __FLT_HAS_INFINITY__ = 1;
620 | pub const __UINTPTR_FMTu__ = "lu";
621 | pub const __INT_FAST32_TYPE__ = int;
622 | pub const __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1 = 1;
623 | pub const __UINT16_FMTx__ = "hx";
624 | pub const __ADX__ = 1;
625 | pub const __UINT_LEAST32_FMTo__ = "o";
626 | pub const __FLT_MIN_10_EXP__ = -37;
627 | pub const __UINT_LEAST16_FMTX__ = "hX";
628 | pub const __UINT_LEAST32_MAX__ = @as(c_uint, 4294967295);
629 | pub const __UINT_FAST8_FMTx__ = "hhx";
630 | pub const __SIZE_FMTu__ = "lu";
631 | pub const __SIZEOF_POINTER__ = 8;
632 | pub const __SIZE_FMTX__ = "lX";
633 | pub const __nullable = _Nullable;
634 | pub const __INT16_FMTd__ = "hd";
635 | pub const __clang_version__ = "9.0.1 (https://github.com/llvm/llvm-project.git 432bf48c08da748e2542cf40e3ab9aee53a744b0)";
636 | pub const __ATOMIC_RELEASE = 3;
637 | pub const __UINT_FAST64_FMTX__ = "llX";
638 | pub const __INTMAX_FMTd__ = "ld";
639 | pub const __SEG_FS = 1;
640 | pub const __UINT_FAST8_FMTo__ = "hho";
641 | pub const __WINT_WIDTH__ = 32;
642 | pub const __FLT_MAX_10_EXP__ = 38;
643 | pub const __LDBL_MAX__ = @as(f64, 1.18973149535723176502e+4932);
644 | pub const __GCC_ATOMIC_LONG_LOCK_FREE = 2;
645 | pub const _DEBUG = 1;
646 | pub const __UINTPTR_WIDTH__ = 64;
647 | pub const __INT_LEAST32_FMTi__ = "i";
648 | pub const __WCHAR_WIDTH__ = 32;
649 | pub const __UINT16_FMTX__ = "hX";
650 | pub const __MACH__ = 1;
651 | pub const __GNUC_PATCHLEVEL__ = 1;
652 | pub const __INT_LEAST16_TYPE__ = short;
653 | pub const __APPLE_CC__ = 6000;
654 | pub const __INT64_FMTd__ = "lld";
655 | pub const __SSE3__ = 1;
656 | pub const __UINT16_MAX__ = 65535;
657 | pub const __ATOMIC_RELAXED = 0;
658 | pub const __GCC_ATOMIC_CHAR_LOCK_FREE = 2;
659 | pub const __UINT_LEAST16_FMTx__ = "hx";
660 | pub const __UINT_FAST64_FMTu__ = "llu";
661 | pub const __CLANG_ATOMIC_CHAR16_T_LOCK_FREE = 2;
662 | pub const __SSE2__ = 1;
663 | pub const __STDC__ = 1;
664 | pub const __block = __attribute__(__blocks__(byref));
665 | pub const __INT_FAST16_TYPE__ = short;
666 | pub const __UINT64_C_SUFFIX__ = ULL;
667 | pub const __LONG_MAX__ = @as(c_long, 9223372036854775807);
668 | pub const __DBL_MAX__ = 1.7976931348623157e+308;
669 | pub const __CHAR_BIT__ = 8;
670 | pub const __DBL_DECIMAL_DIG__ = 17;
671 | pub const __UINT_LEAST8_FMTx__ = "hhx";
672 | pub const __FSGSBASE__ = 1;
673 | pub const __ORDER_BIG_ENDIAN__ = 4321;
674 | pub const __DYNAMIC__ = 1;
675 | pub const __INTPTR_MAX__ = @as(c_long, 9223372036854775807);
676 | pub const __INT_LEAST8_FMTd__ = "hhd";
677 | pub const __INTMAX_WIDTH__ = 64;
678 | pub const __CLANG_ATOMIC_SHORT_LOCK_FREE = 2;
679 | pub const __LDBL_DENORM_MIN__ = @as(f64, 3.64519953188247460253e-4951);
680 | pub const __x86_64 = 1;
681 | pub const __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ = 101500;
682 | pub const __CLANG_ATOMIC_LONG_LOCK_FREE = 2;
683 | pub const __INTMAX_MAX__ = @as(c_long, 9223372036854775807);
684 | pub const __INT8_FMTd__ = "hhd";
685 | pub const __UINTMAX_WIDTH__ = 64;
686 | pub const __UINT8_MAX__ = 255;
687 | pub const __DBL_MIN__ = 2.2250738585072014e-308;
688 | pub const __PRAGMA_REDEFINE_EXTNAME = 1;
689 | pub const __DBL_HAS_QUIET_NAN__ = 1;
690 | pub const __clang_minor__ = 0;
691 | pub const __LDBL_DECIMAL_DIG__ = 21;
692 | pub const __SSE4_1__ = 1;
693 | pub const __WCHAR_TYPE__ = int;
694 | pub const __INT_FAST64_FMTd__ = "lld";
695 | pub const __RDRND__ = 1;
696 | pub const __GCC_ATOMIC_WCHAR_T_LOCK_FREE = 2;
697 | pub const __seg_fs = __attribute__(address_space(257));
698 | pub const __XSAVEOPT__ = 1;
699 | pub const __UINTMAX_FMTX__ = "lX";
700 | pub const __INT16_FMTi__ = "hi";
701 | pub const __LDBL_MIN_EXP__ = -16381;
702 | pub const __PRFCHW__ = 1;
703 | pub const __AVX2__ = 1;
704 | pub const __UINTMAX_FMTu__ = "lu";
705 | pub const __UINT_LEAST16_FMTo__ = "ho";
706 | pub const __UINT32_FMTu__ = "u";
707 | pub const __GCC_HAVE_SYNC_COMPARE_AND_SWAP_2 = 1;
708 | pub const __SIG_ATOMIC_WIDTH__ = 32;
709 | pub const __amd64__ = 1;
710 | pub const __null_unspecified = _Null_unspecified;
711 | pub const __INT64_C_SUFFIX__ = LL;
712 | pub const __LDBL_EPSILON__ = @as(f64, 1.08420217248550443401e-19);
713 | pub const __CLANG_ATOMIC_INT_LOCK_FREE = 2;
714 | pub const __SSE2_MATH__ = 1;
715 | pub const __GCC_ATOMIC_SHORT_LOCK_FREE = 2;
716 | pub const __SGX__ = 1;
717 | pub const __POPCNT__ = 1;
718 | pub const __POINTER_WIDTH__ = 64;
719 | pub const __UINT64_FMTx__ = "llx";
720 | pub const __ATOMIC_ACQ_REL = 4;
721 | pub const __UINT_LEAST32_FMTx__ = "x";
722 | pub const __STDC_HOSTED__ = 1;
723 | pub const __GNUC__ = 4;
724 | pub const __INT_FAST32_FMTi__ = "i";
725 | pub const __PIC__ = 2;
726 | pub const __GCC_ATOMIC_BOOL_LOCK_FREE = 2;
727 | pub const __seg_gs = __attribute__(address_space(256));
728 | pub const __FXSR__ = 1;
729 | pub const __UINT64_FMTo__ = "llo";
730 | pub const __UINT_FAST16_FMTx__ = "hx";
731 | pub const __UINT_LEAST64_FMTo__ = "llo";
732 | pub const __STDC_UTF_32__ = 1;
733 | pub const __PTRDIFF_WIDTH__ = 64;
734 | pub const __SIZE_WIDTH__ = 64;
735 | pub const __LDBL_MIN__ = @as(f64, 3.36210314311209350626e-4932);
736 | pub const __UINTMAX_MAX__ = @as(c_ulong, 18446744073709551615);
737 | pub const __INT_LEAST16_FMTd__ = "hd";
738 | pub const __SIZEOF_PTRDIFF_T__ = 8;
739 | pub const __UINT_LEAST16_FMTu__ = "hu";
740 | pub const __UINT16_FMTu__ = "hu";
741 | pub const __DBL_MANT_DIG__ = 53;
742 | pub const __CLANG_ATOMIC_WCHAR_T_LOCK_FREE = 2;
743 | pub const __INT_LEAST64_FMTi__ = "lli";
744 | pub const __GNUC_STDC_INLINE__ = 1;
745 | pub const __weak = __attribute__(objc_gc(weak));
746 | pub const __UINT32_FMTX__ = "X";
747 | pub const __DBL_DIG__ = 15;
748 | pub const __SHRT_MAX__ = 32767;
749 | pub const __ATOMIC_CONSUME = 1;
750 | pub const __UINT_FAST16_FMTX__ = "hX";
751 | pub const __INT_FAST16_FMTi__ = "hi";
752 | pub const __INT32_FMTd__ = "d";
753 | pub const __INT8_MAX__ = 127;
754 | pub const __FLT_DECIMAL_DIG__ = 9;
755 | pub const __INT_LEAST32_FMTd__ = "d";
756 | pub const __UINT8_FMTo__ = "hho";
757 | pub const __FLT_HAS_DENORM__ = 1;
758 | pub const __FLT_DIG__ = 6;
759 | pub const __USER_LABEL_PREFIX__ = _;
760 | pub const __INTPTR_FMTi__ = "li";
761 | pub const __UINT32_FMTo__ = "o";
762 | pub const __UINT_FAST64_MAX__ = @as(c_ulonglong, 18446744073709551615);
763 | pub const __UINT_FAST64_FMTo__ = "llo";
764 | pub const __GXX_ABI_VERSION = 1002;
765 | pub const __SIZEOF_LONG_LONG__ = 8;
766 | pub const __WINT_TYPE__ = int;
767 | pub const __INT32_TYPE__ = int;
768 | pub const __OPENCL_MEMORY_SCOPE_ALL_SVM_DEVICES = 3;
769 | pub const __UINTPTR_FMTX__ = "lX";
770 | pub const __INT8_FMTi__ = "hhi";
771 | pub const __SIZEOF_LONG_DOUBLE__ = 16;
772 | pub const __DBL_MIN_EXP__ = -1021;
773 | pub const __INT_FAST64_FMTi__ = "lli";
774 | pub const __INT64_FMTi__ = "lli";
775 | pub const __GCC_ATOMIC_TEST_AND_SET_TRUEVAL = 1;
776 | pub const __clang_major__ = 9;
777 | pub const __OPENCL_MEMORY_SCOPE_SUB_GROUP = 4;
778 | pub const __INT16_MAX__ = 32767;
779 | pub const __GCC_ATOMIC_LLONG_LOCK_FREE = 2;
780 | pub const __UINT16_FMTo__ = "ho";
781 | pub const __INT_FAST8_FMTi__ = "hhi";
782 | pub const __UINT_FAST64_FMTx__ = "llx";
783 | pub const __XSAVES__ = 1;
784 | pub const __UINT_LEAST8_MAX__ = 255;
785 | pub const __LDBL_HAS_INFINITY__ = 1;
786 | pub const __UINT_LEAST32_FMTX__ = "X";
787 | pub const __nonnull = _Nonnull;
788 | pub const __UINT_LEAST16_MAX__ = 65535;
789 | pub const __CONSTANT_CFSTRINGS__ = 1;
790 | pub const __SSE_MATH__ = 1;
791 | pub const __DBL_EPSILON__ = 2.2204460492503131e-16;
792 | pub const __llvm__ = 1;
793 | pub const __DBL_MAX_EXP__ = 1024;
794 | pub const __CLANG_ATOMIC_CHAR_LOCK_FREE = 2;
795 | pub const __CLANG_ATOMIC_CHAR32_T_LOCK_FREE = 2;
796 | pub const __GCC_ASM_FLAG_OUTPUTS__ = 1;
797 | pub const __PTRDIFF_MAX__ = @as(c_long, 9223372036854775807);
798 | pub const __ORDER_LITTLE_ENDIAN__ = 1234;
799 | pub const __INT16_TYPE__ = short;
800 | pub const __PCLMUL__ = 1;
801 | pub const __UINTPTR_FMTx__ = "lx";
802 | pub const __LDBL_MAX_EXP__ = 16384;
803 | pub const __UINT_FAST32_MAX__ = @as(c_uint, 4294967295);
804 | pub const __AES__ = 1;
805 | pub const __FLT_RADIX__ = 2;
806 | pub const __amd64 = 1;
807 | pub const __WINT_MAX__ = 2147483647;
808 | pub const __UINTPTR_FMTo__ = "lo";
809 | pub const __INT32_MAX__ = 2147483647;
810 | pub const __INTPTR_FMTd__ = "ld";
811 | pub const __INTPTR_WIDTH__ = 64;
812 | pub const __XSAVE__ = 1;
813 | pub const __INT_FAST32_MAX__ = 2147483647;
814 | pub const __INT32_FMTi__ = "i";
815 | pub const __GCC_ATOMIC_CHAR32_T_LOCK_FREE = 2;
816 | pub const __UINT_FAST16_FMTo__ = "ho";
817 | pub const __RDSEED__ = 1;
818 | pub const __GCC_ATOMIC_INT_LOCK_FREE = 2;
819 | pub const __FLT_HAS_QUIET_NAN__ = 1;
820 | pub const __corei7__ = 1;
821 | pub const __MOVBE__ = 1;
822 | pub const __INT_LEAST32_TYPE__ = int;
823 | pub const __BIGGEST_ALIGNMENT__ = 16;
824 | pub const __GCC_ATOMIC_POINTER_LOCK_FREE = 2;
825 | pub const __SIZE_MAX__ = @as(c_ulong, 18446744073709551615);
826 | pub const __INT_FAST64_MAX__ = @as(c_longlong, 9223372036854775807);
827 | pub const __BLOCKS__ = 1;
828 | pub const __XSAVEC__ = 1;
829 | pub const __CLANG_ATOMIC_LLONG_LOCK_FREE = 2;
830 | pub const __UINTPTR_MAX__ = @as(c_ulong, 18446744073709551615);
831 | pub const __UINT_FAST32_FMTx__ = "x";
832 | pub const __PTRDIFF_FMTd__ = "ld";
833 | pub const __LONG_LONG_MAX__ = @as(c_longlong, 9223372036854775807);
834 | pub const __CLANG_ATOMIC_BOOL_LOCK_FREE = 2;
835 | pub const __WCHAR_MAX__ = 2147483647;
836 | pub const __ATOMIC_SEQ_CST = 5;
837 | pub const __LDBL_MANT_DIG__ = 64;
838 | pub const __UINT_FAST8_MAX__ = 255;
839 | pub const __SIZEOF_SIZE_T__ = 8;
840 | pub const __BMI2__ = 1;
841 | pub const __STDC_VERSION__ = @as(c_long, 201112);
842 | pub const __F16C__ = 1;
843 | pub const __GCC_HAVE_SYNC_COMPARE_AND_SWAP_16 = 1;
844 | pub const __OPENCL_MEMORY_SCOPE_WORK_GROUP = 1;
845 | pub const __SIZEOF_INT__ = 4;
846 | pub const OBJC_NEW_PROPERTIES = 1;
847 | pub const __UINT32_C_SUFFIX__ = U;
848 | pub const __x86_64__ = 1;
849 | pub const __BMI__ = 1;
850 | pub const __FLT_MANT_DIG__ = 24;
851 | pub const __INT_LEAST8_MAX__ = 127;
852 | pub const __UINTMAX_FMTo__ = "lo";
853 | pub const __SIZE_FMTo__ = "lo";
854 | pub const __SIZEOF_DOUBLE__ = 8;
855 | pub const __SIZEOF_WCHAR_T__ = 4;
856 | pub const __darwin_pthread_handler_rec = struct___darwin_pthread_handler_rec;
857 | pub const _opaque_pthread_attr_t = struct__opaque_pthread_attr_t;
858 | pub const _opaque_pthread_cond_t = struct__opaque_pthread_cond_t;
859 | pub const _opaque_pthread_condattr_t = struct__opaque_pthread_condattr_t;
860 | pub const _opaque_pthread_mutex_t = struct__opaque_pthread_mutex_t;
861 | pub const _opaque_pthread_mutexattr_t = struct__opaque_pthread_mutexattr_t;
862 | pub const _opaque_pthread_once_t = struct__opaque_pthread_once_t;
863 | pub const _opaque_pthread_rwlock_t = struct__opaque_pthread_rwlock_t;
864 | pub const _opaque_pthread_rwlockattr_t = struct__opaque_pthread_rwlockattr_t;
865 | pub const _opaque_pthread_t = struct__opaque_pthread_t;
866 | pub const objc_ivar = struct_objc_ivar;
867 | pub const objc_ivar_list = struct_objc_ivar_list;
868 | pub const objc_method = struct_objc_method;
869 | pub const objc_method_list = struct_objc_method_list;
870 | pub const objc_cache = struct_objc_cache;
871 | pub const objc_protocol_list = struct_objc_protocol_list;
872 | pub const objc_class = struct_objc_class;
873 | pub const objc_object = struct_objc_object;
874 | pub const objc_selector = struct_objc_selector;
875 | pub const objc_category = struct_objc_category;
876 | pub const objc_property = struct_objc_property;
877 | pub const objc_method_description = struct_objc_method_description;
878 | pub const mach_header = struct_mach_header;
879 | pub const objc_method_description_list = struct_objc_method_description_list;
880 | pub const objc_symtab = struct_objc_symtab;
881 | pub const objc_module = struct_objc_module;
882 | pub const __va_list_tag = struct___va_list_tag;
883 | pub const objc_super = struct_objc_super;
884 |
--------------------------------------------------------------------------------
/src/main.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const target = std.Target.current;
3 |
4 | pub usingnamespace switch (target.os.tag) {
5 | .freestanding => switch (target.cpu.arch) {
6 | .wasm32, .wasm64 => @import("host/browser.zig"),
7 | else => @compileError("CPU architecture not supported"),
8 | },
9 | .macosx => @import("host/macos.zig"),
10 | else => @compileError("OS not (yet) supported"),
11 | };
12 |
13 | test "main" {
14 | _ = @import("midi.zig");
15 | _ = @import("util.zig");
16 | _ = @import("synth.zig");
17 | _ = @import("wavetable.zig");
18 | _ = @import("adsr.zig");
19 | }
20 |
--------------------------------------------------------------------------------
/src/midi.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | pub const Message = union(enum) {
4 | NoteOff: struct {
5 | channel: u4,
6 | note: u7,
7 | velocity: u7,
8 | },
9 | NoteOn: struct {
10 | channel: u4,
11 | note: u7,
12 | velocity: u7,
13 | },
14 | PolyphonicAftertouch: struct {
15 | note: u7,
16 | pressure: u7,
17 | },
18 | ControlChange: struct {
19 | channel: u4,
20 | controller: u7,
21 | value: u7,
22 | },
23 | ProgramChange: struct {
24 | number: u7,
25 | },
26 | ChannelAftertouch: struct {
27 | pressure: u7,
28 | },
29 | PitchBend: struct {
30 | channel: u4,
31 | value: u14,
32 | },
33 | TimingClock: void,
34 | Unknown: struct {
35 | length: usize,
36 | },
37 |
38 | pub fn parse(data: []u8) Message {
39 | std.debug.assert(data[0] >> 7 == 1);
40 |
41 | const status = @intCast(u3, data[0] >> 4 & 0b111);
42 |
43 | return switch (status) {
44 | 0 => .{
45 | .NoteOff = .{
46 | .channel = @intCast(u4, data[0] & 0xf),
47 | .note = @intCast(u7, data[1] & 0x7f),
48 | .velocity = @intCast(u7, data[2] & 0x7f),
49 | },
50 | },
51 | 1 => .{
52 | .NoteOn = .{
53 | .channel = @intCast(u4, data[0] & 0xf),
54 | .note = @intCast(u7, data[1] & 0x7f),
55 | .velocity = @intCast(u7, data[2] & 0x7f),
56 | },
57 | },
58 | 2 => .{
59 | .PolyphonicAftertouch = .{
60 | .note = @intCast(u7, data[1] & 0x7f),
61 | .pressure = @intCast(u7, data[2] & 0x7f),
62 | },
63 | },
64 | 3 => .{
65 | .ControlChange = .{
66 | .channel = @intCast(u4, data[0] & 0xf),
67 | .controller = @intCast(u7, data[1] & 0x7f),
68 | .value = @intCast(u7, data[2] & 0x7f),
69 | },
70 | },
71 | 4 => .{
72 | .ProgramChange = .{
73 | .number = @intCast(u7, data[1] & 0x7f),
74 | },
75 | },
76 | 5 => .{
77 | .ChannelAftertouch = .{
78 | .pressure = @intCast(u7, data[1] & 0x7f),
79 | },
80 | },
81 | 6 => .{
82 | .PitchBend = .{
83 | .channel = @intCast(u4, data[0] & 0xf),
84 | .value = @intCast(u14, data[1] & 0x7f) | (@intCast(u14, data[2] & 0x7f) << 7),
85 | },
86 | },
87 | 7 => parseExtended(data),
88 | };
89 | }
90 |
91 | fn parseExtended(data: []u8) Message {
92 | return switch (data[0]) {
93 | 0xf8 => .{ .TimingClock = {} },
94 | else => .{
95 | .Unknown = .{
96 | .length = data.len,
97 | },
98 | },
99 | };
100 | }
101 | };
102 |
103 | pub fn noteToFrequency(note: u7) f64 {
104 | const note_float = @intToFloat(f64, note);
105 |
106 | return 440 * std.math.pow(f64, 2, (@intToFloat(f32, note) - 69) / 12);
107 | }
108 |
109 | pub fn frequencyToNote(frequency: f64) ?f64 {
110 | const result = 12 * std.math.log2(frequency / 440) + 69;
111 |
112 | const upper_bound = @intToFloat(f64, std.math.maxInt(u7));
113 | const lower_bound = @intToFloat(f64, std.math.minInt(u7));
114 |
115 | if (result >= lower_bound and result <= upper_bound) {
116 | return result;
117 | } else {
118 | return null;
119 | }
120 | }
121 |
122 | // TODO Figure out if a logarithmic relation makes sense here
123 | pub fn velocityToFloat(comptime T: type, velocity: u7) T {
124 | const max = std.math.log(T, 10, std.math.maxInt(u7) + 1);
125 | return std.math.log(T, 10, @intToFloat(T, velocity) + 1) / max;
126 | }
127 |
128 | test "noteToFrequency" {
129 | std.testing.expectEqual(@as(f64, 440), noteToFrequency(69));
130 | }
131 |
132 | test "frequencyToNote" {
133 | std.testing.expectEqual(@as(?f64, 69), frequencyToNote(440));
134 | }
135 |
136 | test "velocityToFloat" {
137 | std.testing.expectEqual(@as(f64, 0), velocityToFloat(f64, 0));
138 | std.testing.expectEqual(@as(f64, 1), velocityToFloat(f64, 127));
139 | }
140 |
141 | test "Message.parse" {}
142 |
--------------------------------------------------------------------------------
/src/synth.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const wave = @import("wavetable.zig");
3 | const midi = @import("midi.zig");
4 | const util = @import("util.zig");
5 | const adsr = @import("adsr.zig");
6 | const Host = @import("root").Host;
7 |
8 | var default_table: ?wave.WaveformTable = null;
9 |
10 | fn populateDefaultTable(allocator: *std.mem.Allocator) !void {
11 | var table = wave.WaveformTable{
12 | .waveforms = try allocator.alloc(wave.Waveform, 3),
13 | };
14 |
15 | table.waveforms[0] = wave.Waveform.sineWave(1);
16 | table.waveforms[1] = wave.Waveform.sawtoothWave(1, 40);
17 | table.waveforms[2] = wave.Waveform.squareWave(1, 40);
18 |
19 | default_table = table;
20 | }
21 |
22 | const NoteOnMessage = std.meta.fieldInfo(midi.Message, "NoteOn").field_type;
23 |
24 | pub const Synth = struct {
25 | const Voice = struct {
26 | envelope: adsr.ADSR,
27 | oscillator_one: wave.WavetableOscillator,
28 | oscillator_two: wave.WavetableOscillator,
29 | oscillator_mix: f32 = 0.5,
30 | volume: f32 = 1,
31 |
32 | pub inline fn sample(self: *Voice) f32 {
33 | const one = self.oscillator_one.sample() * (1 - self.oscillator_mix);
34 | const two = self.oscillator_two.sample() * self.oscillator_mix;
35 | const multiplier = self.envelope.getMultiplier(1);
36 |
37 | return (one + two) * multiplier * self.volume;
38 | }
39 |
40 | pub fn reset(self: *Voice) void {
41 | self.oscillator_one.reset();
42 | self.oscillator_two.reset();
43 | }
44 |
45 | pub fn start(self: *Voice, msg: NoteOnMessage) void {
46 | const frequency = @floatCast(f32, midi.noteToFrequency(msg.note));
47 | self.volume = midi.velocityToFloat(f32, msg.velocity);
48 |
49 | var params_one = self.oscillator_one.params.read();
50 | params_one.frequency = frequency;
51 | self.oscillator_one.updateParams(params_one);
52 | self.oscillator_one.reset();
53 |
54 | var params_two = self.oscillator_two.params.read();
55 | params_two.frequency = frequency;
56 | self.oscillator_two.updateParams(params_two);
57 | self.oscillator_two.reset();
58 |
59 | self.envelope.gate(true);
60 | }
61 | };
62 |
63 | const VoiceWrapper = struct {
64 | // The u8 is required for @atomicStore to work right now.
65 | const State = enum(u8) {
66 | Free,
67 | Scheduled,
68 | Playing,
69 | };
70 |
71 | state: State = .Free,
72 | note: u7,
73 | voice: Voice,
74 |
75 | pub fn setup(self: *VoiceWrapper, msg: NoteOnMessage) bool {
76 | const xchg = @cmpxchgStrong(State, &self.state, .Free, .Scheduled, .SeqCst, .SeqCst);
77 |
78 | // Failed to acquire the voice
79 | if (xchg != null) {
80 | return false;
81 | }
82 |
83 | self.note = msg.note;
84 | self.voice.start(msg);
85 |
86 | @atomicStore(State, &self.state, .Playing, .SeqCst);
87 |
88 | return true;
89 | }
90 |
91 | pub fn free(self: *VoiceWrapper) void {
92 | @atomicStore(State, &self.state, .Free, .SeqCst);
93 | }
94 | };
95 |
96 | const VoiceCount = 8;
97 |
98 | allocator: *std.mem.Allocator,
99 | voices: []VoiceWrapper,
100 |
101 | pub fn init(allocator: *std.mem.Allocator, format: util.Format) !Synth {
102 | if (default_table == null) {
103 | try populateDefaultTable(allocator);
104 | }
105 |
106 | var self = Synth{
107 | .allocator = allocator,
108 | .voices = try allocator.alloc(VoiceWrapper, VoiceCount),
109 | };
110 |
111 | for (self.voices) |*wrapper| {
112 | wrapper.state = .Free;
113 | wrapper.voice = .{
114 | .envelope = adsr.ADSR.init(.{
115 | .format = format,
116 | .attack = 0.1,
117 | .decay = 0.03,
118 | .sustain = 0.4,
119 | .release = 0.7,
120 | }),
121 | .oscillator_one = try wave.WavetableOscillator.init(allocator, .{
122 | .format = format,
123 | .table = default_table.?,
124 | }),
125 | .oscillator_two = try wave.WavetableOscillator.init(allocator, .{
126 | .format = format,
127 | .table = default_table.?,
128 | }),
129 | };
130 | }
131 |
132 | return self;
133 | }
134 |
135 | pub fn deinit(self: *Synth) void {
136 | const table = default_table;
137 | default_table = null;
138 |
139 | self.allocator.free(table.waveforms);
140 | self.allocator.free(self.voices);
141 | }
142 |
143 | pub fn handleMIDIMessage(self: *Synth, message: midi.Message) void {
144 | switch (message) {
145 | .NoteOn => |info| {
146 | for (self.voices) |*wrapper, i| {
147 | switch (wrapper.state) {
148 | .Scheduled, .Playing => {},
149 | .Free => {
150 | if (wrapper.setup(info)) {
151 | break;
152 | } else {
153 | // This should only be possible when handleMIDIMessage is called from
154 | // two different threads (spoiler: it shouldn't ever, or should it?).
155 | // For my sanities sake I want to know when that is the case.
156 | unreachable;
157 | }
158 | },
159 | }
160 | } else {
161 | var quiet_voice: ?*VoiceWrapper = null;
162 | var quiet_volume: ?f32 = null;
163 |
164 | for (self.voices) |*wrapper| {
165 | const volume = wrapper.voice.volume * wrapper.voice.envelope.getMultiplier(0);
166 |
167 | if (quiet_volume == null or volume < quiet_volume.?) {
168 | quiet_volume = volume;
169 | quiet_voice = wrapper;
170 | }
171 | }
172 |
173 | if (quiet_voice) |wrapper| {
174 | // TODO Maybe we should give the stolen voice a few milliseconds to fade out
175 | wrapper.free();
176 |
177 | if (!wrapper.setup(info)) {
178 | unreachable;
179 | }
180 | } else unreachable;
181 | }
182 | },
183 | .NoteOff => |info| {
184 | // Find the voice that is playing the released note and
185 | // stop its envelope.
186 | for (self.voices) |*wrapper| {
187 | switch (wrapper.state) {
188 | .Free, .Scheduled => {},
189 | .Playing => {
190 | if (wrapper.note == info.note) {
191 | wrapper.voice.envelope.gate(false);
192 | }
193 | },
194 | }
195 | }
196 | },
197 |
198 | // TODO Implement polyphonic parameter update
199 | .PitchBend => |info| {
200 | const percentage = @intToFloat(f32, info.value) / std.math.maxInt(@TypeOf(info.value));
201 | const detune = (percentage * 2 - 1) * 30;
202 | },
203 | .ControlChange => |cc| {},
204 | else => {},
205 | }
206 | }
207 |
208 | pub fn sampleInto(self: *Synth, buffer: []f32) void {
209 | // TODO Find it out if this is the way to go. The idea is that all voices
210 | // combined add up to one when they play at their loudest.
211 | const scale = 1 / @intToFloat(f32, self.voices.len);
212 |
213 | for (self.voices) |*wrapper, wrapper_i| {
214 | for (buffer) |_, i| {
215 | const value = switch (wrapper.state) {
216 | .Free, .Scheduled => 0,
217 | .Playing => result: {
218 | const voice_value = wrapper.voice.sample();
219 |
220 | if (wrapper.voice.envelope.stage == .Idle) {
221 | wrapper.free();
222 | }
223 |
224 | break :result voice_value;
225 | },
226 | } * scale;
227 |
228 | // On macOS the buffer is filled with garbage data in the beginning.
229 | // The first voice should write without consideration of that data.
230 | // Maybe the host should take care of zeroing the buffer before
231 | // passing it to the synthesizer?
232 | if (wrapper_i == 0) {
233 | buffer[i] = value;
234 | } else {
235 | buffer[i] += value;
236 | }
237 | }
238 | }
239 | }
240 | };
241 |
--------------------------------------------------------------------------------
/src/util.zig:
--------------------------------------------------------------------------------
1 | pub const Parameters = @import("util/parameters.zig").Parameters;
2 | pub const buffer_math = @import("util/buffer_math.zig");
3 |
4 | pub const Format = struct {
5 | sample_rate: f32 = 44100,
6 | channel_count: u8 = 1,
7 | };
8 |
9 | test "util" {
10 | _ = @import("util/parameters.zig");
11 | _ = @import("util/buffer_math.zig");
12 | }
13 |
--------------------------------------------------------------------------------
/src/util/buffer_math.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const testing = std.testing;
3 |
4 | pub fn zero(comptime T: type, out: []T) void {
5 | std.mem.set(T, out, 0);
6 | }
7 |
8 | test "zero" {
9 | var a: [100]f64 = undefined;
10 |
11 | for (a) |_, i| {
12 | a[i] = @intToFloat(f64, i);
13 | }
14 |
15 | zero(f64, &a);
16 |
17 | for (a) |v| {
18 | testing.expectEqual(@as(f64, 0), v);
19 | }
20 | }
21 |
22 | pub fn add(comptime T: type, a: []const T, b: []const T, out: []T) void {
23 | std.debug.assert(a.len == b.len);
24 | std.debug.assert(out.len == a.len);
25 |
26 | for (a) |v, i| {
27 | out[i] = v + b[i];
28 | }
29 | }
30 |
31 | test "add" {
32 | const a = [3]f32{ 1, 2, 3 };
33 | const b = [3]f32{ 1, 2, 3 };
34 |
35 | var out: [3]f32 = undefined;
36 |
37 | add(f32, &a, &b, &out);
38 |
39 | testing.expectEqual(@as(f32, 2), out[0]);
40 | testing.expectEqual(@as(f32, 4), out[1]);
41 | testing.expectEqual(@as(f32, 6), out[2]);
42 | }
43 |
44 | pub fn mix(comptime T: type, a: []const T, b: []const T, balance: T, out: []T) void {
45 | std.debug.assert(a.len == b.len);
46 | std.debug.assert(out.len == a.len);
47 |
48 | const b_balance = 1 - balance;
49 |
50 | for (a) |v, i| {
51 | out[i] = v * balance + b[i] * b_balance;
52 | }
53 | }
54 |
55 | test "mix" {
56 | const a = [3]f32{ 1, 2, 3 };
57 | const b = [3]f32{ 0.7, 2, 3 };
58 |
59 | var out: [3]f32 = undefined;
60 |
61 | mix(f32, &a, &b, 0.5, &out);
62 |
63 | testing.expectEqual(@as(f32, 0.85), out[0]);
64 | testing.expectEqual(@as(f32, 2), out[1]);
65 | testing.expectEqual(@as(f32, 3), out[2]);
66 | }
67 |
68 | pub fn mult(comptime T: type, a: []const T, b: []const T, out: []T) void {
69 | std.debug.assert(a.len == b.len);
70 | std.debug.assert(out.len == a.len);
71 |
72 | for (a) |v, i| {
73 | out[i] = v * b[i];
74 | }
75 | }
76 |
77 | test "mult" {
78 | const a = [3]f32{ 1, 2, 3 };
79 | const b = [3]f32{ 0, 2, 0.5 };
80 |
81 | var out: [3]f32 = undefined;
82 |
83 | mult(f32, &a, &b, &out);
84 |
85 | testing.expectEqual(@as(f32, 0), out[0]);
86 | testing.expectEqual(@as(f32, 4), out[1]);
87 | testing.expectEqual(@as(f32, 1.5), out[2]);
88 | }
89 |
90 | pub fn multScalar(comptime T: type, a: []const T, scalar: T, out: []T) void {
91 | std.debug.assert(out.len == a.len);
92 |
93 | for (a) |v, i| {
94 | out[i] = v * scalar;
95 | }
96 | }
97 |
98 | test "multScalar" {
99 | const a = [3]f32{ 1, 2, 3 };
100 |
101 | var out: [3]f32 = undefined;
102 |
103 | multScalar(f32, &a, 125.5, &out);
104 |
105 | testing.expectEqual(@as(f32, 125.5), out[0]);
106 | testing.expectEqual(@as(f32, 251), out[1]);
107 | testing.expectEqual(@as(f32, 376.5), out[2]);
108 | }
109 |
110 | pub fn approxEq(comptime T: type, a: []const T, b: []const T, epsilon: T) bool {
111 | std.debug.assert(a.len == b.len);
112 |
113 | for (a) |v, i| {
114 | if (!std.math.approxEq(T, v, b[i], epsilon)) {
115 | return false;
116 | }
117 | }
118 |
119 | return true;
120 | }
121 |
122 | test "approxEq" {
123 | const a = [_]f32{ 0.005, 0.003, 5 };
124 | const b = [_]f32{ 0.004, 0.002, 5.001 };
125 | const c = [_]f32{ 0.004, 0.001, 5.001 };
126 |
127 | testing.expect(approxEq(f32, &a, &b, 0.001));
128 | testing.expect(!approxEq(f32, &a, &c, 0.001));
129 | }
130 |
--------------------------------------------------------------------------------
/src/util/parameters.zig:
--------------------------------------------------------------------------------
1 | const testing = @import("std").testing;
2 |
3 | pub fn Parameters(comptime T: type) type {
4 | return struct {
5 | const Self = @This();
6 |
7 | pub const Child = T;
8 |
9 | a: T,
10 | b: T,
11 | current: u8 = 0,
12 |
13 | pub fn init(state: T) Self {
14 | return Self{
15 | .a = state,
16 | .b = state,
17 | };
18 | }
19 |
20 | pub fn initSeparate(state_a: T, state_b: T) Self {
21 | return Self{
22 | .a = state_a,
23 | .b = state_b,
24 | };
25 | }
26 |
27 | pub fn read(self: Self) T {
28 | const current = @atomicLoad(u8, &self.current, .SeqCst);
29 |
30 | if (current == 0) {
31 | return self.a;
32 | } else {
33 | return self.b;
34 | }
35 | }
36 |
37 | pub fn write(self: *Self, new_state: T) void {
38 | const current = @atomicLoad(u8, &self.current, .SeqCst);
39 |
40 | if (current == 0) {
41 | self.b = new_state;
42 | @atomicStore(u8, &self.current, 1, .SeqCst);
43 | } else {
44 | self.a = new_state;
45 | @atomicStore(u8, &self.current, 0, .SeqCst);
46 | }
47 | }
48 | };
49 | }
50 |
51 | test "Parameters" {
52 | const Point = Parameters(struct {
53 | x: u8 = 0,
54 | y: u8 = 1,
55 | });
56 |
57 | var p = Point.init(.{});
58 |
59 | const state = p.read();
60 | testing.expectEqual(@as(u8, 0), state.x);
61 | testing.expectEqual(@as(u8, 1), state.y);
62 |
63 | p.write(.{
64 | .x = 1,
65 | .y = 0,
66 | });
67 |
68 | const state2 = p.read();
69 | testing.expectEqual(@as(u8, 0), state2.y);
70 | testing.expectEqual(@as(u8, 1), state2.x);
71 | }
72 |
--------------------------------------------------------------------------------
/src/wavetable.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const testing = std.testing;
3 | const util = @import("util.zig");
4 |
5 | pub const Waveform = struct {
6 | const Bits = 12;
7 |
8 | pub const Size = std.math.pow(usize, 2, Bits);
9 | pub const IndexMask = Size - 1;
10 | pub const Index = @Type(.{
11 | .Int = .{
12 | .is_signed = false,
13 | .bits = Bits,
14 | },
15 | });
16 |
17 | pub const Scanner = struct {
18 | pub const T = switch (std.Target.current.cpu.arch) {
19 | .wasm32 => f32,
20 | else => f64,
21 | };
22 |
23 | position: T = 0,
24 | step_size: T = 0,
25 |
26 | pub inline fn advance(self: *Scanner) void {
27 | self.position += self.step_size;
28 |
29 | // TODO Find out if the while loop is more expensive than a single if.
30 | // If so, for whatever reason, we can use that. It's not so correct,
31 | // but the index masking in the interpolate function should take care
32 | // of that.
33 | while (self.position >= Waveform.Size) {
34 | self.position -= @intToFloat(T, Waveform.Size);
35 | }
36 | }
37 |
38 | pub inline fn calculateStepSize(self: *Scanner, format: util.Format, frequency: f32) void {
39 | const step_size = @intToFloat(T, Waveform.Size) * frequency / format.sample_rate;
40 | @atomicStore(T, &self.step_size, step_size, .SeqCst);
41 | }
42 |
43 | pub inline fn reset(self: *Scanner) void {
44 | @atomicStore(T, &self.position, 0, .SeqCst);
45 | }
46 | };
47 |
48 | data: [Size]f32,
49 |
50 | pub inline fn interpolate(self: Waveform, scanner: Scanner) f32 {
51 | const position = scanner.position;
52 | const index_a = @floatToInt(usize, @trunc(position));
53 | const index_b = @floatToInt(usize, @ceil(position)) & IndexMask;
54 |
55 | const value_a = self.data[index_a];
56 | const value_b = self.data[index_b];
57 | const lerp_t = position - @trunc(position);
58 |
59 | return value_a + (value_b - value_a) * @floatCast(f32, lerp_t);
60 | }
61 |
62 | pub fn squareWave(base_frequency: f64, harmonics: usize) Waveform {
63 | var result = Waveform{
64 | .data = undefined,
65 | };
66 |
67 | for (result.data) |_, i| {
68 | const progress = @intToFloat(f64, i) / @intToFloat(f64, result.data.len);
69 |
70 | var value: f64 = 0;
71 | var value_index: usize = 0;
72 |
73 | while (value_index < harmonics) : (value_index += 1) {
74 | const k = @intToFloat(f64, value_index);
75 | const two_k_one = 2 * k + 1;
76 |
77 | const value_f64 = (1 / two_k_one) * std.math.sin(progress * std.math.pi * 2 * base_frequency * two_k_one);
78 | value += value_f64;
79 | }
80 |
81 | result.data[i] = @floatCast(f32, value);
82 | }
83 |
84 | return result;
85 | }
86 |
87 | pub fn sineWave(base_frequency: f64) Waveform {
88 | var result = Waveform{
89 | .data = undefined,
90 | };
91 |
92 | for (result.data) |_, i| {
93 | const progress = @intToFloat(f64, i) / @intToFloat(f64, result.data.len);
94 | const value = std.math.sin(progress * std.math.pi * 2 * base_frequency);
95 |
96 | result.data[i] = @floatCast(f32, value);
97 | }
98 |
99 | return result;
100 | }
101 |
102 | pub fn sawtoothWave(base_frequency: f64, harmonics: usize) Waveform {
103 | var result = Waveform{
104 | .data = undefined,
105 | };
106 |
107 | const factor = @as(f64, 2) / std.math.pi;
108 |
109 | for (result.data) |_, i| {
110 | const progress = @intToFloat(f64, i) / @intToFloat(f64, result.data.len);
111 |
112 | var value: f64 = 0;
113 | var value_index: usize = 1;
114 |
115 | while (value_index < harmonics + 1) : (value_index += 1) {
116 | const k = @intToFloat(f64, value_index);
117 | const sign = std.math.pow(f64, -1, k - 1);
118 |
119 | value += sign * std.math.sin(progress * std.math.pi * 2 * base_frequency * k) / k;
120 | }
121 |
122 | result.data[i] = @floatCast(f32, factor * value);
123 | }
124 |
125 | return result;
126 | }
127 |
128 | pub fn triangleWave(base_frequency: f64, harmonics: usize) Waveform {
129 | var result = Waveform{
130 | .data = undefined,
131 | };
132 |
133 | return result;
134 | }
135 |
136 | pub fn noise(rand: *std.rand.Random) Waveform {
137 | var result = Waveform{
138 | .data = undefined,
139 | };
140 |
141 | for (result.data) |_, i| {
142 | result.data[i] = rand.float(f32) * 2 - 1;
143 | }
144 |
145 | return result;
146 | }
147 | };
148 |
149 | pub const WaveformTable = struct {
150 | waveforms: []Waveform,
151 | };
152 |
153 | pub const WavetableOscillator = struct {
154 | pub const Params = util.Parameters(struct {
155 | table: WaveformTable,
156 | format: util.Format,
157 | frequency: f32 = 0,
158 | detune: f32 = 0,
159 | position: f32 = 0,
160 | });
161 |
162 | params: Params,
163 |
164 | // Internal
165 | allocator: *std.mem.Allocator,
166 |
167 | // Can we add Unison by using multiple scanners with
168 | // slightly offset step sizes?
169 | scanner: Waveform.Scanner,
170 | index_a: usize,
171 | index_b: usize,
172 | mix: f32,
173 |
174 | pub fn init(allocator: *std.mem.Allocator, initial_params: Params.Child) !WavetableOscillator {
175 | var self = WavetableOscillator{
176 | .params = Params.init(initial_params),
177 |
178 | .allocator = allocator,
179 |
180 | .scanner = .{},
181 | .index_a = undefined,
182 | .index_b = undefined,
183 | .mix = undefined,
184 | };
185 |
186 | self.updateParams(initial_params);
187 |
188 | return self;
189 | }
190 |
191 | pub fn sample(self: *WavetableOscillator) f32 {
192 | defer self.scanner.advance();
193 |
194 | const table = self.params.read().table;
195 | const value_a = table.waveforms[self.index_a].interpolate(self.scanner);
196 | const value_b = table.waveforms[self.index_b].interpolate(self.scanner);
197 |
198 | return value_a * self.mix + value_b * (1 - self.mix);
199 | }
200 |
201 | pub fn reset(self: *WavetableOscillator) void {
202 | self.scanner.reset();
203 | }
204 |
205 | pub fn updateParams(self: *WavetableOscillator, params: Params.Child) void {
206 | const freq = params.frequency + params.detune;
207 | const adjusted_pos = params.position * @intToFloat(f32, params.table.waveforms.len - 1);
208 |
209 | self.params.write(params);
210 | self.index_a = @floatToInt(usize, @floor(adjusted_pos));
211 | self.index_b = @floatToInt(usize, @ceil(adjusted_pos));
212 | self.mix = @ceil(adjusted_pos) - adjusted_pos;
213 | self.scanner.calculateStepSize(params.format, freq);
214 | }
215 | };
216 |
217 | test "Waveform.squareWave" {
218 | const square = Waveform.squareWave(1, 1);
219 | const sine = Waveform.sineWave(1);
220 |
221 | testing.expect(std.mem.eql(f32, &square.data, &sine.data));
222 |
223 | const square2 = Waveform.squareWave(1, 4);
224 |
225 | var sine1 = Waveform.sineWave(1);
226 | var sine2 = Waveform.sineWave(3);
227 | var sine3 = Waveform.sineWave(5);
228 | var sine4 = Waveform.sineWave(7);
229 | var sine_final = Waveform{ .data = undefined };
230 |
231 | const bm = util.buffer_math;
232 |
233 | bm.multScalar(f32, &sine2.data, 1 / @as(f32, 3), &sine2.data);
234 | bm.multScalar(f32, &sine3.data, 1 / @as(f32, 5), &sine3.data);
235 | bm.multScalar(f32, &sine4.data, 1 / @as(f32, 7), &sine4.data);
236 |
237 | bm.add(f32, &sine1.data, &sine2.data, &sine_final.data);
238 | bm.add(f32, &sine_final.data, &sine3.data, &sine_final.data);
239 | bm.add(f32, &sine_final.data, &sine4.data, &sine_final.data);
240 |
241 | const eql = bm.approxEq(f32, &square2.data, &sine_final.data, 0.000001);
242 |
243 | testing.expect(eql);
244 | }
245 |
246 | test "Waveform.Scanner" {
247 | const T = Waveform.Scanner.T;
248 | var scanner = Waveform.Scanner{};
249 |
250 | scanner.calculateStepSize(.{
251 | .sample_rate = 44100,
252 | .channel_count = 1,
253 | }, 440);
254 |
255 | std.testing.expectEqual(@as(T, 0), scanner.position);
256 | std.testing.expect(std.math.approxEq(T, 40.8671201814, scanner.step_size, 0.0000001));
257 |
258 | scanner.position = 4080;
259 | scanner.advance();
260 |
261 | std.testing.expect(std.math.approxEq(T, scanner.step_size - 16, scanner.position, 0.0000001));
262 | }
263 |
264 | test "Waveform.interpolate" {
265 | var waveform = Waveform{
266 | .data = undefined,
267 | };
268 |
269 | waveform.data[0] = 0;
270 | waveform.data[1] = 1;
271 | waveform.data[2] = 11;
272 |
273 | var scanner: Waveform.Scanner = .{};
274 |
275 | scanner.position = 0.5;
276 | std.testing.expectEqual(@as(f32, 0.5), waveform.interpolate(scanner));
277 |
278 | scanner.position = 0.25;
279 | std.testing.expectEqual(@as(f32, 0.25), waveform.interpolate(scanner));
280 |
281 | scanner.position = 1.5;
282 | std.testing.expectEqual(@as(f32, 6), waveform.interpolate(scanner));
283 | }
284 |
--------------------------------------------------------------------------------