├── .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 |
89 | 90 |
91 | This button is required, because your browser prevents audio from playing automatically. 92 | Connect a MIDI input device and start playing. 93 | 94 |
95 | 96 | 97 | Due to missing API implementations in most browsers, this is currently only known to work in Google Chrome. 98 |
99 | Check Audio Worklet Support to find out if this has changed. 100 |
101 |
102 |
Build: VERSION_HASH
103 |
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 | --------------------------------------------------------------------------------