From cf8c5549f854dd96fc2c2173bd9cf75010c7a609 Mon Sep 17 00:00:00 2001 From: Maisha Thasin Date: Tue, 25 Mar 2025 20:05:51 -0400 Subject: [PATCH 1/3] adding cross compatability for macos --- Cargo.toml | 3 ++ drive/config.txt | 2 +- src/hal/linux/mod.rs | 36 ------------------ src/hal/macos/mod.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++ src/hal/mod.rs | 42 ++++++++++++++++++++- 5 files changed, 135 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d034e58..a5246d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,9 @@ urlencoding = "2.1" scraper = "0.19" futures = "0.3" +[target.'cfg(target_os = "macos")'.dependencies] +nix = { version = "0.29", features = ["fs", "process", "signal"] } + [target.'cfg(target_os = "linux")'.dependencies] nix = { version = "0.29", features = ["fs", "process", "signal"] } bluer = { version = "0.17.3", features = ["full"] } diff --git a/drive/config.txt b/drive/config.txt index 45792ba..0505d1e 100644 --- a/drive/config.txt +++ b/drive/config.txt @@ -30,7 +30,7 @@ foreground_sleep_ms 2 // number of milliseconds to sleep each frame. Try 10 to c background_sleep_ms 10 // number of milliseconds to sleep each frame when running in the background -sessions 800 // number of times program has been run +sessions 802 // number of times program has been run // (scancode) hold this key down and left-click to simulate right-click rmb_key 0 // 0 for none 226 for LALT diff --git a/src/hal/linux/mod.rs b/src/hal/linux/mod.rs index 1bc3fde..7ca51cf 100644 --- a/src/hal/linux/mod.rs +++ b/src/hal/linux/mod.rs @@ -151,39 +151,3 @@ pub async fn pico8_to_bg(pico8_process: &Child, mut child: Child) { child.wait().await.unwrap(); resume_pico8_process(pico8_process); } - -/// Attempts to spawn pico8 binary by trying multiple potential binary names depending on the -/// platform -pub fn launch_pico8_binary(bin_names: &Vec, args: Vec<&str>) -> anyhow::Result { - for bin_name in bin_names { - let pico8_process = Command::new(bin_name.clone()) - .args(args.clone()) - // .stdout(Stdio::piped()) - .spawn(); - - match pico8_process { - Ok(process) => return Ok(process), - Err(e) => warn!("failed launching {bin_name}: {e}"), - } - } - Err(anyhow!("failed to launch pico8")) -} - -/// Use the pico8 binary to export games from *.p8.png to *.p8 -pub async fn pico8_export( - bin_names: &Vec, - in_file: &Path, - out_file: &Path, -) -> anyhow::Result<()> { - let mut pico8_process = launch_pico8_binary( - bin_names, - vec![ - "-x", - in_file.to_str().unwrap(), - "-export", - out_file.to_str().unwrap(), - ], - )?; - pico8_process.wait().await?; - Ok(()) -} diff --git a/src/hal/macos/mod.rs b/src/hal/macos/mod.rs index cdfe49b..ee121b7 100644 --- a/src/hal/macos/mod.rs +++ b/src/hal/macos/mod.rs @@ -1 +1,91 @@ mod network; +use tokio::process::Child; + +use log::warn; +use nix::{ + sys::signal::{kill, Signal}, + unistd::Pid, +}; +use std::{ + fs::{File, OpenOptions}, + path::{Path, PathBuf}, + time::Duration, +}; + +pub const IN_PIPE: &str = "in_pipe"; +pub const OUT_PIPE: &str = "out_pipe"; + + +lazy_static::lazy_static! { + pub static ref PICO8_BINS: Vec = vec!["pico8".into(), "pico8_64".into(), "pico8_dyn".into()]; +} + +/// Suspend the pico8 process until child process exits +pub async fn pico8_to_bg(pico8_process: &Child, mut child: Child) { + + warn!("pico8_to_bg not implemented for macos") + +} + +// create named pipes if they don't exist +fn create_pipe(pipe: &Path) -> anyhow::Result<()> { + use nix::{sys::stat::Mode, unistd::mkfifo}; + if !pipe.exists() { + mkfifo(pipe, Mode::S_IRUSR | Mode::S_IWUSR).expect("failed to create pipe {pipe}"); + } + Ok(()) +} + +pub fn open_in_pipe() -> anyhow::Result { + create_pipe(&PathBuf::from(IN_PIPE))?; + + let in_pipe = OpenOptions::new().write(true).open(IN_PIPE)?; + + Ok(in_pipe) +} + +pub fn open_out_pipe() -> anyhow::Result { + create_pipe(&PathBuf::from(IN_PIPE))?; + + let out_pipe = OpenOptions::new().read(true).open(OUT_PIPE)?; + + Ok(out_pipe) +} + +pub fn kill_pico8_process(pico8_process: &Child) -> anyhow::Result<()> { + let pico8_pid = Pid::from_raw( + pico8_process + .id() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "PID not found"))? + as i32, + ); + + kill(pico8_pid, Signal::SIGKILL)?; + Ok(()) +} + +pub fn stop_pico8_process(pico8_process: &Child) -> anyhow::Result<()> { + let pico8_pid = Pid::from_raw( + pico8_process + .id() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "PID not found"))? + as i32, + ); + kill(pico8_pid, Signal::SIGSTOP)?; + Ok(()) +} + +pub fn resume_pico8_process(pico8_process: &Child) -> anyhow::Result<()> { + let pico8_pid = Pid::from_raw( + pico8_process + .id() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "PID not found"))? + as i32, + ); + kill(pico8_pid, Signal::SIGCONT)?; + Ok(()) +} + +pub fn screenshot_watcher() { + warn!("screenshot_watcher does not work in macos"); +} \ No newline at end of file diff --git a/src/hal/mod.rs b/src/hal/mod.rs index 53f74a0..cecdbc7 100644 --- a/src/hal/mod.rs +++ b/src/hal/mod.rs @@ -1,12 +1,14 @@ #[cfg(target_os = "linux")] mod linux; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; + #[cfg(target_os = "linux")] pub use linux::*; #[cfg(target_os = "macos")] mod macos; +use log::warn; #[cfg(target_os = "macos")] pub use macos::*; @@ -17,8 +19,10 @@ pub use windows::*; mod dummy; use anyhow::Result; +use anyhow::anyhow; use async_trait::async_trait; use dummy::*; +use tokio::process::{Command,Child}; use crate::p8util::serialize_table; @@ -93,3 +97,39 @@ pub fn init_gyro_hal() -> Result> { Ok(Arc::new(DummyGyroHAL::new()) as Arc) } + +/// Attempts to spawn pico8 binary by trying multiple potential binary names depending on the +/// platform +pub fn launch_pico8_binary(bin_names: &Vec, args: Vec<&str>) -> anyhow::Result { + for bin_name in bin_names { + let pico8_process = Command::new(bin_name.clone()) + .args(args.clone()) + // .stdout(Stdio::piped()) + .spawn(); + + match pico8_process { + Ok(process) => return Ok(process), + Err(e) => warn!("failed launching {bin_name}: {e}"), + } + } + Err(anyhow!("failed to launch pico8")) +} + +/// Use the pico8 binary to export games from *.p8.png to *.p8 +pub async fn pico8_export( + bin_names: &Vec, + in_file: &Path, + out_file: &Path, +) -> anyhow::Result<()> { + let mut pico8_process = launch_pico8_binary( + bin_names, + vec![ + "-x", + in_file.to_str().unwrap(), + "-export", + out_file.to_str().unwrap(), + ], + )?; + pico8_process.wait().await?; + Ok(()) +} From 1df5b73282cedbcb307867bd733c63c58820abcc Mon Sep 17 00:00:00 2001 From: Maisha Thasin Date: Fri, 4 Apr 2025 16:15:25 -0400 Subject: [PATCH 2/3] Simple visualiser for tunes --- drive/carts/tunes.p8 | 282 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 263 insertions(+), 19 deletions(-) diff --git a/drive/carts/tunes.p8 b/drive/carts/tunes.p8 index 396b92f..fa7a7cd 100644 --- a/drive/carts/tunes.p8 +++ b/drive/carts/tunes.p8 @@ -12,26 +12,160 @@ song_dir='music' label_dir='labels' local song_menu=menu_new(ls(song_dir)) +local show_visualizer = false -- Toggle for showing visualizer or album art + +-- Music events, code from https://www.lexaloffle.com/bbs/?pid=51460 +music_events = { + wave = {-1, -1, -1, -1}, + pitch = {-1, -1, -1, -1}, + volume = {0, 0, 0, 0}, + notevol = {0, 0, 0, 0}, + effect = {0, 0, 0, 0}, + pattern = {-1, -1, -1, -1}, + row = {-1, -1, -1, -1}, + speed = {0, 0, 0, 0}, + last = 0, + notestr = {"c ","c#","d ","d#","e ","f ","f#","g ","g#","a ","a#","b "} +} + +local bars = {0, 0, 0, 0} +local bar_colors = {8, 9, 12, 14} +local note_particles = {} +local wave_intensity = {0, 0, 0, 0} function _init() load_label() load_song() - -- graphics init_title_bar(11) + + note_particles = {} +end + +function music_events:update() + local t = time() + local d = t - self.last + self.last = t + + for i=1, 4 do + self:update_channel(i) + + if self.effect[i] == 5 then + local voldec = 128 * self.notevol[i] * d / (self.speed[i] + 1) + self.volume[i] = max(self.volume[i] - voldec, 0) + end + + if self.notevol[i] == 0 then + self.wave[i] = -1 + self.pitch[i] = -1 + end + + -- smooth decay + bars[i] = max(bars[i] - 0.125, self.volume[i]) + end +end + +function music_events:update_channel(i) + local speed = 0 + local wave = -1 + local pitch = -1 + local volume = 0 + local fx = 0 + local pattern = stat(15 + i) + local row = -1 + + if pattern >= 0 then + row = stat(19 + i) + if pattern == self.pattern[i] and row == self.row[i] then + return + end + + -- data from memory + local notedat = peek4(0x31fe + pattern * 68 + 2 * row) + speed = peek(0x3241 + pattern * 68) + wave = band(shr(notedat, 6), 0x07) + pitch = band(notedat, 0x3f) + volume = band(shr(notedat, 9), 0x07) + fx = band(shr(notedat, 12), 0x07) + + if pitch != self.pitch[i] and pitch > 0 then + local bin = pitch % 12 + 1 + + if show_visualizer then + add_note_particles(i, pitch, volume) + end + + -- wave intensity, idk if I like this, too noisy + wave_intensity[i] = volume + end + end + + self.speed[i] = speed + self.wave[i] = wave + self.pitch[i] = pitch + self.volume[i] = volume + self.notevol[i] = volume + self.effect[i] = fx + self.pattern[i] = pattern + self.row[i] = row +end + +function music_events:getnote(i) + if self.pitch[i] == -1 then + return "" + end + return self.notestr[self.pitch[i] % 12 + 1] .. flr(self.pitch[i] / 12 + 1) +end + +function add_note_particles(channel, pitch, volume) + local x = 20 + (channel - 1) * 28 + local y = 100 + local note_color = bar_colors[channel] + + for i=1, 2 + flr(rnd(2)) do + add(note_particles, { + x = x + rnd(10) - 5, + y = y - volume * 5 - rnd(10), + vx = (rnd() - 0.5) * 1.5, + vy = -1 - rnd(2), + life = 15 + rnd(10), + color = note_color, + size = 1 + volume / 3 + }) + end +end + +function update_particles() + local i = 1 + while i <= #note_particles do + local p = note_particles[i] + p.x += p.vx + p.y += p.vy + p.vy += 0.2 -- gravity + p.life -= 1 + + if p.life <= 0 then + deli(note_particles, i) + else + i += 1 + end + end + + for i=1,4 do + wave_intensity[i] = max(0, wave_intensity[i] * 0.95) + end end --- load song art of current song into memory function load_label() song_name=tostring(song_menu:cur()) - -- replace .p8 with .64.p8 + -- replace .p8 with .64.p8 + -- if ends_with(song_name, ".p8") then stripped_path = sub(song_name, 0, -#(".p8")-1) reload(0x0000, 0x0000, 0x1000, label_dir .. '/' .. stripped_path .. '.64.p8') end end --- load sfx and music section function load_song() song_name=tostring(song_menu:cur()) reload(0x3100, 0x3100, 0x0200, song_dir .. '/' .. song_name) @@ -106,6 +240,14 @@ function on_song_change() if cur_play_state then play_song() end + + -- Reset visualizer data + for i=1,4 do + bars[i] = 0 + wave_intensity[i] = 0 + end + + note_particles = {} end function _update60() @@ -123,8 +265,9 @@ function _update60() song_menu:down() end on_song_change() - -- elseif btnp(4) then -- shuffle - -- shuffle = not shuffle + elseif btnp(2) then -- toggle visualizer + show_visualizer = not show_visualizer + note_particles = {} -- Clear particles when toggling elseif btnp(4) then os_back() elseif btnp(5) then -- pause/play @@ -136,20 +279,24 @@ function _update60() end end - -- detect when the current song ends if play_state then - if stat(54) == -1 then song_menu:down() on_song_change() end end + + if play_state then + music_events:update() + if show_visualizer then + update_particles() + end + end end function _draw() - cls(1) -- dark blue background + cls(1) - -- header draw_title_bar('tunes', 11, 0, true, '♪', 0) print(song_menu:cur(), 64-#(song_menu:cur())*2, 20, 12) @@ -158,8 +305,16 @@ function _draw() local x=64 local y=64 local w=64 - rectfill(x-w/2, y-w/2, x+w/2, y+w/2, 0) - draw_label(x, y, w) + -- rectfill(x-w/2, y-w/2, x+w/2, y+w/2, 0) + + -- either the album art or the visualizer based on toggle + if show_visualizer and play_state then + -- Draw full visualizer in place of album art + draw_full_visualizer() + else + -- Draw album art + draw_label(x, y, w) + end draw_control_icon(64-16, 100, 2) if play_state then @@ -169,13 +324,102 @@ function _draw() end draw_control_icon(64+8, 100, 5) - -- draw help text + -- Draw help text print('❎pause/play', 3, 120, 6) + print('🅾️back', 94, 120, 6) + print('⬆️vis', 60, 120, 6) +end - -- if shuffle then - -- shuffle_text='on' - -- else - -- shuffle_text='off' - -- end - -- print('🅾️shuffle ' .. shuffle_text, 74, 120, 6) +-- Draw the full-screen visualizer (in place of album art) +function draw_full_visualizer() + + + for p in all(note_particles) do + if p.size <= 1 then + pset(p.x, p.y, p.color) + else + circfill(p.x, p.y, p.size * (p.life / 25), p.color) + end + end + + for i=1, 4 do + local bar_height = min(bars[i] * 12, 50) -- Make taller + local x = 39 + (i - 1) * 14 + local w = 10 + + if bar_height > 0 then + rectfill( + x, + 90 - bar_height, + x + w, + 90, + music_events.wave[i] >= 0 and music_events.wave[i] + 8 or bar_colors[i] + ) + + -- drawing notes here + if music_events.pitch[i] >= 0 then + print( + music_events:getnote(i), + x + 1, + 92, + 7 + ) + end + + -- wave visualization + draw_wave(i, x + w/2, 90 - bar_height - 6, wave_intensity[i]) + end + end + + -- border? idk doesnt look great + -- rect(32, 32, 96, 96, 5) end + +function draw_wave(channel, x, y, intensity) + if intensity <= 0 then return end + + local pitch = music_events.pitch[channel] + if pitch < 0 then return end + + local wave_type = music_events.wave[channel] + local amp = intensity * 2.5 + + local freq = (pitch + 10) / 40 + + for i=0, 8 do + local x1 = x + i - 4 + local y1, y2 + + if wave_type == 0 then + -- Sine wave + y1 = y - sin(time() * freq + i/10) * amp + y2 = y - sin(time() * freq + (i+1)/10) * amp + elseif wave_type == 1 then + -- Triangle wave + local t = (time() * freq * 2 + i/10) % 1 + if t < 0.5 then + y1 = y - (t * 4 - 1) * amp + else + y1 = y - (3 - t * 4) * amp + end + + t = (time() * freq * 2 + (i+1)/10) % 1 + if t < 0.5 then + y2 = y - (t * 4 - 1) * amp + else + y2 = y - (3 - t * 4) * amp + end + else + -- Square/pulse wave + local t = (time() * freq + i/10) % 1 + y1 = y - (t < 0.5 and amp or -amp) + + t = (time() * freq + (i+1)/10) % 1 + y2 = y - (t < 0.5 and amp or -amp) + end + + if amp > 0.5 then + line(x1, y1, x1 + 1, y2, bar_colors[channel]) + end + end +end \ No newline at end of file From 17ee75cbd22324288d579ffb009f426a79a6e41a Mon Sep 17 00:00:00 2001 From: Maisha Thasin Date: Sun, 6 Apr 2025 04:12:41 -0400 Subject: [PATCH 3/3] commented out particles for now --- drive/carts/tunes.p8 | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drive/carts/tunes.p8 b/drive/carts/tunes.p8 index fa7a7cd..3cb85de 100644 --- a/drive/carts/tunes.p8 +++ b/drive/carts/tunes.p8 @@ -30,7 +30,7 @@ music_events = { local bars = {0, 0, 0, 0} local bar_colors = {8, 9, 12, 14} -local note_particles = {} +-- local note_particles = {} local wave_intensity = {0, 0, 0, 0} function _init() @@ -39,7 +39,7 @@ function _init() init_title_bar(11) - note_particles = {} + -- note_particles = {} end function music_events:update() @@ -92,7 +92,7 @@ function music_events:update_channel(i) local bin = pitch % 12 + 1 if show_visualizer then - add_note_particles(i, pitch, volume) + -- add_note_particles(i, pitch, volume) end -- wave intensity, idk if I like this, too noisy @@ -247,7 +247,7 @@ function on_song_change() wave_intensity[i] = 0 end - note_particles = {} + -- note_particles = {} end function _update60() @@ -267,7 +267,7 @@ function _update60() on_song_change() elseif btnp(2) then -- toggle visualizer show_visualizer = not show_visualizer - note_particles = {} -- Clear particles when toggling + -- note_particles = {} -- Clear particles elseif btnp(4) then os_back() elseif btnp(5) then -- pause/play @@ -289,7 +289,7 @@ function _update60() if play_state then music_events:update() if show_visualizer then - update_particles() + -- update_particles() end end end @@ -334,13 +334,13 @@ end function draw_full_visualizer() - for p in all(note_particles) do - if p.size <= 1 then - pset(p.x, p.y, p.color) - else - circfill(p.x, p.y, p.size * (p.life / 25), p.color) - end - end + -- for p in all(note_particles) do + -- if p.size <= 1 then + -- pset(p.x, p.y, p.color) + -- else + -- circfill(p.x, p.y, p.size * (p.life / 25), p.color) + -- end + -- end for i=1, 4 do local bar_height = min(bars[i] * 12, 50) -- Make taller