├── .gitignore ├── moon.pkg.json ├── example └── snake │ ├── moon.mod.json │ ├── moon.pkg.json │ ├── pkg.generated.mbti │ ├── main.mbt │ ├── snake.mbt │ └── game.mbt ├── moon.mod.json ├── ffi.wasm.mbt ├── ffi.wasm-gc.mbt ├── .github └── workflows │ └── publish.yml ├── NOTICE.md ├── utils.mbt ├── README.md ├── ffi.mbt ├── pkg.generated.mbti ├── memory.mbt ├── function.mbt └── LICENCE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .mooncakes/ 3 | .moonbit-lsp.json -------------------------------------------------------------------------------- /moon.pkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": { 3 | "ffi.wasm-gc.mbt": [ 4 | "wasm-gc" 5 | ], 6 | "ffi.wasm.mbt": [ 7 | "wasm" 8 | ] 9 | } 10 | } -------------------------------------------------------------------------------- /example/snake/moon.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snake", 3 | "license": "ISC", 4 | "deps": { 5 | "moonbitlang/wasm4": { 6 | "path": "../.." 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /moon.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moonbitlang/wasm4", 3 | "version": "0.2.9", 4 | "readme": "README.md", 5 | "repository": "https://github.com/moonbitlang/wasm4", 6 | "license": "Apache-2.0", 7 | "keywords": [ 8 | "game" 9 | ], 10 | "description": "An opinionated binding for WASM-4, a retro game framework using WebAssembly" 11 | } 12 | -------------------------------------------------------------------------------- /example/snake/moon.pkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "import": [ 3 | "moonbitlang/wasm4" 4 | ], 5 | "link": { 6 | "wasm-gc": { 7 | "exports": [ 8 | "start", 9 | "upd:update" 10 | ], 11 | "import-memory": { 12 | "module": "env", 13 | "name": "memory" 14 | } 15 | }, 16 | "wasm": { 17 | "exports": [ 18 | "start", 19 | "upd:update" 20 | ], 21 | "import-memory": { 22 | "module": "env", 23 | "name": "memory" 24 | }, 25 | "heap-start-address": 6560 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /ffi.wasm.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 International Digital Economy Academy 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | ///| 16 | extern "wasm" fn get_addr(bytes : FixedArray[Byte]) -> Int = 17 | #|(func (param i32) (result i32) (call $moonbit.decref (local.get 0)) (i32.add (local.get 0) (i32.const 8))) 18 | -------------------------------------------------------------------------------- /ffi.wasm-gc.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 International Digital Economy Academy 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | ///| 16 | fn get_addr(bytes : FixedArray[Byte]) -> Int { 17 | for i = 0; i < bytes.length(); i = i + 1 { 18 | store_byte(address_HEAP + i, bytes[i]) 19 | } 20 | address_HEAP 21 | } 22 | 23 | ///| 24 | let address_HEAP = 0x19a0 25 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish-pacakge 2 | run-name: publish to mooncakes 3 | on: 4 | release: 5 | types: [released] 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: install 12 | run: | 13 | curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash 14 | echo "$HOME/.moon/bin" >> $GITHUB_PATH 15 | - name: moon version 16 | run: | 17 | moon version --all 18 | moonrun --version 19 | - name: moon check 20 | run: | 21 | moon check 22 | moon check --target wasm 23 | moon check --source-dir example/snake 24 | moon check --source-dir example/snake --target wasm 25 | - name: publish 26 | run: | 27 | rm -r example 28 | echo $SECRET > ~/.moon/credentials.json 29 | moon publish 30 | rm ~/.moon/credentials.json 31 | env: 32 | SECRET: ${{ secrets.MOONCAKES_MOONBITLANG_TOKEN }} -------------------------------------------------------------------------------- /example/snake/pkg.generated.mbti: -------------------------------------------------------------------------------- 1 | // Generated using `moon info`, DON'T EDIT IT 2 | package "snake" 3 | 4 | import( 5 | "moonbitlang/core/deque" 6 | "moonbitlang/wasm4" 7 | ) 8 | 9 | // Values 10 | fn start() -> Unit 11 | 12 | fn upd() -> Unit 13 | 14 | // Errors 15 | 16 | // Types and methods 17 | pub(all) struct Game { 18 | snake : Snake 19 | mut frame_count : UInt 20 | mut prev_gamepad : @wasm4.GamePad 21 | mut fruit : Point 22 | } 23 | fn Game::input(Self) -> Unit 24 | fn Game::new() -> Self 25 | fn Game::update(Self) -> Unit 26 | 27 | pub(all) struct Point { 28 | x : Int 29 | y : Int 30 | } 31 | impl Eq for Point 32 | 33 | pub(all) struct Snake { 34 | body : @deque.Deque[Point] 35 | mut direction : Point 36 | } 37 | fn Snake::down(Self) -> Unit 38 | fn Snake::draw(Self) -> Unit 39 | fn Snake::is_dead(Self) -> Bool 40 | fn Snake::left(Self) -> Unit 41 | fn Snake::new() -> Self 42 | fn Snake::right(Self) -> Unit 43 | fn Snake::up(Self) -> Unit 44 | fn Snake::update(Self) -> Point? 45 | 46 | // Type aliases 47 | 48 | // Traits 49 | 50 | -------------------------------------------------------------------------------- /example/snake/main.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Bruno Garcia 2 | // Copyright 2024 International Digital Economy Academy 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | ///| 17 | pub fn start() -> Unit { 18 | @wasm4.set_palette(1, @wasm4.rgb(0xfbf7f3)) 19 | @wasm4.set_palette(2, @wasm4.rgb(0xe5b083)) 20 | @wasm4.set_palette(3, @wasm4.rgb(0x426e5d)) 21 | @wasm4.set_palette(4, @wasm4.rgb(0x20283d)) 22 | } 23 | 24 | ///| 25 | let game : Game = Game::new() 26 | 27 | ///| 28 | pub fn upd() -> Unit { 29 | game.update() 30 | } 31 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | # LICENSE 2 | 3 | ## License for the library 4 | 5 | The library itself is under 6 | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). 7 | 8 | ## Original License for the examples 9 | 10 | ### License for the snake and some comments 11 | 12 | The example of snake and some comments are adapted from 13 | [the tutorial of WASM-4](https://github.com/aduros/wasm4/tree/39ccdfb54182e0c3a71e57356def9f66d0a5c0a8): 14 | 15 | Copyright (c) Bruno Garcia 16 | 17 | Permission to use, copy, modify, and/or distribute this software for any purpose 18 | with or without fee is hereby granted, provided that the above copyright notice 19 | and this permission notice appear in all copies. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 22 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 23 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 24 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 25 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 26 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 27 | THIS SOFTWARE. 28 | -------------------------------------------------------------------------------- /utils.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 International Digital Economy Academy 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | ///| 16 | /// (origin and not mask) or (value shift left and mask) 17 | fn bitset(origin : UInt, value : UInt, offset : UInt, width : UInt) -> UInt { 18 | let mask = ((1 << width.reinterpret_as_int()) - 1) << 19 | offset.reinterpret_as_int() 20 | (origin & mask.reinterpret_as_uint().lnot()) | 21 | ((value << offset.reinterpret_as_int()) & mask.reinterpret_as_uint()) 22 | } 23 | 24 | ///| 25 | fn bitget(origin : UInt, offset : UInt, width : UInt) -> UInt { 26 | let mask = ((1 << width.reinterpret_as_int()) - 1) << 27 | offset.reinterpret_as_int() 28 | (origin & mask.reinterpret_as_uint()) >> offset.reinterpret_as_int() 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wasm4 Binding for MoonBit 2 | 3 | This is an opinionated binding for [Wasm4](https://wasm4.org) in MoonBit. 4 | 5 | ## Prerequisites 6 | 7 | - MoonBit toolchain 8 | - [Node.js](https://nodejs.org/en) 9 | 10 | ## Usage 11 | 12 | - Add this package: `moon add moonbitlang/wasm4` 13 | - Develop with this package as other packages 14 | - Optionally export a function called `start` that will be executed once on 15 | initialization and export a function called `update` that will be executed at 16 | 60Hz for the expected backend 17 | - Import a memory with the module of `env` and name of `memory` 18 | - Build with `moon build --target ` with the respective backend 19 | - Execute `npx wasm4 run .wasm`. The target should be located in 20 | `target/wasm/release/build//.wasm` for wasm 21 | backend, or `target/wasm-gc/release/build//.wasm` 22 | for wasm-gc backend. The browser should open automatically and display the 23 | game. Enjoy 24 | 25 | ## Examples 26 | 27 | The snake example (adapted from the Wasm4 documentation) demonstrates the usage. You may execute 28 | 29 | ```bash 30 | moon build --source-dir example/snake --target wasm 31 | npx wasm4 run example/snake/target/wasm/release/build/snake.wasm 32 | ``` 33 | 34 | and enjoy the game. 35 | 36 | ## References 37 | 38 | - For more information about MoonBit, visit: 39 | [MoonBit official website](https://www.moonbitlang.com/docs/syntax) 40 | - For more information about Wasm4 and how to play, visit: 41 | [WASM-4 Documentation](https://wasm4.org/docs/) 42 | -------------------------------------------------------------------------------- /example/snake/snake.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Bruno Garcia 2 | // Copyright 2024 International Digital Economy Academy 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | ///| 17 | pub(all) struct Point { 18 | x : Int 19 | y : Int 20 | } derive(Eq) 21 | 22 | ///| 23 | pub(all) struct Snake { 24 | body : @deque.Deque[Point] 25 | mut direction : Point 26 | } 27 | 28 | ///| 29 | pub fn Snake::new() -> Snake { 30 | { 31 | body: @deque.from_array([ 32 | Point::{ x: 2, y: 0 }, 33 | Point::{ x: 1, y: 0 }, 34 | Point::{ x: 0, y: 0 }, 35 | ]), 36 | direction: Point::{ x: 1, y: 0 }, 37 | } 38 | } 39 | 40 | ///| 41 | pub fn Snake::draw(self : Snake) -> Unit { 42 | @wasm4.set_draw_colors(4, index=2) 43 | @wasm4.set_draw_colors(3) 44 | self.body 45 | .iter() 46 | .each(p => { 47 | let { x, y } = p 48 | @wasm4.rect(x * 8, y * 8, 8, 8) 49 | }) 50 | @wasm4.set_draw_colors(4) 51 | self.body 52 | .front() 53 | .map(p => { 54 | let { x, y } = p 55 | @wasm4.rect(x * 8, y * 8, 8, 8) 56 | }) 57 | |> ignore 58 | } 59 | 60 | ///| 61 | pub fn Snake::update(self : Snake) -> Point? { 62 | let head = self.body.front().unwrap() 63 | self.body.push_front(Point::{ 64 | x: (head.x + self.direction.x + 20) % 20, 65 | y: (head.y + self.direction.y + 20) % 20, 66 | }) 67 | self.body.pop_back() 68 | } 69 | 70 | ///| 71 | pub fn Snake::up(self : Snake) -> Unit { 72 | if self.direction.y == 0 { 73 | self.direction = Point::{ x: 0, y: -1 } 74 | } 75 | } 76 | 77 | ///| 78 | pub fn Snake::down(self : Snake) -> Unit { 79 | if self.direction.y == 0 { 80 | self.direction = Point::{ x: 0, y: 1 } 81 | } 82 | } 83 | 84 | ///| 85 | pub fn Snake::left(self : Snake) -> Unit { 86 | if self.direction.x == 0 { 87 | self.direction = Point::{ x: -1, y: 0 } 88 | } 89 | } 90 | 91 | ///| 92 | pub fn Snake::right(self : Snake) -> Unit { 93 | if self.direction.x == 0 { 94 | self.direction = Point::{ x: 1, y: 0 } 95 | } 96 | } 97 | 98 | ///| 99 | pub fn Snake::is_dead(self : Snake) -> Bool { 100 | self.body.iter().drop(1).find_first(p => p == self.body.front().unwrap()) 101 | is Some(_) 102 | } 103 | -------------------------------------------------------------------------------- /example/snake/game.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Bruno Garcia 2 | // Copyright 2024 International Digital Economy Academy 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | ///| 17 | let random : @random.Rand = @random.Rand::new() 18 | 19 | ///| 20 | let fruit : @wasm4.Sprite = @wasm4.sprite( 21 | b"\x00\xa0\x02\x00\x0e\xf0\x36\x5c\xd6\x57\xd5\x57\x35\x5c\x0f\xf0".to_fixedarray(), 22 | ) 23 | 24 | ///| 25 | pub(all) struct Game { 26 | snake : Snake 27 | mut frame_count : UInt 28 | mut prev_gamepad : @wasm4.GamePad 29 | mut fruit : Point 30 | } 31 | 32 | ///| 33 | pub fn Game::new() -> Game { 34 | { 35 | snake: Snake::new(), 36 | frame_count: 0, 37 | prev_gamepad: @wasm4.GamePad::default(), 38 | fruit: Point::{ x: random.int(limit=20), y: random.int(limit=20) }, 39 | } 40 | } 41 | 42 | ///| 43 | pub fn Game::update(self : Game) -> Unit { 44 | self.frame_count += 1 45 | self.input() 46 | if self.snake.is_dead() { 47 | @wasm4.trace("Game Over...Or not?") 48 | @wasm4.tone_note_mode( 49 | (@wasm4.Note::new(60, bend=0), None), 50 | @wasm4.ADSR::new(60), 51 | @wasm4.ADSRVolume::new(100), 52 | @wasm4.ToneFlag::new(), 53 | ) 54 | } 55 | if self.frame_count % 15 == 0 { 56 | let dropped_pos = self.snake.update() 57 | if self.snake.body.front() == Some(self.fruit) { 58 | match dropped_pos { 59 | Some(pos) => self.snake.body.push_back(pos) 60 | None => () 61 | } 62 | self.fruit = Point::{ x: random.int(limit=20), y: random.int(limit=20) } 63 | } 64 | } 65 | self.snake.draw() 66 | @wasm4.set_draw_colors(0, index=1) 67 | @wasm4.set_draw_colors(2, index=2) 68 | @wasm4.set_draw_colors(3, index=3) 69 | @wasm4.set_draw_colors(4, index=4) 70 | fruit.blit(self.fruit.x * 8, self.fruit.y * 8, 8, 8, { 71 | one_bit_per_pixel: false, 72 | flip_x: false, 73 | flip_y: false, 74 | rotate: false, 75 | }) 76 | } 77 | 78 | ///| 79 | pub fn Game::input(self : Game) -> Unit { 80 | let gamepad = @wasm4.get_gamepad() 81 | if gamepad != self.prev_gamepad { 82 | if gamepad.button_down { 83 | self.snake.down() 84 | } 85 | if gamepad.button_left { 86 | self.snake.left() 87 | } 88 | if gamepad.button_right { 89 | self.snake.right() 90 | } 91 | if gamepad.button_up { 92 | self.snake.up() 93 | } 94 | } 95 | self.prev_gamepad = gamepad 96 | } 97 | -------------------------------------------------------------------------------- /ffi.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 International Digital Economy Academy 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Addresses: https://wasm4.org/docs/reference/memory#memory-map 16 | 17 | ///| 18 | let address_PALETTE = 0x4 19 | 20 | ///| 21 | let address_DRAW_COLORS = 0x14 22 | 23 | ///| 24 | let address_GAMEPADS = 0x16 25 | 26 | ///| 27 | let address_MOUSE_X = 0x1a 28 | 29 | ///| 30 | let address_MOUSE_Y = 0x1c 31 | 32 | ///| 33 | let address_MOUSE_BUTTONS = 0x1e 34 | 35 | ///| 36 | let address_SYSTEM_FLAGS = 0x1f 37 | 38 | ///| 39 | let address_NETPLAY = 0x20 40 | 41 | ///| 42 | let address_FRAMEBUFFER = 0xa0 43 | 44 | ///| 45 | extern "wasm" fn load_byte(offset : Int) -> Byte = 46 | #|(func (param i32) (result i32) (i32.load8_u (local.get 0))) 47 | 48 | ///| 49 | extern "wasm" fn store_byte(offset : Int, byte : Byte) = 50 | #|(func (param i32) (param i32) (i32.store8 (local.get 0) (local.get 1))) 51 | 52 | ///| 53 | extern "wasm" fn load_s16(offset : Int) -> Int = 54 | #|(func (param i32) (result i32) (i32.load16_s (local.get 0))) 55 | 56 | ///| 57 | extern "wasm" fn load_u16(offset : Int) -> UInt = 58 | #|(func (param i32) (result i32) (i32.load16_u (local.get 0))) 59 | 60 | ///| 61 | extern "wasm" fn store_i16(offset : Int, value : Int) = 62 | #|(func (param i32) (param i32) (i32.store16 (local.get 0) (local.get 1))) 63 | 64 | ///| 65 | extern "wasm" fn load_s32(offset : Int) -> Int = 66 | #|(func (param i32) (result i32) (i32.load (local.get 0))) 67 | 68 | ///| 69 | extern "wasm" fn store_i32(offset : Int, value : Int) = 70 | #|(func (param i32) (param i32) (i32.store (local.get 0) (local.get 1))) 71 | 72 | ///| 73 | fn blit_ffi( 74 | spritePtr : Int, 75 | x : Int, 76 | y : Int, 77 | width : Int, 78 | height : Int, 79 | flags : Int, 80 | ) = "env" "blit" 81 | 82 | ///| 83 | fn blit_sub_ffi( 84 | spritePtr : Int, 85 | x : Int, 86 | y : Int, 87 | width : Int, 88 | height : Int, 89 | src_x : Int, 90 | src_y : Int, 91 | stride : Int, 92 | flags : Int, 93 | ) = "env" "blitSub" 94 | 95 | ///| 96 | fn text_ffi(str : Int, x : Int, y : Int) = "env" "text" 97 | 98 | ///| 99 | fn tone_ffi(frequency : UInt, duration : UInt, volume : UInt, flags : UInt) = "env" "tone" 100 | 101 | ///| 102 | fn trace_ffi(offset : Int) = "env" "trace" 103 | 104 | ///| 105 | fn diskr_ffi(ptr : Int, size : Int) -> Int = "env" "diskr" 106 | 107 | ///| 108 | fn diskw_ffi(ptr : Int, size : Int) -> Int = "env" "diskw" 109 | -------------------------------------------------------------------------------- /pkg.generated.mbti: -------------------------------------------------------------------------------- 1 | // Generated using `moon info`, DON'T EDIT IT 2 | package "moonbitlang/wasm4" 3 | 4 | // Values 5 | fn disk_read(FixedArray[Byte], UInt) -> Int 6 | 7 | fn disk_write(FixedArray[Byte], UInt) -> Int 8 | 9 | fn get_draw_colors(UInt) -> UInt 10 | 11 | fn get_frame_buffer(UInt) -> UInt 12 | 13 | fn get_gamepad(index? : UInt) -> GamePad 14 | 15 | fn get_mouse() -> Mouse 16 | 17 | fn get_netplay() -> Netplay 18 | 19 | fn get_palette(UInt) -> Color 20 | 21 | fn get_system_hide_gamepad_overlay() -> Bool 22 | 23 | fn get_system_preserve_framebuffer() -> Bool 24 | 25 | fn hline(Int, Int, Int) -> Unit 26 | 27 | fn line(Int, Int, Int, Int) -> Unit 28 | 29 | fn oval(Int, Int, Int, Int) -> Unit 30 | 31 | fn rect(Int, Int, Int, Int) -> Unit 32 | 33 | fn rgb(UInt) -> Color 34 | 35 | let screen_height : UInt 36 | 37 | let screen_width : UInt 38 | 39 | fn set_draw_colors(UInt, index? : UInt) -> Unit 40 | 41 | fn set_frame_buffer(UInt, UInt) -> Unit 42 | 43 | fn set_palette(UInt, Color) -> Unit 44 | 45 | fn set_system_hide_gamepad_overlay(Bool) -> Unit 46 | 47 | fn set_system_preserve_framebuffer(Bool) -> Unit 48 | 49 | fn sprite(FixedArray[Byte]) -> Sprite 50 | 51 | fn text(String, Int, Int) -> Unit 52 | 53 | fn tone((UInt, UInt), ADSR, ADSRVolume, ToneFlag) -> Unit 54 | 55 | fn tone_note_mode((Note, Note?), ADSR, ADSRVolume, ToneFlag) -> Unit 56 | 57 | fn trace(String) -> Unit 58 | 59 | fn vline(Int, Int, Int) -> Unit 60 | 61 | // Errors 62 | 63 | // Types and methods 64 | pub struct ADSR { 65 | sustain : UInt 66 | release : UInt 67 | decay : UInt 68 | attack : UInt 69 | } 70 | fn ADSR::new(UInt, release? : UInt, decay? : UInt, attack? : UInt) -> Self 71 | 72 | pub struct ADSRVolume { 73 | sustain : UInt 74 | peak : UInt 75 | } 76 | fn ADSRVolume::new(UInt, peak? : UInt) -> Self 77 | 78 | pub(all) struct BlitFlag { 79 | one_bit_per_pixel : Bool 80 | flip_x : Bool 81 | flip_y : Bool 82 | rotate : Bool 83 | } 84 | 85 | type Color 86 | 87 | pub struct GamePad { 88 | button_1 : Bool 89 | button_2 : Bool 90 | button_left : Bool 91 | button_right : Bool 92 | button_up : Bool 93 | button_down : Bool 94 | } 95 | impl Default for GamePad 96 | impl Eq for GamePad 97 | 98 | pub struct Mouse { 99 | x : Int 100 | y : Int 101 | left : Bool 102 | middle : Bool 103 | right : Bool 104 | } 105 | impl Default for Mouse 106 | impl Eq for Mouse 107 | 108 | pub struct Netplay { 109 | index : UInt 110 | active : Bool 111 | } 112 | 113 | pub struct Note { 114 | note : UInt 115 | bend : UInt 116 | } 117 | fn Note::new(UInt, bend? : UInt) -> Self 118 | 119 | type Sprite 120 | fn Sprite::blit(Self, Int, Int, Int, Int, BlitFlag) -> Unit 121 | fn Sprite::blit_sub(Self, Int, Int, Int, Int, Int, Int, Int, BlitFlag) -> Unit 122 | 123 | pub(all) enum ToneChannel { 124 | Pulse1 125 | Pulse2 126 | Triangle 127 | Noise 128 | } 129 | 130 | pub struct ToneFlag { 131 | channel : ToneChannel 132 | mode : ToneMode 133 | pan : TonePan 134 | } 135 | fn ToneFlag::new(channel? : ToneChannel, mode? : ToneMode, pan? : TonePan) -> Self 136 | 137 | pub(all) enum ToneMode { 138 | Duty_1_8 139 | Duty_1_4 140 | Duty_1_2 141 | Duty_3_4 142 | } 143 | 144 | pub(all) enum TonePan { 145 | Center 146 | Left 147 | Right 148 | } 149 | 150 | // Type aliases 151 | 152 | // Traits 153 | 154 | -------------------------------------------------------------------------------- /memory.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 International Digital Economy Academy 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | ///| 16 | struct Color(UInt) 17 | 18 | ///| 19 | pub fn rgb(color : UInt) -> Color { 20 | color 21 | } 22 | 23 | ///| 24 | /// Sets the color of the palette at the given index. 25 | /// 26 | /// @param index the index of the palette to set, from 1 to 4 (inclusive) 27 | /// @param color the color to set 28 | pub fn set_palette(index : UInt, color : Color) -> Unit { 29 | if index == 0 || index > 4 { 30 | trace("Palette index out of range") 31 | panic() 32 | } 33 | store_i32( 34 | address_PALETTE + (index.reinterpret_as_int() - 1) * 4, 35 | color.0.reinterpret_as_int(), 36 | ) 37 | } 38 | 39 | ///| 40 | /// Gets the color of the palette at the given index. 41 | /// 42 | /// @param index the index of the palette to get, from 1 to 4 (inclusive) 43 | /// @return the color at the given index 44 | pub fn get_palette(index : UInt) -> Color { 45 | if index == 0 || index > 4 { 46 | trace("Palette index out of range") 47 | panic() 48 | } 49 | load_s32(address_PALETTE + (index.reinterpret_as_int() - 1) * 4).reinterpret_as_uint() 50 | } 51 | 52 | ///| 53 | /// Sets the draw color at the given index. 54 | /// 55 | /// @param index the index of the draw color to set, from 1 to 4 (inclusive) 56 | /// @param palette the index of the palette to set the draw color to, from 1 to 4 (inclusive), or 0 for transparent 57 | pub fn set_draw_colors(palette : UInt, index? : UInt = 1) -> Unit { 58 | if index == 0 || index > 4 || palette > 4 { 59 | trace("Draw color index or palette index out of range") 60 | panic() 61 | } 62 | let current_color = load_u16(address_DRAW_COLORS) 63 | let updated_color = bitset(current_color, palette, (index - 1U) * 4U, 4U) 64 | store_i16(address_DRAW_COLORS, updated_color.reinterpret_as_int()) 65 | } 66 | 67 | ///| 68 | /// Gets the draw color at the given index. 69 | /// 70 | /// @param index the index of the draw color to get, from 1 to 4 (inclusive) 71 | /// @return the index of the palette that the draw color is set to 72 | pub fn get_draw_colors(index : UInt) -> UInt { 73 | if index == 0 || index > 4 { 74 | trace("Draw color index out of range") 75 | panic() 76 | } 77 | bitget(load_u16(address_DRAW_COLORS), (index - 1U) * 4U, 4U) 78 | } 79 | 80 | ///| 81 | pub struct GamePad { 82 | button_1 : Bool 83 | button_2 : Bool 84 | button_left : Bool 85 | button_right : Bool 86 | button_up : Bool 87 | button_down : Bool 88 | } derive(Eq, Default) 89 | 90 | ///| 91 | /// Gets the state of the gamepads. 92 | /// 93 | /// @param index the index of the gamepad to get, from 1 to 4 (inclusive) 94 | /// @return the state of the gamepads 95 | pub fn get_gamepad(index? : UInt = 1) -> GamePad { 96 | if index.reinterpret_as_int() > 4 { 97 | trace("Gamepad index out of range") 98 | panic() 99 | } 100 | let state = load_byte(address_GAMEPADS + index.reinterpret_as_int() - 1).to_int() 101 | GamePad::{ 102 | button_1: (state & 1) == 1, 103 | button_2: (state & 2) == 2, 104 | button_left: (state & 16) == 16, 105 | button_right: (state & 32) == 32, 106 | button_up: (state & 64) == 64, 107 | button_down: (state & 128) == 128, 108 | } 109 | } 110 | 111 | ///| 112 | pub struct Mouse { 113 | x : Int 114 | y : Int 115 | left : Bool 116 | middle : Bool 117 | right : Bool 118 | } derive(Eq, Default) 119 | 120 | ///| 121 | /// Gets the state of the mouse. 122 | /// 123 | /// @return the state of the mouse 124 | pub fn get_mouse() -> Mouse { 125 | let buttons = load_byte(address_MOUSE_BUTTONS).to_int().reinterpret_as_uint() 126 | Mouse::{ 127 | x: load_s16(address_MOUSE_X), 128 | y: load_s16(address_MOUSE_Y), 129 | left: (buttons & 1U) == 1U, 130 | right: (buttons & 2U) == 2U, 131 | middle: (buttons & 4U) == 4U, 132 | } 133 | } 134 | 135 | ///| 136 | /// Manipulate the framebuffer directly. 137 | /// 138 | /// @param index the index of the pixel to set, from 0 to 160 * 160 (exclusive) 139 | /// @param palette the index of the palette to set the pixel to, from 1 to 4 (inclusive) 140 | pub fn set_frame_buffer(index : UInt, palette : UInt) -> Unit { 141 | if index >= 160U * 160U || palette == 0 || palette > 4 { 142 | trace("Frame buffer index or palette index out of range") 143 | panic() 144 | } 145 | let byte = load_byte(address_FRAMEBUFFER + index.reinterpret_as_int() / 4) 146 | let new_byte = bitset( 147 | byte.to_int().reinterpret_as_uint(), 148 | palette - 1, 149 | index % 4U * 2U, 150 | 2U, 151 | ) 152 | store_byte( 153 | address_FRAMEBUFFER + index.reinterpret_as_int() / 4, 154 | new_byte.reinterpret_as_int().to_byte(), 155 | ) 156 | } 157 | 158 | ///| 159 | /// Get the palette index of the pixel at the specified index. 160 | /// 161 | /// # Parameters 162 | /// 163 | /// - `index` : The index of the frame buffer to retrieve. Must be a `UInt` and 164 | /// should be within the valid range of the frame buffer (0 to 160 * 160 - 1). 165 | /// 166 | /// # Returns 167 | /// 168 | /// - the index of the palette. 169 | pub fn get_frame_buffer(index : UInt) -> UInt { 170 | if index >= 160U * 160U { 171 | trace("Frame buffer index out of range") 172 | panic() 173 | } 174 | let byte = load_byte(address_FRAMEBUFFER + index.reinterpret_as_int() / 4) 175 | bitget(byte.to_int().reinterpret_as_uint(), index % 4U * 2U, 2U) + 1U 176 | } 177 | 178 | ///| 179 | /// Status of netplay 180 | /// 181 | /// The index is from 1 to 4 (inclusive) 182 | pub struct Netplay { 183 | index : UInt 184 | active : Bool 185 | } 186 | 187 | ///| 188 | /// Gets the state of the netplay. 189 | /// 190 | /// @return the state of the netplay 191 | pub fn get_netplay() -> Netplay { 192 | let flags = load_byte(address_NETPLAY).to_int().reinterpret_as_uint() 193 | Netplay::{ index: (flags & 0b11U) + 1, active: (flags & 4U) == 4U } 194 | } 195 | 196 | ///| 197 | pub fn set_system_preserve_framebuffer(b : Bool) -> Unit { 198 | let flags = load_byte(address_SYSTEM_FLAGS) 199 | let new_flags = bitset( 200 | flags.to_int().reinterpret_as_uint(), 201 | if b { 202 | 1 203 | } else { 204 | 0 205 | }, 206 | 0U, 207 | 1U, 208 | ) 209 | store_byte(address_SYSTEM_FLAGS, new_flags.reinterpret_as_int().to_byte()) 210 | } 211 | 212 | ///| 213 | pub fn get_system_preserve_framebuffer() -> Bool { 214 | let flags = load_byte(address_SYSTEM_FLAGS) 215 | (flags.to_int() & 1) == 1 216 | } 217 | 218 | ///| 219 | pub fn set_system_hide_gamepad_overlay(b : Bool) -> Unit { 220 | let flags = load_byte(address_SYSTEM_FLAGS) 221 | let new_flags = bitset( 222 | flags.to_int().reinterpret_as_uint(), 223 | if b { 224 | 1 225 | } else { 226 | 0 227 | }, 228 | 1U, 229 | 1U, 230 | ) 231 | store_byte(address_SYSTEM_FLAGS, new_flags.reinterpret_as_int().to_byte()) 232 | } 233 | 234 | ///| 235 | pub fn get_system_hide_gamepad_overlay() -> Bool { 236 | let flags = load_byte(address_SYSTEM_FLAGS) 237 | (flags.to_int() & 2) == 2 238 | } 239 | -------------------------------------------------------------------------------- /function.mbt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 International Digital Economy Academy 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | ///| 16 | struct Sprite(FixedArray[Byte]) 17 | 18 | ///| 19 | pub fn sprite(bytes : FixedArray[Byte]) -> Sprite { 20 | Sprite(bytes) 21 | } 22 | 23 | ///| 24 | /// one_bit_per_pixel: Sprite pixel format: 1BPP or 2BPP 25 | /// flip_x: flip the sprite horizontally 26 | /// flip_y: flip the sprite vertically 27 | /// rotate: rotate the sprite anti-clockwise 90 degrees, applied after any flipping 28 | pub(all) struct BlitFlag { 29 | one_bit_per_pixel : Bool 30 | flip_x : Bool 31 | flip_y : Bool 32 | rotate : Bool 33 | } 34 | 35 | ///| 36 | fn BlitFlag::to_int(self : BlitFlag) -> Int { 37 | (if self.one_bit_per_pixel { 0 } else { 1 }) | 38 | (if self.flip_x { 2 } else { 0 }) | 39 | (if self.flip_y { 4 } else { 0 }) | 40 | (if self.rotate { 8 } else { 0 }) 41 | } 42 | 43 | ///| 44 | /// Copies pixels to the framebuffer. 45 | /// 46 | /// @param spritePtr raw pixel data stored in either 1BPP or 2BPP format. 47 | /// @param x X position in the destination framebuffer. 48 | /// @param y Y position in the destination framebuffer. 49 | /// @param width Width of the sprite. 50 | /// @param height Height of the sprite. 51 | /// @param flags Flags that modify behavior. 52 | pub fn Sprite::blit( 53 | self : Sprite, 54 | x : Int, 55 | y : Int, 56 | width : Int, 57 | height : Int, 58 | flags : BlitFlag, 59 | ) -> Unit { 60 | blit_ffi(get_addr(self.0), x, y, width, height, flags.to_int()) 61 | ignore(self) 62 | } 63 | 64 | ///| 65 | /// Copies a subregion within a larger sprite atlas to the framebuffer. Same as blit, but with 3 additional parameters. 66 | /// 67 | /// @param srcX Source X position of the sprite region. 68 | /// @param srcY Source Y position of the sprite region. 69 | /// @param stride Total width of the overall sprite atlas. This is typically larger than width. 70 | /// For info on other parameters, see blit(). 71 | pub fn Sprite::blit_sub( 72 | self : Sprite, 73 | x : Int, 74 | y : Int, 75 | width : Int, 76 | height : Int, 77 | src_x : Int, 78 | src_y : Int, 79 | stride : Int, 80 | flags : BlitFlag, 81 | ) -> Unit { 82 | blit_sub_ffi( 83 | get_addr(self.0), 84 | x, 85 | y, 86 | width, 87 | height, 88 | src_x, 89 | src_y, 90 | stride, 91 | flags.to_int(), 92 | ) 93 | ignore(self) 94 | } 95 | 96 | ///| 97 | /// Draws a line between two points 98 | /// 99 | /// `DRAW_COLORS` color 1 is used as the line color 100 | pub fn line(x1 : Int, y1 : Int, x2 : Int, y2 : Int) = "env" "line" 101 | 102 | ///| 103 | /// Draws a horizontal line between `(x, y)` and `(x + len - 1, y)` 104 | /// 105 | /// `DRAW_COLORS` color 1 is used as the line color 106 | pub fn hline(x : Int, y : Int, len : Int) = "env" "hline" 107 | 108 | ///| 109 | /// Draws a vertical line between `(x, y)` and `(x, y + len - 1)` 110 | /// 111 | /// `DRAW_COLORS` color 1 is used as the line color 112 | pub fn vline(x : Int, y : Int, len : Int) = "env" "vline" 113 | 114 | ///| 115 | /// Draws an oval (or circle). 116 | /// 117 | /// `DRAW_COLORS` color 1 is used as the fill color, `DRAW_COLORS` color 2 is used as the outline color. 118 | pub fn oval(x : Int, y : Int, width : Int, height : Int) = "env" "oval" 119 | 120 | ///| 121 | /// Draws a rectangle. 122 | /// 123 | /// `DRAW_COLORS` color 1 is used as the fill color, `DRAW_COLORS` color 2 is used as the outline color. 124 | pub fn rect(x : Int, y : Int, width : Int, height : Int) = "env" "rect" 125 | 126 | ///| 127 | /// Draws text using the built-in system font. The string may contain new-line (`\n`) characters. 128 | /// 129 | /// The font is 8x8 pixels per character 130 | /// `DRAW_COLORS` color 1 is used as the text color, `DRAW_COLORS` color 2 is used as the background color. 131 | pub fn text(s : String, x : Int, y : Int) -> Unit { 132 | let bytes = FixedArray::make(s.length() * 4 + 1, Byte::default()) 133 | let mut offset = 0 134 | s.iter().each(ch => offset += bytes.set_utf8_char(offset, ch)) 135 | text_ffi(get_addr(bytes), x, y) 136 | ignore(bytes) 137 | } 138 | 139 | ///| 140 | /// An ADSR volume envelop 141 | /// 142 | /// The envelope starts at zero volume, 143 | /// then raises to the peak volume over the attack time, 144 | /// lowers to the sustain volume during the decay time, 145 | /// remains at the sustain volume during the sustain time, 146 | /// and finally fades to zero volume during the release time. 147 | /// Duration of each phase is specified in frames (1/60th of a second). 148 | pub struct ADSR { 149 | sustain : UInt 150 | release : UInt 151 | decay : UInt 152 | attack : UInt 153 | } 154 | 155 | ///| 156 | /// An ADSR volume envelop 157 | /// 158 | /// The envelope starts at zero volume, 159 | /// then raises to the peak volume over the attack time, 160 | /// lowers to the sustain volume during the decay time, 161 | /// remains at the sustain volume during the sustain time, 162 | /// and finally fades to zero volume during the release time. 163 | /// Duration of each phase is specified in frames (1/60th of a second). 164 | pub fn ADSR::new( 165 | sustain : UInt, 166 | release? : UInt = 0, 167 | decay? : UInt = 0, 168 | attack? : UInt = 0, 169 | ) -> ADSR { 170 | { sustain, release, decay, attack } 171 | } 172 | 173 | ///| 174 | /// The volume of an ADSR envelope 175 | /// 176 | /// The volume used for the sustain duration, and the peak volume (default to 100 if zero) reached by the attack duration. 177 | pub struct ADSRVolume { 178 | sustain : UInt 179 | peak : UInt 180 | } 181 | 182 | ///| 183 | /// The volume of an ADSR envelope 184 | /// 185 | /// The volume used for the sustain duration, and the peak volume (default to 100 if zero) reached by the attack duration. 186 | pub fn ADSRVolume::new(sustain : UInt, peak? : UInt = 100) -> ADSRVolume { 187 | { sustain, peak } 188 | } 189 | 190 | ///| 191 | /// Notes with pitch bend 192 | /// 193 | /// @param note Specified in MIDI note format, e.g. 60 = C4, 69 = A4. 194 | /// @param bend Bend note upwards. 0 = Nothing, 255 = One 256th away from the next note above 195 | pub struct Note { 196 | note : UInt 197 | bend : UInt 198 | } 199 | 200 | ///| 201 | /// Notes with pitch bend 202 | /// 203 | /// @param note Specified in MIDI note format, e.g. 60 = C4, 69 = A4. 204 | /// @param bend Bend note upwards. 0 = Nothing, 255 = One 256th away from the next note above 205 | pub fn Note::new(note : UInt, bend? : UInt = 0) -> Note { 206 | { note, bend } 207 | } 208 | 209 | ///| 210 | /// Flags that modify behavior of `tone` 211 | pub struct ToneFlag { 212 | channel : ToneChannel 213 | mode : ToneMode 214 | pan : TonePan 215 | } 216 | 217 | ///| 218 | /// Flags that modify behavior of `tone` 219 | pub fn ToneFlag::new( 220 | channel? : ToneChannel = Pulse1, 221 | mode? : ToneMode = Duty_1_8, 222 | pan? : TonePan = Center, 223 | ) -> ToneFlag { 224 | { channel, mode, pan } 225 | } 226 | 227 | ///| 228 | pub(all) enum ToneChannel { 229 | Pulse1 230 | Pulse2 231 | Triangle 232 | Noise 233 | } 234 | 235 | ///| 236 | pub(all) enum ToneMode { 237 | Duty_1_8 238 | Duty_1_4 239 | Duty_1_2 240 | Duty_3_4 241 | } 242 | 243 | ///| 244 | pub(all) enum TonePan { 245 | Center 246 | Left 247 | Right 248 | } 249 | 250 | ///| 251 | /// Plays a sound tone 252 | /// 253 | /// @param frequency Start frequency and optional end frequency presented in hertz 254 | /// @param duration Duration of the tone in frames (1/60th of a second), up to 255 frames for each phase 255 | /// @param volume Volume of the sustain and attack durations, between 0 and 100 256 | /// @param flags Flags that modify behavior 257 | pub fn tone( 258 | frequency : (UInt, UInt), 259 | duration : ADSR, 260 | volume : ADSRVolume, 261 | flags : ToneFlag, 262 | ) -> Unit { 263 | let frequency = frequency.0 | (frequency.1 << 16) 264 | let duration = (duration.attack << 24) | 265 | (duration.decay << 16) | 266 | (duration.release << 8) | 267 | duration.sustain 268 | let volume = (volume.peak << 8) | volume.sustain 269 | let flags = { 270 | let pan = match flags.pan { 271 | Center => 0U 272 | Left => 1 273 | Right => 2 274 | } 275 | let mode = match flags.mode { 276 | Duty_1_8 => 0U 277 | Duty_1_4 => 1 278 | Duty_1_2 => 2 279 | Duty_3_4 => 3 280 | } 281 | let channel = match flags.channel { 282 | Pulse1 => 0U 283 | Pulse2 => 1 284 | Triangle => 2 285 | Noise => 3 286 | } 287 | (pan << 4) | (mode << 2) | channel 288 | } 289 | tone_ffi(frequency, duration, volume, flags) 290 | } 291 | 292 | ///| 293 | /// Plays a sound tone in note mode 294 | /// 295 | /// @param frequency Start frequency and optional end frequency presented in MIDI note 296 | /// @param duration Duration of the tone in frames (1/60th of a second), up to 255 frames for each phase 297 | /// @param volume Volume of the sustain and attack durations, between 0 and 100 298 | /// @param flags Flags that modify behavior 299 | pub fn tone_note_mode( 300 | frequency : (Note, Note?), 301 | duration : ADSR, 302 | volume : ADSRVolume, 303 | flags : ToneFlag, 304 | ) -> Unit { 305 | let higher_note = frequency.1.unwrap_or({ note: 0, bend: 0 }) 306 | let frequency = frequency.0.note | 307 | (frequency.0.bend << 8) | 308 | ((higher_note.note | (higher_note.bend << 8)) << 16) 309 | let duration = (duration.attack << 24) | 310 | (duration.decay << 16) | 311 | (duration.release << 8) | 312 | duration.sustain 313 | let volume = (volume.peak << 8) | volume.sustain 314 | let flags = { 315 | let pan = match flags.pan { 316 | Center => 0U 317 | Left => 1 318 | Right => 2 319 | } 320 | let mode = match flags.mode { 321 | Duty_1_8 => 0U 322 | Duty_1_4 => 1 323 | Duty_1_2 => 2 324 | Duty_3_4 => 3 325 | } 326 | let channel = match flags.channel { 327 | Pulse1 => 0U 328 | Pulse2 => 1 329 | Triangle => 2 330 | Noise => 3 331 | } 332 | (pan << 4) | (mode << 2) | channel | 0b1000000 333 | } 334 | tone_ffi(frequency, duration, volume, flags) 335 | } 336 | 337 | ///| 338 | /// Writes up to `size` bytes from `bytes` into persistent storage. 339 | /// 340 | /// Any previously saved data on the disk is replaced. 341 | /// @return the number of bytes written, which may be less than `size`. 342 | pub fn disk_write(bytes : FixedArray[Byte], size : UInt) -> Int { 343 | if size > 1024 { 344 | trace("Disk write size exceeds 1024 bytes") 345 | panic() 346 | } 347 | diskw_ffi(get_addr(bytes), size.reinterpret_as_int()) 348 | } 349 | 350 | ///| 351 | /// Reads up to `size` bytes from persistent storage into `bytes`. 352 | /// 353 | /// @return the number of bytes read, which may be less than `size` 354 | pub fn disk_read(bytes : FixedArray[Byte], size : UInt) -> Int { 355 | if size > 1024 { 356 | trace("Disk write size exceeds 1024 bytes") 357 | panic() 358 | } 359 | diskr_ffi(get_addr(bytes), size.reinterpret_as_int()) 360 | } 361 | 362 | ///| 363 | /// Prints a message to the debug console 364 | pub fn trace(s : String) -> Unit { 365 | let bytes = FixedArray::make(s.length() * 4 + 1, Byte::default()) 366 | let mut offset = 0 367 | s.iter().each(ch => offset += bytes.set_utf8_char(offset, ch)) 368 | trace_ffi(get_addr(bytes)) 369 | ignore(bytes) 370 | } 371 | 372 | ///| 373 | pub let screen_width = 160U 374 | 375 | ///| 376 | pub let screen_height = 160U 377 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2024 International Digital Economy Academy 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------