Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start implementing a wasm4 sandbox #7

Merged
merged 1 commit into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading