// Copyright 2024 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
struct Sprite(FixedArray[Byte])
///|
pub fn sprite(bytes : FixedArray[Byte]) -> Sprite {
Sprite(bytes)
}
///|
/// one_bit_per_pixel: Sprite pixel format: 1BPP or 2BPP
/// flip_x: flip the sprite horizontally
/// flip_y: flip the sprite vertically
/// rotate: rotate the sprite anti-clockwise 90 degrees, applied after any flipping
pub(all) struct BlitFlag {
one_bit_per_pixel : Bool
flip_x : Bool
flip_y : Bool
rotate : Bool
}
///|
fn BlitFlag::to_int(self : BlitFlag) -> Int {
(if self.one_bit_per_pixel { 0 } else { 1 }) |
(if self.flip_x { 2 } else { 0 }) |
(if self.flip_y { 4 } else { 0 }) |
(if self.rotate { 8 } else { 0 })
}
///|
/// Copies pixels to the framebuffer.
///
/// @param spritePtr raw pixel data stored in either 1BPP or 2BPP format.
/// @param x X position in the destination framebuffer.
/// @param y Y position in the destination framebuffer.
/// @param width Width of the sprite.
/// @param height Height of the sprite.
/// @param flags Flags that modify behavior.
pub fn Sprite::blit(
self : Sprite,
x : Int,
y : Int,
width : Int,
height : Int,
flags : BlitFlag,
) -> Unit {
blit_ffi(get_addr(self.0), x, y, width, height, flags.to_int())
ignore(self)
}
///|
/// Copies a subregion within a larger sprite atlas to the framebuffer. Same as blit, but with 3 additional parameters.
///
/// @param srcX Source X position of the sprite region.
/// @param srcY Source Y position of the sprite region.
/// @param stride Total width of the overall sprite atlas. This is typically larger than width.
/// For info on other parameters, see blit().
pub fn Sprite::blit_sub(
self : Sprite,
x : Int,
y : Int,
width : Int,
height : Int,
src_x : Int,
src_y : Int,
stride : Int,
flags : BlitFlag,
) -> Unit {
blit_sub_ffi(
get_addr(self.0),
x,
y,
width,
height,
src_x,
src_y,
stride,
flags.to_int(),
)
ignore(self)
}
///|
/// Draws a line between two points
///
/// `DRAW_COLORS` color 1 is used as the line color
pub fn line(x1 : Int, y1 : Int, x2 : Int, y2 : Int) = "env" "line"
///|
/// Draws a horizontal line between `(x, y)` and `(x + len - 1, y)`
///
/// `DRAW_COLORS` color 1 is used as the line color
pub fn hline(x : Int, y : Int, len : Int) = "env" "hline"
///|
/// Draws a vertical line between `(x, y)` and `(x, y + len - 1)`
///
/// `DRAW_COLORS` color 1 is used as the line color
pub fn vline(x : Int, y : Int, len : Int) = "env" "vline"
///|
/// Draws an oval (or circle).
///
/// `DRAW_COLORS` color 1 is used as the fill color, `DRAW_COLORS` color 2 is used as the outline color.
pub fn oval(x : Int, y : Int, width : Int, height : Int) = "env" "oval"
///|
/// Draws a rectangle.
///
/// `DRAW_COLORS` color 1 is used as the fill color, `DRAW_COLORS` color 2 is used as the outline color.
pub fn rect(x : Int, y : Int, width : Int, height : Int) = "env" "rect"
///|
/// Draws text using the built-in system font. The string may contain new-line (`\n`) characters.
///
/// The font is 8x8 pixels per character
/// `DRAW_COLORS` color 1 is used as the text color, `DRAW_COLORS` color 2 is used as the background color.
pub fn text(s : String, x : Int, y : Int) -> Unit {
let bytes = FixedArray::make(s.length() * 4 + 1, Byte::default())
let mut offset = 0
s.iter().each(ch => offset += bytes.set_utf8_char(offset, ch))
text_ffi(get_addr(bytes), x, y)
ignore(bytes)
}
///|
/// An ADSR volume envelop
///
/// The envelope starts at zero volume,
/// then raises to the peak volume over the attack time,
/// lowers to the sustain volume during the decay time,
/// remains at the sustain volume during the sustain time,
/// and finally fades to zero volume during the release time.
/// Duration of each phase is specified in frames (1/60th of a second).
pub struct ADSR {
sustain : UInt
release : UInt
decay : UInt
attack : UInt
}
///|
/// An ADSR volume envelop
///
/// The envelope starts at zero volume,
/// then raises to the peak volume over the attack time,
/// lowers to the sustain volume during the decay time,
/// remains at the sustain volume during the sustain time,
/// and finally fades to zero volume during the release time.
/// Duration of each phase is specified in frames (1/60th of a second).
pub fn ADSR::new(
sustain : UInt,
release? : UInt = 0,
decay? : UInt = 0,
attack? : UInt = 0,
) -> ADSR {
{ sustain, release, decay, attack }
}
///|
/// The volume of an ADSR envelope
///
/// The volume used for the sustain duration, and the peak volume (default to 100 if zero) reached by the attack duration.
pub struct ADSRVolume {
sustain : UInt
peak : UInt
}
///|
/// The volume of an ADSR envelope
///
/// The volume used for the sustain duration, and the peak volume (default to 100 if zero) reached by the attack duration.
pub fn ADSRVolume::new(sustain : UInt, peak? : UInt = 100) -> ADSRVolume {
{ sustain, peak }
}
///|
/// Notes with pitch bend
///
/// @param note Specified in MIDI note format, e.g. 60 = C4, 69 = A4.
/// @param bend Bend note upwards. 0 = Nothing, 255 = One 256th away from the next note above
pub struct Note {
note : UInt
bend : UInt
}
///|
/// Notes with pitch bend
///
/// @param note Specified in MIDI note format, e.g. 60 = C4, 69 = A4.
/// @param bend Bend note upwards. 0 = Nothing, 255 = One 256th away from the next note above
pub fn Note::new(note : UInt, bend? : UInt = 0) -> Note {
{ note, bend }
}
///|
/// Flags that modify behavior of `tone`
pub struct ToneFlag {
channel : ToneChannel
mode : ToneMode
pan : TonePan
}
///|
/// Flags that modify behavior of `tone`
pub fn ToneFlag::new(
channel? : ToneChannel = Pulse1,
mode? : ToneMode = Duty_1_8,
pan? : TonePan = Center,
) -> ToneFlag {
{ channel, mode, pan }
}
///|
pub(all) enum ToneChannel {
Pulse1
Pulse2
Triangle
Noise
}
///|
pub(all) enum ToneMode {
Duty_1_8
Duty_1_4
Duty_1_2
Duty_3_4
}
///|
pub(all) enum TonePan {
Center
Left
Right
}
///|
/// Plays a sound tone
///
/// @param frequency Start frequency and optional end frequency presented in hertz
/// @param duration Duration of the tone in frames (1/60th of a second), up to 255 frames for each phase
/// @param volume Volume of the sustain and attack durations, between 0 and 100
/// @param flags Flags that modify behavior
pub fn tone(
frequency : (UInt, UInt),
duration : ADSR,
volume : ADSRVolume,
flags : ToneFlag,
) -> Unit {
let frequency = frequency.0 | (frequency.1 << 16)
let duration = (duration.attack << 24) |
(duration.decay << 16) |
(duration.release << 8) |
duration.sustain
let volume = (volume.peak << 8) | volume.sustain
let flags = {
let pan = match flags.pan {
Center => 0U
Left => 1
Right => 2
}
let mode = match flags.mode {
Duty_1_8 => 0U
Duty_1_4 => 1
Duty_1_2 => 2
Duty_3_4 => 3
}
let channel = match flags.channel {
Pulse1 => 0U
Pulse2 => 1
Triangle => 2
Noise => 3
}
(pan << 4) | (mode << 2) | channel
}
tone_ffi(frequency, duration, volume, flags)
}
///|
/// Plays a sound tone in note mode
///
/// @param frequency Start frequency and optional end frequency presented in MIDI note
/// @param duration Duration of the tone in frames (1/60th of a second), up to 255 frames for each phase
/// @param volume Volume of the sustain and attack durations, between 0 and 100
/// @param flags Flags that modify behavior
pub fn tone_note_mode(
frequency : (Note, Note?),
duration : ADSR,
volume : ADSRVolume,
flags : ToneFlag,
) -> Unit {
let higher_note = frequency.1.unwrap_or({ note: 0, bend: 0 })
let frequency = frequency.0.note |
(frequency.0.bend << 8) |
((higher_note.note | (higher_note.bend << 8)) << 16)
let duration = (duration.attack << 24) |
(duration.decay << 16) |
(duration.release << 8) |
duration.sustain
let volume = (volume.peak << 8) | volume.sustain
let flags = {
let pan = match flags.pan {
Center => 0U
Left => 1
Right => 2
}
let mode = match flags.mode {
Duty_1_8 => 0U
Duty_1_4 => 1
Duty_1_2 => 2
Duty_3_4 => 3
}
let channel = match flags.channel {
Pulse1 => 0U
Pulse2 => 1
Triangle => 2
Noise => 3
}
(pan << 4) | (mode << 2) | channel | 0b1000000
}
tone_ffi(frequency, duration, volume, flags)
}
///|
/// Writes up to `size` bytes from `bytes` into persistent storage.
///
/// Any previously saved data on the disk is replaced.
/// @return the number of bytes written, which may be less than `size`.
pub fn disk_write(bytes : FixedArray[Byte], size : UInt) -> Int {
if size > 1024 {
trace("Disk write size exceeds 1024 bytes")
panic()
}
diskw_ffi(get_addr(bytes), size.reinterpret_as_int())
}
///|
/// Reads up to `size` bytes from persistent storage into `bytes`.
///
/// @return the number of bytes read, which may be less than `size`
pub fn disk_read(bytes : FixedArray[Byte], size : UInt) -> Int {
if size > 1024 {
trace("Disk write size exceeds 1024 bytes")
panic()
}
diskr_ffi(get_addr(bytes), size.reinterpret_as_int())
}
///|
/// Prints a message to the debug console
pub fn trace(s : String) -> Unit {
let bytes = FixedArray::make(s.length() * 4 + 1, Byte::default())
let mut offset = 0
s.iter().each(ch => offset += bytes.set_utf8_char(offset, ch))
trace_ffi(get_addr(bytes))
ignore(bytes)
}
///|
pub let screen_width = 160U
///|
pub let screen_height = 160U