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:
parent
d64d30c7db
commit
bca1543391
16 changed files with 7876 additions and 0 deletions
92
src/sysaudio/examples/record.zig
Normal file
92
src/sysaudio/examples/record.zig
Normal 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 {};
|
||||
}
|
||||
77
src/sysaudio/examples/sine.zig
Normal file
77
src/sysaudio/examples/sine.zig
Normal 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!", .{});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue