mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-06 23:21:33 +11:00
Even more tracker commands (#757)
Adds support for some of `Retrigger` and `Sample Offset` - [x] Changelog updated
This commit is contained in:
commit
9cbf29ed98
|
@ -39,6 +39,14 @@ pub enum Jump {
|
||||||
Combined { pattern: u8, row: u8 },
|
Combined { pattern: u8, row: u8 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum RetriggerVolumeChange {
|
||||||
|
/// Decrease the volume by one each retrigger
|
||||||
|
DecreaseByOne,
|
||||||
|
/// Don't change it
|
||||||
|
NoChange,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Pattern {
|
pub struct Pattern {
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
|
@ -93,6 +101,9 @@ pub enum PatternEffect {
|
||||||
/// Increase / decrease the pitch by the specified amount immediately
|
/// Increase / decrease the pitch by the specified amount immediately
|
||||||
PitchBend(Num<u32, 8>),
|
PitchBend(Num<u32, 8>),
|
||||||
Jump(Jump),
|
Jump(Jump),
|
||||||
|
SampleOffset(u16),
|
||||||
|
/// Retrigger the note every u8 ticks with the volume change specified
|
||||||
|
Retrigger(RetriggerVolumeChange, u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
@ -128,6 +139,22 @@ impl quote::ToTokens for Jump {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "quote")]
|
||||||
|
impl quote::ToTokens for RetriggerVolumeChange {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
use quote::{quote, TokenStreamExt};
|
||||||
|
|
||||||
|
let type_bit = match self {
|
||||||
|
RetriggerVolumeChange::DecreaseByOne => quote!(DecreaseByOne),
|
||||||
|
RetriggerVolumeChange::NoChange => quote!(NoChange),
|
||||||
|
};
|
||||||
|
|
||||||
|
tokens.append_all(quote! {
|
||||||
|
agb_tracker::__private::agb_tracker_interop::RetriggerVolumeChange::#type_bit
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "quote")]
|
#[cfg(feature = "quote")]
|
||||||
impl quote::ToTokens for Track {
|
impl quote::ToTokens for Track {
|
||||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
@ -401,6 +428,10 @@ impl quote::ToTokens for PatternEffect {
|
||||||
PatternEffect::Jump(jump) => {
|
PatternEffect::Jump(jump) => {
|
||||||
quote! { Jump(#jump) }
|
quote! { Jump(#jump) }
|
||||||
}
|
}
|
||||||
|
PatternEffect::SampleOffset(offset) => quote! { SampleOffset(#offset) },
|
||||||
|
PatternEffect::Retrigger(retrigger_volume_change, ticks) => {
|
||||||
|
quote! { Retrigger(#retrigger_volume_change, #ticks) }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
|
|
Binary file not shown.
|
@ -7,8 +7,7 @@ use agb::sound::mixer::Frequency;
|
||||||
use agb::Gba;
|
use agb::Gba;
|
||||||
use agb_tracker::{include_xm, Track, Tracker};
|
use agb_tracker::{include_xm, Track, Tracker};
|
||||||
|
|
||||||
// Found on: https://modarchive.org/index.php?request=view_by_moduleid&query=36662
|
static SPECTRUM: Track = include_xm!("examples/tracks/peak_and_drozerix_-_spectrum.xm");
|
||||||
static DB_TOFFE: Track = include_xm!("examples/db_toffe.xm");
|
|
||||||
|
|
||||||
#[agb::entry]
|
#[agb::entry]
|
||||||
fn main(mut gba: Gba) -> ! {
|
fn main(mut gba: Gba) -> ! {
|
||||||
|
@ -17,7 +16,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
|
||||||
let mut tracker = Tracker::new(&DB_TOFFE);
|
let mut tracker = Tracker::new(&SPECTRUM);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tracker.step(&mut mixer);
|
tracker.step(&mut mixer);
|
||||||
|
|
Binary file not shown.
|
@ -7,8 +7,7 @@ use agb::sound::mixer::Frequency;
|
||||||
use agb::Gba;
|
use agb::Gba;
|
||||||
use agb_tracker::{include_xm, Track, Tracker};
|
use agb_tracker::{include_xm, Track, Tracker};
|
||||||
|
|
||||||
// Found on: https://modarchive.org/index.php?request=view_by_moduleid&query=36662
|
static SPECTRUM: Track = include_xm!("examples/tracks/peak_and_drozerix_-_spectrum.xm");
|
||||||
static DB_TOFFE: Track = include_xm!("examples/db_toffe.xm");
|
|
||||||
|
|
||||||
#[agb::entry]
|
#[agb::entry]
|
||||||
fn main(mut gba: Gba) -> ! {
|
fn main(mut gba: Gba) -> ! {
|
||||||
|
@ -23,7 +22,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
|
||||||
let mut tracker = Tracker::new(&DB_TOFFE);
|
let mut tracker = Tracker::new(&SPECTRUM);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let before_mixing_cycles_high = timer2.value();
|
let before_mixing_cycles_high = timer2.value();
|
||||||
|
|
Binary file not shown.
BIN
tracker/agb-tracker/examples/tracks/kokesz_-_natural.xm
Normal file
BIN
tracker/agb-tracker/examples/tracks/kokesz_-_natural.xm
Normal file
Binary file not shown.
Binary file not shown.
|
@ -126,6 +126,9 @@ struct TrackerChannel {
|
||||||
current_speed: Num<u32, 16>,
|
current_speed: Num<u32, 16>,
|
||||||
current_panning: Num<i32, 8>,
|
current_panning: Num<i32, 8>,
|
||||||
is_playing: bool,
|
is_playing: bool,
|
||||||
|
|
||||||
|
// if some, should set the current position to this
|
||||||
|
current_pos: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -337,6 +340,10 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
||||||
channel.volume(tracker_channel.current_volume.try_change_base().unwrap());
|
channel.volume(tracker_channel.current_volume.try_change_base().unwrap());
|
||||||
channel.panning(tracker_channel.current_panning.try_change_base().unwrap());
|
channel.panning(tracker_channel.current_panning.try_change_base().unwrap());
|
||||||
|
|
||||||
|
if let Some(offset) = tracker_channel.current_pos.take() {
|
||||||
|
channel.set_pos(offset as u32);
|
||||||
|
}
|
||||||
|
|
||||||
if tracker_channel.is_playing {
|
if tracker_channel.is_playing {
|
||||||
channel.resume();
|
channel.resume();
|
||||||
} else {
|
} else {
|
||||||
|
@ -601,6 +608,26 @@ impl TrackerChannel {
|
||||||
PatternEffect::Jump(jump) => {
|
PatternEffect::Jump(jump) => {
|
||||||
*current_jump = Some(jump.clone());
|
*current_jump = Some(jump.clone());
|
||||||
}
|
}
|
||||||
|
PatternEffect::SampleOffset(offset) => {
|
||||||
|
if tick == 0 {
|
||||||
|
self.current_pos = Some(*offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PatternEffect::Retrigger(volume_change, ticks) => {
|
||||||
|
if tick % *ticks as u32 == 0 {
|
||||||
|
match volume_change {
|
||||||
|
agb_tracker_interop::RetriggerVolumeChange::DecreaseByOne => {
|
||||||
|
self.volume = (self.volume - Num::new(1) / 64).max(0.into());
|
||||||
|
self.current_volume = (self.volume * global_settings.volume)
|
||||||
|
.try_change_base()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
agb_tracker_interop::RetriggerVolumeChange::NoChange => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_pos = Some(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,6 +704,10 @@ impl SoundChannel for agb::sound::mixer::SoundChannel {
|
||||||
fn panning(&mut self, panning: impl Into<Num<i16, 8>>) -> &mut Self {
|
fn panning(&mut self, panning: impl Into<Num<i16, 8>>) -> &mut Self {
|
||||||
self.panning(panning)
|
self.panning(panning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_pos(&mut self, pos: impl Into<Num<u32, 8>>) -> &mut Self {
|
||||||
|
self.set_pos(pos)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "agb")]
|
#[cfg(feature = "agb")]
|
||||||
|
|
|
@ -17,6 +17,8 @@ pub trait SoundChannel {
|
||||||
fn restart_point(&mut self, value: impl Into<Num<u32, 8>>) -> &mut Self;
|
fn restart_point(&mut self, value: impl Into<Num<u32, 8>>) -> &mut Self;
|
||||||
fn playback(&mut self, playback_speed: impl Into<Num<u32, 8>>) -> &mut Self;
|
fn playback(&mut self, playback_speed: impl Into<Num<u32, 8>>) -> &mut Self;
|
||||||
fn panning(&mut self, panning: impl Into<Num<i16, 8>>) -> &mut Self;
|
fn panning(&mut self, panning: impl Into<Num<i16, 8>>) -> &mut Self;
|
||||||
|
|
||||||
|
fn set_pos(&mut self, pos: impl Into<Num<u32, 8>>) -> &mut Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Mixer {
|
pub trait Mixer {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use agb_fixnum::Num;
|
use agb_fixnum::Num;
|
||||||
use agb_tracker_interop::{Jump, PatternEffect, Waveform};
|
use agb_tracker_interop::{Jump, PatternEffect, RetriggerVolumeChange, Waveform};
|
||||||
|
|
||||||
use xmrs::prelude::*;
|
use xmrs::prelude::*;
|
||||||
|
|
||||||
|
@ -99,6 +99,8 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
|
||||||
let mut effect_parameters: [u8; 255] = [0; u8::MAX as usize];
|
let mut effect_parameters: [u8; 255] = [0; u8::MAX as usize];
|
||||||
let mut tone_portamento_directions = vec![0; module.get_num_channels()];
|
let mut tone_portamento_directions = vec![0; module.get_num_channels()];
|
||||||
let mut note_and_sample = vec![None; module.get_num_channels()];
|
let mut note_and_sample = vec![None; module.get_num_channels()];
|
||||||
|
let mut previous_retriggers: Vec<Option<(RetriggerVolumeChange, u8)>> =
|
||||||
|
vec![None; module.get_num_channels()];
|
||||||
|
|
||||||
for row in pattern.iter() {
|
for row in pattern.iter() {
|
||||||
// the combined jump for each row
|
// the combined jump for each row
|
||||||
|
@ -324,6 +326,7 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
0x9 => PatternEffect::SampleOffset(effect_parameter as u16 * 256),
|
||||||
0xB => {
|
0xB => {
|
||||||
let pattern_idx = slot.effect_parameter;
|
let pattern_idx = slot.effect_parameter;
|
||||||
|
|
||||||
|
@ -403,6 +406,30 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
0x8 => PatternEffect::Panning(
|
||||||
|
Num::new(((slot.effect_parameter & 0xf) as i16) - 8) / 8,
|
||||||
|
),
|
||||||
|
0x9 => {
|
||||||
|
let retrigger_amount = slot.effect_parameter & 0xf;
|
||||||
|
let modified_amount = if retrigger_amount == 0 {
|
||||||
|
if let Some((_, previous_retrigger)) =
|
||||||
|
previous_retriggers[channel_number]
|
||||||
|
{
|
||||||
|
previous_retrigger
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
previous_retriggers[channel_number] =
|
||||||
|
Some((RetriggerVolumeChange::NoChange, retrigger_amount));
|
||||||
|
retrigger_amount
|
||||||
|
};
|
||||||
|
|
||||||
|
PatternEffect::Retrigger(
|
||||||
|
RetriggerVolumeChange::NoChange,
|
||||||
|
modified_amount,
|
||||||
|
)
|
||||||
|
}
|
||||||
0xA => PatternEffect::FineVolumeSlide(
|
0xA => PatternEffect::FineVolumeSlide(
|
||||||
Num::new((slot.effect_parameter & 0xf) as i16) / 128,
|
Num::new((slot.effect_parameter & 0xf) as i16) / 128,
|
||||||
),
|
),
|
||||||
|
@ -438,8 +465,43 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track {
|
||||||
PatternEffect::GlobalVolumeSlide(Num::new(first as i32) / 0x40)
|
PatternEffect::GlobalVolumeSlide(Num::new(first as i32) / 0x40)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// R
|
||||||
|
0x1B => {
|
||||||
|
let first = effect_parameter >> 4;
|
||||||
|
let second = effect_parameter & 0xF;
|
||||||
|
|
||||||
|
let previous_retrigger = &mut previous_retriggers[channel_number];
|
||||||
|
let volume_type = match first {
|
||||||
|
0 => previous_retrigger
|
||||||
|
.map(|retrigger| retrigger.0)
|
||||||
|
.unwrap_or(RetriggerVolumeChange::NoChange),
|
||||||
|
1 => RetriggerVolumeChange::DecreaseByOne,
|
||||||
|
8 => RetriggerVolumeChange::NoChange,
|
||||||
|
_ => {
|
||||||
|
eprintln!("Unsupported retrigger effect volume {first}");
|
||||||
|
RetriggerVolumeChange::NoChange
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ticks_between_retriggers = if second == 0 {
|
||||||
|
if let Some((_, previous_retrigger)) = previous_retrigger {
|
||||||
|
*previous_retrigger
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
second
|
||||||
|
};
|
||||||
|
|
||||||
|
*previous_retrigger = Some((volume_type, ticks_between_retriggers));
|
||||||
|
|
||||||
|
PatternEffect::Retrigger(volume_type, ticks_between_retriggers)
|
||||||
|
}
|
||||||
e => {
|
e => {
|
||||||
eprintln!("Unsupported effect {e:X}xy");
|
let effect_char = char::from_digit(e as u32, 36)
|
||||||
|
.unwrap_or('?')
|
||||||
|
.to_ascii_uppercase();
|
||||||
|
eprintln!("Unsupported effect {effect_char}xy");
|
||||||
|
|
||||||
PatternEffect::None
|
PatternEffect::None
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,6 +171,11 @@ impl agb_tracker::SoundChannel for SoundChannel {
|
||||||
self.panning = panning.into();
|
self.panning = panning.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_pos(&mut self, pos: impl Into<Num<u32, 8>>) -> &mut Self {
|
||||||
|
self.pos = pos.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl agb_tracker::Mixer for Mixer {
|
impl agb_tracker::Mixer for Mixer {
|
||||||
|
|
BIN
tracker/desktop-player/tests/delay.xm
Normal file
BIN
tracker/desktop-player/tests/delay.xm
Normal file
Binary file not shown.
BIN
tracker/desktop-player/tests/retrigger.xm
Normal file
BIN
tracker/desktop-player/tests/retrigger.xm
Normal file
Binary file not shown.
Loading…
Reference in a new issue