mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
Massive refactor ahead of waveforms
This commit is contained in:
parent
0daeba1876
commit
f3cc62db6f
|
@ -80,7 +80,6 @@ pub enum PatternEffect {
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub enum Waveform {
|
pub enum Waveform {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
|
||||||
Sine,
|
Sine,
|
||||||
Saw,
|
Saw,
|
||||||
Square,
|
Square,
|
||||||
|
@ -349,7 +348,6 @@ impl quote::ToTokens for Waveform {
|
||||||
use quote::{quote, TokenStreamExt};
|
use quote::{quote, TokenStreamExt};
|
||||||
|
|
||||||
let name = match self {
|
let name = match self {
|
||||||
Waveform::None => quote!(None),
|
|
||||||
Waveform::Sine => quote!(Sine),
|
Waveform::Sine => quote!(Sine),
|
||||||
Waveform::Saw => quote!(Saw),
|
Waveform::Saw => quote!(Saw),
|
||||||
Waveform::Square => quote!(Square),
|
Waveform::Square => quote!(Square),
|
||||||
|
|
|
@ -98,9 +98,11 @@ pub use agb_tracker_interop::Track;
|
||||||
/// Stores the required state in order to play tracker music.
|
/// Stores the required state in order to play tracker music.
|
||||||
pub struct TrackerInner<'track, TChannelId> {
|
pub struct TrackerInner<'track, TChannelId> {
|
||||||
track: &'track Track,
|
track: &'track Track,
|
||||||
channels: Vec<TrackerChannel<TChannelId>>,
|
channels: Vec<TrackerChannel>,
|
||||||
envelopes: Vec<Option<EnvelopeState>>,
|
envelopes: Vec<Option<EnvelopeState>>,
|
||||||
|
|
||||||
|
mixer_channels: Vec<Option<TChannelId>>,
|
||||||
|
|
||||||
frame: Num<u32, 8>,
|
frame: Num<u32, 8>,
|
||||||
tick: u32,
|
tick: u32,
|
||||||
first: bool,
|
first: bool,
|
||||||
|
@ -111,11 +113,16 @@ pub struct TrackerInner<'track, TChannelId> {
|
||||||
current_pattern: usize,
|
current_pattern: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TrackerChannel<TChannelId> {
|
#[derive(Default)]
|
||||||
channel_id: Option<TChannelId>,
|
struct TrackerChannel {
|
||||||
original_speed: Num<u32, 16>,
|
original_speed: Num<u32, 16>,
|
||||||
base_speed: Num<u32, 16>,
|
base_speed: Num<u32, 16>,
|
||||||
volume: Num<i32, 8>,
|
volume: Num<i32, 8>,
|
||||||
|
|
||||||
|
current_volume: Num<i32, 8>,
|
||||||
|
current_speed: Num<u32, 16>,
|
||||||
|
current_panning: Num<i32, 8>,
|
||||||
|
is_playing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EnvelopeState {
|
struct EnvelopeState {
|
||||||
|
@ -142,6 +149,9 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
||||||
let mut envelopes = Vec::new();
|
let mut envelopes = Vec::new();
|
||||||
envelopes.resize_with(track.num_channels, || None);
|
envelopes.resize_with(track.num_channels, || None);
|
||||||
|
|
||||||
|
let mut mixer_channels = Vec::new();
|
||||||
|
mixer_channels.resize_with(track.num_channels, || None);
|
||||||
|
|
||||||
let global_settings = GlobalSettings {
|
let global_settings = GlobalSettings {
|
||||||
ticks_per_step: track.ticks_per_step,
|
ticks_per_step: track.ticks_per_step,
|
||||||
frames_per_tick: track.frames_per_tick,
|
frames_per_tick: track.frames_per_tick,
|
||||||
|
@ -150,6 +160,7 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
track,
|
track,
|
||||||
|
mixer_channels,
|
||||||
channels,
|
channels,
|
||||||
envelopes,
|
envelopes,
|
||||||
|
|
||||||
|
@ -168,7 +179,9 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
||||||
/// See the [example](crate#example) for how to use the tracker.
|
/// See the [example](crate#example) for how to use the tracker.
|
||||||
pub fn step<M: Mixer<ChannelId = TChannelId>>(&mut self, mixer: &mut M) {
|
pub fn step<M: Mixer<ChannelId = TChannelId>>(&mut self, mixer: &mut M) {
|
||||||
if !self.increment_frame() {
|
if !self.increment_frame() {
|
||||||
self.update_envelopes(mixer);
|
self.update_envelopes();
|
||||||
|
|
||||||
|
self.realise(mixer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +197,25 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
||||||
{
|
{
|
||||||
if pattern_slot.sample != 0 && self.tick == 0 {
|
if pattern_slot.sample != 0 && self.tick == 0 {
|
||||||
let sample = &self.track.samples[pattern_slot.sample as usize - 1];
|
let sample = &self.track.samples[pattern_slot.sample as usize - 1];
|
||||||
channel.play_sound(mixer, sample, &self.global_settings);
|
|
||||||
|
if let Some(channel) = self.mixer_channels[i]
|
||||||
|
.take()
|
||||||
|
.and_then(|channel_id| mixer.channel(&channel_id))
|
||||||
|
{
|
||||||
|
channel.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_channel = M::SoundChannel::new(&sample.data);
|
||||||
|
if sample.should_loop {
|
||||||
|
new_channel
|
||||||
|
.should_loop()
|
||||||
|
.restart_point(sample.restart_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mixer_channels[i] = mixer.play_sound(new_channel);
|
||||||
|
|
||||||
|
channel.reset(sample);
|
||||||
|
|
||||||
self.envelopes[i] = sample.volume_envelope.map(|envelope_id| EnvelopeState {
|
self.envelopes[i] = sample.volume_envelope.map(|envelope_id| EnvelopeState {
|
||||||
frame: 0,
|
frame: 0,
|
||||||
envelope_id,
|
envelope_id,
|
||||||
|
@ -194,18 +225,16 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.tick == 0 {
|
if self.tick == 0 {
|
||||||
channel.set_speed(mixer, pattern_slot.speed.change_base());
|
channel.set_speed(pattern_slot.speed.change_base());
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.apply_effect(
|
channel.apply_effect(
|
||||||
mixer,
|
|
||||||
&pattern_slot.effect1,
|
&pattern_slot.effect1,
|
||||||
self.tick,
|
self.tick,
|
||||||
&mut self.global_settings,
|
&mut self.global_settings,
|
||||||
&mut self.envelopes[i],
|
&mut self.envelopes[i],
|
||||||
);
|
);
|
||||||
channel.apply_effect(
|
channel.apply_effect(
|
||||||
mixer,
|
|
||||||
&pattern_slot.effect2,
|
&pattern_slot.effect2,
|
||||||
self.tick,
|
self.tick,
|
||||||
&mut self.global_settings,
|
&mut self.global_settings,
|
||||||
|
@ -213,20 +242,36 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_envelopes(mixer);
|
self.update_envelopes();
|
||||||
|
self.realise(mixer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_envelopes<M: Mixer<ChannelId = TChannelId>>(&mut self, mixer: &mut M) {
|
fn realise<M: Mixer<ChannelId = TChannelId>>(&mut self, mixer: &mut M) {
|
||||||
|
for (mixer_channel, tracker_channel) in self.mixer_channels.iter().zip(&self.channels) {
|
||||||
|
if let Some(channel) = mixer_channel
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|channel_id| mixer.channel(channel_id))
|
||||||
|
{
|
||||||
|
channel.playback(tracker_channel.current_speed.change_base());
|
||||||
|
channel.volume(tracker_channel.current_volume.try_change_base().unwrap());
|
||||||
|
channel.panning(tracker_channel.current_panning.try_change_base().unwrap());
|
||||||
|
|
||||||
|
if tracker_channel.is_playing {
|
||||||
|
channel.resume();
|
||||||
|
} else {
|
||||||
|
channel.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_envelopes(&mut self) {
|
||||||
for (channel, envelope_state_option) in self.channels.iter_mut().zip(&mut self.envelopes) {
|
for (channel, envelope_state_option) in self.channels.iter_mut().zip(&mut self.envelopes) {
|
||||||
if let Some(envelope_state) = envelope_state_option {
|
if let Some(envelope_state) = envelope_state_option {
|
||||||
let envelope = &self.track.envelopes[envelope_state.envelope_id];
|
let envelope = &self.track.envelopes[envelope_state.envelope_id];
|
||||||
|
|
||||||
if !channel.update_volume_envelope(
|
if !channel.update_volume_envelope(envelope_state, envelope, &self.global_settings)
|
||||||
mixer,
|
{
|
||||||
envelope_state,
|
|
||||||
envelope,
|
|
||||||
&self.global_settings,
|
|
||||||
) {
|
|
||||||
envelope_state_option.take();
|
envelope_state_option.take();
|
||||||
} else {
|
} else {
|
||||||
envelope_state.frame += 1;
|
envelope_state.frame += 1;
|
||||||
|
@ -289,117 +334,76 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TChannelId> TrackerChannel<TChannelId> {
|
impl TrackerChannel {
|
||||||
fn play_sound<M: Mixer<ChannelId = TChannelId>>(
|
fn reset(&mut self, sample: &Sample) {
|
||||||
&mut self,
|
|
||||||
mixer: &mut M,
|
|
||||||
sample: &Sample,
|
|
||||||
global_settings: &GlobalSettings,
|
|
||||||
) {
|
|
||||||
if let Some(channel) = self
|
|
||||||
.channel_id
|
|
||||||
.take()
|
|
||||||
.and_then(|channel_id| mixer.channel(&channel_id))
|
|
||||||
{
|
|
||||||
channel.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut new_channel = M::SoundChannel::new(&sample.data);
|
|
||||||
|
|
||||||
new_channel.volume(
|
|
||||||
(sample.volume.change_base() * global_settings.volume)
|
|
||||||
.try_change_base()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if sample.should_loop {
|
|
||||||
new_channel
|
|
||||||
.should_loop()
|
|
||||||
.restart_point(sample.restart_point);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.channel_id = mixer.play_sound(new_channel);
|
|
||||||
self.volume = sample.volume.change_base();
|
self.volume = sample.volume.change_base();
|
||||||
|
self.current_volume = self.volume;
|
||||||
|
self.current_panning = 0.into();
|
||||||
|
self.is_playing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_speed<M: Mixer<ChannelId = TChannelId>>(&mut self, mixer: &mut M, speed: Num<u32, 8>) {
|
fn set_speed(&mut self, speed: Num<u32, 8>) {
|
||||||
if let Some(channel) = self
|
|
||||||
.channel_id
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|channel_id| mixer.channel(channel_id))
|
|
||||||
{
|
|
||||||
if speed != 0.into() {
|
if speed != 0.into() {
|
||||||
self.base_speed = speed.change_base();
|
self.base_speed = speed.change_base();
|
||||||
self.original_speed = self.base_speed;
|
self.original_speed = self.base_speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.playback(self.base_speed.change_base());
|
self.current_speed = self.base_speed;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_effect<M: Mixer<ChannelId = TChannelId>>(
|
fn apply_effect(
|
||||||
&mut self,
|
&mut self,
|
||||||
mixer: &mut M,
|
|
||||||
effect: &PatternEffect,
|
effect: &PatternEffect,
|
||||||
tick: u32,
|
tick: u32,
|
||||||
global_settings: &mut GlobalSettings,
|
global_settings: &mut GlobalSettings,
|
||||||
envelope_state: &mut Option<EnvelopeState>,
|
envelope_state: &mut Option<EnvelopeState>,
|
||||||
) {
|
) {
|
||||||
if let Some(channel) = self
|
|
||||||
.channel_id
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|channel_id| mixer.channel(channel_id))
|
|
||||||
{
|
|
||||||
match effect {
|
match effect {
|
||||||
PatternEffect::None => {}
|
PatternEffect::None => {}
|
||||||
PatternEffect::Stop => {
|
PatternEffect::Stop => {
|
||||||
channel.volume(0);
|
self.current_volume = 0.into();
|
||||||
|
|
||||||
if let Some(envelope_state) = envelope_state {
|
if let Some(envelope_state) = envelope_state {
|
||||||
envelope_state.finished = true;
|
envelope_state.finished = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PatternEffect::Arpeggio(first, second) => {
|
PatternEffect::Arpeggio(first, second) => {
|
||||||
match tick % 3 {
|
match tick % 3 {
|
||||||
0 => channel.playback(self.base_speed.change_base()),
|
0 => self.current_speed = self.base_speed.change_base(),
|
||||||
1 => channel.playback(first.change_base()),
|
1 => self.current_speed = first.change_base(),
|
||||||
2 => channel.playback(second.change_base()),
|
2 => self.current_speed = second.change_base(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
PatternEffect::Panning(panning) => {
|
PatternEffect::Panning(panning) => {
|
||||||
channel.panning(panning.change_base());
|
self.current_panning = panning.change_base();
|
||||||
}
|
}
|
||||||
PatternEffect::Volume(volume) => {
|
PatternEffect::Volume(volume) => {
|
||||||
channel.volume(
|
self.current_volume = (volume.change_base() * global_settings.volume)
|
||||||
(volume.change_base() * global_settings.volume)
|
|
||||||
.try_change_base()
|
.try_change_base()
|
||||||
.unwrap(),
|
.unwrap();
|
||||||
);
|
|
||||||
self.volume = volume.change_base();
|
self.volume = volume.change_base();
|
||||||
}
|
}
|
||||||
PatternEffect::VolumeSlide(amount) => {
|
PatternEffect::VolumeSlide(amount) => {
|
||||||
if tick != 0 {
|
if tick != 0 {
|
||||||
self.volume = (self.volume + amount.change_base()).max(0.into());
|
self.volume = (self.volume + amount.change_base()).max(0.into());
|
||||||
channel.volume(
|
self.current_volume = (self.volume * global_settings.volume)
|
||||||
(self.volume * global_settings.volume)
|
|
||||||
.try_change_base()
|
.try_change_base()
|
||||||
.unwrap(),
|
.unwrap();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PatternEffect::FineVolumeSlide(amount) => {
|
PatternEffect::FineVolumeSlide(amount) => {
|
||||||
if tick == 0 {
|
if tick == 0 {
|
||||||
self.volume = (self.volume + amount.change_base()).max(0.into());
|
self.volume = (self.volume + amount.change_base()).max(0.into());
|
||||||
channel.volume(
|
self.current_volume = (self.volume * global_settings.volume)
|
||||||
(self.volume * global_settings.volume)
|
|
||||||
.try_change_base()
|
.try_change_base()
|
||||||
.unwrap(),
|
.unwrap();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PatternEffect::NoteCut(wait) => {
|
PatternEffect::NoteCut(wait) => {
|
||||||
if tick == *wait {
|
if tick == *wait {
|
||||||
channel.volume(0);
|
self.current_volume = 0.into();
|
||||||
|
|
||||||
if let Some(envelope_state) = envelope_state {
|
if let Some(envelope_state) = envelope_state {
|
||||||
envelope_state.finished = true;
|
envelope_state.finished = true;
|
||||||
|
@ -408,30 +412,26 @@ impl<TChannelId> TrackerChannel<TChannelId> {
|
||||||
}
|
}
|
||||||
PatternEffect::NoteDelay(wait) => {
|
PatternEffect::NoteDelay(wait) => {
|
||||||
if tick < *wait {
|
if tick < *wait {
|
||||||
channel.pause();
|
self.is_playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if tick == *wait {
|
if tick == *wait {
|
||||||
channel.resume();
|
self.is_playing = true;
|
||||||
channel.volume(
|
self.current_volume = (self.volume * global_settings.volume)
|
||||||
(self.volume * global_settings.volume)
|
|
||||||
.try_change_base()
|
.try_change_base()
|
||||||
.unwrap(),
|
.unwrap();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PatternEffect::Portamento(amount) => {
|
PatternEffect::Portamento(amount) => {
|
||||||
if tick != 0 {
|
if tick != 0 {
|
||||||
self.base_speed *= amount.change_base();
|
self.base_speed *= amount.change_base();
|
||||||
channel.playback(self.base_speed.change_base());
|
self.current_speed = self.base_speed.change_base();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PatternEffect::TonePortamento(amount, target) => {
|
PatternEffect::TonePortamento(amount, target) => {
|
||||||
channel.volume(
|
self.current_volume = (self.volume * global_settings.volume)
|
||||||
(self.volume * global_settings.volume)
|
|
||||||
.try_change_base()
|
.try_change_base()
|
||||||
.unwrap(),
|
.unwrap();
|
||||||
);
|
|
||||||
|
|
||||||
if tick != 0 {
|
if tick != 0 {
|
||||||
if *amount < 1.into() {
|
if *amount < 1.into() {
|
||||||
|
@ -443,24 +443,15 @@ impl<TChannelId> TrackerChannel<TChannelId> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.playback(self.base_speed.change_base());
|
self.current_speed = self.base_speed.change_base();
|
||||||
}
|
}
|
||||||
PatternEffect::PitchBend(amount) => {
|
PatternEffect::PitchBend(amount) => {
|
||||||
if tick == 0 {
|
if tick == 0 {
|
||||||
self.base_speed = self.original_speed * amount.change_base();
|
self.base_speed = self.original_speed * amount.change_base();
|
||||||
channel.playback(self.base_speed.change_base());
|
self.current_speed = self.base_speed.change_base();
|
||||||
}
|
|
||||||
}
|
|
||||||
// These are global effects handled below
|
|
||||||
PatternEffect::SetTicksPerStep(_)
|
|
||||||
| PatternEffect::SetFramesPerTick(_)
|
|
||||||
| PatternEffect::SetGlobalVolume(_)
|
|
||||||
| PatternEffect::GlobalVolumeSlide(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some effects have to happen regardless of if we're actually playing anything
|
|
||||||
match effect {
|
|
||||||
PatternEffect::SetTicksPerStep(amount) => {
|
PatternEffect::SetTicksPerStep(amount) => {
|
||||||
global_settings.ticks_per_step = *amount;
|
global_settings.ticks_per_step = *amount;
|
||||||
}
|
}
|
||||||
|
@ -474,50 +465,27 @@ impl<TChannelId> TrackerChannel<TChannelId> {
|
||||||
global_settings.volume =
|
global_settings.volume =
|
||||||
(global_settings.volume + *volume_delta).clamp(0.into(), 1.into());
|
(global_settings.volume + *volume_delta).clamp(0.into(), 1.into());
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn update_volume_envelope<M: Mixer<ChannelId = TChannelId>>(
|
fn update_volume_envelope(
|
||||||
&mut self,
|
&mut self,
|
||||||
mixer: &mut M,
|
|
||||||
envelope_state: &EnvelopeState,
|
envelope_state: &EnvelopeState,
|
||||||
envelope: &agb_tracker_interop::Envelope,
|
envelope: &agb_tracker_interop::Envelope,
|
||||||
global_settings: &GlobalSettings,
|
global_settings: &GlobalSettings,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(channel) = self
|
|
||||||
.channel_id
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|channel_id| mixer.channel(channel_id))
|
|
||||||
{
|
|
||||||
let amount = envelope.amount[envelope_state.frame];
|
let amount = envelope.amount[envelope_state.frame];
|
||||||
|
|
||||||
if envelope_state.finished {
|
if envelope_state.finished {
|
||||||
self.volume = (self.volume - envelope_state.fadeout).max(0.into());
|
self.volume = (self.volume - envelope_state.fadeout).max(0.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.volume(
|
self.current_volume = (self.volume * amount.change_base() * global_settings.volume)
|
||||||
(self.volume * amount.change_base() * global_settings.volume)
|
|
||||||
.try_change_base()
|
.try_change_base()
|
||||||
.unwrap(),
|
.unwrap();
|
||||||
);
|
|
||||||
|
|
||||||
self.volume != 0.into()
|
self.volume != 0.into()
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<TChannelId> Default for TrackerChannel<TChannelId> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
channel_id: None,
|
|
||||||
original_speed: Num::default(),
|
|
||||||
base_speed: Num::default(),
|
|
||||||
volume: Num::default(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue