2023-08-05 09:02:50 +10:00
|
|
|
use std::{collections::HashMap, error::Error, fs, path::Path};
|
2023-07-13 00:38:09 +10:00
|
|
|
|
2023-07-17 08:57:11 +10:00
|
|
|
use agb_tracker_interop::PatternEffect;
|
2023-07-12 21:33:15 +10:00
|
|
|
use proc_macro2::TokenStream;
|
|
|
|
use proc_macro_error::abort;
|
|
|
|
|
|
|
|
use quote::quote;
|
2023-07-13 00:38:09 +10:00
|
|
|
use syn::LitStr;
|
|
|
|
|
|
|
|
use agb_fixnum::Num;
|
|
|
|
|
2023-08-05 09:02:50 +10:00
|
|
|
use xmrs::{prelude::*, xm::xmmodule::XmModule};
|
2023-07-12 21:33:15 +10:00
|
|
|
|
|
|
|
pub fn agb_xm_core(args: TokenStream) -> TokenStream {
|
2023-07-13 00:38:09 +10:00
|
|
|
let input = match syn::parse::<LitStr>(args.into()) {
|
|
|
|
Ok(input) => input,
|
2023-07-24 04:08:51 +10:00
|
|
|
Err(err) => return err.to_compile_error(),
|
2023-07-13 00:38:09 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
let filename = input.value();
|
|
|
|
|
|
|
|
let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
|
|
|
|
let path = Path::new(&root).join(&*filename);
|
|
|
|
|
|
|
|
let include_path = path.to_string_lossy();
|
|
|
|
|
|
|
|
let module = match load_module_from_file(&path) {
|
|
|
|
Ok(track) => track,
|
|
|
|
Err(e) => abort!(input, e),
|
|
|
|
};
|
|
|
|
|
|
|
|
let parsed = parse_module(&module);
|
2023-07-12 21:33:15 +10:00
|
|
|
|
|
|
|
quote! {
|
2023-07-13 00:38:09 +10:00
|
|
|
{
|
|
|
|
const _: &[u8] = include_bytes!(#include_path);
|
|
|
|
|
|
|
|
#parsed
|
|
|
|
}
|
2023-07-12 21:33:15 +10:00
|
|
|
}
|
|
|
|
}
|
2023-07-13 00:38:09 +10:00
|
|
|
|
|
|
|
pub fn load_module_from_file(xm_path: &Path) -> Result<Module, Box<dyn Error>> {
|
|
|
|
let file_content = fs::read(xm_path)?;
|
|
|
|
Ok(XmModule::load(&file_content)?.to_module())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_module(module: &Module) -> TokenStream {
|
|
|
|
let instruments = &module.instrument;
|
|
|
|
let mut instruments_map = HashMap::new();
|
|
|
|
|
2023-07-13 02:36:41 +10:00
|
|
|
struct SampleData {
|
|
|
|
data: Vec<u8>,
|
|
|
|
should_loop: bool,
|
|
|
|
fine_tune: f64,
|
|
|
|
relative_note: i8,
|
2023-07-17 08:12:42 +10:00
|
|
|
restart_point: u32,
|
2023-07-24 07:10:25 +10:00
|
|
|
volume: Num<i16, 8>,
|
2023-08-05 08:25:48 +10:00
|
|
|
envelope_id: Option<usize>,
|
2023-08-06 08:51:12 +10:00
|
|
|
fadeout: Num<i32, 8>,
|
2023-07-13 02:36:41 +10:00
|
|
|
}
|
|
|
|
|
2023-07-13 00:38:09 +10:00
|
|
|
let mut samples = vec![];
|
2023-08-05 08:25:48 +10:00
|
|
|
let mut envelopes: Vec<EnvelopeData> = vec![];
|
2023-08-05 09:58:39 +10:00
|
|
|
let mut existing_envelopes: HashMap<EnvelopeData, usize> = Default::default();
|
2023-07-13 00:38:09 +10:00
|
|
|
|
|
|
|
for (instrument_index, instrument) in instruments.iter().enumerate() {
|
2023-07-24 04:08:51 +10:00
|
|
|
let InstrumentType::Default(ref instrument) = instrument.instr_type else {
|
|
|
|
continue;
|
|
|
|
};
|
2023-07-13 00:38:09 +10:00
|
|
|
|
2023-08-05 08:25:48 +10:00
|
|
|
let envelope = &instrument.volume_envelope;
|
|
|
|
let envelope_id = if envelope.enabled {
|
2023-08-05 09:58:39 +10:00
|
|
|
let envelope: EnvelopeData = envelope.as_ref().into();
|
|
|
|
let id = existing_envelopes
|
|
|
|
.entry(envelope)
|
|
|
|
.or_insert_with_key(|envelope| {
|
|
|
|
envelopes.push(envelope.clone());
|
|
|
|
envelopes.len() - 1
|
|
|
|
});
|
|
|
|
|
|
|
|
Some(*id)
|
2023-08-05 08:25:48 +10:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2023-07-13 00:38:09 +10:00
|
|
|
for (sample_index, sample) in instrument.sample.iter().enumerate() {
|
2023-07-13 02:36:41 +10:00
|
|
|
let should_loop = !matches!(sample.flags, LoopType::No);
|
2023-08-02 06:41:33 +10:00
|
|
|
let fine_tune = sample.finetune as f64 * 128.0;
|
2023-07-13 02:36:41 +10:00
|
|
|
let relative_note = sample.relative_note;
|
2023-07-17 08:12:42 +10:00
|
|
|
let restart_point = sample.loop_start;
|
|
|
|
let sample_len = if sample.loop_length > 0 {
|
|
|
|
(sample.loop_length + sample.loop_start) as usize
|
|
|
|
} else {
|
|
|
|
usize::MAX
|
|
|
|
};
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-08-07 05:24:59 +10:00
|
|
|
let volume = Num::from_f32(sample.volume);
|
2023-07-19 06:36:37 +10:00
|
|
|
|
2023-07-16 06:07:55 +10:00
|
|
|
let sample = match &sample.data {
|
2023-07-17 08:12:42 +10:00
|
|
|
SampleDataType::Depth8(depth8) => depth8
|
|
|
|
.iter()
|
|
|
|
.map(|value| *value as u8)
|
|
|
|
.take(sample_len)
|
|
|
|
.collect::<Vec<_>>(),
|
2023-07-13 00:38:09 +10:00
|
|
|
SampleDataType::Depth16(depth16) => depth16
|
|
|
|
.iter()
|
|
|
|
.map(|sample| (sample >> 8) as i8 as u8)
|
2023-07-17 08:12:42 +10:00
|
|
|
.take(sample_len)
|
2023-07-13 00:38:09 +10:00
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
};
|
|
|
|
|
2023-08-07 05:24:59 +10:00
|
|
|
let fadeout = Num::from_f32(instrument.volume_fadeout);
|
2023-08-06 08:51:12 +10:00
|
|
|
|
2023-07-13 00:38:09 +10:00
|
|
|
instruments_map.insert((instrument_index, sample_index), samples.len());
|
2023-07-13 02:36:41 +10:00
|
|
|
samples.push(SampleData {
|
|
|
|
data: sample,
|
|
|
|
should_loop,
|
|
|
|
fine_tune,
|
|
|
|
relative_note,
|
2023-07-17 08:12:42 +10:00
|
|
|
restart_point,
|
2023-07-19 06:36:37 +10:00
|
|
|
volume,
|
2023-08-05 08:25:48 +10:00
|
|
|
envelope_id,
|
2023-08-06 08:51:12 +10:00
|
|
|
fadeout,
|
2023-07-13 02:36:41 +10:00
|
|
|
});
|
2023-07-13 00:38:09 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut patterns = vec![];
|
|
|
|
let mut pattern_data = vec![];
|
|
|
|
|
|
|
|
for pattern in &module.pattern {
|
2023-07-13 02:36:41 +10:00
|
|
|
let start_pos = pattern_data.len();
|
2023-07-24 08:36:19 +10:00
|
|
|
let mut effect_parameters: [u8; 255] = [0; u8::MAX as usize];
|
2023-08-03 01:48:41 +10:00
|
|
|
let mut tone_portamento_directions = vec![0; module.get_num_channels()];
|
2023-07-24 08:36:19 +10:00
|
|
|
let mut note_and_sample = vec![None; module.get_num_channels()];
|
2023-07-13 00:38:09 +10:00
|
|
|
|
|
|
|
for row in pattern.iter() {
|
2023-07-17 09:27:20 +10:00
|
|
|
for (i, slot) in row.iter().enumerate() {
|
|
|
|
let channel_number = i % module.get_num_channels();
|
|
|
|
|
2023-07-13 00:38:09 +10:00
|
|
|
let sample = if slot.instrument == 0 {
|
|
|
|
0
|
|
|
|
} else {
|
|
|
|
let instrument_index = (slot.instrument - 1) as usize;
|
|
|
|
|
|
|
|
if let InstrumentType::Default(ref instrument) =
|
|
|
|
module.instrument[instrument_index].instr_type
|
|
|
|
{
|
2023-08-06 08:51:26 +10:00
|
|
|
let sample_slot = *instrument
|
|
|
|
.sample_for_note
|
|
|
|
.get(slot.note as usize)
|
|
|
|
.unwrap_or(&0) as usize;
|
2023-07-13 00:38:09 +10:00
|
|
|
instruments_map
|
|
|
|
.get(&(instrument_index, sample_slot))
|
2023-07-13 02:36:41 +10:00
|
|
|
.map(|sample_idx| sample_idx + 1)
|
2023-07-13 00:38:09 +10:00
|
|
|
.unwrap_or(0)
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-07-19 06:36:37 +10:00
|
|
|
let mut effect1 = PatternEffect::None;
|
2023-07-13 08:41:30 +10:00
|
|
|
|
2023-07-24 06:36:02 +10:00
|
|
|
let previous_note_and_sample = note_and_sample[channel_number];
|
2023-07-19 06:36:37 +10:00
|
|
|
let maybe_note_and_sample = if matches!(slot.note, Note::KeyOff) {
|
2023-07-17 09:27:20 +10:00
|
|
|
effect1 = PatternEffect::Stop;
|
2023-08-06 08:51:26 +10:00
|
|
|
|
2023-08-03 01:48:41 +10:00
|
|
|
¬e_and_sample[channel_number]
|
2023-07-17 10:19:02 +10:00
|
|
|
} else if !matches!(slot.note, Note::None) {
|
|
|
|
if sample != 0 {
|
|
|
|
note_and_sample[channel_number] = Some((slot.note, &samples[sample - 1]));
|
|
|
|
} else if let Some((note, _)) = &mut note_and_sample[channel_number] {
|
|
|
|
*note = slot.note;
|
|
|
|
}
|
2023-07-19 06:36:37 +10:00
|
|
|
|
|
|
|
¬e_and_sample[channel_number]
|
|
|
|
} else {
|
|
|
|
¬e_and_sample[channel_number]
|
|
|
|
};
|
|
|
|
|
|
|
|
if matches!(effect1, PatternEffect::None) {
|
|
|
|
effect1 = match slot.volume {
|
|
|
|
0x10..=0x50 => PatternEffect::Volume(
|
|
|
|
(Num::new((slot.volume - 0x10) as i16) / 64)
|
|
|
|
* maybe_note_and_sample
|
|
|
|
.map(|note_and_sample| note_and_sample.1.volume)
|
|
|
|
.unwrap_or(1.into()),
|
|
|
|
),
|
2023-07-24 08:36:19 +10:00
|
|
|
0x60..=0x6F => {
|
|
|
|
PatternEffect::VolumeSlide(-Num::new((slot.volume - 0x60) as i16) / 64)
|
|
|
|
}
|
|
|
|
0x70..=0x7F => {
|
|
|
|
PatternEffect::VolumeSlide(Num::new((slot.volume - 0x70) as i16) / 64)
|
|
|
|
}
|
2023-07-24 07:03:32 +10:00
|
|
|
0x80..=0x8F => PatternEffect::FineVolumeSlide(
|
2023-08-05 09:58:39 +10:00
|
|
|
-Num::new((slot.volume - 0x80) as i16) / 128,
|
2023-07-24 07:03:32 +10:00
|
|
|
),
|
|
|
|
0x90..=0x9F => PatternEffect::FineVolumeSlide(
|
2023-08-05 09:58:39 +10:00
|
|
|
Num::new((slot.volume - 0x90) as i16) / 128,
|
2023-07-24 07:03:32 +10:00
|
|
|
),
|
2023-07-19 06:36:37 +10:00
|
|
|
0xC0..=0xCF => PatternEffect::Panning(
|
2023-07-24 08:36:19 +10:00
|
|
|
Num::new(slot.volume as i16 - (0xC0 + (0xCF - 0xC0) / 2)) / 8,
|
2023-07-19 06:36:37 +10:00
|
|
|
),
|
|
|
|
_ => PatternEffect::None,
|
|
|
|
};
|
2023-07-17 09:27:20 +10:00
|
|
|
}
|
|
|
|
|
2023-07-24 05:54:24 +10:00
|
|
|
let effect_parameter = if slot.effect_parameter != 0 {
|
|
|
|
effect_parameters[slot.effect_type as usize] = slot.effect_parameter;
|
|
|
|
slot.effect_parameter
|
|
|
|
} else {
|
|
|
|
effect_parameters[slot.effect_type as usize]
|
|
|
|
};
|
|
|
|
|
2023-07-17 08:57:11 +10:00
|
|
|
let effect2 = match slot.effect_type {
|
2023-07-17 09:27:20 +10:00
|
|
|
0x0 => {
|
|
|
|
if slot.effect_parameter == 0 {
|
|
|
|
PatternEffect::None
|
2023-07-19 06:36:37 +10:00
|
|
|
} else if let Some((note, sample)) = maybe_note_and_sample {
|
2023-07-17 09:27:20 +10:00
|
|
|
let first_arpeggio = slot.effect_parameter >> 4;
|
|
|
|
let second_arpeggio = slot.effect_parameter & 0xF;
|
|
|
|
|
2023-07-17 10:19:02 +10:00
|
|
|
let first_arpeggio_speed = note_to_speed(
|
2023-07-19 06:36:37 +10:00
|
|
|
*note,
|
2023-07-17 10:19:02 +10:00
|
|
|
sample.fine_tune,
|
|
|
|
sample.relative_note + first_arpeggio as i8,
|
|
|
|
module.frequency_type,
|
|
|
|
);
|
|
|
|
let second_arpeggio_speed = note_to_speed(
|
2023-07-19 06:36:37 +10:00
|
|
|
*note,
|
2023-07-17 10:19:02 +10:00
|
|
|
sample.fine_tune,
|
|
|
|
sample.relative_note + second_arpeggio as i8,
|
|
|
|
module.frequency_type,
|
|
|
|
);
|
2023-07-17 09:27:20 +10:00
|
|
|
|
|
|
|
PatternEffect::Arpeggio(
|
2023-07-19 07:17:17 +10:00
|
|
|
first_arpeggio_speed
|
|
|
|
.try_change_base()
|
|
|
|
.expect("Arpeggio size too large"),
|
|
|
|
second_arpeggio_speed
|
|
|
|
.try_change_base()
|
|
|
|
.expect("Arpeggio size too large"),
|
2023-07-17 09:27:20 +10:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
PatternEffect::None
|
|
|
|
}
|
|
|
|
}
|
2023-07-19 22:38:32 +10:00
|
|
|
0x1 => {
|
2023-08-03 01:48:41 +10:00
|
|
|
let c4_speed: Num<u32, 12> =
|
|
|
|
note_to_speed(Note::C4, 0.0, 0, module.frequency_type).change_base();
|
|
|
|
let speed: Num<u32, 12> = note_to_speed(
|
2023-07-19 22:38:32 +10:00
|
|
|
Note::C4,
|
2023-08-03 02:18:27 +10:00
|
|
|
effect_parameter as f64 * 8.0,
|
2023-07-19 22:38:32 +10:00
|
|
|
0,
|
|
|
|
module.frequency_type,
|
2023-08-03 01:48:41 +10:00
|
|
|
)
|
|
|
|
.change_base();
|
2023-07-19 22:38:32 +10:00
|
|
|
|
|
|
|
let portamento_amount = speed / c4_speed;
|
|
|
|
|
|
|
|
PatternEffect::Portamento(portamento_amount.try_change_base().unwrap())
|
|
|
|
}
|
|
|
|
0x2 => {
|
|
|
|
let c4_speed = note_to_speed(Note::C4, 0.0, 0, module.frequency_type);
|
|
|
|
let speed = note_to_speed(
|
|
|
|
Note::C4,
|
2023-08-03 02:18:27 +10:00
|
|
|
effect_parameter as f64 * 8.0,
|
2023-07-19 22:38:32 +10:00
|
|
|
0,
|
|
|
|
module.frequency_type,
|
|
|
|
);
|
|
|
|
|
2023-08-03 01:48:41 +10:00
|
|
|
let portamento_amount = c4_speed / speed;
|
2023-07-19 22:38:32 +10:00
|
|
|
|
|
|
|
PatternEffect::Portamento(portamento_amount.try_change_base().unwrap())
|
|
|
|
}
|
2023-07-24 06:36:02 +10:00
|
|
|
0x3 => {
|
2023-07-24 08:36:19 +10:00
|
|
|
if let (Some((note, sample)), Some((prev_note, _))) =
|
|
|
|
(maybe_note_and_sample, previous_note_and_sample)
|
|
|
|
{
|
|
|
|
let target_speed = note_to_speed(
|
|
|
|
*note,
|
|
|
|
sample.fine_tune,
|
|
|
|
sample.relative_note,
|
|
|
|
module.frequency_type,
|
|
|
|
);
|
2023-07-24 06:36:02 +10:00
|
|
|
|
2023-07-24 08:36:19 +10:00
|
|
|
let direction = match (prev_note as usize).cmp(&(*note as usize)) {
|
2023-08-03 01:48:41 +10:00
|
|
|
std::cmp::Ordering::Less => 1,
|
2023-07-24 08:36:19 +10:00
|
|
|
std::cmp::Ordering::Equal => {
|
|
|
|
tone_portamento_directions[channel_number]
|
|
|
|
}
|
2023-08-03 01:48:41 +10:00
|
|
|
std::cmp::Ordering::Greater => -1,
|
2023-07-24 06:36:02 +10:00
|
|
|
};
|
2023-07-24 08:36:19 +10:00
|
|
|
|
|
|
|
tone_portamento_directions[channel_number] = direction;
|
|
|
|
|
|
|
|
let c4_speed = note_to_speed(Note::C4, 0.0, 0, module.frequency_type);
|
2023-07-24 06:36:02 +10:00
|
|
|
let speed = note_to_speed(
|
|
|
|
Note::C4,
|
2023-08-03 01:48:41 +10:00
|
|
|
effect_parameter as f64 * 8.0,
|
2023-07-24 06:36:02 +10:00
|
|
|
0,
|
|
|
|
module.frequency_type,
|
|
|
|
);
|
|
|
|
|
2023-08-03 01:48:41 +10:00
|
|
|
let portamento_amount = if direction > 0 {
|
|
|
|
speed / c4_speed
|
|
|
|
} else {
|
|
|
|
c4_speed / speed
|
|
|
|
};
|
|
|
|
|
2023-07-24 06:36:02 +10:00
|
|
|
PatternEffect::TonePortamento(
|
|
|
|
portamento_amount.try_change_base().unwrap(),
|
|
|
|
target_speed.try_change_base().unwrap(),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
PatternEffect::None
|
|
|
|
}
|
|
|
|
}
|
2023-07-17 08:57:11 +10:00
|
|
|
0x8 => {
|
|
|
|
PatternEffect::Panning(Num::new(slot.effect_parameter as i16 - 128) / 128)
|
|
|
|
}
|
2023-08-05 09:58:39 +10:00
|
|
|
0x5 | 0x6 | 0xA => {
|
2023-07-24 05:54:24 +10:00
|
|
|
let first = effect_parameter >> 4;
|
|
|
|
let second = effect_parameter & 0xF;
|
2023-07-17 09:45:58 +10:00
|
|
|
|
|
|
|
if first == 0 {
|
2023-08-05 09:58:39 +10:00
|
|
|
PatternEffect::VolumeSlide(-Num::new(second as i16) / 64)
|
2023-07-17 09:45:58 +10:00
|
|
|
} else {
|
2023-08-05 09:58:39 +10:00
|
|
|
PatternEffect::VolumeSlide(Num::new(first as i16) / 64)
|
2023-07-17 09:45:58 +10:00
|
|
|
}
|
|
|
|
}
|
2023-07-19 06:36:37 +10:00
|
|
|
0xC => {
|
|
|
|
if let Some((_, sample)) = maybe_note_and_sample {
|
|
|
|
PatternEffect::Volume(
|
2023-08-02 06:41:33 +10:00
|
|
|
(Num::new(slot.effect_parameter as i16) / 64) * sample.volume,
|
2023-07-19 06:36:37 +10:00
|
|
|
)
|
|
|
|
} else {
|
|
|
|
PatternEffect::None
|
|
|
|
}
|
|
|
|
}
|
2023-07-19 22:22:26 +10:00
|
|
|
0xE => match slot.effect_parameter >> 4 {
|
2023-07-24 07:03:32 +10:00
|
|
|
0xA => PatternEffect::FineVolumeSlide(
|
2023-08-05 09:58:39 +10:00
|
|
|
Num::new((slot.effect_parameter & 0xf) as i16) / 128,
|
2023-07-24 07:03:32 +10:00
|
|
|
),
|
|
|
|
0xB => PatternEffect::FineVolumeSlide(
|
2023-08-05 09:58:39 +10:00
|
|
|
-Num::new((slot.effect_parameter & 0xf) as i16) / 128,
|
2023-07-24 07:03:32 +10:00
|
|
|
),
|
2023-07-19 22:22:26 +10:00
|
|
|
0xC => PatternEffect::NoteCut((slot.effect_parameter & 0xf).into()),
|
|
|
|
_ => PatternEffect::None,
|
|
|
|
},
|
2023-08-06 07:29:31 +10:00
|
|
|
0xF => match slot.effect_parameter {
|
|
|
|
0 => PatternEffect::SetTicksPerStep(u32::MAX),
|
|
|
|
1..=0x20 => PatternEffect::SetTicksPerStep(slot.effect_parameter as u32),
|
|
|
|
0x21.. => PatternEffect::SetFramesPerTick(bpm_to_frames_per_tick(
|
|
|
|
slot.effect_parameter as u32,
|
|
|
|
)),
|
|
|
|
},
|
2023-08-06 07:41:55 +10:00
|
|
|
// G
|
|
|
|
0x10 => PatternEffect::SetGlobalVolume(
|
|
|
|
Num::new(slot.effect_parameter as i32) / 0x40,
|
|
|
|
),
|
|
|
|
// H
|
|
|
|
0x11 => {
|
|
|
|
let first = effect_parameter >> 4;
|
|
|
|
let second = effect_parameter & 0xF;
|
|
|
|
|
|
|
|
if first == 0 {
|
|
|
|
PatternEffect::GlobalVolumeSlide(-Num::new(second as i32) / 0x40)
|
|
|
|
} else {
|
|
|
|
PatternEffect::GlobalVolumeSlide(Num::new(first as i32) / 0x40)
|
|
|
|
}
|
|
|
|
}
|
2023-07-17 08:57:11 +10:00
|
|
|
_ => PatternEffect::None,
|
|
|
|
};
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-08-03 01:48:41 +10:00
|
|
|
if sample == 0
|
|
|
|
|| matches!(effect2, PatternEffect::TonePortamento(_, _))
|
|
|
|
|| matches!(effect1, PatternEffect::Stop)
|
|
|
|
{
|
2023-07-17 08:57:11 +10:00
|
|
|
pattern_data.push(agb_tracker_interop::PatternSlot {
|
|
|
|
speed: 0.into(),
|
|
|
|
sample: 0,
|
|
|
|
effect1,
|
|
|
|
effect2,
|
|
|
|
});
|
2023-07-13 02:36:41 +10:00
|
|
|
} else {
|
|
|
|
let sample_played = &samples[sample - 1];
|
|
|
|
|
|
|
|
let speed = note_to_speed(
|
|
|
|
slot.note,
|
|
|
|
sample_played.fine_tune,
|
|
|
|
sample_played.relative_note,
|
2023-07-13 08:41:30 +10:00
|
|
|
module.frequency_type,
|
2023-07-13 02:36:41 +10:00
|
|
|
);
|
|
|
|
|
|
|
|
pattern_data.push(agb_tracker_interop::PatternSlot {
|
2023-07-17 10:27:22 +10:00
|
|
|
speed: speed.try_change_base().unwrap(),
|
|
|
|
sample: sample as u16,
|
2023-07-17 08:57:11 +10:00
|
|
|
effect1,
|
|
|
|
effect2,
|
2023-07-13 02:36:41 +10:00
|
|
|
});
|
|
|
|
}
|
2023-07-13 00:38:09 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-13 02:36:41 +10:00
|
|
|
patterns.push(agb_tracker_interop::Pattern {
|
|
|
|
length: pattern.len(),
|
|
|
|
start_position: start_pos,
|
|
|
|
});
|
2023-07-13 00:38:09 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
let samples: Vec<_> = samples
|
|
|
|
.iter()
|
2023-07-13 02:36:41 +10:00
|
|
|
.map(|sample| agb_tracker_interop::Sample {
|
|
|
|
data: &sample.data,
|
|
|
|
should_loop: sample.should_loop,
|
2023-07-17 08:12:42 +10:00
|
|
|
restart_point: sample.restart_point,
|
2023-07-19 06:36:37 +10:00
|
|
|
volume: sample.volume,
|
2023-08-05 08:25:48 +10:00
|
|
|
volume_envelope: sample.envelope_id,
|
2023-08-06 08:51:12 +10:00
|
|
|
fadeout: sample.fadeout,
|
2023-07-13 02:36:41 +10:00
|
|
|
})
|
2023-07-13 00:38:09 +10:00
|
|
|
.collect();
|
|
|
|
|
2023-07-13 04:06:55 +10:00
|
|
|
let patterns_to_play = module
|
|
|
|
.pattern_order
|
|
|
|
.iter()
|
|
|
|
.map(|order| *order as usize)
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2023-08-05 08:25:48 +10:00
|
|
|
let envelopes = envelopes
|
|
|
|
.iter()
|
|
|
|
.map(|envelope| agb_tracker_interop::Envelope {
|
|
|
|
amount: &envelope.amounts,
|
|
|
|
sustain: envelope.sustain,
|
|
|
|
loop_start: envelope.loop_start,
|
|
|
|
loop_end: envelope.loop_end,
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2023-08-05 07:30:49 +10:00
|
|
|
let frames_per_tick = bpm_to_frames_per_tick(module.default_bpm as u32);
|
2023-07-17 06:12:12 +10:00
|
|
|
let ticks_per_step = module.default_tempo;
|
|
|
|
|
2023-07-13 00:38:09 +10:00
|
|
|
let interop = agb_tracker_interop::Track {
|
|
|
|
samples: &samples,
|
|
|
|
pattern_data: &pattern_data,
|
|
|
|
patterns: &patterns,
|
2023-07-13 03:52:29 +10:00
|
|
|
num_channels: module.get_num_channels(),
|
2023-07-13 04:06:55 +10:00
|
|
|
patterns_to_play: &patterns_to_play,
|
2023-08-05 08:25:48 +10:00
|
|
|
envelopes: &envelopes,
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-17 06:12:12 +10:00
|
|
|
frames_per_tick,
|
2023-07-19 07:49:56 +10:00
|
|
|
ticks_per_step: ticks_per_step.into(),
|
2023-08-03 01:48:41 +10:00
|
|
|
repeat: module.restart_position as usize,
|
2023-07-13 00:38:09 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
quote!(#interop)
|
|
|
|
}
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-08-05 07:30:49 +10:00
|
|
|
fn bpm_to_frames_per_tick(bpm: u32) -> Num<u32, 8> {
|
2023-08-05 08:25:48 +10:00
|
|
|
// Number 150 here deduced experimentally
|
2023-08-05 07:30:49 +10:00
|
|
|
Num::<u32, 8>::new(150) / bpm
|
|
|
|
}
|
|
|
|
|
2023-07-13 08:41:30 +10:00
|
|
|
fn note_to_speed(
|
|
|
|
note: Note,
|
|
|
|
fine_tune: f64,
|
|
|
|
relative_note: i8,
|
|
|
|
frequency_type: FrequencyType,
|
2023-08-03 01:48:41 +10:00
|
|
|
) -> Num<u32, 12> {
|
2023-07-13 08:41:30 +10:00
|
|
|
let frequency = match frequency_type {
|
|
|
|
FrequencyType::LinearFrequencies => {
|
|
|
|
note_to_frequency_linear(note, fine_tune, relative_note)
|
|
|
|
}
|
|
|
|
FrequencyType::AmigaFrequencies => note_to_frequency_amega(note, fine_tune, relative_note),
|
|
|
|
};
|
|
|
|
|
2023-09-06 08:22:50 +10:00
|
|
|
let gba_audio_frequency = 32768f64;
|
2023-07-13 08:41:30 +10:00
|
|
|
|
2023-08-07 05:24:59 +10:00
|
|
|
let speed = frequency / gba_audio_frequency;
|
|
|
|
Num::from_f64(speed)
|
2023-07-13 08:41:30 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
fn note_to_frequency_linear(note: Note, fine_tune: f64, relative_note: i8) -> f64 {
|
2023-07-13 02:36:41 +10:00
|
|
|
let real_note = (note as usize as f64) + (relative_note as f64);
|
2023-07-24 04:08:51 +10:00
|
|
|
let period = 10.0 * 12.0 * 16.0 * 4.0 - real_note * 16.0 * 4.0 - fine_tune / 2.0;
|
2023-07-13 02:36:41 +10:00
|
|
|
8363.0 * 2.0f64.powf((6.0 * 12.0 * 16.0 * 4.0 - period) / (12.0 * 16.0 * 4.0))
|
|
|
|
}
|
|
|
|
|
2023-07-13 08:41:30 +10:00
|
|
|
fn note_to_frequency_amega(note: Note, fine_tune: f64, relative_note: i8) -> f64 {
|
2023-07-24 06:36:02 +10:00
|
|
|
let note = (note as usize)
|
|
|
|
.checked_add_signed(relative_note as isize)
|
|
|
|
.expect("Note gone negative");
|
2023-08-05 10:33:48 +10:00
|
|
|
let pos = ((note % 12) * 8 + (fine_tune / 16.0) as usize).min(AMEGA_FREQUENCIES.len() - 2);
|
2023-07-13 08:41:30 +10:00
|
|
|
let frac = (fine_tune / 16.0) - (fine_tune / 16.0).floor();
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-13 08:41:30 +10:00
|
|
|
let period = ((AMEGA_FREQUENCIES[pos] as f64 * (1.0 - frac))
|
|
|
|
+ AMEGA_FREQUENCIES[pos + 1] as f64 * frac)
|
|
|
|
* 32.0 // docs say 16 here, but for some reason I need 32 :/
|
|
|
|
/ (1 << ((note as i64) / 12)) as f64;
|
2023-07-13 02:36:41 +10:00
|
|
|
|
2023-07-13 08:41:30 +10:00
|
|
|
8363.0 * 1712.0 / period
|
2023-07-13 02:36:41 +10:00
|
|
|
}
|
2023-07-13 08:41:30 +10:00
|
|
|
|
2023-12-11 03:35:15 +11:00
|
|
|
static AMEGA_FREQUENCIES: &[u32] = &[
|
2023-07-13 08:41:30 +10:00
|
|
|
907, 900, 894, 887, 881, 875, 868, 862, 856, 850, 844, 838, 832, 826, 820, 814, 808, 802, 796,
|
|
|
|
791, 785, 779, 774, 768, 762, 757, 752, 746, 741, 736, 730, 725, 720, 715, 709, 704, 699, 694,
|
|
|
|
689, 684, 678, 675, 670, 665, 660, 655, 651, 646, 640, 636, 632, 628, 623, 619, 614, 610, 604,
|
|
|
|
601, 597, 592, 588, 584, 580, 575, 570, 567, 563, 559, 555, 551, 547, 543, 538, 535, 532, 528,
|
|
|
|
524, 520, 516, 513, 508, 505, 502, 498, 494, 491, 487, 484, 480, 477, 474, 470, 467, 463, 460,
|
|
|
|
457,
|
|
|
|
];
|
2023-08-05 08:25:48 +10:00
|
|
|
|
2023-08-05 09:58:39 +10:00
|
|
|
#[derive(PartialEq, Eq, Hash, Clone)]
|
2023-08-05 08:25:48 +10:00
|
|
|
struct EnvelopeData {
|
|
|
|
amounts: Vec<Num<i16, 8>>,
|
|
|
|
sustain: Option<usize>,
|
|
|
|
loop_start: Option<usize>,
|
|
|
|
loop_end: Option<usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&xmrs::envelope::Envelope> for EnvelopeData {
|
2023-08-05 09:02:50 +10:00
|
|
|
fn from(e: &xmrs::envelope::Envelope) -> Self {
|
2023-08-05 09:24:11 +10:00
|
|
|
let mut amounts = vec![];
|
2023-08-05 09:02:50 +10:00
|
|
|
|
|
|
|
// it should be sampled at 50fps, but we're sampling at 60fps, so need to do a bit of cheating here.
|
2023-08-05 09:24:11 +10:00
|
|
|
for frame in 0..(e.point.last().unwrap().frame * 60 / 50) {
|
|
|
|
let xm_frame = frame * 50 / 60;
|
2023-08-05 09:02:50 +10:00
|
|
|
let index = e
|
|
|
|
.point
|
|
|
|
.iter()
|
|
|
|
.rposition(|point| point.frame < xm_frame)
|
2023-08-05 09:24:11 +10:00
|
|
|
.unwrap_or(0);
|
2023-08-05 09:02:50 +10:00
|
|
|
|
|
|
|
let first_point = &e.point[index];
|
|
|
|
let second_point = &e.point[index + 1];
|
|
|
|
|
2023-08-05 09:24:11 +10:00
|
|
|
let amount = EnvelopePoint::lerp(first_point, second_point, xm_frame) / 64.0;
|
2023-08-07 05:24:59 +10:00
|
|
|
let amount = Num::from_f32(amount);
|
2023-08-05 09:02:50 +10:00
|
|
|
|
|
|
|
amounts.push(amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
let sustain = if e.sustain_enabled {
|
|
|
|
Some(e.point[e.sustain_point as usize].frame as usize * 60 / 50)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let (loop_start, loop_end) = if e.loop_enabled {
|
|
|
|
(
|
|
|
|
Some(e.point[e.loop_start_point as usize].frame as usize * 60 / 50),
|
|
|
|
Some(e.point[e.loop_end_point as usize].frame as usize * 60 / 50),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(None, None)
|
|
|
|
};
|
|
|
|
|
|
|
|
EnvelopeData {
|
|
|
|
amounts,
|
|
|
|
sustain,
|
|
|
|
loop_start,
|
|
|
|
loop_end,
|
|
|
|
}
|
2023-08-05 08:25:48 +10:00
|
|
|
}
|
|
|
|
}
|