Skip to content
Open
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
7 changes: 3 additions & 4 deletions tracker/agb-midi-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::{
borrow::Cow,
collections::HashMap,
error::Error,
fs::{self, File},
Expand All @@ -8,7 +7,7 @@ use std::{
};

use agb_fixnum::Num;
use agb_tracker_interop::{Envelope, Pattern, PatternEffect, PatternSlot, Sample, Track};
use agb_tracker_interop::{Data, Envelope, Pattern, PatternEffect, PatternSlot, Sample, Track};
use midly::{Format, MetaMessage, Smf, Timing, TrackEventKind};
use rustysynth::SoundFont;

Expand Down Expand Up @@ -368,12 +367,12 @@ pub fn parse_midi(midi_info: &MidiInfo) -> Track {
Track {
samples: samples.into(),
envelopes: envelopes.into(),
patterns: Cow::from(vec![Pattern {
patterns: Data::from(vec![Pattern {
length: pattern.len() / resulting_num_channels,
start_position: 0,
}]),
pattern_data: pattern.into(),
patterns_to_play: Cow::from(vec![0]),
patterns_to_play: Data::from(vec![0]),
num_channels: resulting_num_channels,
frames_per_tick: Num::from_f64(frames_per_tick),
ticks_per_step: 1,
Expand Down
75 changes: 58 additions & 17 deletions tracker/agb-tracker-interop/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,58 @@
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;
use core::ops::Deref;

use agb_fixnum::Num;
use alloc::borrow::Cow;

#[derive(Debug, Clone)]
pub enum Data<Content>
where
Content: 'static,
{
Static(&'static [Content]),
#[cfg(feature = "std")]
Owned(Box<[Content]>),
}

#[cfg(feature = "std")]
impl<Content> From<Vec<Content>> for Data<Content> {
fn from(value: Vec<Content>) -> Self {
Data::Owned(value.into_boxed_slice())
}
}

impl<Content> Deref for Data<Content>
where
Content: 'static,
{
type Target = [Content];

fn deref(&self) -> &[Content] {
match self {
Data::Static(items) => items,
#[cfg(feature = "std")]
Data::Owned(items) => items,
}
}
}

impl<Content> Data<Content> {
pub fn get_static(&self) -> &'static [Content] {
match self {
Data::Static(items) => items,
#[cfg(feature = "std")]
Data::Owned(_) => panic!("Cannot get static from owned data"),
}
}
}

#[derive(Debug)]
pub struct Track {
pub samples: Cow<'static, [Sample]>,
pub envelopes: Cow<'static, [Envelope]>,
pub pattern_data: Cow<'static, [PatternSlot]>,
pub patterns: Cow<'static, [Pattern]>,
pub patterns_to_play: Cow<'static, [usize]>,
pub samples: Data<Sample>,
pub envelopes: Data<Envelope>,
pub pattern_data: Data<PatternSlot>,
pub patterns: Data<Pattern>,
pub patterns_to_play: Data<usize>,

pub num_channels: usize,
pub frames_per_tick: Num<u32, 8>,
Expand All @@ -21,7 +62,7 @@ pub struct Track {

#[derive(Debug, Clone)]
pub struct Sample {
pub data: Cow<'static, [u8]>,
pub data: Data<u8>,
pub should_loop: bool,
pub restart_point: u32,
pub volume: Num<i16, 8>,
Expand Down Expand Up @@ -63,7 +104,7 @@ pub struct PatternSlot {

#[derive(Debug, Clone)]
pub struct Envelope {
pub amount: Cow<'static, [Num<i16, 8>]>,
pub amount: Data<Num<i16, 8>>,
pub sustain: Option<usize>,
pub loop_start: Option<usize>,
pub loop_end: Option<usize>,
Expand Down Expand Up @@ -176,9 +217,9 @@ impl quote::ToTokens for Track {

tokens.append_all(quote! {
{
use alloc::borrow::Cow;
use agb_tracker::__private::agb_tracker_interop::*;
use agb_tracker::__private::Num;
use agb_tracker::__private::static_data;

static SAMPLES: &[Sample] = &[#(#samples),*];
static PATTERN_DATA: &[PatternSlot] = &[#(#pattern_data),*];
Expand All @@ -187,11 +228,11 @@ impl quote::ToTokens for Track {
static ENVELOPES: &[Envelope] = &[#(#envelopes),*];

agb_tracker::Track {
samples: Cow::Borrowed(SAMPLES),
envelopes: Cow::Borrowed(ENVELOPES),
pattern_data: Cow::Borrowed(PATTERN_DATA),
patterns: Cow::Borrowed(PATTERNS),
patterns_to_play: Cow::Borrowed(PATTERNS_TO_PLAY),
samples: static_data(SAMPLES),
envelopes: static_data(ENVELOPES),
pattern_data: static_data(PATTERN_DATA),
patterns: static_data(PATTERNS),
patterns_to_play: static_data(PATTERNS_TO_PLAY),

frames_per_tick: Num::from_raw(#frames_per_tick),
num_channels: #num_channels,
Expand Down Expand Up @@ -246,7 +287,7 @@ impl quote::ToTokens for Envelope {
static AMOUNTS: &[agb_tracker::__private::Num<i16, 8>] = &[#(#amount),*];

agb_tracker::__private::agb_tracker_interop::Envelope {
amount: Cow::Borrowed(AMOUNTS),
amount: static_data(AMOUNTS),
sustain: #sustain,
loop_start: #loop_start,
loop_end: #loop_end,
Expand Down Expand Up @@ -301,7 +342,7 @@ impl quote::ToTokens for Sample {

static SAMPLE_DATA: &[u8] = &AlignmentWrapper(*#samples).0;
agb_tracker::__private::agb_tracker_interop::Sample {
data: Cow::Borrowed(SAMPLE_DATA),
data: static_data(SAMPLE_DATA),
should_loop: #should_loop,
restart_point: #restart_point,
volume: agb_tracker::__private::Num::from_raw(#volume),
Expand Down
59 changes: 40 additions & 19 deletions tracker/agb-tracker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,16 @@
//! Currently, only XM is implemented, however, more formats could be added in future depending
//! on demand.

#[cfg(not(feature = "agb"))]
extern crate alloc;

mod lookups;
mod mixer;

use agb_fixnum::num;
#[cfg(feature = "agb")]
use agb_tracker_interop::Data;
use agb_tracker_interop::{Jump, PatternEffect, Sample, Waveform};
use alloc::vec::Vec;

pub use mixer::{Mixer, SoundChannel};

Expand Down Expand Up @@ -98,18 +101,25 @@ pub use agb_midi::include_midi;
pub mod __private {
pub use agb_fixnum::Num;
pub use agb_tracker_interop;
use agb_tracker_interop::Data;

pub const fn static_data<Content>(data: &'static [Content]) -> Data<Content> {
Data::Static(data)
}
}

/// A reference to a track. You should create this using one of the include macros.
pub use agb_tracker_interop::Track;

const NUMBER_OF_CHANNELS: usize = 8;

/// Stores the required state in order to play tracker music.
pub struct TrackerInner<'track, TChannelId> {
track: &'track Track,
channels: Vec<TrackerChannel>,
envelopes: Vec<Option<EnvelopeState>>,
channels: [TrackerChannel; NUMBER_OF_CHANNELS],
envelopes: [Option<EnvelopeState>; NUMBER_OF_CHANNELS],

mixer_channels: Vec<Option<TChannelId>>,
mixer_channels: [Option<TChannelId>; NUMBER_OF_CHANNELS],

frame: Num<u32, 8>,
tick: u32,
Expand Down Expand Up @@ -187,14 +197,12 @@ struct GlobalSettings {
impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
/// Create a new tracker playing a specified track. See the [example](crate#example) for how to use the tracker.
pub fn new(track: &'track Track) -> Self {
let mut channels = Vec::new();
channels.resize_with(track.num_channels, Default::default);
assert!(track.num_channels < NUMBER_OF_CHANNELS);
let channels = [const { TrackerChannel::new() }; NUMBER_OF_CHANNELS];

let mut envelopes = Vec::new();
envelopes.resize_with(track.num_channels, || None);
let envelopes = [const { None }; NUMBER_OF_CHANNELS];

let mut mixer_channels = Vec::new();
mixer_channels.resize_with(track.num_channels, || None);
let mixer_channels = [const { None }; NUMBER_OF_CHANNELS];

let global_settings = GlobalSettings {
ticks_per_step: track.ticks_per_step,
Expand Down Expand Up @@ -460,6 +468,26 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
}

impl TrackerChannel {
const fn new() -> Self {
Self {
original_speed: num!(0),
base_speed: num!(0),
volume: num!(0),
vibrato: Waves {
waveform: Waveform::Sine,
frame: 0,
speed: 0,
amount: num!(0),
enable: false,
},
current_volume: num!(0),
current_speed: num!(0),
current_panning: num!(0),
is_playing: false,
current_pos: None,
}
}

fn reset(&mut self, sample: &Sample) {
self.volume = sample.volume.change_base();
self.current_volume = self.volume;
Expand Down Expand Up @@ -672,15 +700,8 @@ fn main(_gba: agb::Gba) -> ! {

#[cfg(feature = "agb")]
impl SoundChannel for agb::sound::mixer::SoundChannel {
fn new(data: &alloc::borrow::Cow<'static, [u8]>) -> Self {
Self::new(match data {
alloc::borrow::Cow::Borrowed(data) =>
// Safety: should be good by construction, but it'll blow up if you try and play it and it isn't aligned
unsafe { agb::sound::mixer::SoundData::new(data) },
alloc::borrow::Cow::Owned(_) => {
unimplemented!("Must use borrowed COW data for tracker")
}
})
fn new(data: &Data<u8>) -> Self {
Self::new(unsafe { agb::sound::mixer::SoundData::new(data.get_static()) })
}

fn stop(&mut self) {
Expand Down
6 changes: 2 additions & 4 deletions tracker/agb-tracker/src/mixer.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
#![allow(missing_docs)]

use agb_fixnum::Num;
use alloc::borrow::Cow;
use agb_tracker_interop::Data;

pub trait SoundChannel {
// I need a reference to a cow here to support the static data correctly
#[allow(clippy::ptr_arg)]
fn new(data: &Cow<'static, [u8]>) -> Self;
fn new(data: &Data<u8>) -> Self;

fn stop(&mut self);
fn pause(&mut self) -> &mut Self;
Expand Down
1 change: 1 addition & 0 deletions tracker/desktop-player/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/agbrs/agb"
agb_xm_core = { version = "0.22.6", path = "../agb-xm-core" }
agb_tracker = { version = "0.22.6", path = "../agb-tracker", default-features = false }
agb_fixnum = { version = "0.22.6", path = "../../agb-fixnum" }
agb_tracker_interop = { version = "0.22.6", path = "../agb-tracker-interop", features = ["std"] }

anyhow = "1"
xmrs = "=0.8.5"
Expand Down
9 changes: 5 additions & 4 deletions tracker/desktop-player/src/mixer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use agb_fixnum::Num;
use std::{borrow::Cow, num::Wrapping};
use agb_tracker_interop::Data;
use std::num::Wrapping;

const BUFFER_SIZE: usize = 560;
const NUM_CHANNELS: usize = 8;
Expand Down Expand Up @@ -81,7 +82,7 @@ impl Mixer {
}

pub struct SoundChannel {
data: Cow<'static, [u8]>,
data: Data<u8>,
pos: Num<u32, 8>,
should_loop: bool,
restart_point: Num<u32, 8>,
Expand Down Expand Up @@ -110,7 +111,7 @@ impl std::fmt::Debug for SoundChannel {
}

impl SoundChannel {
fn new(data: Cow<'static, [u8]>) -> Self {
fn new(data: Data<u8>) -> Self {
Self {
data: data.clone(),

Expand All @@ -129,7 +130,7 @@ impl SoundChannel {
pub struct SoundChannelId(usize, Wrapping<usize>);

impl agb_tracker::SoundChannel for SoundChannel {
fn new(data: &Cow<'static, [u8]>) -> Self {
fn new(data: &Data<u8>) -> Self {
Self::new(data.clone())
}

Expand Down