Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[workspace]
members = [
"neotron-os",
"utilities/flames",
"utilities/*",
]
resolver = "2"
exclude = [
Expand Down
17 changes: 17 additions & 0 deletions nbuild/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,37 @@ pub struct NBuildApp {

fn packages() -> Vec<nbuild::Package> {
vec![
// *** build system ***
nbuild::Package {
name: "nbuild",
path: std::path::Path::new("./nbuild/Cargo.toml"),
output_template: None,
kind: nbuild::PackageKind::NBuild,
testable: nbuild::Testable::All,
},
// *** utilities ***
nbuild::Package {
name: "flames",
path: std::path::Path::new("./utilities/flames/Cargo.toml"),
output_template: Some("./target/{target}/{profile}/flames"),
kind: nbuild::PackageKind::Utility,
testable: nbuild::Testable::No,
},
nbuild::Package {
name: "neoplay",
path: std::path::Path::new("./utilities/neoplay/Cargo.toml"),
output_template: Some("./target/{target}/{profile}/neoplay"),
kind: nbuild::PackageKind::Utility,
testable: nbuild::Testable::No,
},
nbuild::Package {
name: "snake",
path: std::path::Path::new("./utilities/snake/Cargo.toml"),
output_template: Some("./target/{target}/{profile}/snake"),
kind: nbuild::PackageKind::Utility,
testable: nbuild::Testable::No,
},
// *** OS ***
nbuild::Package {
name: "Neotron OS",
path: std::path::Path::new("./neotron-os/Cargo.toml"),
Expand Down
14 changes: 14 additions & 0 deletions utilities/neoplay/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "neoplay"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
authors = ["Jonathan 'theJPster' Pallant <neotron@thejpster.org.uk>"]
description = "4-channel ProTracker player for Neotro"

[dependencies]
grounded = { version = "0.2.0", features = ["critical-section", "cas"] }
neotracker = { git = "https://github.com/thejpster/neotracker.git", rev = "2ee7a85006a9461b876bdf47e45b6105437a38f6" }
neotron-sdk = { workspace = true }

# See workspace for profile settings
27 changes: 27 additions & 0 deletions utilities/neoplay/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Neoplay

A ProTracker MOD player for the Neotron Pico.

Runs at 11,025 Hz, quadrupling samples for the audio codec which runs at 44,100 Hz.

```console
$ cargo build --release --target=thumbv6m-none-eabi
$ cp ../target/thumbv6m-none-eabi/release/neoplay /media/USER/SDCARD/NEOPLAY.ELF

```

```console
> load neoplay.elf
> run airwolf.mod
Loading "airwolf.mod"
audio 44100, SixteenBitStereo
Playing "airwolf.mod"

000 000000 12 00fe 0f04|-- ---- ----|-- ---- ----|-- ---- ----|
000 000001 -- ---- ----|-- ---- ----|-- ---- ----|-- ---- ----|
000 000002 -- ---- ----|-- ---- ----|-- ---- ----|-- ---- ----|
000 000003 -- ---- ----|-- ---- ----|-- ---- ----|-- ---- ----|
etc
```

Here's a video of it in action: https://youtu.be/ONZhDrZsmDU
3 changes: 3 additions & 0 deletions utilities/neoplay/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("cargo:rustc-link-arg-bin=neoplay=-Tneotron-cortex-m.ld");
}
88 changes: 88 additions & 0 deletions utilities/neoplay/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#![cfg_attr(target_os = "none", no_std)]
#![cfg_attr(target_os = "none", no_main)]

use core::{fmt::Write, ptr::addr_of_mut};

const FILE_BUFFER_LEN: usize = 192 * 1024;
static mut FILE_BUFFER: [u8; FILE_BUFFER_LEN] = [0u8; FILE_BUFFER_LEN];

mod player;

#[cfg(not(target_os = "none"))]
fn main() {
neotron_sdk::init();
}

#[no_mangle]
extern "C" fn neotron_main() -> i32 {
if let Err(e) = real_main() {
let mut stdout = neotron_sdk::stdout();
let _ = writeln!(stdout, "Error: {:?}", e);
1
} else {
0
}
}

fn real_main() -> Result<(), neotron_sdk::Error> {
let mut stdout = neotron_sdk::stdout();
let stdin = neotron_sdk::stdin();
let Some(filename) = neotron_sdk::arg(0) else {
return Err(neotron_sdk::Error::InvalidArg);
};
let _ = writeln!(stdout, "Loading {:?}...", filename);
let path = neotron_sdk::path::Path::new(&filename)?;
let f = neotron_sdk::File::open(path, neotron_sdk::Flags::empty())?;
let file_buffer = unsafe {
let file_buffer = &mut *addr_of_mut!(FILE_BUFFER);
let n = f.read(file_buffer)?;
&file_buffer[0..n]
};
drop(f);
// Set 16-bit stereo, 44.1 kHz
let dsp_path = neotron_sdk::path::Path::new("AUDIO:")?;
let dsp = neotron_sdk::File::open(dsp_path, neotron_sdk::Flags::empty())?;
if dsp.ioctl(1, 3 << 60 | 44100).is_err() {
let _ = writeln!(stdout, "Failed to configure audio");
return neotron_sdk::Result::Err(neotron_sdk::Error::DeviceSpecific);
}

let mut player = match player::Player::new(file_buffer, 44100) {
Ok(player) => player,
Err(e) => {
let _ = writeln!(stdout, "Failed to create player: {:?}", e);
return Err(neotron_sdk::Error::InvalidArg);
}
};

let _ = writeln!(stdout, "Playing {:?}...", filename);
let mut sample_buffer = [0u8; 1024];
// loop some some silence to give us a head-start
for _i in 0..11 {
let _ = dsp.write(&sample_buffer);
}

loop {
for chunk in sample_buffer.chunks_exact_mut(4) {
let (left, right) = player.next_sample(&mut stdout);
let left_bytes = left.to_le_bytes();
let right_bytes = right.to_le_bytes();
chunk[0] = left_bytes[0];
chunk[1] = left_bytes[1];
chunk[2] = right_bytes[0];
chunk[3] = right_bytes[1];
}
let _ = dsp.write(&sample_buffer);
let mut in_buf = [0u8; 1];
if player.is_finished() {
break;
}
if stdin.read(&mut in_buf).is_ok() && in_buf[0].to_ascii_lowercase() == b'q' {
break;
}
}

let _ = writeln!(stdout, "Bye!");

Ok(())
}
Loading
Loading