Skip to content
This repository was archived by the owner on Dec 29, 2024. It is now read-only.

Commit d2199b7

Browse files
authored
Merge pull request #7 from Neotron-Compute/add-neoplay
Add ProTracker MOD player.
2 parents 44ebf84 + 9c13a45 commit d2199b7

File tree

12 files changed

+482
-165
lines changed

12 files changed

+482
-165
lines changed

.vscode/settings.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

Cargo.lock

Lines changed: 62 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
[workspace]
22
resolver = "2"
3-
members = [ "snake", "flames" ]
3+
members = [ "snake", "flames" , "neoplay" ]
4+
5+
[workspace.dependencies]
6+
neotron-sdk = "0.2"
47

58
[profile.release]
69
opt-level = "s"

flames/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ authors = ["Jonathan 'theJPster' Pallant <[email protected]>"]
77
description = "ANSI Flames for Neotron systems"
88

99
[dependencies]
10-
neotron-sdk = { git = "https://github.com/neotron-compute/neotron-sdk.git" }
10+
neotron-sdk = { workspace = true }
1111

1212
# See workspace for profile settings

flames/build.rs

Lines changed: 0 additions & 21 deletions
This file was deleted.

neoplay/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "neoplay"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MIT OR Apache-2.0"
6+
authors = ["Jonathan 'theJPster' Pallant <[email protected]>"]
7+
description = "4-channel ProTracker player for Neotro"
8+
9+
[dependencies]
10+
grounded = { version = "0.2.0", features = ["critical-section", "cas"] }
11+
neotracker = { git = "https://github.com/thejpster/neotracker.git", rev = "2ee7a85006a9461b876bdf47e45b6105437a38f6" }
12+
neotron-sdk = { workspace = true }
13+
14+
# See workspace for profile settings

neoplay/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Neoplay
2+
3+
A ProTracker MOD player for the Neotron Pico.
4+
5+
Runs at 11,025 Hz, quadrupling samples for the audio codec which runs at 44,100 Hz.
6+
7+
```console
8+
$ cargo build --release --target=thumbv6m-none-eabi
9+
$ cp ../target/thumbv6m-none-eabi/release/neoplay /media/USER/SDCARD/NEOPLAY.ELF
10+
11+
```
12+
13+
```console
14+
> load neoplay.elf
15+
> run airwolf.mod
16+
Loading "airwolf.mod"
17+
audio 44100, SixteenBitStereo
18+
Playing "airwolf.mod"
19+
20+
000 000000 12 00fe 0f04|-- ---- ----|-- ---- ----|-- ---- ----|
21+
000 000001 -- ---- ----|-- ---- ----|-- ---- ----|-- ---- ----|
22+
000 000002 -- ---- ----|-- ---- ----|-- ---- ----|-- ---- ----|
23+
000 000003 -- ---- ----|-- ---- ----|-- ---- ----|-- ---- ----|
24+
etc
25+
```
26+
27+
Here's a video of it in action: https://youtu.be/ONZhDrZsmDU

neoplay/src/main.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#![cfg_attr(target_os = "none", no_std)]
2+
#![cfg_attr(target_os = "none", no_main)]
3+
4+
use core::{fmt::Write, ptr::addr_of_mut};
5+
6+
const FILE_BUFFER_LEN: usize = 192 * 1024;
7+
static mut FILE_BUFFER: [u8; FILE_BUFFER_LEN] = [0u8; FILE_BUFFER_LEN];
8+
9+
mod player;
10+
11+
#[cfg(not(target_os = "none"))]
12+
fn main() {
13+
neotron_sdk::init();
14+
}
15+
16+
#[no_mangle]
17+
extern "C" fn neotron_main() -> i32 {
18+
if let Err(e) = real_main() {
19+
let mut stdout = neotron_sdk::stdout();
20+
let _ = writeln!(stdout, "Error: {:?}", e);
21+
1
22+
} else {
23+
0
24+
}
25+
}
26+
27+
fn real_main() -> Result<(), neotron_sdk::Error> {
28+
let mut stdout = neotron_sdk::stdout();
29+
let stdin = neotron_sdk::stdin();
30+
let Some(filename) = neotron_sdk::arg(0) else {
31+
return Err(neotron_sdk::Error::InvalidArg);
32+
};
33+
let _ = writeln!(stdout, "Loading {:?}...", filename);
34+
let path = neotron_sdk::path::Path::new(&filename)?;
35+
let f = neotron_sdk::File::open(path, neotron_sdk::Flags::empty())?;
36+
let file_buffer = unsafe {
37+
let file_buffer = &mut *addr_of_mut!(FILE_BUFFER);
38+
let n = f.read(file_buffer)?;
39+
&file_buffer[0..n]
40+
};
41+
drop(f);
42+
// Set 16-bit stereo, 44.1 kHz
43+
let dsp_path = neotron_sdk::path::Path::new("AUDIO:")?;
44+
let dsp = neotron_sdk::File::open(dsp_path, neotron_sdk::Flags::empty())?;
45+
if dsp.ioctl(1, 3 << 60 | 44100).is_err() {
46+
let _ = writeln!(stdout, "Failed to configure audio");
47+
return neotron_sdk::Result::Err(neotron_sdk::Error::DeviceSpecific);
48+
}
49+
50+
let mut player = match player::Player::new(file_buffer, 44100) {
51+
Ok(player) => player,
52+
Err(e) => {
53+
let _ = writeln!(stdout, "Failed to create player: {:?}", e);
54+
return Err(neotron_sdk::Error::InvalidArg);
55+
}
56+
};
57+
58+
let _ = writeln!(stdout, "Playing {:?}...", filename);
59+
let mut sample_buffer = [0u8; 1024];
60+
// loop some some silence to give us a head-start
61+
for _i in 0..11 {
62+
let _ = dsp.write(&sample_buffer);
63+
}
64+
65+
loop {
66+
for chunk in sample_buffer.chunks_exact_mut(4) {
67+
let (left, right) = player.next_sample(&mut stdout);
68+
let left_bytes = left.to_le_bytes();
69+
let right_bytes = right.to_le_bytes();
70+
chunk[0] = left_bytes[0];
71+
chunk[1] = left_bytes[1];
72+
chunk[2] = right_bytes[0];
73+
chunk[3] = right_bytes[1];
74+
}
75+
let _ = dsp.write(&sample_buffer);
76+
let mut in_buf = [0u8; 1];
77+
if player.is_finished() {
78+
break;
79+
}
80+
if stdin.read(&mut in_buf).is_ok() && in_buf[0].to_ascii_lowercase() == b'q' {
81+
break;
82+
}
83+
}
84+
85+
let _ = writeln!(stdout, "Bye!");
86+
87+
Ok(())
88+
}

0 commit comments

Comments
 (0)