+491
-490
Diff
round #2
+3
-3
build.zig
+3
-3
build.zig
···
28
28
// to our consumers. We must give it a name because a Zig package can expose
29
29
// multiple modules and consumers will need to be able to specify which
30
30
// module they want to access.
31
-
const mod = b.addModule("midi", .{
31
+
const mod = b.addModule("midi-synth", .{
32
32
// The root source file is the "entry point" of this module. Users of
33
33
// this module will only be able to access public declarations contained
34
34
// in this file, which means that if you have declarations that you
···
58
58
// If neither case applies to you, feel free to delete the declaration you
59
59
// don't need and to put everything under a single module.
60
60
const exe = b.addExecutable(.{
61
-
.name = "midi",
61
+
.name = "midi-synth",
62
62
.root_module = b.createModule(.{
63
63
// b.createModule defines a new module just like b.addModule but,
64
64
// unlike b.addModule, it does not expose the module to consumers of
···
78
78
// repeated because you are allowed to rename your imports, which
79
79
// can be extremely useful in case of collisions (which can happen
80
80
// importing modules from different packages).
81
-
.{ .name = "midi", .module = mod },
81
+
.{ .name = "midi-synth", .module = mod },
82
82
},
83
83
}),
84
84
});
+1
-1
src/main.zig
+1
-1
src/main.zig
+486
src/midi.zig
+486
src/midi.zig
···
1
+
const std = @import("std");
2
+
const Io = std.Io;
3
+
const Allocator = std.mem.Allocator;
4
+
5
+
pub const protocol = struct {
6
+
const unpacked = @import("midi/protocol/unpacked.zig");
7
+
const @"packed" = @import("midi/protocol/packed.zig");
8
+
9
+
pub const MidiMessage = unpacked.MidiMessage;
10
+
pub const Status = unpacked.Status;
11
+
pub const StatusPayload = unpacked.StatusPayload;
12
+
pub const SystemMessage = unpacked.SystemMessage;
13
+
pub const SmpteFrame = unpacked.SmpteFrame;
14
+
pub const MtcQuarterFrame = unpacked.MtcQuarterFrame;
15
+
pub const Framerate = @import("midi/protocol.zig").Framerate;
16
+
pub const MtcPieceType = @import("midi/protocol.zig").MtcPieceType;
17
+
};
18
+
19
+
pub fn streamMidi(
20
+
io: Io,
21
+
gpa: Allocator,
22
+
midi_dev_reader: *Io.Reader,
23
+
out_queue: *Io.Queue(protocol.MidiMessage),
24
+
) (Allocator.Error || Io.Reader.Error || Io.Cancelable)!void {
25
+
const MidiByte = protocol.@"packed".MidiByte;
26
+
27
+
var state: StreamState = .idle;
28
+
defer state.deinit(gpa);
29
+
30
+
var mtc_frame_progress: MtcFrameProgress = .none;
31
+
32
+
while (true) {
33
+
const byte: MidiByte = @bitCast(try midi_dev_reader.takeByte());
34
+
switch (byte.kind) {
35
+
.status => switch (handleStatusByte(io, gpa, &state, byte.payload.status, out_queue)) {
36
+
.@"continue" => continue,
37
+
.@"break" => break,
38
+
.@"return" => |res| return res,
39
+
},
40
+
.data => switch (handleDataByte(io, gpa, &state, &mtc_frame_progress, @bitCast(byte.payload), out_queue)) {
41
+
.@"continue" => continue,
42
+
.@"break" => break,
43
+
.@"return" => |res| return res,
44
+
},
45
+
}
46
+
}
47
+
}
48
+
49
+
fn ControlFlow(comptime C: type, comptime B: type, comptime R: type) type {
50
+
return union(enum) {
51
+
@"continue": C,
52
+
@"break": B,
53
+
@"return": R,
54
+
};
55
+
}
56
+
57
+
inline fn handleStatusByte(
58
+
io: Io,
59
+
gpa: Allocator,
60
+
state: *StreamState,
61
+
status: protocol.@"packed".StatusByte,
62
+
out_queue: *Io.Queue(protocol.MidiMessage),
63
+
) ControlFlow(void, void, (Io.Cancelable || Allocator.Error)!void) {
64
+
switch (status.message) {
65
+
.system_message => switch (status.payload.system) {
66
+
.system_exclusive => state.replace(gpa, .{ .system_exclusive = .empty }),
67
+
inline .mtc_quarter_frame,
68
+
.song_position_pointer,
69
+
.song_select,
70
+
=> |tag| state.replace(gpa, @unionInit(StreamState, @tagName(tag), {})),
71
+
.end_of_exclusive => {
72
+
const message = switch (state.*) {
73
+
.system_exclusive => |*array_list| array_list.toOwnedSlice(gpa) catch |err| return .{ .@"return" = err },
74
+
else => { // missed the start of the message, so discard
75
+
// std.debug.print("end of exclusive:\n", .{});
76
+
return .@"continue";
77
+
},
78
+
};
79
+
errdefer gpa.free(message);
80
+
81
+
state.* = .idle;
82
+
out_queue.putOne(
83
+
io,
84
+
.{ .system = .{ .system_exclusive = message } },
85
+
) catch |err| switch (err) {
86
+
error.Canceled => return .{ .@"return" = error.Canceled },
87
+
error.Closed => return .@"break",
88
+
};
89
+
},
90
+
91
+
inline .tune_request,
92
+
// real-time
93
+
.timing_clock,
94
+
.start,
95
+
.@"continue",
96
+
.stop,
97
+
.active_sensing,
98
+
.system_reset,
99
+
=> |tag| out_queue.putOne(io, .{ .system = @unionInit(
100
+
protocol.SystemMessage,
101
+
@tagName(tag),
102
+
{},
103
+
) }) catch |err| switch (err) {
104
+
error.Canceled => return .{ .@"return" = error.Canceled },
105
+
error.Closed => return .@"break",
106
+
},
107
+
},
108
+
inline .note_off,
109
+
.note_on,
110
+
.polyphonic_aftertouch,
111
+
.control_change,
112
+
.program_change,
113
+
.channel_pressure,
114
+
.pitch_bend,
115
+
=> |tag| state.replace(gpa, @unionInit(
116
+
StreamState,
117
+
@tagName(tag),
118
+
.{ .channel = status.payload.channel },
119
+
)),
120
+
}
121
+
return .@"continue";
122
+
}
123
+
124
+
inline fn handleDataByte(
125
+
io: Io,
126
+
gpa: Allocator,
127
+
state: *StreamState,
128
+
mtc_frame_progress: *MtcFrameProgress,
129
+
payload: packed union(u7) {
130
+
data: u7,
131
+
mtc_quarter_frame: protocol.@"packed".MtcQuarterFrame,
132
+
},
133
+
out_queue: *Io.Queue(protocol.MidiMessage),
134
+
) ControlFlow(void, void, (Io.Cancelable || Allocator.Error)!void) {
135
+
switch (state.*) {
136
+
.idle => { // missed the start of the message, so no clue where to put the data
137
+
// std.debug.print("data byte when idle: {x}\n", .{byte.payload.data});
138
+
},
139
+
.system_exclusive => |*message| {
140
+
message.append(gpa, payload.data) catch |err| return .{ .@"return" = err };
141
+
},
142
+
inline .note_off,
143
+
.note_on,
144
+
.polyphonic_aftertouch,
145
+
.control_change,
146
+
.pitch_bend,
147
+
=> |data, tag| {
148
+
const next_state_tag: std.meta.Tag(StreamState) = switch (tag) {
149
+
else => unreachable,
150
+
.note_off => .note_off_pitch,
151
+
.note_on => .note_on_pitch,
152
+
.polyphonic_aftertouch => .polyphonic_aftertouch_pitch,
153
+
.control_change => .control_change_number,
154
+
.pitch_bend => .pitch_bend_lsb,
155
+
};
156
+
state.* = @unionInit(
157
+
StreamState,
158
+
@tagName(next_state_tag),
159
+
.{ .channel = data.channel, .last_byte = payload.data },
160
+
);
161
+
},
162
+
inline .note_off_pitch,
163
+
.note_on_pitch,
164
+
.polyphonic_aftertouch_pitch,
165
+
.control_change_number,
166
+
.pitch_bend_lsb,
167
+
=> |data, tag| {
168
+
const next_state_tag: std.meta.Tag(StreamState) = switch (tag) {
169
+
else => unreachable,
170
+
.note_off_pitch => .note_off,
171
+
.note_on_pitch => .note_on,
172
+
.polyphonic_aftertouch_pitch => .polyphonic_aftertouch,
173
+
.control_change_number => .control_change,
174
+
.pitch_bend_lsb => .pitch_bend,
175
+
};
176
+
state.* = @unionInit(
177
+
StreamState,
178
+
@tagName(next_state_tag),
179
+
.{ .channel = data.channel },
180
+
);
181
+
182
+
const message_tag = @tagName(next_state_tag);
183
+
const status_payload = @unionInit(
184
+
protocol.StatusPayload,
185
+
message_tag,
186
+
.init(data.last_byte, payload.data),
187
+
);
188
+
189
+
out_queue.putOne(
190
+
io,
191
+
.{ .status = .{ .channel = data.channel, .payload = status_payload } },
192
+
) catch |err| switch (err) {
193
+
error.Canceled => return .{ .@"return" = error.Canceled },
194
+
error.Closed => return .@"break",
195
+
};
196
+
},
197
+
inline .program_change,
198
+
.channel_pressure,
199
+
=> |data, tag| {
200
+
const message_tag = @tagName(tag);
201
+
const status_payload = @unionInit(
202
+
protocol.StatusPayload,
203
+
message_tag,
204
+
.init(payload.data),
205
+
);
206
+
207
+
out_queue.putOne(
208
+
io,
209
+
.{ .status = .{ .channel = data.channel, .payload = status_payload } },
210
+
) catch |err| switch (err) {
211
+
error.Canceled => return .{ .@"return" = error.Canceled },
212
+
error.Closed => return .@"break",
213
+
};
214
+
},
215
+
.mtc_quarter_frame => {
216
+
state.* = .idle;
217
+
const packed_frame = payload.mtc_quarter_frame;
218
+
const unpacked_frame: protocol.MtcQuarterFrame = switch (packed_frame.piece_type) {
219
+
inline .frame_lsb,
220
+
.frame_msb,
221
+
.second_lsb,
222
+
.second_msb,
223
+
.minute_lsb,
224
+
.minute_msb,
225
+
.hour_lsb,
226
+
=> |piece_type| @unionInit(
227
+
protocol.MtcQuarterFrame,
228
+
@tagName(piece_type),
229
+
packed_frame.data.value,
230
+
),
231
+
.hour_msb_framerate => .{ .hour_msb_framerate = .{
232
+
.hour_msb = packed_frame.data.hour_msb_framerate.hour_msb,
233
+
.framerate = packed_frame.data.hour_msb_framerate.framerate,
234
+
} },
235
+
};
236
+
if (mtc_frame_progress.add(unpacked_frame)) |smpte_frame| {
237
+
out_queue.putOne(io, .{ .system = .{
238
+
.smpte_frame = smpte_frame,
239
+
} }) catch |err| switch (err) {
240
+
error.Canceled => return .{ .@"return" = error.Canceled },
241
+
error.Closed => return .@"break",
242
+
};
243
+
}
244
+
},
245
+
.song_position_pointer => {
246
+
state.* = .{ .song_position_pointer_lsb = .{ .lsb = payload.data } };
247
+
},
248
+
.song_position_pointer_lsb => |data| {
249
+
state.* = .idle;
250
+
out_queue.putOne(io, .{
251
+
.system = .{
252
+
.song_position_pointer = .init(data.lsb, payload.data),
253
+
},
254
+
}) catch |err| switch (err) {
255
+
error.Canceled => return .{ .@"return" = error.Canceled },
256
+
error.Closed => return .@"break",
257
+
};
258
+
},
259
+
.song_select => {
260
+
state.* = .idle;
261
+
out_queue.putOne(io, .{ .system = .{
262
+
.song_select = .{ .song_number = payload.data },
263
+
} }) catch |err| switch (err) {
264
+
error.Canceled => return .{ .@"return" = error.Canceled },
265
+
error.Closed => return .@"break",
266
+
};
267
+
},
268
+
}
269
+
270
+
return .@"continue";
271
+
}
272
+
273
+
const StreamState = union(enum) {
274
+
idle,
275
+
note_off: ChannelPayload,
276
+
note_off_pitch: ChannelAndLastBytePayload,
277
+
note_on: ChannelPayload,
278
+
note_on_pitch: ChannelAndLastBytePayload,
279
+
polyphonic_aftertouch: ChannelPayload,
280
+
polyphonic_aftertouch_pitch: ChannelAndLastBytePayload,
281
+
control_change: ChannelPayload,
282
+
control_change_number: ChannelAndLastBytePayload,
283
+
program_change: ChannelPayload,
284
+
channel_pressure: ChannelPayload,
285
+
pitch_bend: ChannelPayload,
286
+
pitch_bend_lsb: ChannelAndLastBytePayload,
287
+
system_exclusive: std.ArrayList(u7),
288
+
// system
289
+
mtc_quarter_frame,
290
+
song_position_pointer,
291
+
song_position_pointer_lsb: struct { lsb: u7 },
292
+
song_select,
293
+
294
+
const ChannelPayload = struct { channel: u4 };
295
+
const ChannelAndLastBytePayload = struct { channel: u4, last_byte: u7 };
296
+
297
+
fn replace(self: *@This(), alloc: Allocator, new: @This()) void {
298
+
self.deinit(alloc);
299
+
self.* = new;
300
+
}
301
+
302
+
fn deinit(self: *@This(), alloc: Allocator) void {
303
+
switch (self.*) {
304
+
.system_exclusive => |*s| s.deinit(alloc),
305
+
else => {},
306
+
}
307
+
}
308
+
};
309
+
310
+
const MtcFrameProgress = union(enum) {
311
+
none,
312
+
313
+
// direct order
314
+
frame_lsb: u4,
315
+
frame: protocol.SmpteFrame.Frame,
316
+
frame_second_lsb: struct {
317
+
frame: protocol.SmpteFrame.Frame,
318
+
second_lsb: u4,
319
+
},
320
+
frame_second: struct {
321
+
frame: protocol.SmpteFrame.Frame,
322
+
second: protocol.SmpteFrame.Second,
323
+
},
324
+
frame_second_minute_lsb: struct {
325
+
frame: protocol.SmpteFrame.Frame,
326
+
second: protocol.SmpteFrame.Second,
327
+
minute_lsb: u4,
328
+
},
329
+
frame_second_minute: struct {
330
+
frame: protocol.SmpteFrame.Frame,
331
+
second: protocol.SmpteFrame.Second,
332
+
minute: protocol.SmpteFrame.Minute,
333
+
},
334
+
frame_second_minute_hour_lsb: struct {
335
+
frame: protocol.SmpteFrame.Frame,
336
+
second: protocol.SmpteFrame.Second,
337
+
minute: protocol.SmpteFrame.Minute,
338
+
hour_lsb: u4,
339
+
},
340
+
341
+
// reverse order
342
+
hour_msb_framerate: protocol.MtcQuarterFrame.HourMsbAndFramerate,
343
+
hour_framerate: struct {
344
+
hour: protocol.SmpteFrame.Hour,
345
+
framerate: protocol.Framerate,
346
+
},
347
+
minute_msb_hour_framerate: struct {
348
+
minute_msb: u4,
349
+
hour: protocol.SmpteFrame.Hour,
350
+
framerate: protocol.Framerate,
351
+
},
352
+
minute_hour_framerate: struct {
353
+
minute: protocol.SmpteFrame.Minute,
354
+
hour: protocol.SmpteFrame.Hour,
355
+
framerate: protocol.Framerate,
356
+
},
357
+
second_msb_minute_hour_framerate: struct {
358
+
second_msb: u4,
359
+
minute: protocol.SmpteFrame.Minute,
360
+
hour: protocol.SmpteFrame.Hour,
361
+
framerate: protocol.Framerate,
362
+
},
363
+
second_minute_hour_framerate: struct {
364
+
second: protocol.SmpteFrame.Second,
365
+
minute: protocol.SmpteFrame.Minute,
366
+
hour: protocol.SmpteFrame.Hour,
367
+
framerate: protocol.Framerate,
368
+
},
369
+
frame_msb_second_minute_hour_framerate: struct {
370
+
frame_msb: u4,
371
+
second: protocol.SmpteFrame.Second,
372
+
minute: protocol.SmpteFrame.Minute,
373
+
hour: protocol.SmpteFrame.Hour,
374
+
framerate: protocol.Framerate,
375
+
},
376
+
377
+
fn add(self: *@This(), value: protocol.MtcQuarterFrame) ?protocol.SmpteFrame {
378
+
switch (value) {
379
+
.frame_lsb => |lsb| switch (self.*) {
380
+
.none => self.* = .{ .frame_lsb = lsb },
381
+
.frame_msb_second_minute_hour_framerate => |data| {
382
+
self.* = .none;
383
+
return protocol.SmpteFrame{
384
+
.frame = (@as(protocol.SmpteFrame.Frame, data.frame_msb) << 4) | lsb,
385
+
.second = data.second,
386
+
.minute = data.minute,
387
+
.hour = data.hour,
388
+
.framerate = data.framerate,
389
+
};
390
+
},
391
+
else => self.* = .none, // reset the builder to avoid stream corruption
392
+
},
393
+
.frame_msb => |msb| switch (self.*) {
394
+
.frame_lsb => |lsb| self.* = .{ .frame = (@as(protocol.SmpteFrame.Frame, msb) << 4) | lsb },
395
+
.second_minute_hour_framerate => |data| self.* = .{ .frame_msb_second_minute_hour_framerate = .{
396
+
.frame_msb = msb,
397
+
.second = data.second,
398
+
.minute = data.minute,
399
+
.hour = data.hour,
400
+
.framerate = data.framerate,
401
+
} },
402
+
else => self.* = .none, // reset the builder to avoid stream corruption
403
+
},
404
+
.second_lsb => |lsb| switch (self.*) {
405
+
.frame => |frame| self.* = .{ .frame_second_lsb = .{
406
+
.frame = frame,
407
+
.second_lsb = lsb,
408
+
} },
409
+
.second_msb_minute_hour_framerate => |data| self.* = .{ .second_minute_hour_framerate = .{
410
+
.second = (@as(protocol.SmpteFrame.Second, data.second_msb) << 4) | lsb,
411
+
.minute = data.minute,
412
+
.hour = data.hour,
413
+
.framerate = data.framerate,
414
+
} },
415
+
else => self.* = .none, // reset the builder to avoid stream corruption
416
+
},
417
+
.second_msb => |msb| switch (self.*) {
418
+
.frame_second_lsb => |data| self.* = .{ .frame_second = .{
419
+
.frame = data.frame,
420
+
.second = (@as(protocol.SmpteFrame.Second, msb) << 4) | data.second_lsb,
421
+
} },
422
+
.minute_hour_framerate => |data| self.* = .{ .second_msb_minute_hour_framerate = .{
423
+
.second_msb = msb,
424
+
.minute = data.minute,
425
+
.hour = data.hour,
426
+
.framerate = data.framerate,
427
+
} },
428
+
else => self.* = .none, // reset the builder to avoid stream corruption
429
+
},
430
+
.minute_lsb => |lsb| switch (self.*) {
431
+
.frame_second => |data| self.* = .{ .frame_second_minute_lsb = .{
432
+
.frame = data.frame,
433
+
.second = data.second,
434
+
.minute_lsb = lsb,
435
+
} },
436
+
.minute_msb_hour_framerate => |data| self.* = .{ .minute_hour_framerate = .{
437
+
.minute = (@as(protocol.SmpteFrame.Minute, data.minute_msb) << 4) | lsb,
438
+
.hour = data.hour,
439
+
.framerate = data.framerate,
440
+
} },
441
+
else => self.* = .none, // reset the builder to avoid stream corruption
442
+
},
443
+
.minute_msb => |msb| switch (self.*) {
444
+
.frame_second_minute_lsb => |data| self.* = .{ .frame_second_minute = .{
445
+
.frame = data.frame,
446
+
.second = data.second,
447
+
.minute = (@as(protocol.SmpteFrame.Minute, msb) << 4) | data.minute_lsb,
448
+
} },
449
+
.hour_framerate => |data| self.* = .{ .minute_msb_hour_framerate = .{
450
+
.minute_msb = msb,
451
+
.hour = data.hour,
452
+
.framerate = data.framerate,
453
+
} },
454
+
else => self.* = .none, // reset the builder to avoid stream corruption
455
+
},
456
+
.hour_lsb => |lsb| switch (self.*) {
457
+
.frame_second_minute => |data| self.* = .{ .frame_second_minute_hour_lsb = .{
458
+
.frame = data.frame,
459
+
.second = data.second,
460
+
.minute = data.minute,
461
+
.hour_lsb = lsb,
462
+
} },
463
+
.hour_msb_framerate => |data| self.* = .{ .hour_framerate = .{
464
+
.hour = (@as(protocol.SmpteFrame.Hour, data.hour_msb) << 4) | lsb,
465
+
.framerate = data.framerate,
466
+
} },
467
+
else => self.* = .none, // reset the builder to avoid stream corruption
468
+
},
469
+
.hour_msb_framerate => |hour_msb_framerate| switch (self.*) {
470
+
.frame_second_minute_hour_lsb => |data| {
471
+
self.* = .none;
472
+
return protocol.SmpteFrame{
473
+
.hour = (@as(protocol.SmpteFrame.Hour, hour_msb_framerate.hour_msb) << 4) | data.hour_lsb,
474
+
.minute = data.minute,
475
+
.second = data.second,
476
+
.frame = data.frame,
477
+
.framerate = hour_msb_framerate.framerate,
478
+
};
479
+
},
480
+
.none => self.* = .{ .hour_msb_framerate = hour_msb_framerate },
481
+
else => self.* = .none, // reset the builder to avoid stream corruption
482
+
},
483
+
}
484
+
return null;
485
+
}
486
+
};
src/protocol.zig
src/midi/protocol.zig
src/protocol.zig
src/midi/protocol.zig
src/protocol/packed.zig
src/midi/protocol/packed.zig
src/protocol/packed.zig
src/midi/protocol/packed.zig
src/protocol/unpacked.zig
src/midi/protocol/unpacked.zig
src/protocol/unpacked.zig
src/midi/protocol/unpacked.zig
+1
-486
src/root.zig
+1
-486
src/root.zig
···
1
-
const std = @import("std");
2
-
const Io = std.Io;
3
-
const Allocator = std.mem.Allocator;
4
-
5
-
pub const protocol = struct {
6
-
const unpacked = @import("protocol/unpacked.zig");
7
-
const @"packed" = @import("protocol/packed.zig");
8
-
9
-
pub const MidiMessage = unpacked.MidiMessage;
10
-
pub const Status = unpacked.Status;
11
-
pub const StatusPayload = unpacked.StatusPayload;
12
-
pub const SystemMessage = unpacked.SystemMessage;
13
-
pub const SmpteFrame = unpacked.SmpteFrame;
14
-
pub const MtcQuarterFrame = unpacked.MtcQuarterFrame;
15
-
pub const Framerate = @import("protocol.zig").Framerate;
16
-
pub const MtcPieceType = @import("protocol.zig").MtcPieceType;
17
-
};
18
-
19
-
pub fn streamMidi(
20
-
io: Io,
21
-
gpa: Allocator,
22
-
midi_dev_reader: *Io.Reader,
23
-
out_queue: *Io.Queue(protocol.MidiMessage),
24
-
) (Allocator.Error || Io.Reader.Error || Io.Cancelable)!void {
25
-
const MidiByte = protocol.@"packed".MidiByte;
26
-
27
-
var state: StreamState = .idle;
28
-
defer state.deinit(gpa);
29
-
30
-
var mtc_frame_progress: MtcFrameProgress = .none;
31
-
32
-
while (true) {
33
-
const byte: MidiByte = @bitCast(try midi_dev_reader.takeByte());
34
-
switch (byte.kind) {
35
-
.status => switch (handleStatusByte(io, gpa, &state, byte.payload.status, out_queue)) {
36
-
.@"continue" => continue,
37
-
.@"break" => break,
38
-
.@"return" => |res| return res,
39
-
},
40
-
.data => switch (handleDataByte(io, gpa, &state, &mtc_frame_progress, @bitCast(byte.payload), out_queue)) {
41
-
.@"continue" => continue,
42
-
.@"break" => break,
43
-
.@"return" => |res| return res,
44
-
},
45
-
}
46
-
}
47
-
}
48
-
49
-
fn ControlFlow(comptime C: type, comptime B: type, comptime R: type) type {
50
-
return union(enum) {
51
-
@"continue": C,
52
-
@"break": B,
53
-
@"return": R,
54
-
};
55
-
}
56
-
57
-
inline fn handleStatusByte(
58
-
io: Io,
59
-
gpa: Allocator,
60
-
state: *StreamState,
61
-
status: protocol.@"packed".StatusByte,
62
-
out_queue: *Io.Queue(protocol.MidiMessage),
63
-
) ControlFlow(void, void, (Io.Cancelable || Allocator.Error)!void) {
64
-
switch (status.message) {
65
-
.system_message => switch (status.payload.system) {
66
-
.system_exclusive => state.replace(gpa, .{ .system_exclusive = .empty }),
67
-
inline .mtc_quarter_frame,
68
-
.song_position_pointer,
69
-
.song_select,
70
-
=> |tag| state.replace(gpa, @unionInit(StreamState, @tagName(tag), {})),
71
-
.end_of_exclusive => {
72
-
const message = switch (state.*) {
73
-
.system_exclusive => |*array_list| array_list.toOwnedSlice(gpa) catch |err| return .{ .@"return" = err },
74
-
else => { // missed the start of the message, so discard
75
-
// std.debug.print("end of exclusive:\n", .{});
76
-
return .@"continue";
77
-
},
78
-
};
79
-
errdefer gpa.free(message);
80
-
81
-
state.* = .idle;
82
-
out_queue.putOne(
83
-
io,
84
-
.{ .system = .{ .system_exclusive = message } },
85
-
) catch |err| switch (err) {
86
-
error.Canceled => return .{ .@"return" = error.Canceled },
87
-
error.Closed => return .@"break",
88
-
};
89
-
},
90
-
91
-
inline .tune_request,
92
-
// real-time
93
-
.timing_clock,
94
-
.start,
95
-
.@"continue",
96
-
.stop,
97
-
.active_sensing,
98
-
.system_reset,
99
-
=> |tag| out_queue.putOne(io, .{ .system = @unionInit(
100
-
protocol.SystemMessage,
101
-
@tagName(tag),
102
-
{},
103
-
) }) catch |err| switch (err) {
104
-
error.Canceled => return .{ .@"return" = error.Canceled },
105
-
error.Closed => return .@"break",
106
-
},
107
-
},
108
-
inline .note_off,
109
-
.note_on,
110
-
.polyphonic_aftertouch,
111
-
.control_change,
112
-
.program_change,
113
-
.channel_pressure,
114
-
.pitch_bend,
115
-
=> |tag| state.replace(gpa, @unionInit(
116
-
StreamState,
117
-
@tagName(tag),
118
-
.{ .channel = status.payload.channel },
119
-
)),
120
-
}
121
-
return .@"continue";
122
-
}
123
-
124
-
inline fn handleDataByte(
125
-
io: Io,
126
-
gpa: Allocator,
127
-
state: *StreamState,
128
-
mtc_frame_progress: *MtcFrameProgress,
129
-
payload: packed union(u7) {
130
-
data: u7,
131
-
mtc_quarter_frame: protocol.@"packed".MtcQuarterFrame,
132
-
},
133
-
out_queue: *Io.Queue(protocol.MidiMessage),
134
-
) ControlFlow(void, void, (Io.Cancelable || Allocator.Error)!void) {
135
-
switch (state.*) {
136
-
.idle => { // missed the start of the message, so no clue where to put the data
137
-
// std.debug.print("data byte when idle: {x}\n", .{byte.payload.data});
138
-
},
139
-
.system_exclusive => |*message| {
140
-
message.append(gpa, payload.data) catch |err| return .{ .@"return" = err };
141
-
},
142
-
inline .note_off,
143
-
.note_on,
144
-
.polyphonic_aftertouch,
145
-
.control_change,
146
-
.pitch_bend,
147
-
=> |data, tag| {
148
-
const next_state_tag: std.meta.Tag(StreamState) = switch (tag) {
149
-
else => unreachable,
150
-
.note_off => .note_off_pitch,
151
-
.note_on => .note_on_pitch,
152
-
.polyphonic_aftertouch => .polyphonic_aftertouch_pitch,
153
-
.control_change => .control_change_number,
154
-
.pitch_bend => .pitch_bend_lsb,
155
-
};
156
-
state.* = @unionInit(
157
-
StreamState,
158
-
@tagName(next_state_tag),
159
-
.{ .channel = data.channel, .last_byte = payload.data },
160
-
);
161
-
},
162
-
inline .note_off_pitch,
163
-
.note_on_pitch,
164
-
.polyphonic_aftertouch_pitch,
165
-
.control_change_number,
166
-
.pitch_bend_lsb,
167
-
=> |data, tag| {
168
-
const next_state_tag: std.meta.Tag(StreamState) = switch (tag) {
169
-
else => unreachable,
170
-
.note_off_pitch => .note_off,
171
-
.note_on_pitch => .note_on,
172
-
.polyphonic_aftertouch_pitch => .polyphonic_aftertouch,
173
-
.control_change_number => .control_change,
174
-
.pitch_bend_lsb => .pitch_bend,
175
-
};
176
-
state.* = @unionInit(
177
-
StreamState,
178
-
@tagName(next_state_tag),
179
-
.{ .channel = data.channel },
180
-
);
181
-
182
-
const message_tag = @tagName(next_state_tag);
183
-
const status_payload = @unionInit(
184
-
protocol.StatusPayload,
185
-
message_tag,
186
-
.init(data.last_byte, payload.data),
187
-
);
188
-
189
-
out_queue.putOne(
190
-
io,
191
-
.{ .status = .{ .channel = data.channel, .payload = status_payload } },
192
-
) catch |err| switch (err) {
193
-
error.Canceled => return .{ .@"return" = error.Canceled },
194
-
error.Closed => return .@"break",
195
-
};
196
-
},
197
-
inline .program_change,
198
-
.channel_pressure,
199
-
=> |data, tag| {
200
-
const message_tag = @tagName(tag);
201
-
const status_payload = @unionInit(
202
-
protocol.StatusPayload,
203
-
message_tag,
204
-
.init(payload.data),
205
-
);
206
-
207
-
out_queue.putOne(
208
-
io,
209
-
.{ .status = .{ .channel = data.channel, .payload = status_payload } },
210
-
) catch |err| switch (err) {
211
-
error.Canceled => return .{ .@"return" = error.Canceled },
212
-
error.Closed => return .@"break",
213
-
};
214
-
},
215
-
.mtc_quarter_frame => {
216
-
state.* = .idle;
217
-
const packed_frame = payload.mtc_quarter_frame;
218
-
const unpacked_frame: protocol.MtcQuarterFrame = switch (packed_frame.piece_type) {
219
-
inline .frame_lsb,
220
-
.frame_msb,
221
-
.second_lsb,
222
-
.second_msb,
223
-
.minute_lsb,
224
-
.minute_msb,
225
-
.hour_lsb,
226
-
=> |piece_type| @unionInit(
227
-
protocol.MtcQuarterFrame,
228
-
@tagName(piece_type),
229
-
packed_frame.data.value,
230
-
),
231
-
.hour_msb_framerate => .{ .hour_msb_framerate = .{
232
-
.hour_msb = packed_frame.data.hour_msb_framerate.hour_msb,
233
-
.framerate = packed_frame.data.hour_msb_framerate.framerate,
234
-
} },
235
-
};
236
-
if (mtc_frame_progress.add(unpacked_frame)) |smpte_frame| {
237
-
out_queue.putOne(io, .{ .system = .{
238
-
.smpte_frame = smpte_frame,
239
-
} }) catch |err| switch (err) {
240
-
error.Canceled => return .{ .@"return" = error.Canceled },
241
-
error.Closed => return .@"break",
242
-
};
243
-
}
244
-
},
245
-
.song_position_pointer => {
246
-
state.* = .{ .song_position_pointer_lsb = .{ .lsb = payload.data } };
247
-
},
248
-
.song_position_pointer_lsb => |data| {
249
-
state.* = .idle;
250
-
out_queue.putOne(io, .{
251
-
.system = .{
252
-
.song_position_pointer = .init(data.lsb, payload.data),
253
-
},
254
-
}) catch |err| switch (err) {
255
-
error.Canceled => return .{ .@"return" = error.Canceled },
256
-
error.Closed => return .@"break",
257
-
};
258
-
},
259
-
.song_select => {
260
-
state.* = .idle;
261
-
out_queue.putOne(io, .{ .system = .{
262
-
.song_select = .{ .song_number = payload.data },
263
-
} }) catch |err| switch (err) {
264
-
error.Canceled => return .{ .@"return" = error.Canceled },
265
-
error.Closed => return .@"break",
266
-
};
267
-
},
268
-
}
269
-
270
-
return .@"continue";
271
-
}
272
-
273
-
const StreamState = union(enum) {
274
-
idle,
275
-
note_off: ChannelPayload,
276
-
note_off_pitch: ChannelAndLastBytePayload,
277
-
note_on: ChannelPayload,
278
-
note_on_pitch: ChannelAndLastBytePayload,
279
-
polyphonic_aftertouch: ChannelPayload,
280
-
polyphonic_aftertouch_pitch: ChannelAndLastBytePayload,
281
-
control_change: ChannelPayload,
282
-
control_change_number: ChannelAndLastBytePayload,
283
-
program_change: ChannelPayload,
284
-
channel_pressure: ChannelPayload,
285
-
pitch_bend: ChannelPayload,
286
-
pitch_bend_lsb: ChannelAndLastBytePayload,
287
-
system_exclusive: std.ArrayList(u7),
288
-
// system
289
-
mtc_quarter_frame,
290
-
song_position_pointer,
291
-
song_position_pointer_lsb: struct { lsb: u7 },
292
-
song_select,
293
-
294
-
const ChannelPayload = struct { channel: u4 };
295
-
const ChannelAndLastBytePayload = struct { channel: u4, last_byte: u7 };
296
-
297
-
fn replace(self: *@This(), alloc: Allocator, new: @This()) void {
298
-
self.deinit(alloc);
299
-
self.* = new;
300
-
}
301
-
302
-
fn deinit(self: *@This(), alloc: Allocator) void {
303
-
switch (self.*) {
304
-
.system_exclusive => |*s| s.deinit(alloc),
305
-
else => {},
306
-
}
307
-
}
308
-
};
309
-
310
-
const MtcFrameProgress = union(enum) {
311
-
none,
312
-
313
-
// direct order
314
-
frame_lsb: u4,
315
-
frame: protocol.SmpteFrame.Frame,
316
-
frame_second_lsb: struct {
317
-
frame: protocol.SmpteFrame.Frame,
318
-
second_lsb: u4,
319
-
},
320
-
frame_second: struct {
321
-
frame: protocol.SmpteFrame.Frame,
322
-
second: protocol.SmpteFrame.Second,
323
-
},
324
-
frame_second_minute_lsb: struct {
325
-
frame: protocol.SmpteFrame.Frame,
326
-
second: protocol.SmpteFrame.Second,
327
-
minute_lsb: u4,
328
-
},
329
-
frame_second_minute: struct {
330
-
frame: protocol.SmpteFrame.Frame,
331
-
second: protocol.SmpteFrame.Second,
332
-
minute: protocol.SmpteFrame.Minute,
333
-
},
334
-
frame_second_minute_hour_lsb: struct {
335
-
frame: protocol.SmpteFrame.Frame,
336
-
second: protocol.SmpteFrame.Second,
337
-
minute: protocol.SmpteFrame.Minute,
338
-
hour_lsb: u4,
339
-
},
340
-
341
-
// reverse order
342
-
hour_msb_framerate: protocol.MtcQuarterFrame.HourMsbAndFramerate,
343
-
hour_framerate: struct {
344
-
hour: protocol.SmpteFrame.Hour,
345
-
framerate: protocol.Framerate,
346
-
},
347
-
minute_msb_hour_framerate: struct {
348
-
minute_msb: u4,
349
-
hour: protocol.SmpteFrame.Hour,
350
-
framerate: protocol.Framerate,
351
-
},
352
-
minute_hour_framerate: struct {
353
-
minute: protocol.SmpteFrame.Minute,
354
-
hour: protocol.SmpteFrame.Hour,
355
-
framerate: protocol.Framerate,
356
-
},
357
-
second_msb_minute_hour_framerate: struct {
358
-
second_msb: u4,
359
-
minute: protocol.SmpteFrame.Minute,
360
-
hour: protocol.SmpteFrame.Hour,
361
-
framerate: protocol.Framerate,
362
-
},
363
-
second_minute_hour_framerate: struct {
364
-
second: protocol.SmpteFrame.Second,
365
-
minute: protocol.SmpteFrame.Minute,
366
-
hour: protocol.SmpteFrame.Hour,
367
-
framerate: protocol.Framerate,
368
-
},
369
-
frame_msb_second_minute_hour_framerate: struct {
370
-
frame_msb: u4,
371
-
second: protocol.SmpteFrame.Second,
372
-
minute: protocol.SmpteFrame.Minute,
373
-
hour: protocol.SmpteFrame.Hour,
374
-
framerate: protocol.Framerate,
375
-
},
376
-
377
-
fn add(self: *@This(), value: protocol.MtcQuarterFrame) ?protocol.SmpteFrame {
378
-
switch (value) {
379
-
.frame_lsb => |lsb| switch (self.*) {
380
-
.none => self.* = .{ .frame_lsb = lsb },
381
-
.frame_msb_second_minute_hour_framerate => |data| {
382
-
self.* = .none;
383
-
return protocol.SmpteFrame{
384
-
.frame = (@as(protocol.SmpteFrame.Frame, data.frame_msb) << 4) | lsb,
385
-
.second = data.second,
386
-
.minute = data.minute,
387
-
.hour = data.hour,
388
-
.framerate = data.framerate,
389
-
};
390
-
},
391
-
else => self.* = .none, // reset the builder to avoid stream corruption
392
-
},
393
-
.frame_msb => |msb| switch (self.*) {
394
-
.frame_lsb => |lsb| self.* = .{ .frame = (@as(protocol.SmpteFrame.Frame, msb) << 4) | lsb },
395
-
.second_minute_hour_framerate => |data| self.* = .{ .frame_msb_second_minute_hour_framerate = .{
396
-
.frame_msb = msb,
397
-
.second = data.second,
398
-
.minute = data.minute,
399
-
.hour = data.hour,
400
-
.framerate = data.framerate,
401
-
} },
402
-
else => self.* = .none, // reset the builder to avoid stream corruption
403
-
},
404
-
.second_lsb => |lsb| switch (self.*) {
405
-
.frame => |frame| self.* = .{ .frame_second_lsb = .{
406
-
.frame = frame,
407
-
.second_lsb = lsb,
408
-
} },
409
-
.second_msb_minute_hour_framerate => |data| self.* = .{ .second_minute_hour_framerate = .{
410
-
.second = (@as(protocol.SmpteFrame.Second, data.second_msb) << 4) | lsb,
411
-
.minute = data.minute,
412
-
.hour = data.hour,
413
-
.framerate = data.framerate,
414
-
} },
415
-
else => self.* = .none, // reset the builder to avoid stream corruption
416
-
},
417
-
.second_msb => |msb| switch (self.*) {
418
-
.frame_second_lsb => |data| self.* = .{ .frame_second = .{
419
-
.frame = data.frame,
420
-
.second = (@as(protocol.SmpteFrame.Second, msb) << 4) | data.second_lsb,
421
-
} },
422
-
.minute_hour_framerate => |data| self.* = .{ .second_msb_minute_hour_framerate = .{
423
-
.second_msb = msb,
424
-
.minute = data.minute,
425
-
.hour = data.hour,
426
-
.framerate = data.framerate,
427
-
} },
428
-
else => self.* = .none, // reset the builder to avoid stream corruption
429
-
},
430
-
.minute_lsb => |lsb| switch (self.*) {
431
-
.frame_second => |data| self.* = .{ .frame_second_minute_lsb = .{
432
-
.frame = data.frame,
433
-
.second = data.second,
434
-
.minute_lsb = lsb,
435
-
} },
436
-
.minute_msb_hour_framerate => |data| self.* = .{ .minute_hour_framerate = .{
437
-
.minute = (@as(protocol.SmpteFrame.Minute, data.minute_msb) << 4) | lsb,
438
-
.hour = data.hour,
439
-
.framerate = data.framerate,
440
-
} },
441
-
else => self.* = .none, // reset the builder to avoid stream corruption
442
-
},
443
-
.minute_msb => |msb| switch (self.*) {
444
-
.frame_second_minute_lsb => |data| self.* = .{ .frame_second_minute = .{
445
-
.frame = data.frame,
446
-
.second = data.second,
447
-
.minute = (@as(protocol.SmpteFrame.Minute, msb) << 4) | data.minute_lsb,
448
-
} },
449
-
.hour_framerate => |data| self.* = .{ .minute_msb_hour_framerate = .{
450
-
.minute_msb = msb,
451
-
.hour = data.hour,
452
-
.framerate = data.framerate,
453
-
} },
454
-
else => self.* = .none, // reset the builder to avoid stream corruption
455
-
},
456
-
.hour_lsb => |lsb| switch (self.*) {
457
-
.frame_second_minute => |data| self.* = .{ .frame_second_minute_hour_lsb = .{
458
-
.frame = data.frame,
459
-
.second = data.second,
460
-
.minute = data.minute,
461
-
.hour_lsb = lsb,
462
-
} },
463
-
.hour_msb_framerate => |data| self.* = .{ .hour_framerate = .{
464
-
.hour = (@as(protocol.SmpteFrame.Hour, data.hour_msb) << 4) | lsb,
465
-
.framerate = data.framerate,
466
-
} },
467
-
else => self.* = .none, // reset the builder to avoid stream corruption
468
-
},
469
-
.hour_msb_framerate => |hour_msb_framerate| switch (self.*) {
470
-
.frame_second_minute_hour_lsb => |data| {
471
-
self.* = .none;
472
-
return protocol.SmpteFrame{
473
-
.hour = (@as(protocol.SmpteFrame.Hour, hour_msb_framerate.hour_msb) << 4) | data.hour_lsb,
474
-
.minute = data.minute,
475
-
.second = data.second,
476
-
.frame = data.frame,
477
-
.framerate = hour_msb_framerate.framerate,
478
-
};
479
-
},
480
-
.none => self.* = .{ .hour_msb_framerate = hour_msb_framerate },
481
-
else => self.* = .none, // reset the builder to avoid stream corruption
482
-
},
483
-
}
484
-
return null;
485
-
}
486
-
};
1
+
pub const midi = @import("midi.zig");
History
3 rounds
0 comments
danikvitek.eurosky.social
submitted
#2
1 commit
expand
collapse
feat!: Move MIDI-related code into the
midi module; Rename root module to midi-synth
merge conflicts detected
expand
collapse
expand
collapse
- build.zig:28
- src/main.zig:2
- src/root.zig:1
expand 0 comments
danikvitek.eurosky.social
submitted
#1
1 commit
expand
collapse
feat!: Move MIDI-related code into the
midi module; Rename root module to midi-synth
expand 0 comments
danikvitek.eurosky.social
submitted
#0
1 commit
expand
collapse
feat!: Move MIDI-related code into the
midi module; Rename root module to midi-synth