Skip to content

Commit

Permalink
Start implementing a wasm4 sandbox
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobly0 committed Nov 28, 2023
1 parent 1751b5c commit 94d86e5
Show file tree
Hide file tree
Showing 12 changed files with 3,544 additions and 112 deletions.
83 changes: 76 additions & 7 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,22 +1,91 @@
const std = @import("std");
const atsam = @import("atsam");
const MicroZig = @import("microzig");
const std = @import("std");

pub const py_badge = .{
pub const py_badge: MicroZig.Target = .{
.preferred_format = .elf,
.chip = atsam.chips.atsamd51j19,
.hal = null,
};

pub fn build(b: *std.Build) void {
const microzig = @import("microzig").init(b, "microzig");
const mz = MicroZig.init(b, "microzig");
const optimize = b.standardOptimizeOption(.{});

const firmware = microzig.addFirmware(b, .{
const fw_options = b.addOptions();
fw_options.addOption(bool, "have_cart", false);

const modified_memory_regions = b.allocator.dupe(MicroZig.MemoryRegion, py_badge.chip.memory_regions) catch @panic("out of memory");
for (modified_memory_regions) |*memory_region| {
if (memory_region.kind != .ram) continue;
memory_region.offset += 0x19A0;
memory_region.length -= 0x19A0;
break;
}
var modified_py_badge = py_badge;
modified_py_badge.chip.memory_regions = modified_memory_regions;

const fw = mz.addFirmware(b, .{
.name = "pybadge-io",
.target = py_badge,
.target = modified_py_badge,
.optimize = optimize,
.source_file = .{ .path = "src/main.zig" },
});
microzig.installFirmware(b, firmware, .{});
microzig.installFirmware(b, firmware, .{ .format = .{ .uf2 = .SAMD51 } });
fw.artifact.step.dependOn(&fw_options.step);
fw.modules.app.dependencies.put("options", fw_options.createModule()) catch @panic("out of memory");
mz.installFirmware(b, fw, .{});
mz.installFirmware(b, fw, .{ .format = .{ .uf2 = .SAMD51 } });
}

pub const Cart = struct {
mz: *MicroZig,
fw: *MicroZig.Firmware,
};

pub const CartOptions = struct {
name: []const u8,
optimize: std.builtin.OptimizeMode,
source_file: std.Build.LazyPath,
};

pub fn addCart(
d: *std.Build.Dependency,
b: *std.Build,
options: CartOptions,
) *Cart {
const cart_lib = b.addStaticLibrary(.{
.name = "cart",
.root_source_file = options.source_file,
.target = py_badge.chip.cpu.getDescriptor().target,
.optimize = options.optimize,
.link_libc = false,
.single_threaded = true,
.use_llvm = true,
.use_lld = true,
});
cart_lib.addModule("wasm4", d.builder.createModule(.{ .source_file = .{ .path = "src/wasm4.zig" } }));

const fw_options = b.addOptions();
fw_options.addOption(bool, "have_cart", true);

const mz = MicroZig.init(d.builder, "microzig");
const fw = mz.addFirmware(d.builder, .{
.name = options.name,
.target = py_badge,
.optimize = .Debug, // TODO
.source_file = .{ .path = "src/main.zig" },
.linker_script = .{ .source_file = .{ .path = "src/cart.ld" } },
});
fw.artifact.linkLibrary(cart_lib);
fw.artifact.step.dependOn(&fw_options.step);
fw.modules.app.dependencies.put("options", fw_options.createModule()) catch @panic("out of memory");

const cart: *Cart = b.allocator.create(Cart) catch @panic("out of memory");
cart.* = .{ .mz = mz, .fw = fw };
return cart;
}

pub fn installCart(b: *std.Build, cart: *Cart) void {
cart.mz.installFirmware(b, cart.fw, .{});
cart.mz.installFirmware(b, cart.fw, .{ .format = .{ .uf2 = .SAMD51 } });
}
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

.dependencies = .{
.microzig = .{
.url = "https://github.com/ZigEmbeddedGroup/microzig/archive/c3baa4a2777634a2b5cd366b87b453e9b72b4b26.tar.gz",
.hash = "122081c44c02c5d6534cad430371a0301169432b04240ca93335e16e16532dd61f06",
.url = "https://github.com/ZigEmbeddedGroup/microzig/archive/57e4379062e396b354d70d5b924d5da2e8905bd4.tar.gz",
.hash = "1220b8b04be6b97020eb19fca51b3cb44c33f5a619c3e36fc48faab8b15261dfff2a",
},
.atsam = .{
.url = "https://github.com/ZigEmbeddedGroup/microchip-atsam/archive/93ddffbcbe7a0b849cca610ee933210280d731b4.tar.gz",
Expand Down
16 changes: 9 additions & 7 deletions src/Port.zig
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,25 @@ group: Group,
pin: u5,

pub const Group = enum { A, B };
pub const Direction = enum { in, out };
pub const Level = enum { low, high };

pub inline fn setDir(port: Port, dir: enum { in, out }) void {
pub inline fn setDir(port: Port, dir: Direction) void {
switch (dir) {
.in => port.groupPtr().DIRCLR.write(.{ .DIRCLR = @as(u32, 1) << port.pin }),
.out => port.groupPtr().DIRSET.write(.{ .DIRSET = @as(u32, 1) << port.pin }),
}
}

pub inline fn write(port: Port, value: bool) void {
switch (value) {
false => port.groupPtr().OUTCLR.write(.{ .OUTCLR = @as(u32, 1) << port.pin }),
true => port.groupPtr().OUTSET.write(.{ .OUTSET = @as(u32, 1) << port.pin }),
pub inline fn write(port: Port, level: Level) void {
switch (level) {
.low => port.groupPtr().OUTCLR.write(.{ .OUTCLR = @as(u32, 1) << port.pin }),
.high => port.groupPtr().OUTSET.write(.{ .OUTSET = @as(u32, 1) << port.pin }),
}
}

pub inline fn read(port: Port) bool {
return port.groupPtr().IN.read() & @as(u32, 1) << port.pin != 0;
pub inline fn read(port: Port) Level {
return @enumFromInt(port.groupPtr().IN.read().IN >> port.pin & 1);
}

pub const Mux = enum(u4) { A, B, C, D, E, F, G, H, I, J, K, L, M, N };
Expand Down
130 changes: 101 additions & 29 deletions src/audio.zig
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
var sample_buffer_storage: [2][512]u16 = .{.{0} ** 512} ** 2;
pub const sample_buffer: *volatile [2][512]u16 = &sample_buffer_storage;

pub const Channel = struct {
duty: u32,
phase: u32,
phase_step: u31,
phase_step_step: i32,

duration: u31,
attack_duration: u31,
decay_duration: u31,
sustain_duration: u31,
release_duration: u31,

volume: u31,
volume_step: i32,
peak_volume: u31,
sustain_volume: u31,
attack_volume_step: i32,
decay_volume_step: i32,
release_volume_step: i32,
};

pub fn init() void {
@setCold(true);

Port.A0.setDir(.out);
Port.AVCC.setDir(.in);
Port.SPKR_EN.setDir(.out);
Port.SPKR_EN.write(false);
Port.SPKR_EN.write(.low);

io.MCLK.APBDMASK.modify(.{ .DAC_ = 1 });
io.GCLK.PCHCTRL[GCLK.PCH.DAC].write(.{
Expand Down Expand Up @@ -131,7 +153,6 @@ pub fn init() void {
.padding = 0,
});
io.TC5.COUNT8.PER.write(.{ .PER = 12 - 1 });
while (io.TC5.COUNT8.SYNCBUSY.read().CC0 != 0) {}
io.TC5.COUNT8.CTRLA.write(.{
.SWRST = 0,
.ENABLE = 1,
Expand Down Expand Up @@ -189,6 +210,7 @@ pub fn init() void {

dma.initAudio();
while (io.DAC.STATUS.read().READY0 != 1) {}
Port.SPKR_EN.write(.high);
io.NVIC.ISER[32 / 32].write(.{ .SETENA = 1 << 32 % 32 });
}

Expand All @@ -197,41 +219,83 @@ pub fn mix() void {
for (&sample_buffer[
(dma.getAudioPart() + sample_buffer.len - 1) % sample_buffer.len
]) |*out_sample| {
var sample: i16 = 0;
var sample: i32 = 0;
inline for (&local_channels) |*channel| {
if (channel.duration > 0) {
if (channel.duty > 0) {
// generate sample;
const mask = channel.phase >> 31;
sample += @intCast(((channel.volume + mask) ^ mask) >> 16);
if (channel.phase < channel.duty) {
sample += channel.volume;
} else {
sample -= channel.volume;
}
// update
channel.phase +%= channel.phase_step;
channel.volume += channel.volume_step;
channel.duration -= 1;
channel.phase_step = @intCast(channel.phase_step + channel.phase_step_step);
channel.volume = @intCast(channel.volume + channel.volume_step);
if (channel.duration > 0) {
channel.duration -= 1;
} else if (channel.attack_duration > 0) {
channel.duration = channel.attack_duration;
channel.attack_duration = 0;
channel.volume = 0;
channel.volume_step = channel.attack_volume_step;
} else if (channel.decay_duration > 0) {
channel.duration = channel.decay_duration;
channel.decay_duration = 0;
channel.volume = channel.peak_volume;
channel.volume_step = channel.decay_volume_step;
} else if (channel.sustain_duration > 0) {
channel.duration = channel.sustain_duration;
channel.sustain_duration = 0;
channel.volume = channel.sustain_volume;
channel.volume_step = 0;
} else if (channel.release_duration > 0) {
channel.duration = channel.release_duration;
channel.release_duration = 0;
channel.volume = channel.sustain_volume;
channel.volume_step = channel.release_volume_step;
} else {
channel.duty = 0;
}
}
}
out_sample.* = @bitCast(sample -% std.math.minInt(i16));
out_sample.* = @intCast((sample >> 16) - std.math.minInt(i16));
}
channels.* = local_channels;
dma.ackAudio();
}

pub fn tone(channel: usize, note: Note) void {
Port.SPKR_EN.write(true);
pub fn setChannel(channel: usize, state: Channel) void {
io.NVIC.ICER[32 / 32].write(.{ .CLRENA = 1 << 32 % 32 });
channels[channel] = state;
io.NVIC.ISER[32 / 32].write(.{ .SETENA = 1 << 32 % 32 });
}

pub fn playNote(channel: usize, note: Note) void {
const sample_rate: f32 = 44100.0;
const state: Channel = .{
setChannel(channel, .{
.duty = 1 << 31,
.phase = 0,
.phase_step = @intFromFloat(0x1p32 / sample_rate * note.frequency + 0.5),
.phase_step_step = 0,

.duration = @intFromFloat(note.duration * sample_rate + 0.5),
.attack_duration = 0,
.decay_duration = 0,
.sustain_duration = 0,
.release_duration = 0,

.volume = 0x8000000,
.volume_step = 0,
.duration = @intFromFloat(note.duration * sample_rate + 0.5),
};
io.NVIC.ICER[32 / 32].write(.{ .CLRENA = 1 << 32 % 32 });
channels[channel] = state;
io.NVIC.ISER[32 / 32].write(.{ .SETENA = 1 << 32 % 32 });
.peak_volume = 0,
.sustain_volume = 0,
.attack_volume_step = 0,
.decay_volume_step = 0,
.release_volume_step = 0,
});
}

pub fn play(channels_notes: []const []const Note) void {
pub fn playSong(channels_notes: []const []const Note) void {
var time: f32 = 0.0;
var channels_note_index = [1]usize{0} ** channels.len;
var channels_note_start = [1]f32{0.0} ** channels.len;
Expand All @@ -253,7 +317,7 @@ pub fn play(channels_notes: []const []const Note) void {
if (note_index.* > notes.len) continue;
const note = notes[note_index.* - 1];
note_start.* = next_note_time;
tone(channel_index, .{
playNote(channel_index, .{
.duration = @max(note.duration - 0.04, 0.0),
.frequency = note.frequency,
});
Expand All @@ -267,7 +331,7 @@ pub fn play(channels_notes: []const []const Note) void {
}

pub fn mute() void {
Port.SPKR_EN.write(false);
Port.SPKR_EN.write(.low);
}

pub const Note = struct {
Expand Down Expand Up @@ -314,20 +378,28 @@ pub const Note = struct {
};

var channels_storage: [4]Channel = .{.{
.duty = 0,
.phase = 0,
.phase_step = 0,
.phase_step_step = 0,

.duration = 0,
.attack_duration = 0,
.decay_duration = 0,
.sustain_duration = 0,
.release_duration = 0,

.volume = 0,
.volume_step = 0,
.duration = 0,
.peak_volume = 0,
.sustain_volume = 0,
.attack_volume_step = 0,
.decay_volume_step = 0,
.release_volume_step = 0,
}} ** 4;
const channels: *volatile [4]Channel = &channels_storage;
const Channel = struct {
phase: i32,
phase_step: i32,
volume: u31,
volume_step: u31,
duration: u32,
};

var sample_buffer_storage: [2][512]u16 = .{.{0} ** 512} ** 2;

const dma = @import("dma.zig");
const EVSYS = @import("chip.zig").EVSYS;
Expand Down
Loading

0 comments on commit 94d86e5

Please sign in to comment.