use super::*;
use crate::error::OutOfRangeError;
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display};
use std::str;
#[derive(Clone, Debug)]
pub struct Pattern {
pub active_channels: ActiveChannels,
pub rows: Vec<Row>,
}
#[derive(Clone)]
pub struct Row {
map: Vec<(Channel, Command)>,
}
#[derive(Clone, Copy, Debug)]
pub struct Command {
pub note: Option<NoteCmd>,
pub instrument: Option<InstrumentId>,
pub volume: Option<VolumeCmd>,
pub effect: Option<EffectCmd>,
}
#[derive(Clone, Copy, Debug)]
pub enum NoteCmd {
Play(Note),
Off,
Cut,
Fade,
}
#[derive(Clone, Copy)]
pub struct Note(u8);
#[derive(Clone, Copy, Debug)]
pub enum VolumeCmd {
SetVolume(RangedU8<0, 64>),
Panning(RangedU8<0, 64>),
FineVolumeUp(Option<RangedU8<1, 9>>),
FineVolumeDown(Option<RangedU8<1, 9>>),
VolumeSlideUp(Option<RangedU8<1, 9>>),
VolumeSlideDown(Option<RangedU8<1, 9>>),
PortamentoDown(Option<RangedU8<1, 9>>),
PortamentoUp(Option<RangedU8<1, 9>>),
TonePortamento(Option<RangedU8<1, 9>>),
Vibrato(Option<RangedU8<1, 9>>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EffectCmd {
SetSpeed(RangedU8<1, 255>),
JumpOrder(u8),
BreakRow(u8),
VolumeSlide(Option<VolumeSlide>),
PortamentoDown(Option<Portamento>),
PortamentoUp(Option<Portamento>),
TonePortamento(Option<RangedU8<1, 0xFF>>),
Vibrato(Option<RangedU8<1, 0x0F>>, Option<RangedU8<1, 0x0F>>),
Tremor(Option<(RangedU8<1, 0x0F>, RangedU8<1, 0x0F>)>),
Arpeggio(Option<(RangedU8<0, 0x0F>, RangedU8<0, 0x0F>)>),
VolumeSlideAndVibrato(Option<VolumeSlide>),
VolumeSlideAndPortamento(Option<VolumeSlide>),
SetChannelVolume(RangedU8<0, 0x40>),
ChannelVolumeSlide(Option<VolumeSlide>),
SetSampleOffset(SetSampleOffset),
PanningSlide(Option<PanningSlide>),
Retrigger(Option<(RangedU8<1, 0x0F>, RangedU8<1, 0x0F>)>),
Tremolo(Option<RangedU8<1, 0x0F>>, Option<RangedU8<1, 0x0F>>),
Special(Option<Special>),
Tempo(Option<Tempo>),
FineVibrato(Option<RangedU8<1, 0x0F>>, Option<RangedU8<1, 0x0F>>),
SetGlobalVolume(RangedU8<0, 0x80>),
GlobalVolumeSlide(Option<VolumeSlide>),
SetPanningPosition(u8),
Panbrello(Option<RangedU8<1, 0x0F>>, Option<RangedU8<1, 0x0F>>),
Midi(u8),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EffectCategory {
GlobalTiming,
GlobalPattern,
Volume,
Pitch,
Panning,
Misc,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VolumeSlide {
Down(RangedU8<1, 0x0F>),
Up(RangedU8<1, 0x0F>),
FineDown(RangedU8<1, 0x0E>),
FineUp(RangedU8<1, 0x0E>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Portamento {
Coarse(RangedU8<1, 0xDF>),
Fine(RangedU8<0, 0x0F>),
ExtraFine(RangedU8<0, 0x0F>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SetSampleOffset {
Low(u8),
High(RangedU8<0, 0x0F>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PanningSlide {
Right(RangedU8<1, 0x0F>),
Left(RangedU8<1, 0x0F>),
FineRight(RangedU8<1, 0x0E>),
FineLeft(RangedU8<1, 0x0E>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Special {
SetGlissando(bool),
SetFinetune(RangedU8<0, 0x0F>),
SetVibratoWaveform(Waveform),
SetTremoloWaveform(Waveform),
SetPanbrelloWaveform(Waveform),
PatternTickDelay(RangedU8<0, 0x0F>),
PastNote(SetPastNote),
SetNewNoteAction(SetNewNoteAction),
SetVolumeEnvelope(bool),
SetPanningEnvelope(bool),
SetPitchEnvelope(bool),
SetPanning(RangedU8<0, 0x0F>),
SetSurround(bool),
SetReverb(bool),
SetSurroundMode(SurroundMode),
SetFilterMode(FilterMode),
SetDirection(PlayDirection),
SetLoopbackPoint,
LoopbackTimes(RangedU8<0x01, 0x0F>),
NoteCut(RangedU8<0, 0x0F>),
NoteDelay(RangedU8<0, 0x0F>),
PatternRowDelay(RangedU8<0, 0x0F>),
SetMidiParam(RangedU8<0, 0x0F>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Waveform {
Sine,
Sawtooth,
Square,
Random,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SetPastNote {
Cut,
Off,
Fade,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SetNewNoteAction {
Cut,
Off,
Fade,
Continue,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SurroundMode {
Center,
Quad,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FilterMode {
Global,
Local,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PlayDirection {
Forward,
Backward,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Tempo {
SlideDown(RangedU8<1, 0x0F>),
SlideUp(RangedU8<1, 0x0F>),
Set(RangedU8<0x20, 0xFF>),
}
impl Row {
pub const fn empty() -> Row {
Row { map: Vec::new() }
}
pub(crate) fn from_vec(mut vec: Vec<(Channel, Command)>) -> Row {
vec.sort_unstable_by_key(|(chan, _)| *chan);
Row { map: vec }
}
pub fn iter(&self) -> impl Iterator<Item=(Channel, &Command)> + '_ {
self.map
.iter()
.map(|(chan, command)| (*chan, command))
}
}
impl Debug for Row {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_map()
.entries(
self.map
.iter()
.map(|pair| (&pair.0, &pair.1))
)
.finish()
}
}
impl Get<Channel> for Row {
type Output = Command;
fn get(&self, index: Channel) -> Option<&Self::Output> {
self.map
.binary_search_by_key(&index, |(chan, _)| *chan)
.ok()
.map(|idx| &self.map[idx].1)
}
}
impl_index_from_get!(Row, Channel);
impl TryFrom<u8> for Note {
type Error = OutOfRangeError<0, 119>;
fn try_from(raw: u8) -> Result<Self, Self::Error> {
if (0..=119).contains(&raw) {
Ok(Note(raw))
} else {
Err(OutOfRangeError(raw))
}
}
}
impl From<Note> for u8 {
fn from(note: Note) -> u8 {
note.0
}
}
fn note_string(Note(idx): Note, buf: &mut [u8; 3]) -> &str {
assert!(idx < 120, "BUG: Note inner value is out of range of 0..=119");
const NAMES: [&[u8; 2]; 12] = [b"C-", b"C#", b"D-", b"D#", b"E-", b"F-", b"F#", b"G-", b"G#", b"A-", b"A#", b"B-"];
let name = NAMES[usize::from(idx % 12)];
let octave = b'0' + (idx / 12);
buf[0] = name[0];
buf[1] = name[1];
buf[2] = octave;
unsafe { str::from_utf8_unchecked(&*buf) }
}
impl Debug for Note {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buf = [0; 3];
f.write_str(note_string(*self, &mut buf))
}
}
impl Display for Note {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buf = [0; 3];
f.write_str(note_string(*self, &mut buf))
}
}
impl Note {
pub fn freq(self) -> f32 {
let (Note(idx), Note(base)) = (self, Note::A_4);
let exp = (f32::from(idx) - f32::from(base)) / 12.0f32;
440.0f32 * 2.0f32.powf(exp)
}
}
macro_rules! define_notes {
( $( $notes: ident ),* $(,)? ) => {
#[allow(non_upper_case_globals)]
impl Note {
define_notes!( @(0) $( $notes, )* );
}
};
( @($_: expr) ) => {};
( @($acc: expr) $note: ident, $( $notes: ident ),* $(,)? ) => {
pub const $note: Note = Note($acc);
define_notes!( @($acc + 1) $( $notes, )* );
};
}
define_notes! {
C_0, Cs0, D_0, Ds0, E_0, F_0, Fs0, G_0, Gs0, A_0, As0, B_0,
C_1, Cs1, D_1, Ds1, E_1, F_1, Fs1, G_1, Gs1, A_1, As1, B_1,
C_2, Cs2, D_2, Ds2, E_2, F_2, Fs2, G_2, Gs2, A_2, As2, B_2,
C_3, Cs3, D_3, Ds3, E_3, F_3, Fs3, G_3, Gs3, A_3, As3, B_3,
C_4, Cs4, D_4, Ds4, E_4, F_4, Fs4, G_4, Gs4, A_4, As4, B_4,
C_5, Cs5, D_5, Ds5, E_5, F_5, Fs5, G_5, Gs5, A_5, As5, B_5,
C_6, Cs6, D_6, Ds6, E_6, F_6, Fs6, G_6, Gs6, A_6, As6, B_6,
C_7, Cs7, D_7, Ds7, E_7, F_7, Fs7, G_7, Gs7, A_7, As7, B_7,
C_8, Cs8, D_8, Ds8, E_8, F_8, Fs8, G_8, Gs8, A_8, As8, B_8,
C_9, Cs9, D_9, Ds9, E_9, F_9, Fs9, G_9, Gs9, A_9, As9, B_9,
}
impl VolumeCmd {
pub fn category(&self) -> EffectCategory {
match self {
VolumeCmd::FineVolumeUp(_) |
VolumeCmd::FineVolumeDown(_) |
VolumeCmd::VolumeSlideUp(_) |
VolumeCmd::VolumeSlideDown(_) |
VolumeCmd::SetVolume(_)
=> EffectCategory::Volume,
VolumeCmd::PortamentoDown(_) |
VolumeCmd::PortamentoUp(_) |
VolumeCmd::TonePortamento(_) |
VolumeCmd::Vibrato(_)
=> EffectCategory::Pitch,
VolumeCmd::Panning(_)
=> EffectCategory::Panning,
}
}
}
impl EffectCmd {
pub fn category(&self) -> EffectCategory {
match self {
EffectCmd::SetSpeed(_) |
EffectCmd::Tempo(_)
=> EffectCategory::GlobalTiming,
EffectCmd::JumpOrder(_) |
EffectCmd::BreakRow(_) |
EffectCmd::Special(Some(Special::PatternTickDelay(_))) |
EffectCmd::Special(Some(Special::SetLoopbackPoint)) |
EffectCmd::Special(Some(Special::LoopbackTimes(_))) |
EffectCmd::Special(Some(Special::PatternRowDelay(_)))
=> EffectCategory::GlobalPattern,
EffectCmd::VolumeSlide(_) |
EffectCmd::Tremor(_) |
EffectCmd::SetChannelVolume(_) |
EffectCmd::ChannelVolumeSlide(_) |
EffectCmd::Tremolo(_, _) |
EffectCmd::Special(Some(Special::SetTremoloWaveform(_))) |
EffectCmd::SetGlobalVolume(_) |
EffectCmd::GlobalVolumeSlide(_)
=> EffectCategory::Volume,
EffectCmd::PortamentoDown(_) |
EffectCmd::PortamentoUp(_) |
EffectCmd::TonePortamento(_) |
EffectCmd::Vibrato(_, _) |
EffectCmd::Arpeggio(_) |
EffectCmd::Special(Some(Special::SetGlissando(_))) |
EffectCmd::Special(Some(Special::SetFinetune(_))) |
EffectCmd::Special(Some(Special::SetVibratoWaveform(_))) |
EffectCmd::Special(Some(Special::SetPanbrelloWaveform(_))) |
EffectCmd::FineVibrato(_, _)
=> EffectCategory::Pitch,
EffectCmd::PanningSlide(_) |
EffectCmd::Special(Some(Special::SetPanning(_))) |
EffectCmd::SetPanningPosition(_) |
EffectCmd::Panbrello(_, _)
=> EffectCategory::Panning,
_ => EffectCategory::Misc,
}
}
}