diff --git a/build.zig b/build.zig index 228e09c..f9576b2 100644 --- a/build.zig +++ b/build.zig @@ -60,7 +60,7 @@ pub fn build(b: *std.build.Builder) !void { lib.initial_memory = 65536; lib.max_memory = 65536; if (try version_supports_stack_first(zig_version)) { - lib.stack_size = 14752; + lib.stack_size = 24752; } else { // `--stack-first` option have been reenabled on wasm targets with https://github.com/ziglang/zig/pull/10572 std.log.warn("Update to Zig >=0.9.1 (or >=0.10.0-dev.258 for nightly) to detect stack overflows at runtime.", .{}); diff --git a/src/main.zig b/src/main.zig index bc537cc..e2dd0f4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,7 @@ const input = @import("input.zig"); const util = @import("util.zig"); const Circuit = @import("circuit.zig"); const Map = @import("map.zig"); +const Music = @import("music.zig"); const Vec2 = util.Vec2; const Vec2f = util.Vec2f; @@ -147,7 +148,7 @@ fn randRangeF(min: f32, max: f32) f32 { // Global vars const KB = 1024; -var heap: [16 * KB]u8 = undefined; +var heap: [10 * KB]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&heap); var world: World = World.init(fba.allocator()); var map: Map = undefined; @@ -156,6 +157,7 @@ var particles: ParticleSystem = undefined; var prng = std.rand.DefaultPrng.init(0); var random = prng.random(); var player: ?usize = null; +var music = Music.Procedural.init(.C3, &Music.Minor, 83); const anim_store = struct { const stand = Anim.frame(0); @@ -177,6 +179,8 @@ const playerAnim = pac: { break :pac animArr.slice(); }; +const WireQuery = World.Query.require(&.{.wire}); + fn showErr(msg: []const u8) noreturn { w4.traceNoF(msg); unreachable; @@ -235,8 +239,7 @@ export fn update() void { // Doing this before clearing the screen since the stack // reaches the screen during this block circuit.clear(); - const q = World.Query.require(&.{.wire}); - var wireIter = world.iter(q); + var wireIter = world.iter(WireQuery); while (wireIter.next()) |wireID| { const e = world.get(wireID); const nodes = e.wire.?.nodes.constSlice(); @@ -259,8 +262,6 @@ export fn update() void { } } } - w4.DRAW_COLORS.* = 0x0004; - w4.rect(.{ 0, 0 }, .{ 160, 160 }); world.process(1, &.{.pos}, velocityProcess); world.process(1, &.{ .pos, .physics }, physicsProcess); @@ -271,8 +272,12 @@ export fn update() void { world.process(1, &.{ .pos, .kinematic }, kinematicProcess); world.process(1, &.{ .sprite, .staticAnim }, staticAnimProcess); world.process(1, &.{ .sprite, .controlAnim, .control }, controlAnimProcess); - world.process(1, &.{ .pos, .sprite }, drawProcess); + particles.update(); + // Drawing + w4.DRAW_COLORS.* = 0x0004; + w4.rect(.{ 0, 0 }, .{ 160, 160 }); + world.process(1, &.{ .pos, .sprite }, drawProcess); map.draw(); for (circuit.cells) |cell, i| { @@ -287,7 +292,6 @@ export fn update() void { world.process(1, &.{.wire}, wireDrawProcess); - particles.update(); particles.draw(); if (player) |p| { @@ -298,7 +302,10 @@ export fn update() void { circuit.isEnabled(pos + util.Dir.left) or circuit.isEnabled(pos + util.Dir.right); if (shouldHum) { - w4.tone(.{ .start = 60 }, .{ .release = 255, .sustain = 0 }, 1, .{ .channel = .pulse1, .mode = .p50 }); + // w4.tone(.{ .start = 60 }, .{ .release = 255, .sustain = 0 }, 1, .{ .channel = .pulse1, .mode = .p50 }); + music.newIntensity = .active; + } else { + music.newIntensity = .calm; } } @@ -312,7 +319,8 @@ export fn update() void { } if (details.active) { - w4.tone(.{ .start = 60 }, .{ .release = 255, .sustain = 0 }, 10, .{ .channel = .pulse1, .mode = .p50 }); + // w4.tone(.{ .start = 60 }, .{ .release = 255, .sustain = 0 }, 10, .{ .channel = .pulse1, .mode = .p50 }); + music.newIntensity = .danger; w4.DRAW_COLORS.* = 0x0020; } else { w4.DRAW_COLORS.* = 0x0030; @@ -325,6 +333,15 @@ export fn update() void { } } + // Music + const musicCommand = music.getNext(1); + if (musicCommand.freq) |freq| { + w4.tone(.{ .start = freq }, .{ .sustain = 2, .release = 2 }, 10, .{ .channel = .pulse2, .mode = .p50 }); + } + if (musicCommand.drum) |drum| { + w4.tone(drum.freq, drum.duration, 10, .{ .channel = .triangle }); + } + indicator = null; input.update(); time += 1; @@ -396,8 +413,7 @@ fn wireManipulationProcess(_: f32, pos: *Pos, control: *Control) void { } else { const interactDistance = 4; var minDistance: f32 = interactDistance; - const q = World.Query.require(&.{.wire}); - var wireIter = world.iter(q); + var wireIter = world.iter(WireQuery); var interactWireID: ?usize = null; var which: usize = 0; while (wireIter.next()) |entityID| { diff --git a/src/music.zig b/src/music.zig new file mode 100644 index 0000000..90f6b6b --- /dev/null +++ b/src/music.zig @@ -0,0 +1,84 @@ +const w4 = @import("wasm4.zig"); + +const midiNote = [_]u16{ + 8, 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, + 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 28, + 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, + 55, 58, 62, 65, 69, 73, 78, 82, 87, 92, 98, + 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, + 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, + 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, + 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, + 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, + 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186, 4435, + 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902, 8372, + 8870, 9397, 9956, 10548, 11175, 11840, 12544, +}; + +pub const Note = enum(usize) { C3 = 57, C4 = 69 }; + +// Defines steps along a musical scale +pub const Major = [8]usize{ 0, 2, 4, 5, 7, 9, 11, 12 }; +pub const Minor = [8]usize{ 0, 2, 3, 5, 7, 8, 11, 12 }; + +pub const MusicCommand = struct { + freq: ?u16 = null, + drum: ?struct { freq: w4.ToneFrequency, duration: w4.ToneDuration } = null, +}; + +pub const Intensity = enum(u8) { + calm = 0, + active = 1, + danger = 2, + pub fn atLeast(lhs: @This(), rhs: @This()) bool { + return @enumToInt(lhs) >= @enumToInt(rhs); + } +}; + +pub const Procedural = struct { + tick: usize, + note: usize, + beat: usize, + beatsPerBar: usize, + seed: usize, + root: usize, + scale: []const usize, + + intensity: Intensity = .calm, + newIntensity: ?Intensity = null, + + pub fn init(root: Note, scale: []const usize, seed: usize) @This() { + return @This(){ + .tick = 0, + .beat = 15, + .beatsPerBar = 6, + .seed = seed, + .root = @enumToInt(root), + .scale = scale, + .note = 0, + }; + } + + pub fn getNext(this: *@This(), dt: u32) MusicCommand { + var cmd = MusicCommand{}; + const beatProgress = this.tick % this.beat; + const beatTotal = @divTrunc(this.tick, this.beat); + const beat = beatTotal % this.beatsPerBar; + const bar = @divTrunc(beat, this.beatsPerBar); + this.tick += dt; + if (beat == 0) this.intensity = this.newIntensity orelse this.intensity; + if (this.intensity.atLeast(.active) and bar % 2 == 0 and beatProgress == 0) { + cmd.freq = midiNote[this.root + this.scale[((this.seed * this.note) % 313) % 8]]; + this.note += 1; + } + if (this.intensity.atLeast(.calm) and beat == 0) cmd.drum = .{ + .freq = .{ .start = 100, .end = 1 }, + .duration = .{ .sustain = 1, .release = 4 }, + }; + if (this.intensity.atLeast(.danger) and beat % 3 == 1) cmd.drum = .{ + .freq = .{ .start = 1761 }, + .duration = .{ .release = 6 }, + }; + return cmd; + } +};