From 56cde0fcf612f9a9147d796889bbcda9936e870d Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Sun, 26 Jun 2022 20:47:20 -0400 Subject: [PATCH] initial commit Signed-off-by: Xe Iaso --- .DS_Store | Bin 0 -> 6148 bytes README.md | 2 +- build.zig | 74 +++++++++--- sprites/Mara.png | Bin 0 -> 395 bytes sprites/TownTiles.ase | Bin 0 -> 2134 bytes sprites/TownTiles.png | Bin 0 -> 1929 bytes sprites/TownTiles.tsx | 4 + src/main.zig | 12 +- src/wasm4.zig | 260 +++++++++--------------------------------- 9 files changed, 125 insertions(+), 227 deletions(-) create mode 100644 .DS_Store create mode 100644 sprites/Mara.png create mode 100644 sprites/TownTiles.ase create mode 100644 sprites/TownTiles.png create mode 100644 sprites/TownTiles.tsx diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..aa3210dc22effcd58530251692b101f531c7abc7 GIT binary patch literal 6148 zcmeHK%}T>S5T317H;B-KqBj@37Hm~S@sieh@n}R3Dz!01gK0K2sXdfJu6p!kd;y=q z=W+I@qE^w1qA~-s-|XznZuU#q%>V$a_kt=w2>>{%!h(a%JEF#wj!D6KrVyE)BZD@i zem_n8Xe^o>zmWm;?xrDy1iBEyi}xpe9J%+CC`_|T<&hO8ijz~*obwre>Rrmdm$kB9 zT5olaRqsH`$RF7)|1fO#o29uO8E369Zg+J;7_>3u#5gplR_>tZ=f2i#(-K{^=-ir#p5S3hhw%WDVjUyRf;0mK%ym&M+i2-7O82DZW z?7_olzIPd#ofsen{vZSDd=RJ#U5mLveRN=>O8~?oy0xIKY6+DiEV>qRgE)f1Oe&&D zWwyj%CLR64#<>=AgC-rAEk2lCnb``3>D6(5p~Hc>2B{?mh=JD(Oqgy--T%8E*Z;4R zs7DMC1OJKvR&02UI&R6{tz(;0cdbA@Lsg>Ua)Zw)Xy~gLW7So>h^htsLK%pz#oQoz QQ0PZM(?AU|@S_Yo0d-hwxc~qF literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 3cfe2f2..7a497c8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# snek +# tamamo A game written in Zig for the [WASM-4](https://wasm4.org) fantasy console. diff --git a/build.zig b/build.zig index d6b1c04..d415388 100644 --- a/build.zig +++ b/build.zig @@ -1,27 +1,69 @@ const std = @import("std"); -pub fn build(b: *std.build.Builder) void { - const mode = b.standardReleaseOptions(); +// Returns true if the version includes https://github.com/ziglang/zig/pull/10572/commits. +// When this is false, trying to place the stack first will result in data corruption. +fn version_supports_stack_first(zig_version: std.SemanticVersion) !bool { + if (zig_version.order(try std.SemanticVersion.parse("0.10.0")).compare(.gte)) { + // Merged here: https://github.com/ziglang/zig/pull/10572 + return true; + } + if (zig_version.major == 0 and zig_version.minor == 10) { + // Check for 0.10.0-dev.258+. Conservatively check the prefix of the tag + // in case zig uses other prefixes that don't respect semver ordering. + if (zig_version.pre) |pre| { + // Merged here: https://github.com/ziglang/zig/pull/10572 + return std.mem.startsWith(u8, pre, "dev.") and zig_version.order(try std.SemanticVersion.parse("0.10.0-dev.258")).compare(.gte); + } + } + // Backported here: https://github.com/ziglang/zig/commit/6f49233ac6a6569b909b689f22fc260dc8c19234 + return zig_version.order(try std.SemanticVersion.parse("0.9.1")).compare(.gte); +} +test "stack version check" { + const expect = std.testing.expect; + const parse = std.SemanticVersion.parse; + try expect(!try version_supports_stack_first(try parse("0.8.0"))); + + try expect(!try version_supports_stack_first(try parse("0.9.0"))); + try expect(!try version_supports_stack_first(try parse("0.9.1-dev.259"))); + try expect(try version_supports_stack_first(try parse("0.9.1"))); + + // Conservatively don't recognize tags other than 'dev'. + try expect(!try version_supports_stack_first(try parse("0.10.0-aev.259"))); + try expect(!try version_supports_stack_first(try parse("0.10.0-zev.259"))); + + try expect(!try version_supports_stack_first(try parse("0.10.0-dev.257"))); + try expect(try version_supports_stack_first(try parse("0.10.0-dev.258"))); + try expect(try version_supports_stack_first(try parse("0.10.0-dev.259"))); + try expect(try version_supports_stack_first(try parse("0.10.0"))); + + try expect(try version_supports_stack_first(try parse("0.10.1-dev.100"))); + try expect(try version_supports_stack_first(try parse("0.10.1-dev.300"))); + try expect(try version_supports_stack_first(try parse("0.10.1"))); + + try expect(try version_supports_stack_first(try parse("1.0.0"))); +} + +pub fn build(b: *std.build.Builder) !void { + const zig_version = @import("builtin").zig_version; + const mode = b.standardReleaseOptions(); const lib = b.addSharedLibrary("cart", "src/main.zig", .unversioned); lib.setBuildMode(mode); lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }); lib.import_memory = true; lib.initial_memory = 65536; lib.max_memory = 65536; - lib.global_base = 6560; - lib.stack_size = 8192; + if (try version_supports_stack_first(zig_version)) { + lib.stack_size = 14752; + } 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.", .{}); + lib.global_base = 6560; + lib.stack_size = 8192; + } + // Workaround https://github.com/ziglang/zig/issues/2910, preventing + // functions from compiler_rt getting incorrectly marked as exported, which + // prevents them from being removed even if unused. lib.export_symbol_names = &[_][]const u8{ "start", "update" }; lib.install(); - - const lib_artifact = b.addInstallArtifact(lib); - - const run_command = b.addSystemCommand(&.{ - "w4", "run", "zig-out/lib/cart.wasm", - "--no-open", - }); - run_command.step.dependOn(&lib_artifact.step); - - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_command.step); -} \ No newline at end of file +} diff --git a/sprites/Mara.png b/sprites/Mara.png new file mode 100644 index 0000000000000000000000000000000000000000..7a00cf0e95753ebda34641313bcabc987e1c6e44 GIT binary patch literal 395 zcmV;60d)R}P)Px#4^T{0MP>g0;P}smz-%yRPzM+xed(4v00005bW%=J0RR90{~XXJQUCw}8A(Jz zR5*>Tlu?cZAqYf;aQ`!}K!HNz$0X}aIy_qo1njj$WMP>3E2q86xKfx{u?Z2urxdUZB%xc>+98?ZLD0Vh$3#yNd(C}g=`^msU;u+z6UW>1?*{;8r zgYGl!s*O$kQ_&_V;q6TdRk(c*ium(R7S{_^3zx! zW-I&1o@^B)mB&9MK3z>@lvd2L#uXuZ(t%f=API4#9Qp$03Tt_j~xt piO$&Y8S;c_N_bD)A5=kx{s78X3Jn1DIuif@002ovPDHLkV1gYtt?mE- literal 0 HcmV?d00001 diff --git a/sprites/TownTiles.ase b/sprites/TownTiles.ase new file mode 100644 index 0000000000000000000000000000000000000000..615a0b01c8ecdf4dfc78cc8b86b433ba8b5e1658 GIT binary patch literal 2134 zcmcIjk2}+A8=s;%yrnuRIM*8zt>K798)$)JboboWGTkUZt1lc|ECT(DMXl;e6+} z(03q^h<%|KPoBfryX&Vi!u-vE1>Y23-zc`Qn~_#Cj4^ii;{z}n8MZ9i?~FF(;@fR$ zJ~-n&?%2Pjn6i^=0EY~JD7RreK5rsHtAW+Hv%6tSoI zeX0|t7Nc4^vdPgXQfUAuJ%rdP`Td>Y6bz@HoCQNcJ)S!Ts)5b>0iI>rRz)}zSI8hg z=;|_!HRw|VBs2)GG73k=+SV%9mA0IsCQ7siy{{&{LXWJ1iIcQUTri*a zOG1jg0-|1_4nd9bsc;(A|3-`4gln5M-6jQSB`^YhRniXPs0^q0rFix!S;3xmmlZ!w zzOU9cK^XpP*DDXrI!i7zQw7Q7F*Lf=J$a+?n;~UR*QXDY-rXrjC3XAX_E*h{N7>A_ z8cmTZy-TyK(fh2!Y+iI3r3Kr~X*ZMkp8?DPva4>&Gtit^0LwMqq&^mYhPf$HMBNGw zs%@ryrM_1K>cqz)92H&l(9~7k12ssC()z9Cku|iqpoNlsD z=I9?Kq0W0PrBX0{bNik1ZibU_3t2#Lf7wzBV=9=->^f<5<2 z`!XdA$HFBLJ$4}Oz%@cXeX!}=-cd99yK|8|3Q0jSbnlR75+xqc_JxFiclK)zJo`6v zEaydlxpdK$S3s7~P@s0SHm?56j?ZxdQLl)LcgZ^A0dB8@r@+H^3w(1HKdw5EMLl;B z?u?Gu`ST~mEBKOk-m!?6J8|Yfn1=sYHWHpUMh-fKV-b|KkmR%{bjjX&0$+$356T5S zvJh+uRJD~VKFDWK8{kb8lz}VW`pok%%>{=n2{D*=yS*RiNATM4vVm0JG9_ts2Qxji zy5P=q$!=|x1!eOVoplIaz@-ac>R8RHdKC4@$@rVVSySam$#30LiTaH+QMRP1x8x=b zHG;B^{q@6knHQPdcmr(C|XRa&9D|#@0UGt&;crtc~dL zg}t(H2bHqG#7T))0E`<5>YA%)(?Yl0g4FC&hL`Iwey9|B**H117L@8o!>_z|;=Ju! zi8s3d@+k4nXl_WfEEjOgeMJAE!8{d*HQu#;^&yYhnY!Y{iY$VbJdJ#^z~1M{*|Ouk znWS5~2@PyLn6xh0Hf1t}XaMycIjR1L`8?YBu&d zD)P7YlE#K+T<{&jn!+Ucg**~6!%Tgmh{`!=zhjVmn^}>teXd>|XG@?|7WGB9NX~(D z`IXj~&Wo@t^?Zp%xtQu?Sp6bWfQw3pnGY6KHs$fd;f0eED|QIkW)@2`{gi>U40Qc; z3?wbHNuTkjFA@fA5#sBiHrh3kj$)w@ zCjD{zis&6(*l%-761a}XU~T6MRW=!r&Fe%#r6?_Qo2pZg{mHNHOQ>L`J2>Z0!la$) zWxi>25M&3_BhIvi-QjGQHm6J~r89G1;*b^SG~Hr~X1&Rp0b-hDGxa*RY4D}9RYbOL ztDP++sd^T^43`6i$Mf3lPdo50Q$Mur8CYu(Amu&6!n970_9M}UZ*jfy7y3K0=kMejJy`P1&c|1y#+70 zS=4C4gS5$@9e3ddVLH6(De{g6#5%FMa#Mz_c;bSV-sSv8VqI}I$a}*bB>t*xhkobL n^nXL>s06o}_R~?f7^~soK58K~*EL)lzgm_3R)HpY-H-b(YUMa* literal 0 HcmV?d00001 diff --git a/sprites/TownTiles.png b/sprites/TownTiles.png new file mode 100644 index 0000000000000000000000000000000000000000..3135e5053c2dc04118b19236c6b38da1a1c7081d GIT binary patch literal 1929 zcmV;42X^?0P))P400001b5ch_0Itp) z=>Px#4^T{0MP>g0;P}smz-%yRPzM+xed(4v00005bW%=J0RR90{~XXJQUCx47fD1x zRCt`#nhB!gAP7Kz(fglysw@I(l+-T6{Art=CyPY=djusr;r%c<9~KgQzL&q~1v!ru zh-soV_f2vo{rPc#Rg>hzAWBjSzSIw#09XV7 z1DG^A1K5OP6z zrA9l(S89kMXzOJF{z0zP$G}3uE;I<@mt2T{(P!7fOMuM?Bo(sHL?^P40*H^a-2o_n zKtE0aP*?UD02+R%pC>vpj{z=aN!5?oO9S!Qj4U>dC!DA^00=u|PsWN{bT zF}_j*0qRS7^&@&G4gh#*2`H*N0-SK69C)Ew3ncwt*=0!B2eDSq9pP%Ux)7$Kco6pr zyWI|85fhYEn##Ei)zD@;#6ydj3%h)ym>U2{6hIGPo4>yU`*hV9lcV(Jkli5y07VDD?W?fv4A?Rhu_lZ5SP4_HYQz{f!7C0@F*R?|I%K>$L{KdtEab7_HqMG_Gx_3<KhE^ z>EJpG^z#8*LTQJKo?ici1h)WOM{Mrj2eF$E*aX0IQ*`wDOVv=1Vlj6jieUoaU1l2q zbN)d82WnDlw}U}^9v~sWM13Tv0IxPb=%0NSveE_W0$^YOYL-+}z}q{r4uD%vT5$z1 z3V{dRAsqvdr<@1q9wej435VCCPULcslO+Hvw;2N&%Msm=vGxdXAczgoc>sCg0N?y| zlJ7uI%nkG&fMiE5n*-u0pAX_31xRFhkj9q?&R+otrqK#uUn^)2&lAUjcoh?|a(M%w z5+q@$TZyw*jt6lR;QHz{z(Zb*lQcaA@V!#vEWmYTK>+10Rr_R`zaIELz)E-mEOX1Z zuCE?F1klpxNefWPkFf#?wC+460W~f0`rMCkC3@li4}w@Ywf1^c;=KUZF4C_;wDc*ryCyYX(a??n zzy-icXxRp+ML=~=*(m_3IqIh}&ItgjM&ZhZ{1_>3t7RK%5fDx;r7Yt@53trMC2Bqv z#60^4G)or(*m0qZvqvib)LEm?0I;un-fJIuIrppF-}0 z%~A74hquu+dJnV!0J}a&8aUImDzN@Y0RH?d7aCgTvaJW(e%V{{+uiygSqek|%?%S} zSarSua0)w1kfr7dJ4=wIRseo|kXj0qX8?Np{Xx6|V7A&)AO_G8Z(S(=8h~92v}@6&225ZsJ`>yEkCZL4Fx1dw}UPurrH0h$p; z{lHfGR{&e-e;dHB57K`#7y4*Fa1B6u$lfkN4P3~a|NS(&S|2p1l?>uP)M}-Cdw + + + diff --git a/src/main.zig b/src/main.zig index 9c38112..15628b4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,17 +11,15 @@ const smiley = [8]u8{ 0b11000011, }; -export fn start() void {} - export fn update() void { - w4.m.colors._0 = .p1; + w4.DRAW_COLORS.* = 2; w4.text("Hello from Zig!", 10, 10); - const gamepad = w4.m.gamepads[0]; - if (gamepad.a) { - w4.m.colors._0 = .p3; + const gamepad = w4.GAMEPAD1.*; + if (gamepad & w4.BUTTON_1 != 0) { + w4.DRAW_COLORS.* = 4; } - w4.blit(&smiley, 76, 76, 8, 8, .{}); + w4.blit(&smiley, 76, 76, 8, 8, w4.BLIT_1BPP); w4.text("Press X to blink", 16, 90); } diff --git a/src/wasm4.zig b/src/wasm4.zig index 97cdd6f..322dc92 100644 --- a/src/wasm4.zig +++ b/src/wasm4.zig @@ -1,25 +1,13 @@ // // WASM-4: https://wasm4.org/docs -const std = @import("std"); - -fn assert_equal(a: anytype, b: anytype, comptime error_msg: []const u8) void { - if (a != b) @compileError(std.fmt.comptimePrint("{} != {} {s}", .{a, b, error_msg})); -} - -comptime { - const builtin = @import("builtin"); - const native_endian = builtin.target.cpu.arch.endian(); - assert_equal(native_endian, .Little, "Bit flags need little endian"); -} - // ┌───────────────────────────────────────────────────────────────────────────┐ // │ │ // │ Platform Constants │ // │ │ // └───────────────────────────────────────────────────────────────────────────┘ -pub const SCREEN_SIZE: u32 = 160; +pub const CANVAS_SIZE: u32 = 160; // ┌───────────────────────────────────────────────────────────────────────────┐ // │ │ @@ -27,99 +15,31 @@ pub const SCREEN_SIZE: u32 = 160; // │ │ // └───────────────────────────────────────────────────────────────────────────┘ -pub const Memory = packed struct { - _padding: [4]u8, - palette: [4]Color, - colors: DrawColors, - gamepads: [4]GamePad, - mouse: Mouse, - system: SystemFlags, - _reserved: [128]u8, - framebuffer: [6400]u8, - userdata: [58976]u8, -}; +pub const PALETTE: *[4]u32 = @intToPtr(*[4]u32, 0x04); +pub const DRAW_COLORS: *u16 = @intToPtr(*u16, 0x14); +pub const GAMEPAD1: *const u8 = @intToPtr(*const u8, 0x16); +pub const GAMEPAD2: *const u8 = @intToPtr(*const u8, 0x17); +pub const GAMEPAD3: *const u8 = @intToPtr(*const u8, 0x18); +pub const GAMEPAD4: *const u8 = @intToPtr(*const u8, 0x19); +pub const MOUSE_X: *const i16 = @intToPtr(*const i16, 0x1a); +pub const MOUSE_Y: *const i16 = @intToPtr(*const i16, 0x1c); +pub const MOUSE_BUTTONS: *const u8 = @intToPtr(*const u8, 0x1e); +pub const SYSTEM_FLAGS: *u8 = @intToPtr(*u8, 0x1f); +pub const FRAMEBUFFER: *[6400]u8 = @intToPtr(*[6400]u8, 0xA0); -comptime { - assert_equal(@bitSizeOf(Memory), 64 * 1024 * 8, "Memory layout wrong"); -} +pub const BUTTON_1: u8 = 1; +pub const BUTTON_2: u8 = 2; +pub const BUTTON_LEFT: u8 = 16; +pub const BUTTON_RIGHT: u8 = 32; +pub const BUTTON_UP: u8 = 64; +pub const BUTTON_DOWN: u8 = 128; -const dummym: Memory = undefined; +pub const MOUSE_LEFT: u8 = 1; +pub const MOUSE_RIGHT: u8 = 2; +pub const MOUSE_MIDDLE: u8 = 4; -pub const m = struct { - pub const palette = @intToPtr(*@TypeOf(dummym.palette ), 0 + @offsetOf(Memory, "palette" )); - pub const colors = @intToPtr(*@TypeOf(dummym.colors ), 0 + @offsetOf(Memory, "colors" )); - pub const gamepads = @intToPtr(*@TypeOf(dummym.gamepads ), 0 + @offsetOf(Memory, "gamepads" )); - pub const mouse = @intToPtr(*@TypeOf(dummym.mouse ), 0 + @offsetOf(Memory, "mouse" )); - pub const system = @intToPtr(*@TypeOf(dummym.system ), 0 + @offsetOf(Memory, "system" )); - pub const framebuffer = @intToPtr(*@TypeOf(dummym.framebuffer), 0 + @offsetOf(Memory, "framebuffer")); - pub const userdata = @intToPtr(*@TypeOf(dummym.userdata ), 0 + @offsetOf(Memory, "userdata" )); -}; - -pub const Color = packed struct { - blue : u8, - green : u8, - red : u8, - _reserved: u8 = 0, -}; - -pub const DrawColors = packed struct { - _0: ColorIndex, - _1: ColorIndex, - _2: ColorIndex, - _3: ColorIndex, -}; - -pub const ColorIndex = enum(u4) { - transparent = 0, - /// Use palette[0] - p0 = 1, - /// Use palette[1] - p1 = 2, - /// Use palette[2] - p2 = 3, - /// Use palette[3] - p3 = 4, -}; - -pub const GamePad = packed struct { - a: bool, - b: bool, - _reserved: u2, - left: bool, - right: bool, - up: bool, - down: bool, -}; - -pub const Mouse = packed struct { - x: i16, - y: i16, - /// primary button - b0: bool, - /// secondary button - b1: bool, - /// third button - b2: bool, - _reserved: u5, -}; - -pub const SystemFlags = packed struct { - preserve_framebuffer: bool, - hide_gamepad_overlay: bool, - _reserved: u6, -}; - -// ┌───────────────────────────────────────────────────────────────────────────┐ -// │ │ -// │ Raw Functions │ -// │ │ -// └───────────────────────────────────────────────────────────────────────────┘ - -const raw_api = struct { - extern fn blit(sprite: [*]const u8, x: u32, y: u32, width: u32, height: u32, flags: u32) void; - extern fn blitSub(sprite: [*]const u8, x: u32, y: u32, width: u32, height: u32, src_x: u32, src_y: u32, stride: u32, flags: u32) void; - extern fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) void; -}; +pub const SYSTEM_PRESERVE_FRAMEBUFFER: u8 = 1; +pub const SYSTEM_HIDE_GAMEPAD_OVERLAY: u8 = 2; // ┌───────────────────────────────────────────────────────────────────────────┐ // │ │ @@ -127,49 +47,38 @@ const raw_api = struct { // │ │ // └───────────────────────────────────────────────────────────────────────────┘ -pub const BlitFlags = packed struct { - /// one or two bits per pixel - two_bits: bool = false, - flip_x: bool = false, - flip_y: bool = false, - rotate: bool = false, - _reserved: u28 = 0, -}; - /// Copies pixels to the framebuffer. -pub fn blit(sprite: []const u8, x: u32, y: u32, width: u32, height: u32, flags: BlitFlags) void { - - raw_api.blit(sprite.ptr, x, y, width, height, @bitCast(u32, flags)); -} +pub extern fn blit(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, flags: u32) void; /// Copies a subregion within a larger sprite atlas to the framebuffer. -/// srcX: Source X position of the sprite region. -/// srcY: Source Y position of the sprite region. -/// stride: Total width of the overall sprite atlas. This is typically larger than width. -pub fn blitSub(sprite: []const u8, x: u32, y: u32, width: u32, height: u32, src_x: u32, src_y: u32, stride: u32, flags: BlitFlags) void { - raw_api.blitSub(sprite.ptr, x, y, width, height, src_x, src_y, stride, @bitCast(u32, flags)); -} +pub extern fn blitSub(sprite: [*]const u8, x: i32, y: i32, width: i32, height: i32, src_x: u32, src_y: u32, stride: i32, flags: u32) void; + +pub const BLIT_2BPP: u32 = 1; +pub const BLIT_1BPP: u32 = 0; +pub const BLIT_FLIP_X: u32 = 2; +pub const BLIT_FLIP_Y: u32 = 4; +pub const BLIT_ROTATE: u32 = 8; /// Draws a line between two points. -pub extern fn line(x1: u32, y1: u32, x2: u32, y2: u32) void; +pub extern fn line(x1: i32, y1: i32, x2: i32, y2: i32) void; /// Draws an oval (or circle). -pub extern fn oval(x: u32, y: u32, width: u32, height: u32) void; +pub extern fn oval(x: i32, y: i32, width: i32, height: i32) void; /// Draws a rectangle. -pub extern fn rect(x: u32, y: u32, width: u32, height: u32) void; +pub extern fn rect(x: i32, y: i32, width: u32, height: u32) void; /// Draws text using the built-in system font. -pub fn text(str: []const u8, x: u32, y: u32) void { +pub fn text(str: []const u8, x: i32, y: i32) void { textUtf8(str.ptr, str.len, x, y); } -extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: u32, y: u32) void; +extern fn textUtf8(strPtr: [*]const u8, strLen: usize, x: i32, y: i32) void; /// Draws a vertical line -pub extern fn vline(x: u32, y: u32, len: u32) void; +pub extern fn vline(x: i32, y: i32, len: u32) void; /// Draws a horizontal line -pub extern fn hline(x: u32, y: u32, len: u32) void; +pub extern fn hline(x: i32, y: i32, len: u32) void; // ┌───────────────────────────────────────────────────────────────────────────┐ // │ │ @@ -178,81 +87,16 @@ pub extern fn hline(x: u32, y: u32, len: u32) void; // └───────────────────────────────────────────────────────────────────────────┘ /// Plays a sound tone. -/// frequency: Wave frequency in hertz. -/// duration: Duration of the tone in frames (1/60th of a second), up to 255 frames. -/// volume: Volume of the sustain and attack durations, between 0 and 100. -pub fn tone(frequency: Tone.Frequency, duration: Tone.Duration, volume: Tone.Volume, flags: Tone.Flags) void { - std.debug.assert(volume.is_valid()); - tone(@bitCast(u32, frequency), @bitCast(u32, duration), @bitCast(u32, volume), @bitCast(u32, flags)); -} +pub extern fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) void; -pub const Tone = struct { - /// Wave frequency in hertz. - pub const Frequency = packed struct { - start: u16, - end: u16 = 0, - }; - - /// Duration of ADSR of note (unit: frames) - /// - /// ^ - /// / \ decay - /// attack / \ - /// / \-------- - /// / sustain \ release - /// / \ - pub const Duration = packed struct { - sustain : u8 = 0, - release : u8 = 0, - decay : u8 = 0, - attack : u8 = 0, - }; - - /// Volume of note (0 to 100) - /// - /// ^ <-- attack volume - /// / \ - /// / \ - /// / \-------- <-- sustain valume - /// / \ - /// / \ - pub const Volume = packed struct { - sustain : u8, - attack : u8 = 100, - - pub fn is_valid(volume: @This()) bool { - return (0 <= volume.sustain) and (volume.sustain <= 100) - and (0 <= volume.attack ) and (volume.attack <= 100); - } - }; - - pub const Flags = packed struct { - channel: Channel, - pulse_duty: DutyCycle = 2, - pan: Pan = .both, - }; - - pub const Channel = enum(u2) { - pulse0 = 0, - pulse1 = 1, - triangle = 2, - noise = 3, - }; - - pub const DutyCycle = enum(u2) { - @"1/8" = 0, - @"1/4" = 1, - @"1/2" = 2, - }; - - /// Which stereo channel(s) to play audio in - pub const Pan = enum(u2) { - /// play audio in both stereo channels - both = 0, - left = 1, - right = 2, - }; -}; +pub const TONE_PULSE1: u32 = 0; +pub const TONE_PULSE2: u32 = 1; +pub const TONE_TRIANGLE: u32 = 2; +pub const TONE_NOISE: u32 = 3; +pub const TONE_MODE1: u32 = 0; +pub const TONE_MODE2: u32 = 4; +pub const TONE_MODE3: u32 = 8; +pub const TONE_MODE4: u32 = 12; // ┌───────────────────────────────────────────────────────────────────────────┐ // │ │ @@ -277,3 +121,13 @@ pub fn trace(x: []const u8) void { traceUtf8(x.ptr, x.len); } extern fn traceUtf8(strPtr: [*]const u8, strLen: usize) void; + +/// Use with caution, as there's no compile-time type checking. +/// +/// * %c, %d, and %x expect 32-bit integers. +/// * %f expects 64-bit floats. +/// * %s expects a *zero-terminated* string pointer. +/// +/// See https://github.com/aduros/wasm4/issues/244 for discussion and type-safe +/// alternatives. +pub extern fn tracef(x: [*:0]const u8, ...) void;