2023-07-12 21:10:05 +10:00
|
|
|
#![no_std]
|
|
|
|
#![no_main]
|
|
|
|
// This is required to allow writing tests
|
|
|
|
#![cfg_attr(test, feature(custom_test_frameworks))]
|
|
|
|
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
|
|
|
|
#![cfg_attr(test, test_runner(agb::test_runner::test_runner))]
|
|
|
|
|
2023-07-13 02:36:41 +10:00
|
|
|
extern crate alloc;
|
|
|
|
|
2023-07-17 08:57:11 +10:00
|
|
|
use agb_tracker_interop::{PatternEffect, Sample};
|
2023-07-13 03:52:29 +10:00
|
|
|
use alloc::vec::Vec;
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-17 06:12:12 +10:00
|
|
|
use agb::{
|
|
|
|
fixnum::Num,
|
|
|
|
sound::mixer::{ChannelId, Mixer, SoundChannel},
|
|
|
|
};
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-13 00:38:09 +10:00
|
|
|
#[cfg(feature = "xm")]
|
|
|
|
pub use agb_xm::import_xm;
|
|
|
|
|
2023-07-13 09:04:41 +10:00
|
|
|
pub mod __private {
|
|
|
|
pub use agb::fixnum::Num;
|
|
|
|
pub use agb_tracker_interop;
|
|
|
|
}
|
2023-07-13 00:38:09 +10:00
|
|
|
|
2023-07-13 09:04:41 +10:00
|
|
|
pub use agb_tracker_interop::Track;
|
2023-07-13 00:38:09 +10:00
|
|
|
|
2023-07-13 02:36:41 +10:00
|
|
|
pub struct Tracker {
|
|
|
|
track: &'static Track<'static>,
|
2023-07-17 08:57:11 +10:00
|
|
|
channels: Vec<TrackerChannel>,
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-19 07:49:56 +10:00
|
|
|
frame: Num<u32, 8>,
|
|
|
|
tick: u32,
|
2023-07-17 09:27:20 +10:00
|
|
|
first: bool,
|
2023-07-17 06:12:12 +10:00
|
|
|
|
2023-07-13 02:36:41 +10:00
|
|
|
current_row: usize,
|
|
|
|
current_pattern: usize,
|
|
|
|
}
|
|
|
|
|
2023-07-17 08:57:11 +10:00
|
|
|
struct TrackerChannel {
|
|
|
|
channel_id: Option<ChannelId>,
|
2023-07-17 09:27:20 +10:00
|
|
|
base_speed: Num<u32, 8>,
|
2023-07-17 09:45:58 +10:00
|
|
|
volume: Num<i16, 4>,
|
2023-07-17 08:57:11 +10:00
|
|
|
}
|
|
|
|
|
2023-07-13 02:36:41 +10:00
|
|
|
impl Tracker {
|
|
|
|
pub fn new(track: &'static Track<'static>) -> Self {
|
2023-07-13 03:52:29 +10:00
|
|
|
let mut channels = Vec::new();
|
2023-07-17 09:27:20 +10:00
|
|
|
channels.resize_with(track.num_channels, || TrackerChannel {
|
|
|
|
channel_id: None,
|
|
|
|
base_speed: 0.into(),
|
2023-07-17 09:45:58 +10:00
|
|
|
volume: 0.into(),
|
2023-07-17 09:27:20 +10:00
|
|
|
});
|
2023-07-13 02:36:41 +10:00
|
|
|
|
|
|
|
Self {
|
|
|
|
track,
|
2023-07-13 03:52:29 +10:00
|
|
|
channels,
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-17 06:12:12 +10:00
|
|
|
frame: 0.into(),
|
2023-07-17 09:27:20 +10:00
|
|
|
first: true,
|
2023-07-17 06:12:12 +10:00
|
|
|
tick: 0,
|
|
|
|
|
2023-07-13 02:36:41 +10:00
|
|
|
current_row: 0,
|
2023-07-19 22:53:46 +10:00
|
|
|
current_pattern: 0,
|
2023-07-13 02:36:41 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn step(&mut self, mixer: &mut Mixer) {
|
2023-07-17 09:27:20 +10:00
|
|
|
if !self.increment_frame() {
|
|
|
|
return;
|
2023-07-13 02:36:41 +10:00
|
|
|
}
|
|
|
|
|
2023-07-13 04:06:55 +10:00
|
|
|
let pattern_to_play = self.track.patterns_to_play[self.current_pattern];
|
|
|
|
let current_pattern = &self.track.patterns[pattern_to_play];
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-13 03:52:29 +10:00
|
|
|
let pattern_data_pos =
|
|
|
|
current_pattern.start_position + self.current_row * self.track.num_channels;
|
2023-07-13 02:36:41 +10:00
|
|
|
let pattern_slots =
|
2023-07-13 03:52:29 +10:00
|
|
|
&self.track.pattern_data[pattern_data_pos..pattern_data_pos + self.track.num_channels];
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-19 22:53:46 +10:00
|
|
|
for (channel, pattern_slot) in self.channels.iter_mut().zip(pattern_slots) {
|
2023-07-17 09:27:20 +10:00
|
|
|
if pattern_slot.sample != 0 && self.tick == 0 {
|
2023-07-17 10:27:22 +10:00
|
|
|
let sample = &self.track.samples[pattern_slot.sample as usize - 1];
|
2023-07-17 08:57:11 +10:00
|
|
|
channel.play_sound(mixer, sample);
|
2023-07-13 02:36:41 +10:00
|
|
|
}
|
2023-07-17 08:57:11 +10:00
|
|
|
|
2023-07-19 22:53:46 +10:00
|
|
|
if self.tick == 0 {
|
|
|
|
channel.set_speed(mixer, pattern_slot.speed.change_base());
|
|
|
|
}
|
|
|
|
|
|
|
|
channel.apply_effect(mixer, &pattern_slot.effect1, self.tick);
|
|
|
|
channel.apply_effect(mixer, &pattern_slot.effect2, self.tick);
|
2023-07-13 02:36:41 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
self.increment_step();
|
|
|
|
}
|
|
|
|
|
2023-07-17 09:27:20 +10:00
|
|
|
fn increment_frame(&mut self) -> bool {
|
|
|
|
if self.first {
|
|
|
|
self.first = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-07-17 06:12:12 +10:00
|
|
|
self.frame += 1;
|
|
|
|
|
|
|
|
if self.frame >= self.track.frames_per_tick {
|
|
|
|
self.tick += 1;
|
|
|
|
self.frame -= self.track.frames_per_tick;
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-17 09:27:20 +10:00
|
|
|
if self.tick == self.track.ticks_per_step {
|
|
|
|
self.current_row += 1;
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-17 09:27:20 +10:00
|
|
|
if self.current_row
|
|
|
|
>= self.track.patterns[self.track.patterns_to_play[self.current_pattern]].length
|
|
|
|
{
|
|
|
|
self.current_pattern += 1;
|
|
|
|
self.current_row = 0;
|
2023-07-13 04:06:55 +10:00
|
|
|
|
2023-07-17 09:27:20 +10:00
|
|
|
if self.current_pattern >= self.track.patterns_to_play.len() {
|
|
|
|
self.current_pattern = 0;
|
|
|
|
}
|
2023-07-13 04:06:55 +10:00
|
|
|
}
|
2023-07-17 09:27:20 +10:00
|
|
|
|
|
|
|
self.tick = 0;
|
2023-07-13 02:36:41 +10:00
|
|
|
}
|
|
|
|
|
2023-07-17 09:27:20 +10:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
2023-07-13 02:36:41 +10:00
|
|
|
}
|
2023-07-12 21:10:05 +10:00
|
|
|
}
|
2023-07-17 09:27:20 +10:00
|
|
|
|
|
|
|
fn increment_step(&mut self) {}
|
2023-07-12 21:10:05 +10:00
|
|
|
}
|
|
|
|
|
2023-07-17 08:57:11 +10:00
|
|
|
impl TrackerChannel {
|
|
|
|
fn play_sound(&mut self, mixer: &mut Mixer<'_>, sample: &Sample<'static>) {
|
|
|
|
self.channel_id
|
|
|
|
.take()
|
|
|
|
.and_then(|channel_id| mixer.channel(&channel_id))
|
|
|
|
.map(|channel| channel.stop());
|
|
|
|
|
|
|
|
let mut new_channel = SoundChannel::new(sample.data);
|
|
|
|
|
2023-07-19 06:36:37 +10:00
|
|
|
new_channel.volume(sample.volume);
|
|
|
|
|
2023-07-17 08:57:11 +10:00
|
|
|
if sample.should_loop {
|
|
|
|
new_channel
|
|
|
|
.should_loop()
|
|
|
|
.restart_point(sample.restart_point);
|
|
|
|
}
|
|
|
|
|
2023-07-17 10:21:33 +10:00
|
|
|
self.channel_id = mixer.play_sound(new_channel);
|
|
|
|
self.volume = 1.into();
|
2023-07-17 08:57:11 +10:00
|
|
|
}
|
|
|
|
|
2023-07-19 22:53:46 +10:00
|
|
|
fn set_speed(&mut self, mixer: &mut Mixer<'_>, speed: Num<u32, 8>) {
|
2023-07-17 08:57:11 +10:00
|
|
|
if let Some(channel) = self
|
|
|
|
.channel_id
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|channel_id| mixer.channel(&channel_id))
|
|
|
|
{
|
|
|
|
if speed != 0.into() {
|
2023-07-17 09:27:20 +10:00
|
|
|
self.base_speed = speed;
|
2023-07-17 08:57:11 +10:00
|
|
|
}
|
|
|
|
|
2023-07-19 07:17:17 +10:00
|
|
|
channel.playback(self.base_speed);
|
2023-07-19 22:53:46 +10:00
|
|
|
}
|
|
|
|
}
|
2023-07-19 07:17:17 +10:00
|
|
|
|
2023-07-19 22:53:46 +10:00
|
|
|
fn apply_effect(&mut self, mixer: &mut Mixer<'_>, effect: &PatternEffect, tick: u32) {
|
|
|
|
if let Some(channel) = self
|
|
|
|
.channel_id
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|channel_id| mixer.channel(&channel_id))
|
|
|
|
{
|
2023-07-17 08:57:11 +10:00
|
|
|
match effect {
|
|
|
|
PatternEffect::None => {}
|
|
|
|
PatternEffect::Stop => {
|
|
|
|
channel.stop();
|
|
|
|
}
|
2023-07-17 09:27:20 +10:00
|
|
|
PatternEffect::Arpeggio(first, second) => {
|
|
|
|
match tick % 3 {
|
|
|
|
0 => channel.playback(self.base_speed),
|
2023-07-19 07:17:17 +10:00
|
|
|
1 => channel.playback(first.change_base()),
|
|
|
|
2 => channel.playback(second.change_base()),
|
2023-07-17 09:27:20 +10:00
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
}
|
2023-07-17 08:57:11 +10:00
|
|
|
PatternEffect::Panning(panning) => {
|
|
|
|
channel.panning(*panning);
|
|
|
|
}
|
|
|
|
PatternEffect::Volume(volume) => {
|
|
|
|
channel.volume(*volume);
|
2023-07-17 09:45:58 +10:00
|
|
|
self.volume = *volume;
|
|
|
|
}
|
|
|
|
PatternEffect::VolumeSlide(amount) => {
|
2023-07-17 17:47:20 +10:00
|
|
|
if tick != 0 {
|
|
|
|
self.volume += *amount;
|
|
|
|
if self.volume < 0.into() {
|
|
|
|
self.volume = 0.into();
|
|
|
|
}
|
|
|
|
channel.volume(self.volume);
|
2023-07-17 09:45:58 +10:00
|
|
|
}
|
2023-07-17 08:57:11 +10:00
|
|
|
}
|
2023-07-19 22:22:26 +10:00
|
|
|
PatternEffect::NoteCut(wait) => {
|
|
|
|
if tick == *wait {
|
|
|
|
channel.volume(0);
|
|
|
|
self.volume = 0.into();
|
|
|
|
}
|
|
|
|
}
|
2023-07-19 22:38:32 +10:00
|
|
|
PatternEffect::Portamento(amount) => {
|
2023-07-19 22:53:46 +10:00
|
|
|
let mut new_speed = self.base_speed;
|
|
|
|
|
|
|
|
for _ in 0..tick {
|
|
|
|
new_speed *= amount.change_base();
|
2023-07-19 22:38:32 +10:00
|
|
|
}
|
2023-07-19 22:53:46 +10:00
|
|
|
|
|
|
|
channel.playback(new_speed);
|
2023-07-19 22:38:32 +10:00
|
|
|
}
|
2023-07-17 08:57:11 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-12 21:10:05 +10:00
|
|
|
#[cfg(test)]
|
|
|
|
#[agb::entry]
|
|
|
|
fn main(gba: agb::Gba) -> ! {
|
|
|
|
loop {}
|
|
|
|
}
|