src/sysaudio: move mach-sysaudio@ce8ab30dd300b822224d14997c58c06520b642c9 package to here

Helps hexops/mach#1165

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-03-04 18:44:39 -07:00 committed by Stephen Gutekanst
parent d64d30c7db
commit bca1543391
16 changed files with 7876 additions and 0 deletions

View file

@ -0,0 +1,92 @@
//! Redirects input device into zig-out/raw_audio file.
const std = @import("std");
const sysaudio = @import("mach").sysaudio;
var recorder: sysaudio.Recorder = undefined;
var file: std.fs.File = undefined;
// Note: this Info.plist file gets embedded into the final binary __TEXT,__info_plist
// linker section. On macOS this means that NSMicrophoneUsageDescription is set. Without
// that being set, the application would be denied access to the microphone (the prompt
// for microphone access would not even appear.)
//
// The linker is just a convenient way to specify this without building a .app bundle with
// a separate Info.plist file.
export var __info_plist: [663:0]u8 linksection("__TEXT,__info_plist") =
(
\\ <?xml version="1.0" encoding="UTF-8"?>
\\ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
\\ <plist version="1.0">
\\ <dict>
\\ <key>CFBundleDevelopmentRegion</key>
\\ <string>English</string>
\\ <key>CFBundleIdentifier</key>
\\ <string>com.my.app</string>
\\ <key>CFBundleInfoDictionaryVersion</key>
\\ <string>6.0</string>
\\ <key>CFBundleName</key>
\\ <string>myapp</string>
\\ <key>CFBundleDisplayName</key>
\\ <string>My App</string>
\\ <key>CFBundleVersion</key>
\\ <string>1.0.0</string>
\\ <key>NSMicrophoneUsageDescription</key>
\\ <string>To record audio from your microphone</string>
\\ </dict>
\\ </plist>
).*;
pub fn main() !void {
_ = __info_plist;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var ctx = try sysaudio.Context.init(null, gpa.allocator(), .{});
defer ctx.deinit();
try ctx.refresh();
const device = ctx.defaultDevice(.capture) orelse return error.NoDevice;
recorder = try ctx.createRecorder(device, readCallback, .{});
defer recorder.deinit();
try recorder.start();
const zig_out = try std.fs.cwd().makeOpenPath("zig-out", .{});
file = try zig_out.createFile("raw_audio", .{});
std.debug.print(
\\Recording to zig-out/raw_audio using:
\\
\\ device: {s}
\\ channels: {}
\\ sample_rate: {}
\\
\\You can play this recording back using e.g.:
\\ $ ffplay -f f32le -ar {} -ac {} zig-out/raw_audio
\\
, .{
device.name,
device.channels.len,
recorder.sampleRate(),
recorder.sampleRate(),
device.channels.len,
});
// Note: you may also use e.g.:
//
// ```
// paplay -p --format=FLOAT32LE --rate 48000 --raw zig-out/raw_audio
// aplay -f FLOAT_LE -r 48000 zig-out/raw_audio
// ```
while (true) {}
}
fn readCallback(_: ?*anyopaque, input: []const u8) void {
const format_size = recorder.format().size();
const samples = input.len / format_size;
var buffer: [16 * 1024]f32 = undefined;
sysaudio.convertFrom(f32, buffer[0..samples], recorder.format(), input);
_ = file.write(std.mem.sliceAsBytes(buffer[0 .. input.len / format_size])) catch {};
}

View file

@ -0,0 +1,77 @@
const std = @import("std");
const sysaudio = @import("mach").sysaudio;
var player: sysaudio.Player = undefined;
pub fn main() !void {
var timer = try std.time.Timer.start();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var ctx = try sysaudio.Context.init(null, gpa.allocator(), .{ .deviceChangeFn = deviceChange });
std.log.info("Took {} to initialize the context...", .{std.fmt.fmtDuration(timer.lap())});
defer ctx.deinit();
try ctx.refresh();
std.log.info("Took {} to refresh the context...", .{std.fmt.fmtDuration(timer.lap())});
const device = ctx.defaultDevice(.playback) orelse return error.NoDevice;
std.log.info("Took {} to get the default playback device...", .{std.fmt.fmtDuration(timer.lap())});
player = try ctx.createPlayer(device, writeCallback, .{});
std.log.info("Took {} to create a player...", .{std.fmt.fmtDuration(timer.lap())});
defer player.deinit();
try player.start();
std.log.info("Took {} to start the player...", .{std.fmt.fmtDuration(timer.lap())});
try player.setVolume(0.85);
std.log.info("Took {} to set the volume...", .{std.fmt.fmtDuration(timer.lap())});
var buf: [16]u8 = undefined;
std.log.info("player created & entering i/o loop...", .{});
while (true) {
std.debug.print("( paused = {}, volume = {d} )\n> ", .{ player.paused(), try player.volume() });
const line = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')) orelse break;
var iter = std.mem.split(u8, line, ":");
const cmd = std.mem.trimRight(u8, iter.first(), &std.ascii.whitespace);
if (std.mem.eql(u8, cmd, "vol")) {
const vol = try std.fmt.parseFloat(f32, std.mem.trim(u8, iter.next().?, &std.ascii.whitespace));
try player.setVolume(vol);
} else if (std.mem.eql(u8, cmd, "pause")) {
try player.pause();
try std.testing.expect(player.paused());
} else if (std.mem.eql(u8, cmd, "play")) {
try player.play();
try std.testing.expect(!player.paused());
} else if (std.mem.eql(u8, cmd, "exit")) {
break;
}
}
}
const pitch = 440.0;
const radians_per_second = pitch * 2.0 * std.math.pi;
var seconds_offset: f32 = 0.0;
fn writeCallback(_: ?*anyopaque, output: []u8) void {
const seconds_per_frame = 1.0 / @as(f32, @floatFromInt(player.sampleRate()));
const frame_size = player.format().frameSize(@intCast(player.channels().len));
const frames = output.len / frame_size;
var i: usize = 0;
while (i < output.len) : (i += frame_size) {
const frame_index: f32 = @floatFromInt(i / frame_size);
const sample = @sin((seconds_offset + frame_index * seconds_per_frame) * radians_per_second);
sysaudio.convertTo(
f32,
&.{ sample, sample },
player.format(),
output[i..][0..frame_size],
);
}
seconds_offset = @mod(seconds_offset + seconds_per_frame * @as(f32, @floatFromInt(frames)), 1.0);
}
fn deviceChange(_: ?*anyopaque) void {
std.log.info("device change detected!", .{});
}