├── .gitignore ├── .gitmodules ├── README.md ├── build.zig ├── examples ├── amp │ ├── amp.ttl │ └── amp.zig ├── fifths │ ├── fifths.ttl │ └── fifths.zig └── params │ ├── params.ttl │ └── params.zig ├── log.a ├── log.b └── src ├── atom ├── atom.zig └── forge.zig ├── c.zig ├── lv2.zig ├── state.zig ├── urid.zig └── utils.zig /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-cache 2 | /zig-out 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lv2"] 2 | path = lv2 3 | url = https://github.com/lv2/lv2 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-lv2 2 | 3 | A toolkit for LV2 plugin authors written in Zig. 4 | 5 | **WARNING: Here be dragons; tons of LV2 features aren't implemented and this isn't ready for production at all.** 6 | 7 | ## Installing 8 | ```bash 9 | git clone --recurse-submodules https://github.com/ziglibs/zig-lv2 10 | ``` 11 | 12 | ## Getting Started 13 | Check out the example in `examples` which contains both sample Zig code as well as its corresponding Turtle manifest. 14 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Builder = @import("std").build.Builder; 3 | 4 | const examples = &[_][]const u8{"amp", "fifths", "params"}; 5 | 6 | pub fn build(b: *Builder) !void { 7 | const mode = b.standardReleaseOptions(); 8 | inline for (examples) |example, i| { 9 | const lib = b.addSharedLibrary(example, "examples/" ++ example ++ "/" ++ example ++ ".zig", .{ .unversioned = {} }); 10 | 11 | lib.addPackagePath("lv2", "src/lv2.zig"); 12 | lib.setBuildMode(mode); 13 | lib.setOutputDir("zig-out/" ++ example ++ ".lv2"); 14 | lib.linkLibC(); 15 | lib.addIncludeDir("lv2"); 16 | 17 | var step = b.step(example, "Build example \"" ++ example ++ "\""); 18 | step.dependOn(&b.addInstallFileWithDir("examples/" ++ example ++ "/" ++ example ++ ".ttl", .Prefix, example ++ ".lv2/manifest.ttl").step); 19 | step.dependOn(&lib.step); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/amp/amp.ttl: -------------------------------------------------------------------------------- 1 | @prefix lv2: . 2 | @prefix doap: . 3 | @prefix rdf: . 4 | @prefix rdfs: . 5 | @prefix units: . 6 | 7 | 8 | a lv2:Plugin, lv2:AmplifierPlugin; 9 | lv2:binary ; 10 | 11 | doap:name "Amp"; 12 | doap:license ; 13 | 14 | lv2:optionalFeature lv2:hardRTCapable; 15 | 16 | lv2:port [ 17 | a lv2:AudioPort, lv2:InputPort; 18 | lv2:index 0; 19 | lv2:symbol "input"; 20 | lv2:name "Input"; 21 | ], 22 | 23 | [ 24 | a lv2:ControlPort, lv2:InputPort; 25 | lv2:index 1; 26 | lv2:symbol "gain"; 27 | lv2:name "Gain"; 28 | 29 | lv2:default 0.0; 30 | lv2:minimum -90.0; 31 | lv2:maximum 24.0; 32 | 33 | units:unit units:db ; 34 | lv2:scalePoint [ 35 | rdfs:label "+5" ; 36 | rdf:value 5.0 37 | ] , [ 38 | rdfs:label "0" ; 39 | rdf:value 0.0 40 | ] , [ 41 | rdfs:label "-5" ; 42 | rdf:value -5.0 43 | ] , [ 44 | rdfs:label "-10" ; 45 | rdf:value -10.0 46 | ] 47 | ], 48 | 49 | [ 50 | a lv2:AudioPort, lv2:OutputPort; 51 | lv2:index 2; 52 | lv2:symbol "output"; 53 | lv2:name "Output"; 54 | ]. -------------------------------------------------------------------------------- /examples/amp/amp.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lv2 = @import("lv2"); 3 | 4 | pub const Amp = lv2.Plugin{ 5 | .uri = "http://augustera.me/amp", 6 | .Handle = struct { 7 | input: [*]f32, 8 | gain: *f32, 9 | output: [*]f32 10 | }, 11 | }; 12 | 13 | comptime { 14 | Amp.exportPlugin(.{ 15 | .run = run 16 | }); 17 | } 18 | 19 | fn decibelsToCoeff(g: f32) f32 { 20 | return if (g > -90) std.math.pow(f32, 10, g * 0.05) else 0; 21 | } 22 | 23 | fn run(handle: *Amp.Handle, samples: u32) void { 24 | const coef = decibelsToCoeff(handle.gain.*); 25 | 26 | var i: usize = 0; 27 | while (i < samples) : (i += 1) { 28 | handle.output[i] = handle.input[i] * coef; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/fifths/fifths.ttl: -------------------------------------------------------------------------------- 1 | @prefix atom: . 2 | @prefix doap: . 3 | @prefix lv2: . 4 | @prefix urid: . 5 | @prefix midi: . 6 | 7 | 8 | a lv2:Plugin ; 9 | lv2:binary ; 10 | 11 | doap:name "Fifths" ; 12 | doap:license ; 13 | 14 | lv2:requiredFeature urid:map ; 15 | lv2:optionalFeature lv2:hardRTCapable ; 16 | lv2:port [ 17 | a lv2:InputPort , 18 | atom:AtomPort ; 19 | atom:bufferType atom:Sequence ; 20 | atom:supports midi:MidiEvent ; 21 | lv2:index 0 ; 22 | lv2:symbol "in" ; 23 | lv2:name "In" 24 | ] , [ 25 | a lv2:OutputPort , 26 | atom:AtomPort ; 27 | atom:bufferType atom:Sequence ; 28 | atom:supports midi:MidiEvent ; 29 | lv2:index 1 ; 30 | lv2:symbol "out" ; 31 | lv2:name "Out" 32 | ] . -------------------------------------------------------------------------------- /examples/fifths/fifths.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lv2 = @import("lv2"); 3 | 4 | pub const FifthsURIs = struct { 5 | atom_path: u32, 6 | atom_resource: u32, 7 | atom_sequence: u32, 8 | atom_urid: u32, 9 | atom_event_transfer: u32, 10 | midi_event: u32, 11 | patch_set: u32, 12 | patch_property: u32, 13 | patch_value: u32, 14 | 15 | pub fn map(self: *@This(), map_: *lv2.URIDMap) void { 16 | self.atom_path = map_.map(lv2.c.LV2_ATOM__Path); 17 | self.atom_resource = map_.map(lv2.c.LV2_ATOM__Resource); 18 | self.atom_sequence = map_.map(lv2.c.LV2_ATOM__Sequence); 19 | self.atom_urid = map_.map(lv2.c.LV2_ATOM__URID); 20 | self.atom_event_transfer = map_.map(lv2.c.LV2_ATOM__eventTransfer); 21 | self.midi_event = map_.map(lv2.c.LV2_MIDI__MidiEvent); 22 | self.patch_set = map_.map(lv2.c.LV2_PATCH__Set); 23 | self.patch_property = map_.map(lv2.c.LV2_PATCH__property); 24 | self.patch_value = map_.map(lv2.c.LV2_PATCH__value); 25 | } 26 | }; 27 | 28 | pub const Fifths = lv2.Plugin{ 29 | .uri = "http://augustera.me/fifths", 30 | .Handle = struct { 31 | in: *lv2.AtomSequence, 32 | out: *lv2.AtomSequence, 33 | 34 | map: *lv2.URIDMap, 35 | uris: FifthsURIs 36 | }, 37 | }; 38 | 39 | comptime { 40 | Fifths.exportPlugin(.{ 41 | .instantiate = instantiate, 42 | .run = run, 43 | }); 44 | } 45 | 46 | fn instantiate ( 47 | handle: *Fifths.Handle, 48 | descriptor: *const lv2.Descriptor, 49 | rate: f64, 50 | bundle_path: []const u8, 51 | features: lv2.Features 52 | ) anyerror!void { 53 | handle.map = features.query(lv2.URIDMap).?; 54 | 55 | handle.uris.map(handle.map); 56 | } 57 | 58 | const MidiNoteData = extern struct { 59 | status: u8, 60 | pitch: u8, 61 | velocity: u8 62 | }; 63 | 64 | const MidiNoteEvent = extern struct { 65 | event: lv2.AtomEvent, 66 | data: MidiNoteData 67 | }; 68 | 69 | fn run(handle: *Fifths.Handle, samples: u32) void { 70 | const out_size = handle.out.atom.size; 71 | handle.out.clear(); 72 | handle.out.atom.kind = handle.in.atom.kind; 73 | 74 | var iter = handle.in.iterator(); 75 | while (iter.next()) |event| { 76 | if (event.body.kind == handle.uris.midi_event) { 77 | _ = handle.out.appendEvent(out_size, event) catch @panic("Error appending!"); 78 | 79 | var data = event.getDataAs(*MidiNoteData); 80 | var fifth = std.mem.zeroes(MidiNoteEvent); 81 | 82 | fifth.event.time.frames = event.time.frames; 83 | fifth.event.body.kind = event.body.kind; 84 | fifth.event.body.size = event.body.size; 85 | 86 | fifth.data.status = data.status; 87 | fifth.data.pitch = data.pitch + 7; 88 | fifth.data.velocity = data.velocity; 89 | 90 | _ = handle.out.appendEvent(out_size, &fifth.event) catch @panic("Error appending!"); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /examples/params/params.ttl: -------------------------------------------------------------------------------- 1 | @prefix atom: . 2 | @prefix doap: . 3 | @prefix lv2: . 4 | @prefix param: . 5 | @prefix patch: . 6 | @prefix plug: . 7 | @prefix rdfs: . 8 | @prefix state: . 9 | @prefix urid: . 10 | @prefix xsd: . 11 | 12 | # An existing parameter or RDF property can be used as a parameter. The LV2 13 | # parameters extension defines many 14 | # common audio parameters. Where possible, existing parameters should be used 15 | # so hosts can intelligently control plugins. 16 | 17 | # If no suitable parameter exists, one can be defined for the plugin like so: 18 | 19 | plug:aint 20 | a lv2:Parameter ; 21 | rdfs:label "int" ; 22 | rdfs:range atom:Int . 23 | 24 | plug:along 25 | a lv2:Parameter ; 26 | rdfs:label "long" ; 27 | rdfs:range atom:Long . 28 | 29 | plug:afloat 30 | a lv2:Parameter ; 31 | rdfs:label "float" ; 32 | rdfs:range atom:Float . 33 | 34 | plug:adouble 35 | a lv2:Parameter ; 36 | rdfs:label "double" ; 37 | rdfs:range atom:Double . 38 | 39 | plug:abool 40 | a lv2:Parameter ; 41 | rdfs:label "bool" ; 42 | rdfs:range atom:Bool . 43 | 44 | plug:astring 45 | a lv2:Parameter ; 46 | rdfs:label "string" ; 47 | rdfs:range atom:String . 48 | 49 | plug:apath 50 | a lv2:Parameter ; 51 | rdfs:label "path" ; 52 | rdfs:range atom:Path . 53 | 54 | plug:lfo 55 | a lv2:Parameter ; 56 | rdfs:label "LFO" ; 57 | rdfs:range atom:Float ; 58 | lv2:minimum -1.0 ; 59 | lv2:maximum 1.0 . 60 | 61 | plug:spring 62 | a lv2:Parameter ; 63 | rdfs:label "spring" ; 64 | rdfs:range atom:Float . 65 | 66 | # Most of the plugin description is similar to the others we have seen, but 67 | # this plugin has only two ports, for receiving and sending messages used to 68 | # manipulate and access parameters. 69 | 70 | a lv2:Plugin , 71 | lv2:UtilityPlugin ; 72 | lv2:binary ; 73 | 74 | doap:name "Params" ; 75 | doap:license ; 76 | 77 | lv2:project ; 78 | lv2:requiredFeature urid:map ; 79 | lv2:optionalFeature lv2:hardRTCapable , 80 | state:loadDefaultState ; 81 | lv2:extensionData state:interface ; 82 | lv2:port [ 83 | a lv2:InputPort , 84 | atom:AtomPort ; 85 | atom:bufferType atom:Sequence ; 86 | atom:supports patch:Message ; 87 | lv2:designation lv2:control ; 88 | lv2:index 0 ; 89 | lv2:symbol "in" ; 90 | lv2:name "In" 91 | ] , [ 92 | a lv2:OutputPort , 93 | atom:AtomPort ; 94 | atom:bufferType atom:Sequence ; 95 | atom:supports patch:Message ; 96 | lv2:designation lv2:control ; 97 | lv2:index 1 ; 98 | lv2:symbol "out" ; 99 | lv2:name "Out" 100 | ] ; 101 | # The plugin must list all parameters that can be written (e.g. changed by the 102 | # user) as patch:writable: 103 | patch:writable plug:int , 104 | plug:long , 105 | plug:float , 106 | plug:double , 107 | plug:bool , 108 | plug:string , 109 | plug:path , 110 | plug:spring ; 111 | # Similarly, parameters that may change internally must be listed as patch:readable, 112 | # meaning to host should watch for changes to the parameter's value: 113 | patch:readable plug:lfo , 114 | plug:spring ; 115 | # Parameters map directly to properties of the plugin's state. So, we can 116 | # specify initial parameter values with the state:state property. The 117 | # state:loadDefaultState feature (required above) requires that the host loads 118 | # the default state after instantiation but before running the plugin. 119 | state:state [ 120 | plug:int 0 ; 121 | plug:long "0"^^xsd:long ; 122 | plug:float "0.1234"^^xsd:float ; 123 | plug:double "0e0"^^xsd:double ; 124 | plug:bool false ; 125 | plug:string "Hello, world" ; 126 | plug:path ; 127 | plug:spring "0.0"^^xsd:float ; 128 | plug:lfo "0.0"^^xsd:float 129 | ] . -------------------------------------------------------------------------------- /examples/params/params.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const lv2 = @import("lv2"); 3 | 4 | pub const URIs = struct { 5 | atom_path: u32, 6 | atom_resource: u32, 7 | atom_sequence: u32, 8 | atom_urid: u32, 9 | atom_event_transfer: u32, 10 | midi_event: u32, 11 | patch_set: u32, 12 | patch_subject: u32, 13 | patch_property: u32, 14 | patch_value: u32, 15 | 16 | pub fn map(self: *@This(), map_: *lv2.URIDMap) void { 17 | self.atom_path = map_.map(lv2.c.LV2_ATOM__Path); 18 | self.atom_resource = map_.map(lv2.c.LV2_ATOM__Resource); 19 | self.atom_sequence = map_.map(lv2.c.LV2_ATOM__Sequence); 20 | self.atom_urid = map_.map(lv2.c.LV2_ATOM__URID); 21 | self.atom_event_transfer = map_.map(lv2.c.LV2_ATOM__eventTransfer); 22 | self.midi_event = map_.map(lv2.c.LV2_MIDI__MidiEvent); 23 | self.patch_set = map_.map(lv2.c.LV2_PATCH__Set); 24 | self.patch_subject = map_.map(lv2.c.LV2_PATCH__subject); 25 | self.patch_property = map_.map(lv2.c.LV2_PATCH__property); 26 | self.patch_value = map_.map(lv2.c.LV2_PATCH__value); 27 | } 28 | }; 29 | 30 | pub const StateManager = lv2.StateManager(struct { 31 | aint: lv2.AtomInt, 32 | along: lv2.AtomLong, 33 | afloat: lv2.AtomFloat, 34 | adouble: lv2.AtomDouble, 35 | abool: lv2.AtomBool, 36 | astring: lv2.AtomString, 37 | apath: lv2.AtomPath, 38 | lfo: lv2.AtomFloat, 39 | spring: lv2.AtomFloat 40 | }); 41 | 42 | pub const Params = lv2.Plugin{ 43 | .uri = "http://augustera.me/params", 44 | .Handle = struct { 45 | // Ports 46 | in: *lv2.AtomSequence, 47 | out: *lv2.AtomSequence, 48 | 49 | // Features 50 | map: *lv2.URIDMap, 51 | unmap: *lv2.URIDUnmap, 52 | forge: lv2.AtomForge, 53 | 54 | // URIs 55 | uris: URIs, 56 | 57 | // State 58 | state_manager: StateManager 59 | }, 60 | }; 61 | 62 | comptime { 63 | Params.exportPlugin(.{ 64 | .instantiate = instantiate, 65 | .run = run, 66 | .activate = activate, 67 | .deactivate = deactivate, 68 | .extensionData = extensionData 69 | }); 70 | } 71 | 72 | fn instantiate ( 73 | handle: *Params.Handle, 74 | descriptor: *const lv2.Descriptor, 75 | rate: f64, 76 | bundle_path: []const u8, 77 | features: lv2.Features 78 | ) anyerror!void { 79 | handle.map = features.query(lv2.URIDMap).?; 80 | handle.unmap = features.query(lv2.URIDUnmap).?; 81 | handle.forge.init(handle.map); 82 | 83 | handle.uris.map(handle.map); 84 | handle.state_manager.map(Params.uri, handle.map); 85 | } 86 | 87 | fn activate(handle: *Params.Handle) void { 88 | } 89 | 90 | fn deactivate(handle: *Params.Handle) void { 91 | // debug_file.close(); 92 | } 93 | 94 | fn log(comptime f: []const u8, a: anytype) void { 95 | var debug_file = std.fs.cwd().createFile("C:/Programming/Zig/zig-lv2/log.b", .{}) catch {std.os.exit(1);}; 96 | debug_file.writer().print(f, a) catch {}; 97 | } 98 | 99 | fn run(handle: *Params.Handle, samples: u32) void { 100 | handle.forge.setBuffer(handle.out.toBuffer(), handle.out.atom.size); 101 | 102 | var out_frame = std.mem.zeroes(lv2.AtomForgeFrame); 103 | _ = handle.forge.writeSequenceHead(&out_frame, 0); 104 | 105 | var iter = handle.in.iterator(); 106 | while (iter.next()) |event| { 107 | @panic("B"); 108 | // var obj = event.toAtomObject(); 109 | // if (obj.body.kind == handle.uris.patch_set) { 110 | // var subject: ?*lv2.AtomURID = null; 111 | // var property: ?*lv2.AtomURID = null; 112 | // var value: ?*lv2.Atom = null; 113 | 114 | // obj.query(&[_]lv2.AtomObjectQuery{ 115 | // .{ .key = handle.uris.patch_subject, .value = @ptrCast(*?*lv2.Atom, &subject) }, 116 | // .{ .key = handle.uris.patch_property, .value = @ptrCast(*?*lv2.Atom, &property) }, 117 | // .{ .key = handle.uris.patch_value, .value = &value } 118 | // }); 119 | 120 | // handle.state_manager.setParameter(property.?.body, value.?); 121 | // } 122 | } 123 | 124 | handle.forge.pop(&out_frame); 125 | // @panic("Empty"); 126 | } 127 | 128 | fn extensionData(uri: []const u8) ?*c_void { 129 | if (StateManager.extensionData(uri)) |ext| return ext; 130 | return null; 131 | } 132 | 133 | pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn { 134 | var debug_file = std.fs.cwd().createFile("C:/Programming/Zig/zig-lv2/log.a", .{}) catch {std.os.exit(1);}; 135 | debug_file.writer().writeAll(msg) catch {}; 136 | 137 | const debug_info = std.debug.getSelfDebugInfo() catch |err| { 138 | debug_file.writer().print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch std.process.exit(1); 139 | std.process.exit(1); 140 | }; 141 | std.debug.writeCurrentStackTrace(debug_file.writer(), std.debug.getSelfDebugInfo() catch std.os.exit(1), std.debug.detectTTYConfig(), @returnAddress()) catch |err| { 142 | debug_file.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch std.process.exit(1); 143 | std.process.exit(1); 144 | }; 145 | 146 | std.process.exit(1); 147 | } 148 | -------------------------------------------------------------------------------- /log.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ziglibs/zig-lv2/9f9edee51e6419b2935ad9b87866193e61f9df4f/log.a -------------------------------------------------------------------------------- /log.b: -------------------------------------------------------------------------------- 1 | 8 16 2 | -------------------------------------------------------------------------------- /src/atom/atom.zig: -------------------------------------------------------------------------------- 1 | const c = @import("../c.zig"); 2 | const std = @import("std"); 3 | const urid = @import("../urid.zig"); 4 | 5 | pub const Atom = extern struct { 6 | const Self = @This(); 7 | 8 | size: urid.URID, 9 | kind: urid.URID 10 | }; 11 | 12 | pub const AtomObjectBody = extern struct { 13 | id: urid.URID, 14 | kind: urid.URID 15 | }; 16 | 17 | pub const AtomPropertyBody = extern struct { 18 | const Self = @This(); 19 | 20 | key: urid.URID, 21 | context: urid.URID, 22 | value: Atom, 23 | 24 | pub fn init(event: *c.LV2_Atom_Property_Body) *Self { 25 | return @ptrCast(*Self, event); 26 | } 27 | }; 28 | 29 | pub const AtomObjectQuery = struct { 30 | key: urid.URID, 31 | value: *?*Atom 32 | }; 33 | 34 | pub const AtomObject = extern struct { 35 | const Self = @This(); 36 | 37 | atom: Atom, 38 | body: AtomObjectBody, 39 | 40 | pub fn iterator(self: *Self) AtomObjectIterator { 41 | return AtomObjectIterator.init(@ptrCast(*c.LV2_Atom_Object, self)); 42 | } 43 | 44 | pub fn query(self: *Self, queries: []AtomObjectQuery) void { 45 | var it = self.iterator(); 46 | while (it.next()) |prop| { 47 | for (queries) |q| { 48 | if (prop.key == q.key and q.value.* == null) { 49 | q.value.* = &prop.value; 50 | break; 51 | } 52 | } 53 | } 54 | } 55 | }; 56 | 57 | pub const AtomObjectIterator = struct { 58 | const Self = @This(); 59 | 60 | object: *c.LV2_Atom_Object, 61 | last: ?*c.LV2_Atom_Property_Body, 62 | 63 | pub fn init(object: *c.LV2_Atom_Object) Self { 64 | return Self{ 65 | .object = object, 66 | .last = null 67 | }; 68 | } 69 | 70 | pub fn next(self: *Self) ?*AtomPropertyBody { 71 | self.last = if (self.last) |last| c.lv2_atom_object_next(last) else c.lv2_atom_object_begin(&self.object.body); 72 | if (c.lv2_atom_object_is_end(&self.object.body, self.object.atom.size, self.last)) return null; 73 | return if (self.last) |l| AtomPropertyBody.init(l) else null; 74 | } 75 | }; 76 | 77 | pub const AtomEventTime = extern union { 78 | frames: i64, 79 | beats: f64, 80 | }; 81 | 82 | pub const AtomEvent = extern struct { 83 | const Self = @This(); 84 | 85 | time: AtomEventTime, 86 | body: Atom, 87 | 88 | pub fn init(event: *c.LV2_Atom_Event) *Self { 89 | return @ptrCast(*Self, event); 90 | } 91 | 92 | pub fn getDataAs(self: *Self, comptime T: type) T { 93 | return @intToPtr(T, @ptrToInt(self) + @sizeOf(Self)); 94 | } 95 | 96 | pub fn toAtomObject(self: *Self) *AtomObject { 97 | return @ptrCast(*AtomObject, &self.body); 98 | } 99 | }; 100 | 101 | pub const AtomSequenceBody = extern struct { 102 | /// URID of unit of event time stamps 103 | unit: urid.URID, 104 | /// Currently unused 105 | pad: u32, 106 | }; 107 | 108 | pub const AtomSequence = extern struct { 109 | const Self = @This(); 110 | 111 | atom: Atom, 112 | body: AtomSequenceBody, 113 | 114 | pub fn iterator(self: *Self) AtomSequenceIterator { 115 | return AtomSequenceIterator.init(@ptrCast(*c.LV2_Atom_Sequence, self)); 116 | } 117 | 118 | pub fn clear(self: *Self) void { 119 | c.lv2_atom_sequence_clear(@ptrCast(*c.LV2_Atom_Sequence, self)); 120 | } 121 | 122 | pub fn appendEvent(self: *Self, out_size: u32, event: *AtomEvent) !*AtomEvent { 123 | var maybe_appended_event = c.lv2_atom_sequence_append_event(@ptrCast(*c.LV2_Atom_Sequence, self), out_size, @ptrCast(*c.LV2_Atom_Event, event)); 124 | return if (maybe_appended_event) |appended_event| AtomEvent.init(appended_event) else error.AppendError; 125 | } 126 | 127 | pub fn toBuffer(self: *Self) [*c]u8 { 128 | return @ptrCast([*c]u8, self); 129 | } 130 | }; 131 | 132 | pub const AtomSequenceIterator = struct { 133 | const Self = @This(); 134 | 135 | seq: *c.LV2_Atom_Sequence, 136 | last: ?*c.LV2_Atom_Event, 137 | 138 | pub fn init(seq: *c.LV2_Atom_Sequence) Self { 139 | return Self{ 140 | .seq = seq, 141 | .last = null 142 | }; 143 | } 144 | 145 | pub fn next(self: *Self) ?*AtomEvent { 146 | self.last = if (self.last) |last| c.lv2_atom_sequence_next(last) else c.lv2_atom_sequence_begin(&self.seq.body); 147 | if (c.lv2_atom_sequence_is_end(&self.seq.body, self.seq.atom.size, self.last)) return null; 148 | return if (self.last) |l| AtomEvent.init(l) else null; 149 | } 150 | }; 151 | 152 | pub fn AtomOf(comptime T: type, atom_type: []const u8) type { 153 | return extern struct { 154 | pub const __atom_type = atom_type; 155 | 156 | atom: Atom, 157 | body: T 158 | }; 159 | } 160 | 161 | pub const AtomInt = AtomOf(i32, c.LV2_ATOM__Int); 162 | pub const AtomLong = AtomOf(i64, c.LV2_ATOM__Long); 163 | pub const AtomFloat = AtomOf(f32, c.LV2_ATOM__Float); 164 | pub const AtomDouble = AtomOf(f64, c.LV2_ATOM__Double); 165 | pub const AtomBool = AtomOf(bool, c.LV2_ATOM__Bool); 166 | pub const AtomURID = AtomOf(u32, c.LV2_ATOM__URID); 167 | pub const AtomString = AtomOf([*:0]const u8, c.LV2_ATOM__String); 168 | pub const AtomPath = AtomOf([*:0]const u8, c.LV2_ATOM__Path); 169 | -------------------------------------------------------------------------------- /src/atom/forge.zig: -------------------------------------------------------------------------------- 1 | const c = @import("../c.zig"); 2 | const std = @import("std"); 3 | const atom = @import("atom.zig"); 4 | const urid = @import("../urid.zig"); 5 | 6 | pub const AtomForgeSinkHandle = ?*c_void; 7 | pub const AtomForgeRef = isize; 8 | pub const AtomForgeSink = ?fn (sink_handle: AtomForgeSinkHandle, buf: ?*const c_void, size: u32) callconv(.C) AtomForgeRef; 9 | pub const AtomForgeDerefFunc = ?fn (sink_handle: AtomForgeSinkHandle, ref: AtomForgeRef) callconv(.C) [*c]atom.Atom; 10 | 11 | pub const AtomForgeFrame = extern struct { 12 | parent: [*c]AtomForgeFrame, 13 | ref: AtomForgeRef 14 | }; 15 | 16 | /// TODO: Actually zigify this so it isn't a total mess 17 | pub const AtomForge = extern struct { 18 | const Self = @This(); 19 | 20 | buf: [*c]u8, 21 | offset: u32, 22 | size: u32, 23 | /// Function that writes output to sink. 24 | sink: AtomForgeSink, 25 | deref_func: AtomForgeDerefFunc, 26 | /// The handle to the output sink. 27 | sink_handle: AtomForgeSinkHandle, 28 | stack: [*c]AtomForgeFrame, 29 | 30 | /// Deprecated 31 | Blank: urid.URID, 32 | Bool: urid.URID, 33 | Chunk: urid.URID, 34 | Double: urid.URID, 35 | Float: urid.URID, 36 | Int: urid.URID, 37 | Long: urid.URID, 38 | Literal: urid.URID, 39 | Object: urid.URID, 40 | Path: urid.URID, 41 | Property: urid.URID, 42 | /// Deprecated 43 | Resource: urid.URID, 44 | Sequence: urid.URID, 45 | String: urid.URID, 46 | Tuple: urid.URID, 47 | URI: urid.URID, 48 | URID: urid.URID, 49 | Vector: urid.URID, 50 | 51 | /// Initalize self. 52 | pub fn init(self: *Self, map: *urid.URIDMap) void { 53 | self.setBuffer(null, 0); 54 | self.Blank = map.map(c.LV2_ATOM__Blank); 55 | self.Bool = map.map(c.LV2_ATOM__Bool); 56 | self.Chunk = map.map(c.LV2_ATOM__Chunk); 57 | self.Double = map.map(c.LV2_ATOM__Double); 58 | self.Float = map.map(c.LV2_ATOM__Float); 59 | self.Int = map.map(c.LV2_ATOM__Int); 60 | self.Long = map.map(c.LV2_ATOM__Long); 61 | self.Literal = map.map(c.LV2_ATOM__Literal); 62 | self.Object = map.map(c.LV2_ATOM__Object); 63 | self.Path = map.map(c.LV2_ATOM__Path); 64 | self.Property = map.map(c.LV2_ATOM__Property); 65 | self.Resource = map.map(c.LV2_ATOM__Resource); 66 | self.Sequence = map.map(c.LV2_ATOM__Sequence); 67 | self.String = map.map(c.LV2_ATOM__String); 68 | self.Tuple = map.map(c.LV2_ATOM__Tuple); 69 | self.URI = map.map(c.LV2_ATOM__URI); 70 | self.URID = map.map(c.LV2_ATOM__URID); 71 | self.Vector = map.map(c.LV2_ATOM__Vector); 72 | } 73 | 74 | /// Access the Atom pointed to by a reference. 75 | pub fn deref(self: *Self, ref: AtomForgeRef) *atom.Atom { 76 | if (ref < 0) @panic("huh"); 77 | return if (self.buf != null) @intToPtr(*atom.Atom, std.math.absCast(ref)) else self.deref_func.?(self.sink_handle, ref); 78 | } 79 | 80 | /// Push a stack frame. Automatically handled by container functions. 81 | pub fn push(self: *Self, frame: *AtomForgeFrame, ref: AtomForgeRef) AtomForgeRef { 82 | frame.parent = self.stack; 83 | frame.ref = ref; 84 | 85 | if (ref != 0) { 86 | self.stack = frame; 87 | } 88 | 89 | return ref; 90 | } 91 | 92 | /// Pop a stack frame. This must be called when a container is finished. 93 | pub fn pop(self: *Self, frame: *AtomForgeFrame) void { 94 | if (frame.ref != 0) { 95 | std.debug.assert(frame == self.stack); 96 | self.stack = frame.parent; 97 | } 98 | } 99 | 100 | /// Return true if the top of the stack is the given kind. 101 | pub fn topIs(self: *Self, kind: urid.URID) bool { 102 | return 103 | self.stack != null and 104 | self.stack.ref != 0 and 105 | self.deref(self.stack.ref).kind == kind; 106 | } 107 | 108 | /// Return true if `kind` is an atom:Object 109 | pub fn isObjectKind(self: *Self, kind: urid.URID) bool { 110 | return kind == self.Object or kind == self.Blank or kind == self.Resource; 111 | } 112 | 113 | /// Return true if `type` is an atom:Object with a blank ID. 114 | pub fn isBlank(self: *Self, kind: urid.URID, body: *atom.AtomObjectBody) bool { 115 | return kind == self.Blank or (kind == self.Object and body.id == 0); 116 | } 117 | 118 | /// Set forge buffer. 119 | pub fn setBuffer(self: *Self, buf: [*c]u8, size: usize) void { 120 | self.buf = buf; 121 | self.size = @truncate(u32, size); 122 | self.offset = 0; 123 | self.deref_func = null; 124 | self.sink = null; 125 | self.sink_handle = null; 126 | self.stack = null; 127 | } 128 | 129 | /// Set forge sink. 130 | pub fn setSink(self: *Self, sink: AtomForgeSink, deref: AtomForgeDerefFunc, sink_handle: AtomForgeSinkHandle) void { 131 | self.buf = null; 132 | self.size = 0; 133 | self.offset = 0; 134 | 135 | self.deref_func = deref; 136 | self.sink = sink; 137 | self.sink_handle = sink_handle; 138 | } 139 | 140 | /// Writes raw output. 141 | pub fn raw(self: *Self, data: ?*const c_void, size: u32) AtomForgeRef { 142 | var out: AtomForgeRef = 0; 143 | 144 | if (self.sink) |sink| { 145 | out = sink(self.sink_handle, data, size); 146 | } else { 147 | out = @intCast(AtomForgeRef, @ptrToInt(self.buf)) + @bitCast(c_longlong, @as(c_ulonglong, self.offset)); 148 | var mem: *u8 = self.buf + self.offset; 149 | if (self.offset + size > self.size) { 150 | return 0; 151 | } 152 | self.offset += size; 153 | _ = c.memcpy(@ptrCast(?*c_void, mem), data, @bitCast(c_ulonglong, @as(c_ulonglong, size))); 154 | // @memcpy(@ptrCast([*]u8, @ptrCast(?*c_void, mem)), @ptrCast([*]const u8, data.?), @bitCast(c_ulonglong, @as(c_ulonglong, size))); 155 | } 156 | 157 | if (self.stack != null) { 158 | self.deref(self.stack.*.parent.*.ref).size += size; 159 | } 160 | 161 | return out; 162 | } 163 | 164 | /// Pad so next write is 64-bit aligned. 165 | pub fn pad(self: *Self, written: u32) void { 166 | const pad_: u64 = 0; 167 | var pad_size: u32 = c.lv2_atom_pad_size(written) - written; 168 | _ = self.raw(@ptrCast(?*const c_void, &pad_), pad_size); 169 | } 170 | 171 | /// `raw` but with padding. 172 | pub fn write(self: *Self, data: ?*const c_void, size: u32) AtomForgeRef { 173 | var out = self.raw(data, size); 174 | if (out != 0) { 175 | self.pad(size); 176 | } 177 | return out; 178 | } 179 | 180 | /// Write a null-terminated string body. 181 | pub fn stringBody(self: *Self, str: []const u8, len: u32) AtomForgeRef { 182 | var out = self.raw(@ptrCast(?*const c_void, str), len); 183 | if (out and o: { 184 | out = self.raw(@ptrCast(?*const c_void, ""), 1); 185 | break :o out; 186 | }) { 187 | self.pad(len + 1); 188 | } 189 | return out; 190 | } 191 | 192 | pub fn writeAtom(self: *Self, size: u32, kind: urid.URID) AtomForgeRef { 193 | const at = atom.Atom{ 194 | .size = size, 195 | .kind = kind 196 | }; 197 | return self.raw(@ptrCast(?*const c_void, &at), @truncate(u32, @sizeOf(at))); 198 | } 199 | 200 | pub fn writeAtomPrimitive(self: *Self, at: *atom.Atom) AtomForgeRef { 201 | return if (self.topIs(self.Vector)) self.raw(@ptrCast(?*const c_void, @ptrCast([*c]const u8, @alignCast(@import("std").meta.alignment(u8), at)) + @sizeOf(atom.Atom)), at.size) else self.write(@ptrCast(?*c_void, at), @truncate(u32, @sizeOf(at)) + at.size); 202 | } 203 | 204 | fn writeAtomOfType(self: *Self, comptime T: type, value: T, kind: urid.URID) AtomForgeRef { 205 | var at = atom.AtomInt{ 206 | .atom = .{ 207 | .size = @sizeOf(value), 208 | .kind = kind 209 | }, 210 | .body = value 211 | }; 212 | return self.writeAtomPrimitive(&at.atom); 213 | } 214 | 215 | pub fn writeAtomInt(self: *Self, value: i32) AtomForgeRef { 216 | return self.writeAtomOfType(i32, value, self.Int); 217 | } 218 | 219 | pub fn writeAtomLong(self: *Self, value: i64) AtomForgeRef { 220 | return self.writeAtomOfType(i64, value, self.Long); 221 | } 222 | 223 | pub fn writeAtomFloat(self: *Self, value: f32) AtomForgeRef { 224 | return self.writeAtomOfType(f32, value, self.Float); 225 | } 226 | 227 | pub fn writeAtomDouble(self: *Self, value: f64) AtomForgeRef { 228 | return self.writeAtomOfType(f64, value, self.Double); 229 | } 230 | 231 | pub fn writeAtomBool(self: *Self, value: bool) AtomForgeRef { 232 | return self.writeAtomOfType(bool, value, self.Bool); 233 | } 234 | 235 | pub fn writeAtomURID(self: *Self, value: urid.URID) AtomForgeRef { 236 | return self.writeAtomOfType(urid.URID, value, self.URID); 237 | } 238 | 239 | pub fn writeSequenceHead(self: *Self, frame: *AtomForgeFrame, unit: u32) AtomForgeRef { 240 | var seq = atom.AtomSequence{ 241 | .atom = .{ 242 | // .size = @as(u32, @sizeOf(atom.AtomSequenceBody)), 243 | .size = @bitCast(u32, @truncate(c_uint, @sizeOf(atom.AtomSequenceBody))), 244 | .kind = self.Sequence 245 | }, 246 | .body = .{ 247 | .unit = unit, 248 | .pad = 0 249 | } 250 | }; 251 | return self.push(frame, self.write(@ptrCast(?*const c_void, &seq), @bitCast(u32, @truncate(c_uint, @sizeOf(atom.AtomSequence))))); 252 | // return self.push(frame, self.write(@ptrCast(?*const c_void, &seq), @sizeOf(atom.AtomSequence))); 253 | } 254 | }; 255 | 256 | 257 | -------------------------------------------------------------------------------- /src/c.zig: -------------------------------------------------------------------------------- 1 | pub usingnamespace @cImport({ 2 | @cInclude("lv2/core/lv2.h"); 3 | @cInclude("lv2/core/lv2_util.h"); 4 | @cInclude("lv2/log/log.h"); 5 | @cInclude("lv2/log/logger.h"); 6 | @cInclude("lv2/urid/urid.h"); 7 | 8 | @cInclude("lv2/atom/atom.h"); 9 | @cInclude("lv2/atom/util.h"); 10 | @cInclude("lv2/atom/forge.h"); 11 | 12 | @cInclude("lv2/midi/midi.h"); 13 | @cInclude("lv2/patch/patch.h"); 14 | 15 | @cInclude("lv2/state/state.h"); 16 | }); 17 | -------------------------------------------------------------------------------- /src/lv2.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | pub const c = @import("c.zig"); 3 | 4 | pub usingnamespace @import("urid.zig"); 5 | 6 | pub usingnamespace @import("atom/atom.zig"); 7 | pub usingnamespace @import("atom/forge.zig"); 8 | 9 | pub usingnamespace @import("state.zig"); 10 | pub usingnamespace @import("utils.zig"); 11 | 12 | pub const Descriptor = c.LV2_Descriptor; 13 | 14 | pub fn Handlers(comptime Handle_: type) type { 15 | return struct { 16 | run: ?fn (handle: *Handle_, samples: u32) void = null, 17 | instantiate: ?fn (handle: *Handle_, descriptor: *const Descriptor, rate: f64, bundle_path: []const u8, features: Features) anyerror!void = null, 18 | activate: ?fn (handle: *Handle_) void = null, 19 | deactivate: ?fn (handle: *Handle_) void = null, 20 | extensionData: ?fn(uri: []const u8) ?*c_void = null 21 | }; 22 | } 23 | 24 | pub const Plugin = struct { 25 | uri: []const u8, 26 | Handle: type, 27 | 28 | pub fn exportPlugin(comptime self: Plugin, comptime handlers_: Handlers(self.Handle)) void { 29 | const lv = struct { 30 | const URI_ = self.uri; 31 | const handlers = handlers_; 32 | const Handle__ = self.Handle; 33 | 34 | fn toHandle(instance: c.LV2_Handle) *Handle__ { 35 | return @ptrCast(*Handle__, @alignCast(@alignOf(*Handle__), instance)); 36 | } 37 | 38 | pub fn instantiate(descriptor: [*c]const Descriptor, rate: f64, bundle_path: [*c]const u8, features: [*c]const [*c]const c.LV2_Feature) callconv(.C) c.LV2_Handle { 39 | var handle = std.heap.c_allocator.create(Handle__) catch { 40 | std.debug.print("Yeah you're kinda screwed!", .{}); 41 | std.os.exit(1); 42 | }; 43 | 44 | if (handlers.instantiate) |act| act(handle, @ptrCast(*const Descriptor, descriptor), rate, std.mem.span(bundle_path), Features.init(@ptrCast(*const []c.LV2_Feature, features).*)) catch { 45 | std.heap.c_allocator.destroy(handle); 46 | std.os.exit(1); 47 | }; 48 | 49 | return @ptrCast(c.LV2_Handle, handle); 50 | } 51 | 52 | pub fn cleanup(instance: c.LV2_Handle) callconv(.C) void { 53 | std.heap.c_allocator.destroy(toHandle(instance)); 54 | } 55 | 56 | pub fn connect_port(instance: c.LV2_Handle, port: u32, data: ?*c_void) callconv(.C) void { 57 | var hnd = toHandle(instance); 58 | 59 | inline for (std.meta.fields(@TypeOf(hnd.*))) |field, i| { 60 | if (i == port) { 61 | if (@typeInfo(field.field_type) != .Pointer) { 62 | if (@hasDecl(field.field_type, "connectPort")) @field(@field(hnd, field.name), "connectPort")(data); 63 | } else @field(hnd, field.name) = @ptrCast(field.field_type, @alignCast(@alignOf(field.field_type), data)); 64 | } 65 | } 66 | } 67 | 68 | pub fn activate(instance: c.LV2_Handle) callconv(.C) void { 69 | if (handlers.activate) |act| act(toHandle(instance)); 70 | } 71 | 72 | pub fn deactivate(instance: c.LV2_Handle) callconv(.C) void { 73 | if (handlers.deactivate) |deact| deact(toHandle(instance)); 74 | } 75 | 76 | pub fn extension_data(uri: [*c]const u8) callconv(.C) ?*c_void { 77 | if (handlers.extensionData) |rn| return rn(std.mem.span(uri)); 78 | return null; 79 | } 80 | 81 | pub fn run(instance: c.LV2_Handle, n_samples: u32) callconv(.C) void { 82 | if (handlers.run) |rn| rn(toHandle(instance), n_samples); 83 | } 84 | 85 | pub const __globalDescriptor = Descriptor{ .URI = URI_.ptr, .instantiate = instantiate, .connect_port = connect_port, .activate = activate, .run = run, .deactivate = deactivate, .cleanup = cleanup, .extension_data = extension_data }; 86 | 87 | pub fn lv2_descriptor(index: u32) callconv(.C) [*c]const Descriptor { 88 | return if (index == 0) &__globalDescriptor else null; 89 | } 90 | }; 91 | 92 | @export(lv.instantiate, .{ .name = "instantiate", .linkage = .Strong }); 93 | @export(lv.connect_port, .{ .name = "connect_port", .linkage = .Strong }); 94 | @export(lv.activate, .{ .name = "activate", .linkage = .Strong }); 95 | @export(lv.run, .{ .name = "run", .linkage = .Strong }); 96 | @export(lv.deactivate, .{ .name = "deactivate", .linkage = .Strong }); 97 | @export(lv.cleanup, .{ .name = "cleanup", .linkage = .Strong }); 98 | @export(lv.extension_data, .{ .name = "extension_data", .linkage = .Strong }); 99 | @export(lv.lv2_descriptor, .{ .name = "lv2_descriptor", .linkage = .Strong }); 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /src/state.zig: -------------------------------------------------------------------------------- 1 | const c = @import("c.zig"); 2 | const std = @import("std"); 3 | const lv2 = @import("lv2.zig"); 4 | 5 | const StateStatus = extern enum(c_int) { 6 | state_success = 0, 7 | state_err_unknown = 1, 8 | state_err_bad_type = 2, 9 | state_err_bad_flags = 3, 10 | state_err_no_feature = 4, 11 | state_err_no_property = 5, 12 | state_err_no_space = 6, 13 | _, 14 | }; 15 | 16 | pub fn StateMap(comptime State: type) type { 17 | var fields: [std.meta.fields(State).len]std.builtin.TypeInfo.StructField = undefined; 18 | 19 | for (std.meta.fields(State)) |field, i| { 20 | fields[i] = .{ 21 | .name = field.name, 22 | .field_type = u32, 23 | .default_value = null, 24 | .is_comptime = false, 25 | .alignment = @alignOf(u32), 26 | }; 27 | } 28 | 29 | return @Type(.{ 30 | .Struct = .{ 31 | .layout = .Auto, 32 | .fields = &fields, 33 | .decls = &[_]std.builtin.TypeInfo.Declaration{}, 34 | .is_tuple = false, 35 | } 36 | }); 37 | } 38 | 39 | pub fn StateManager(comptime State: type) type { 40 | var SM = StateMap(State); 41 | return struct { 42 | const Self = @This(); 43 | 44 | state: State, 45 | state_urid_map: SM, 46 | state_type_map: SM, 47 | 48 | pub fn map(self: *Self, comptime uri: []const u8, map_: *lv2.URIDMap) void { 49 | inline for (std.meta.fields(State)) |field| { 50 | @field(self.state_urid_map, field.name) = map_.map(uri ++ "#" ++ field.name); 51 | @field(self.state_type_map, field.name) = map_.map(field.field_type.__atom_type); 52 | } 53 | } 54 | 55 | pub fn extensionData(uri: []const u8) ?*c_void { 56 | if (std.mem.eql(u8, uri, lv2.c.LV2_STATE__interface)) { 57 | var state = lv2.c.LV2_State_Interface{ 58 | .save = Self.save, 59 | .restore = Self.restore 60 | }; 61 | return @ptrCast(*c_void, &state); 62 | } else return null; 63 | } 64 | 65 | pub fn setParameter(self: *Self, field_urid: lv2.URID, value: *lv2.Atom) void { 66 | inline for (std.meta.fields(SM)) |s| { 67 | if (@field(self.state_urid_map, s.name) == field_urid) { 68 | const to = *@TypeOf(@field(self.state, s.name)); 69 | @field(self.state, s.name) = @ptrCast(to, @alignCast(@alignOf(to), value)).*; 70 | return; 71 | } 72 | } 73 | 74 | @panic("Bad!!!"); 75 | } 76 | 77 | // State save method. 78 | // This is used in the usual way when called by the host to save plugin state, 79 | // but also internally for writing messages in the audio thread by passing a 80 | // "store" function which actually writes the description to the forge. 81 | pub fn save (handle: lv2.c.LV2_Handle, store: lv2.c.LV2_State_Store_Function, state_handle: lv2.c.LV2_State_Handle, flags: u32, features: [*c]const [*c]const lv2.c.LV2_Feature) callconv(.C) lv2.c.LV2_State_Status { 82 | if (store == null) return @intToEnum(lv2.c.LV2_State_Status, 0); 83 | 84 | var state = @ptrCast(*State, @alignCast(@alignOf(*State), state_handle)); 85 | var map_path = lv2.getFeatureData(@ptrCast(*const []lv2.c.LV2_Feature, features).*, lv2.c.LV2_STATE__mapPath).?; 86 | 87 | var status = @intToEnum(lv2.c.LV2_State_Status, 0); 88 | 89 | inline for (std.meta.fields(State)) |field| { 90 | var value = @field(state, field.name); 91 | 92 | status = store.?( 93 | handle, 94 | @field(@fieldParentPtr(Self, "state", state).state_urid_map, field.name), 95 | @intToPtr(*lv2.Atom, @ptrToInt(&value) + @sizeOf(lv2.Atom)), 96 | value.atom.size, 97 | @field(@fieldParentPtr(Self, "state", state).state_type_map, field.name), lv2.c.LV2_STATE_IS_POD | lv2.c.LV2_STATE_IS_PORTABLE 98 | ); 99 | } 100 | 101 | return status; 102 | } 103 | 104 | pub fn restore (handle: lv2.c.LV2_Handle, retrieve: lv2.c.LV2_State_Retrieve_Function, state_handle: lv2.c.LV2_State_Handle, flags: u32, features: [*c]const [*c]const lv2.c.LV2_Feature) callconv(.C) lv2.c.LV2_State_Status { 105 | var state = @ptrCast(*State, @alignCast(@alignOf(*State), state_handle)); 106 | var map_path = lv2.getFeatureData(@ptrCast(*const []lv2.c.LV2_Feature, features).*, lv2.c.LV2_STATE__mapPath).?; 107 | var status = @intToEnum(lv2.c.LV2_State_Status, 0); 108 | 109 | inline for (std.meta.fields(State)) |field| { 110 | var key = @field(@fieldParentPtr(Self, "state", state).state_urid_map, field.name); 111 | 112 | var vsize: usize = 0; 113 | var vtype: u32 = 0; 114 | var vflags: u32 = 0; 115 | 116 | if (retrieve.?(handle, key, &vsize, &vtype, &vflags)) |v| 117 | {} 118 | else status = @intToEnum(lv2.c.LV2_State_Status, 4); 119 | } 120 | 121 | return status; 122 | } 123 | }; 124 | } 125 | -------------------------------------------------------------------------------- /src/urid.zig: -------------------------------------------------------------------------------- 1 | const c = @import("c.zig"); 2 | 3 | pub const URID = u32; 4 | 5 | pub const MapHandle = ?*c_void; 6 | pub const UnmapHandle = ?*c_void; 7 | 8 | pub const URIDMap = extern struct { 9 | const Self = @This(); 10 | 11 | handle: MapHandle, 12 | map_: ?fn (MapHandle, [*c]const u8) callconv(.C) URID, 13 | 14 | pub fn fromData(data: *c_void) *Self { 15 | return @ptrCast(*Self, @alignCast(@alignOf(*Self), data)); 16 | } 17 | 18 | pub fn toURI() []const u8 { 19 | return "http://lv2plug.in/ns/ext/urid#map"; 20 | } 21 | 22 | pub fn map(self: Self, uri: []const u8) u32 { 23 | return self.map_.?(self.handle, @ptrCast([*c]const u8, uri)); 24 | } 25 | }; 26 | 27 | pub const URIDUnmap = extern struct { 28 | const Self = @This(); 29 | 30 | handle: UnmapHandle, 31 | unmap_: ?fn (UnmapHandle, URID) callconv(.C) [*c]const u8, 32 | 33 | pub fn fromData(data: *c_void) *Self { 34 | return @ptrCast(*Self, @alignCast(@alignOf(*Self), data)); 35 | } 36 | 37 | pub fn toURI() []const u8 { 38 | return "http://lv2plug.in/ns/ext/urid#unmap"; 39 | } 40 | 41 | pub fn unmap(self: Self, mapped: u32) []const u8 { 42 | return self.unmap_.?(self.handle, mapped); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/utils.zig: -------------------------------------------------------------------------------- 1 | const c = @import("c.zig"); 2 | const std = @import("std"); 3 | 4 | pub const Logger = struct { 5 | logger_internal: c.LV2_Log_Logger, 6 | 7 | pub fn applyData(self: @This(), data: *c_void) void { 8 | self.logger_internal.log = @ptrCast(*c.LV2_Log_Log, data); 9 | } 10 | }; 11 | 12 | pub const Features = struct { 13 | const Self = @This(); 14 | 15 | features: []const c.LV2_Feature, 16 | 17 | pub fn init(features: []const c.LV2_Feature) Self { 18 | return Self{ 19 | .features = features 20 | }; 21 | } 22 | 23 | pub fn query(self: Self, comptime T: type) ?*T { 24 | return queryFeature(self.features, T); 25 | } 26 | }; 27 | 28 | pub fn getFeatureData(features: []const c.LV2_Feature, uri: []const u8) ?*c_void { 29 | for (features) |filled_feat| { 30 | if (filled_feat.URI != null and std.mem.eql(u8, uri, std.mem.span(filled_feat.URI))) { 31 | if (filled_feat.data) |dd| return dd; 32 | } 33 | } 34 | 35 | return null; 36 | } 37 | 38 | pub fn queryFeature(features: []const c.LV2_Feature, comptime T: type) ?*T { 39 | return @field(T, "fromData")(getFeatureData(features, @field(T, "toURI")()) orelse return null); 40 | } 41 | --------------------------------------------------------------------------------