Skip to content

Commit

Permalink
feat(effect): porta tone
Browse files Browse the repository at this point in the history
  • Loading branch information
nenikitov committed May 7, 2024
1 parent 89f67a6 commit 830668a
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 70 deletions.
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@ members = [
"ashen",
"engine",
]


[profile.perf]
inherits = "release"
codegen-units = 1
incremental = false
lto = "fat"
opt-level = 3
panic = "abort"
8 changes: 7 additions & 1 deletion engine/src/asset/sound/dat/finetune.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::ops::{Add, AddAssign, Neg, Sub};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord)]

Check failure on line 3 in engine/src/asset/sound/dat/finetune.rs

View workflow job for this annotation

GitHub Actions / clippy

you are deriving `Ord` but have implemented `PartialOrd` explicitly

error: you are deriving `Ord` but have implemented `PartialOrd` explicitly --> engine/src/asset/sound/dat/finetune.rs:3:45 | 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord)] | ^^^ | note: `PartialOrd` implemented here --> engine/src/asset/sound/dat/finetune.rs:71:1 | 71 | impl PartialOrd for FineTune { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord = note: `#[deny(clippy::derive_ord_xor_partial_ord)]` on by default = note: this error originates in the derive macro `Ord` (in Nightly builds, run with -Z macro-backtrace for more info)
pub struct FineTune {
cents: i32,
}
Expand Down Expand Up @@ -68,6 +68,12 @@ impl Neg for FineTune {
}
}

impl PartialOrd for FineTune {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.cents.partial_cmp(&other.cents)
}
}

Check warning on line 75 in engine/src/asset/sound/dat/finetune.rs

View workflow job for this annotation

GitHub Actions / clippy

non-canonical implementation of `partial_cmp` on an `Ord` type

warning: non-canonical implementation of `partial_cmp` on an `Ord` type --> engine/src/asset/sound/dat/finetune.rs:71:1 | 71 | / impl PartialOrd for FineTune { 72 | | fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | | _______________________________________________________________________- 73 | || self.cents.partial_cmp(&other.cents) 74 | || } | ||_____- help: change this to: `{ Some(self.cmp(other)) }` 75 | | } | |__^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#non_canonical_partial_ord_impl = note: `#[warn(clippy::non_canonical_partial_ord_impl)]` on by default

#[cfg(test)]
mod tests {
use assert_approx_eq::assert_approx_eq;
Expand Down
57 changes: 40 additions & 17 deletions engine/src/asset/sound/dat/mixer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use std::{collections::HashMap, rc::Rc};

use itertools::Itertools;

use super::{pattern_effect::*, pattern_event::*, t_instrument::*, t_song::*};
use crate::asset::sound::{
dat::finetune::FineTune,
Expand Down Expand Up @@ -92,18 +90,23 @@ impl TSongMixerUtils for TSong {
PatternEffect::Speed(Speed::TicksPerRow(s)) => {
speed = s;
}
PatternEffect::Volume(Volume::Set(volume)) => {
channel.volume = volume;
}
PatternEffect::Porta(Porta::Bump {
up: _,
small: _,
finetune: Some(finetune),
..
}) => {
if let Some(note) = &mut channel.note {
note.finetune += finetune;
}
}
PatternEffect::Volume(Volume::Set(volume)) => {
channel.volume = volume;
}
PatternEffect::Volume(Volume::Bump {
volume: Some(volume),
..
}) => {
channel.volume += volume;
}
PatternEffect::SampleOffset(Some(offset)) => {
channel.sample_position = offset;
}
Expand All @@ -120,9 +123,13 @@ impl TSongMixerUtils for TSong {
+ sample.sample_loop().len();
}
}
PatternEffect::Volume(Volume::Slide(None))
PatternEffect::Porta(Porta::Tone(None))
| PatternEffect::Porta(Porta::Slide { finetune: None, .. })
| PatternEffect::Porta(Porta::Bump { finetune: None, .. })
| PatternEffect::Volume(Volume::Slide(None))
| PatternEffect::Volume(Volume::Bump { volume: None, .. })
| PatternEffect::SampleOffset(None) => {

Check warning on line 131 in engine/src/asset/sound/dat/mixer.rs

View workflow job for this annotation

GitHub Actions / clippy

unnested or-patterns

warning: unnested or-patterns --> engine/src/asset/sound/dat/mixer.rs:126:29 | 126 | / ... PatternEffect::Porta(Porta::Tone(None)) 127 | | ... | PatternEffect::Porta(Porta::Slide { finetune: None, .. }) 128 | | ... | PatternEffect::Porta(Porta::Bump { finetune: None, .. }) 129 | | ... | PatternEffect::Volume(Volume::Slide(None)) 130 | | ... | PatternEffect::Volume(Volume::Bump { volume: None, .. }) 131 | | ... | PatternEffect::SampleOffset(None) => { | |_________________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns note: the lint level is defined here --> engine/src/lib.rs:1:9 | 1 | #![warn(clippy::pedantic)] | ^^^^^^^^^^^^^^^^ = note: `#[warn(clippy::unnested_or_patterns)]` implied by `#[warn(clippy::pedantic)]` help: nest the patterns | 126 ~ PatternEffect::Porta(Porta::Tone(None) | Porta::Slide { finetune: None, .. } | 127 + Porta::Bump { finetune: None, .. }) | 128 + PatternEffect::Volume(Volume::Slide(None) | Volume::Bump { volume: None, .. }) 129 ~ | PatternEffect::SampleOffset(None) => { |
unreachable!("effect memory should already be initialized")
unreachable!("effect {effect:?} ({:?}) at ({p} {r} {c}) memory should already be initialized", effect.memory_key())
}
_ => {}
};
Expand All @@ -131,17 +138,33 @@ impl TSongMixerUtils for TSong {
// Process repeatable effects
for effect in channel.effects.iter().flatten() {
match effect {
PatternEffect::Volume(Volume::Slide(Some(volume))) => {
channel.volume += volume;
PatternEffect::Porta(Porta::Tone(Some(step))) => {
if let Some(note) = &mut channel.note {
note.finetune = match note.finetune.cmp(&note.finetune_initial)
{
std::cmp::Ordering::Less => FineTune::min(
note.finetune + *step,
note.finetune_initial,
),
std::cmp::Ordering::Greater => FineTune::max(
note.finetune - *step,
note.finetune_initial,
),
std::cmp::Ordering::Equal => note.finetune,
}
}
}
PatternEffect::Porta(Porta::Slide {
up: _,
finetune: Some(finetune),
..
}) => {
if let Some(note) = &mut channel.note {
note.finetune += *finetune;
}
}
PatternEffect::Volume(Volume::Slide(Some(volume))) => {
channel.volume += volume;
}
_ => {}
}
}
Expand All @@ -154,11 +177,6 @@ impl TSongMixerUtils for TSong {
let sample_length = sample_length as usize;

Check warning on line 177 in engine/src/asset/sound/dat/mixer.rs

View workflow job for this annotation

GitHub Actions / clippy

casting `f32` to `usize` may lose the sign of the value

warning: casting `f32` to `usize` may lose the sign of the value --> engine/src/asset/sound/dat/mixer.rs:177:37 | 177 | let sample_length = sample_length as usize; | ^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss = note: `#[warn(clippy::cast_sign_loss)]` implied by `#[warn(clippy::pedantic)]`
let tick_length = sample_length / speed;

let volumes = channels
.iter()
.map(|c| format!("{: >10}", c.volume))
.join(" ");

for (i, c) in channels.iter_mut().enumerate() {
for j in 0..speed {
let offset = offset + j * tick_length;
Expand Down Expand Up @@ -187,8 +205,10 @@ impl TSongMixerUtils for TSong {
}
}

#[derive(Clone)]
struct ChannelNote {
finetune: FineTune,
finetune_initial: FineTune,
on: bool,
}

Expand Down Expand Up @@ -216,6 +236,7 @@ impl<'a> Channel<'a> {
(None, PatternEventNote::On(target)) => {
self.note = Some(ChannelNote {
finetune: target,
finetune_initial: target,
on: true,
});
}
Expand Down Expand Up @@ -266,6 +287,8 @@ impl<'a> Channel<'a> {
fn tick(&mut self, duration: usize, volume_scale: f32) -> Vec<[i16; 1]> {
if let Some((note, instrument, sample)) = self.get_note_instrument_sample() {
// Generate data
// TODO(nenikitov): If `volume_envelope` is `0`, this means that the sample already finished playing
// and there is no reason to keep `note.on`.
let volume_envelope = match &instrument.volume {
TInstrumentVolume::Envelope(envelope) => {
let (envelope, default) = if note.on {
Expand Down
63 changes: 32 additions & 31 deletions engine/src/asset/sound/dat/pattern_effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ pub enum Speed {
Bpm(usize),
}

#[derive(Debug, Clone, Copy)]
pub enum Volume {
Set(f32),
Slide(Option<f32>),
Bump { up: bool, volume: Option<f32> },
}

#[derive(Debug, Clone, Copy)]
pub enum Porta {
Tone(Option<FineTune>),
Expand All @@ -33,6 +26,13 @@ pub enum Porta {
},
}

#[derive(Debug, Clone, Copy)]
pub enum Volume {
Set(f32),
Slide(Option<f32>),
Bump { up: bool, volume: Option<f32> },
}

#[derive(Debug, Default, Clone, Copy)]
pub enum PlaybackDirection {
#[default]
Expand Down Expand Up @@ -69,43 +69,39 @@ impl PatternEffect {
pub fn memory_key(&self) -> Option<PatternEffectMemoryKey> {
match self {
PatternEffect::Porta(Porta::Tone(_)) => Some(PatternEffectMemoryKey::PortaTone),
PatternEffect::Porta(Porta::Slide {
up: true,
finetune: _,
}) => Some(PatternEffectMemoryKey::PortaSlideUp),
PatternEffect::Porta(Porta::Slide {
up: false,
finetune: _,
}) => Some(PatternEffectMemoryKey::PortaSlideDown),
PatternEffect::Porta(Porta::Slide { up: true, .. }) => {
Some(PatternEffectMemoryKey::PortaSlideUp)
}
PatternEffect::Porta(Porta::Slide { up: false, .. }) => {
Some(PatternEffectMemoryKey::PortaSlideDown)
}
PatternEffect::Porta(Porta::Bump {
up: true,
small: false,
finetune: _,
..
}) => Some(PatternEffectMemoryKey::PortaBumpUp),
PatternEffect::Porta(Porta::Bump {
up: false,
small: false,
finetune: _,
..
}) => Some(PatternEffectMemoryKey::PortaBumpDown),
PatternEffect::Porta(Porta::Bump {
up: true,
small: true,
finetune: _,
..
}) => Some(PatternEffectMemoryKey::PortaBumpSmallUp),
PatternEffect::Porta(Porta::Bump {
up: false,
small: true,
finetune: _,
..
}) => Some(PatternEffectMemoryKey::PortaBumpSmallDown),
PatternEffect::Volume(Volume::Slide(_)) => Some(PatternEffectMemoryKey::VolumeSlide),
PatternEffect::Volume(Volume::Bump {
up: true,
volume: _,
}) => Some(PatternEffectMemoryKey::VolumeBumpUp),
PatternEffect::Volume(Volume::Bump {
up: down,
volume: _,
}) => Some(PatternEffectMemoryKey::VolumeBumpDown),
PatternEffect::Volume(Volume::Bump { up: true, .. }) => {
Some(PatternEffectMemoryKey::VolumeBumpUp)
}
PatternEffect::Volume(Volume::Bump { up: down, .. }) => {
Some(PatternEffectMemoryKey::VolumeBumpDown)
}
PatternEffect::SampleOffset(_) => Some(PatternEffectMemoryKey::SampleOffset),
_ => None,
}
Expand All @@ -118,7 +114,12 @@ impl PatternEffect {
pub fn is_empty(&self) -> bool {
matches!(
self,
PatternEffect::Volume(Volume::Slide(None)) | PatternEffect::SampleOffset(None)
PatternEffect::Porta(Porta::Tone(None))
| PatternEffect::Porta(Porta::Slide { finetune: None, .. })
| PatternEffect::Porta(Porta::Bump { finetune: None, .. })
| PatternEffect::Volume(Volume::Slide(None))
| PatternEffect::Volume(Volume::Bump { volume: None, .. })
| PatternEffect::SampleOffset(None)

Check warning on line 122 in engine/src/asset/sound/dat/pattern_effect.rs

View workflow job for this annotation

GitHub Actions / clippy

unnested or-patterns

warning: unnested or-patterns --> engine/src/asset/sound/dat/pattern_effect.rs:117:13 | 117 | / PatternEffect::Porta(Porta::Tone(None)) 118 | | | PatternEffect::Porta(Porta::Slide { finetune: None, .. }) 119 | | | PatternEffect::Porta(Porta::Bump { finetune: None, .. }) 120 | | | PatternEffect::Volume(Volume::Slide(None)) 121 | | | PatternEffect::Volume(Volume::Bump { volume: None, .. }) 122 | | | PatternEffect::SampleOffset(None) | |___________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns help: nest the patterns | 117 ~ PatternEffect::Porta(Porta::Tone(None) | Porta::Slide { finetune: None, .. } | 118 + Porta::Bump { finetune: None, .. }) | 119 + PatternEffect::Volume(Volume::Slide(None) | Volume::Bump { volume: None, .. }) 120 + | PatternEffect::SampleOffset(None) |
)
}
}
Expand Down Expand Up @@ -155,17 +156,17 @@ impl AssetParser<Wildcard> for Option<PatternEffect> {
0x16 => PatternEffect::Porta(Porta::Bump {
up: false,
small: false,
finetune: (value != 0).then_some(FineTune::new(8 * value as i32)),
finetune: (value != 0).then_some(-FineTune::new(8 * value as i32)),
}),
0x24 => PatternEffect::Porta(Porta::Bump {
up: true,
small: true,
finetune: (value != 0).then_some(FineTune::new(2 * value as i32)),
finetune: (value != 0).then_some(FineTune::new(2 * (value & 0xF) as i32)),
}),
0x25 => PatternEffect::Porta(Porta::Bump {
up: false,
small: true,
finetune: (value != 0).then_some(FineTune::new(2 * value as i32)),
finetune: (value != 0).then_some(-FineTune::new(2 * (value & 0xF) as i32)),
}),
0x09 => {
PatternEffect::SampleOffset((value != 0).then_some(value as usize * 256))
Expand Down
2 changes: 1 addition & 1 deletion engine/src/asset/sound/dat/pattern_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl AssetParser<Wildcard> for Option<PatternEventInstrumentKind> {
}
}

#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub enum PatternEventVolume {
Sample,
Value(f32),
Expand Down
15 changes: 7 additions & 8 deletions engine/src/asset/sound/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,13 @@ mod tests {
Sound::Song(s) => Some(s),
Sound::Effect(_) => None,
})
.collect::<Vec<_>>()[0x9];
let effects = test_music
.orders
.iter()
.flat_map(|p| p.iter().flat_map(|p| p.iter().flat_map(|p| p.effects)))
.flatten()
.collect::<Vec<_>>();
//dbg!(effects);
.collect::<Vec<_>>()[0x1];
// let effects = test_music
// .orders
// .iter()
// .flat_map(|p| p.iter().flat_map(|p| p.iter().flat_map(|p| p.effects)))
// .flatten()
// .collect::<Vec<_>>();

sounds
.iter()
Expand Down
24 changes: 12 additions & 12 deletions engine/src/utils/iterator.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
use std::mem::{self, MaybeUninit};

use itertools::Itertools;

// Code from [this post](https://www.reddit.com/r/learnrust/comments/lfw6uy/comment/gn16m4o)
pub trait CollectArray: Sized + Iterator {
fn collect_array<const N: usize>(self) -> [Self::Item; N] {
// TODO(nenikitov): Replace with compile-time assertions or const generic expressions
// When it will be supported.
assert!(N > 0 && mem::size_of::<Self::Item>() > 0);
let mut array = MaybeUninit::uninit();
let array_ptr = array.as_mut_ptr() as *mut Self::Item;

let mut i = 0;
unsafe {
for item in self {
assert!(i < N);
array_ptr.add(i).write(item);
i += 1;
}
assert!(i == N);
array.assume_init()
}
let mut array = MaybeUninit::<[Self::Item; N]>::uninit().transpose();

Itertools::zip_eq(array.iter_mut(), self).for_each(|(dest, item)| _ = dest.write(item));

// SAFETY: Every single element in the array is initialized
// because we wrote a valid iterator element into it.
unsafe { array.transpose().assume_init() }
}
}

impl<T> CollectArray for T where T: Iterator {}

0 comments on commit 830668a

Please sign in to comment.