{module,Audio}: ability to store event name and send it later
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
d690814b16
commit
33bfdee520
5 changed files with 86 additions and 37 deletions
|
|
@ -23,14 +23,11 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
pub const name = .app;
|
pub const name = .app;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub const global_events = .{
|
|
||||||
.audio_state_change = .{ .handler = audioStateChange },
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const local_events = .{
|
pub const local_events = .{
|
||||||
.init = .{ .handler = init },
|
.init = .{ .handler = init },
|
||||||
.deinit = .{ .handler = deinit },
|
.deinit = .{ .handler = deinit },
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
|
.audio_state_change = .{ .handler = audioStateChange },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const components = .{
|
pub const components = .{
|
||||||
|
|
@ -39,12 +36,13 @@ pub const components = .{
|
||||||
|
|
||||||
ghost_key_mode: bool = false,
|
ghost_key_mode: bool = false,
|
||||||
|
|
||||||
fn init(audio: *mach.Audio.Mod, piano: *Mod) void {
|
fn init(audio: *mach.Audio.Mod, app: *Mod) void {
|
||||||
// Initialize audio module
|
// Initialize audio module, telling it to send our module's .audio_state_change event when an
|
||||||
audio.send(.init, .{});
|
// entity's sound stops playing
|
||||||
|
audio.send(.init, .{app.event(.audio_state_change)});
|
||||||
|
|
||||||
// Initialize piano module state
|
// Initialize piano module state
|
||||||
piano.init(.{});
|
app.init(.{});
|
||||||
|
|
||||||
std.debug.print("controls:\n", .{});
|
std.debug.print("controls:\n", .{});
|
||||||
std.debug.print("[typing] Play piano noises\n", .{});
|
std.debug.print("[typing] Play piano noises\n", .{});
|
||||||
|
|
@ -60,11 +58,20 @@ fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void {
|
||||||
|
|
||||||
fn audioStateChange(
|
fn audioStateChange(
|
||||||
audio: *mach.Audio.Mod,
|
audio: *mach.Audio.Mod,
|
||||||
which: mach.EntityID,
|
app: *Mod,
|
||||||
piano: *Mod,
|
|
||||||
) !void {
|
) !void {
|
||||||
if (audio.get(which, .playing) == false) {
|
// Find audio entities that are no longer playing
|
||||||
if (piano.get(which, .play_after)) |frequency| {
|
var archetypes_iter = audio.entities.query(.{ .all = &.{
|
||||||
|
.{ .mach_audio = &.{.playing} },
|
||||||
|
} });
|
||||||
|
while (archetypes_iter.next()) |archetype| {
|
||||||
|
for (
|
||||||
|
archetype.slice(.entity, .id),
|
||||||
|
archetype.slice(.mach_audio, .playing),
|
||||||
|
) |id, playing| {
|
||||||
|
if (playing) continue;
|
||||||
|
|
||||||
|
if (app.get(id, .play_after)) |frequency| {
|
||||||
// Play a new sound
|
// Play a new sound
|
||||||
const entity = try audio.newEntity();
|
const entity = try audio.newEntity();
|
||||||
try audio.set(entity, .samples, try fillTone(audio, frequency));
|
try audio.set(entity, .samples, try fillTone(audio, frequency));
|
||||||
|
|
@ -73,26 +80,34 @@ fn audioStateChange(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the entity for the old sound
|
// Remove the entity for the old sound
|
||||||
try audio.removeEntity(which);
|
try audio.removeEntity(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(
|
fn tick(
|
||||||
core: *mach.Core.Mod,
|
core: *mach.Core.Mod,
|
||||||
audio: *mach.Audio.Mod,
|
audio: *mach.Audio.Mod,
|
||||||
piano: *Mod,
|
app: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
// TODO(Core)
|
// TODO(Core)
|
||||||
var iter = mach.core.pollEvents();
|
var iter = mach.core.pollEvents();
|
||||||
while (iter.next()) |event| {
|
while (iter.next()) |event| {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.key_press => |ev| {
|
.key_press => |ev| {
|
||||||
const vol = try audio.state().player.volume();
|
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
// Controls
|
// Controls
|
||||||
.space => piano.state().ghost_key_mode = !piano.state().ghost_key_mode,
|
.space => app.state().ghost_key_mode = !app.state().ghost_key_mode,
|
||||||
.down => try audio.state().player.setVolume(@max(0.0, vol - 0.1)),
|
.down => {
|
||||||
.up => try audio.state().player.setVolume(@min(1.0, vol + 0.1)),
|
const vol = math.clamp(try audio.state().player.volume() - 0.1, 0, 1);
|
||||||
|
try audio.state().player.setVolume(vol);
|
||||||
|
std.debug.print("[volume] {d:.0}%\n", .{vol * 100.0});
|
||||||
|
},
|
||||||
|
.up => {
|
||||||
|
const vol = math.clamp(try audio.state().player.volume() + 0.1, 0, 1);
|
||||||
|
try audio.state().player.setVolume(vol);
|
||||||
|
std.debug.print("[volume] {d:.0}%\n", .{vol * 100.0});
|
||||||
|
},
|
||||||
|
|
||||||
// Piano keys
|
// Piano keys
|
||||||
else => {
|
else => {
|
||||||
|
|
@ -102,10 +117,10 @@ fn tick(
|
||||||
try audio.set(entity, .playing, true);
|
try audio.set(entity, .playing, true);
|
||||||
try audio.set(entity, .index, 0);
|
try audio.set(entity, .index, 0);
|
||||||
|
|
||||||
if (piano.state().ghost_key_mode) {
|
if (app.state().ghost_key_mode) {
|
||||||
// After that sound plays, we'll chain on another sound that is one semi-tone higher.
|
// After that sound plays, we'll chain on another sound that is one semi-tone higher.
|
||||||
const one_semi_tone_higher = keyToFrequency(ev.key) * math.pow(f32, 2.0, (1.0 / 12.0));
|
const one_semi_tone_higher = keyToFrequency(ev.key) * math.pow(f32, 2.0, (1.0 / 12.0));
|
||||||
try piano.set(entity, .play_after, one_semi_tone_higher);
|
try app.set(entity, .play_after, one_semi_tone_higher);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,6 @@ pub const local_events = .{
|
||||||
.audio_tick = .{ .handler = audioTick },
|
.audio_tick = .{ .handler = audioTick },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const global_events = .{
|
|
||||||
.audio_state_change = .{ .handler = fn (mach.EntityID) void },
|
|
||||||
};
|
|
||||||
|
|
||||||
const log = std.log.scoped(name);
|
const log = std.log.scoped(name);
|
||||||
|
|
||||||
// The number of milliseconds worth of audio to render ahead of time. The lower this number is, the
|
// The number of milliseconds worth of audio to render ahead of time. The lower this number is, the
|
||||||
|
|
@ -35,6 +31,7 @@ ms_render_ahead: f32 = 16,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
ctx: sysaudio.Context,
|
ctx: sysaudio.Context,
|
||||||
player: sysaudio.Player,
|
player: sysaudio.Player,
|
||||||
|
on_state_change: mach.AnyEvent,
|
||||||
output_mu: std.Thread.Mutex = .{},
|
output_mu: std.Thread.Mutex = .{},
|
||||||
output: SampleBuffer,
|
output: SampleBuffer,
|
||||||
mixing_buffer: ?std.ArrayListUnmanaged(f32) = null,
|
mixing_buffer: ?std.ArrayListUnmanaged(f32) = null,
|
||||||
|
|
@ -45,7 +42,7 @@ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
|
||||||
const SampleBuffer = std.fifo.LinearFifo(u8, .Dynamic);
|
const SampleBuffer = std.fifo.LinearFifo(u8, .Dynamic);
|
||||||
|
|
||||||
fn init(audio: *Mod) !void {
|
fn init(audio: *Mod, on_state_change: mach.AnyEvent) !void {
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
const ctx = try sysaudio.Context.init(null, allocator, .{});
|
const ctx = try sysaudio.Context.init(null, allocator, .{});
|
||||||
try ctx.refresh();
|
try ctx.refresh();
|
||||||
|
|
@ -71,6 +68,7 @@ fn init(audio: *Mod) !void {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.ctx = ctx,
|
.ctx = ctx,
|
||||||
.player = player,
|
.player = player,
|
||||||
|
.on_state_change = on_state_change,
|
||||||
.output = SampleBuffer.init(allocator),
|
.output = SampleBuffer.init(allocator),
|
||||||
.debug = debug,
|
.debug = debug,
|
||||||
});
|
});
|
||||||
|
|
@ -133,6 +131,7 @@ fn audioTick(audio: *Mod) !void {
|
||||||
// not undefined memory.
|
// not undefined memory.
|
||||||
@memset(mixing_buffer.items, 0);
|
@memset(mixing_buffer.items, 0);
|
||||||
|
|
||||||
|
var did_state_change = false;
|
||||||
var max_samples: usize = 0;
|
var max_samples: usize = 0;
|
||||||
var archetypes_iter = audio.entities.query(.{ .all = &.{
|
var archetypes_iter = audio.entities.query(.{ .all = &.{
|
||||||
.{ .mach_audio = &.{ .samples, .playing, .index } },
|
.{ .mach_audio = &.{ .samples, .playing, .index } },
|
||||||
|
|
@ -151,7 +150,7 @@ fn audioTick(audio: *Mod) !void {
|
||||||
max_samples = @max(max_samples, to_read);
|
max_samples = @max(max_samples, to_read);
|
||||||
if (index + to_read >= samples.len) {
|
if (index + to_read >= samples.len) {
|
||||||
// No longer playing, we've read all samples
|
// No longer playing, we've read all samples
|
||||||
audio.sendGlobal(.audio_state_change, .{id});
|
did_state_change = true;
|
||||||
try audio.set(id, .playing, false);
|
try audio.set(id, .playing, false);
|
||||||
try audio.set(id, .index, 0);
|
try audio.set(id, .index, 0);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -159,6 +158,7 @@ fn audioTick(audio: *Mod) !void {
|
||||||
try audio.set(id, .index, index + to_read);
|
try audio.set(id, .index, index + to_read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (did_state_change) audio.sendAnyEvent(audio.state().on_state_change);
|
||||||
|
|
||||||
// Write our rendered samples to the fifo, expanding its size as needed and converting our f32
|
// Write our rendered samples to the fifo, expanding its size as needed and converting our f32
|
||||||
// samples to the format the driver expects.
|
// samples to the format the driver expects.
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ pub const Mod = ModSet(modules).Mod;
|
||||||
pub const EntityID = @import("module/main.zig").EntityID; // TODO: rename to just Entity?
|
pub const EntityID = @import("module/main.zig").EntityID; // TODO: rename to just Entity?
|
||||||
pub const Archetype = @import("module/main.zig").Archetype;
|
pub const Archetype = @import("module/main.zig").Archetype;
|
||||||
|
|
||||||
|
pub const ModuleID = @import("module/main.zig").ModuleID;
|
||||||
|
pub const EventID = @import("module/main.zig").EventID;
|
||||||
|
pub const AnyEvent = @import("module/main.zig").AnyEvent;
|
||||||
|
|
||||||
/// To use experimental sysgpu graphics API, you can write this in your main.zig:
|
/// To use experimental sysgpu graphics API, you can write this in your main.zig:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ pub const Entities = @import("entities.zig").Entities;
|
||||||
pub const Archetype = @import("Archetype.zig");
|
pub const Archetype = @import("Archetype.zig");
|
||||||
pub const ModSet = @import("module.zig").ModSet;
|
pub const ModSet = @import("module.zig").ModSet;
|
||||||
pub const Modules = @import("module.zig").Modules;
|
pub const Modules = @import("module.zig").Modules;
|
||||||
|
pub const ModuleID = @import("module.zig").ModuleID;
|
||||||
|
pub const EventID = @import("module.zig").EventID;
|
||||||
|
pub const AnyEvent = @import("module.zig").AnyEvent;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
std.testing.refAllDeclsRecursive(@This());
|
std.testing.refAllDeclsRecursive(@This());
|
||||||
|
|
|
||||||
|
|
@ -93,16 +93,21 @@ fn Serializable(comptime T: type) type {
|
||||||
return T;
|
return T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add runtime module support
|
||||||
|
pub const ModuleID = u32;
|
||||||
|
pub const EventID = u32;
|
||||||
|
|
||||||
|
pub const AnyEvent = struct {
|
||||||
|
module_id: ModuleID,
|
||||||
|
event_id: EventID,
|
||||||
|
};
|
||||||
|
|
||||||
/// Manages comptime .{A, B, C} modules and runtime modules.
|
/// Manages comptime .{A, B, C} modules and runtime modules.
|
||||||
pub fn Modules(comptime modules: anytype) type {
|
pub fn Modules(comptime modules: anytype) type {
|
||||||
// Verify that each module is valid.
|
// Verify that each module is valid.
|
||||||
inline for (modules) |M| _ = ModuleInterface(M);
|
inline for (modules) |M| _ = ModuleInterface(M);
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
// TODO: add runtime module support
|
|
||||||
pub const ModuleID = u32;
|
|
||||||
pub const EventID = u32;
|
|
||||||
|
|
||||||
pub const GlobalEvent = GlobalEventEnum(modules);
|
pub const GlobalEvent = GlobalEventEnum(modules);
|
||||||
pub const LocalEvent = LocalEventEnum(modules);
|
pub const LocalEvent = LocalEventEnum(modules);
|
||||||
|
|
||||||
|
|
@ -593,6 +598,28 @@ pub fn ModSet(comptime modules: anytype) type {
|
||||||
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
||||||
mods.sendGlobal(module_tag, event_name, args);
|
mods.sendGlobal(module_tag, event_name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub inline fn event(_: *@This(), comptime event_name: LocalEventEnumM(M)) AnyEvent {
|
||||||
|
const module_name_g: ModuleName(modules) = M.name;
|
||||||
|
const event_name_g: Modules(modules).LocalEvent = comptime Modules(modules).moduleToGlobalEvent(
|
||||||
|
M,
|
||||||
|
LocalEventEnumM,
|
||||||
|
LocalEventEnum,
|
||||||
|
event_name,
|
||||||
|
);
|
||||||
|
return .{
|
||||||
|
.module_id = @intFromEnum(module_name_g),
|
||||||
|
.event_id = @intFromEnum(event_name_g),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn sendAnyEvent(m: *@This(), ev: AnyEvent) void {
|
||||||
|
const ModulesT = Modules(modules);
|
||||||
|
const MByName = ModsByName(modules);
|
||||||
|
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
||||||
|
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
||||||
|
mods.sendDynamic(ev.module_id, ev.event_id, .{});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue