use ym2149::{Ym2149, Ym2149Backend};
use ym2149_gist_replayer::{GistPlayer, GistSound};
use crate::audio::{Pcm, RATE};
pub const NAMES: [&str; 20] = [
"Alien Invasion",
"Red Alert",
"Gunshot",
"Laser 1",
"Jackhammer",
"Teleport",
"Explosion",
"Laser 2",
"Longbell",
"Surprise",
"Radio Broadcast",
"Bounce Ball",
"Eerie Sound",
"Harley Motorcycle",
"Helicopter",
"Steam Locomotive",
"Wave",
"Robot Walk",
"Passing Plane",
"Landing",
];
const MAX_SECONDS: usize = 12;
pub(crate) static EFFECTS: [[i16; 56]; 20] = [
[
28, 477, -1, 15, 1, 1, 4681, -5, 27853, 5, -9830, -1, -1278, 7, -32768, 7, -32768, 14, 1, -4080, -256, -4080,
-256, 0, 0, -4080, -256, 18, -30086, 1696, 13405, -1200, -26066, 1696, 13405, -1200, -26066, -1200, -26066, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
200, 477, -1, 15, 1, 15, 0, -1, -1267, 10, 3277, -1, -2573, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86,
2926, -8, 27220, 7, -16776, -85, -17949, -8, 27220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
8, 1250, 31, 15, 1, 3, -16384, -1, -7562, 0, 0, -1, -10923, 0, 0, 1, 19988, 0, 1, -4080, -256, -4080, -256,
114, -4444, -57, -24712, 56, 24712, 0, 0, -3584, -8192, 28665, 0, 0, 0, -3584, -8192, 0, 1, -3, -14043, -31, 0,
31, 0, 0, 0, 1, -29491, 0, 0, 0, 0, 0,
],
[
8, 284, -1, 15, 1, 3, -16384, -1, -8192, 0, 0, -1, -8937, 0, 0, 0, 0, 0, 1, -4057, 2971, -4057, 2971, 338,
5214, 0, 0, 4056, -2971, 28665, 0, -8, -10895, 57, 21627, -3584, -8192, -8, -10895, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
],
[
8, 625, -1, 15, 1, 3, -16384, -1, -32768, 0, 0, -1, -10923, 5, -19661, 5, -19661, 0, 1, -594, -20719, -2374,
-17340, 1186, -24098, 0, 0, 2373, 17340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,
],
[
500, 477, 16, 15, 1, 0, 17873, 0, 0, 15, 0, -1, -410, 1, -22938, 0, -30933, 0, 1, 12, -28542, 1696, 13405, -13,
18541, -2374, -17340, 0, -26652, 448, -20514, -170, 10530, 188, 29395, -405, -24861, -170, 10530, 0, 1, -1,
-25395, -31, 0, 0, 3694, 31, 0, -1, -4515, 0, 0, -9, -31562, 0,
],
[
200, 477, 31, 15, 1, 15, 0, -1, -1267, 10, 3277, -1, -2573, 0, 0, 0, 0, 0, 1, -9, 18470, -140, -32161, 0, 5504,
28, 31652, 0, 3679, 1656, 13223, 430, -25398, 430, -25398, -1180, -16857, -307, 25764, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
40, 477, -1, 15, 1, 5, 0, -1, -1248, 10, 3277, -1, -2533, 0, 0, 0, 0, 0, 1, -9, 18470, -140, -32161, 0, 2270,
-57, -24712, -1, -3337, 1656, 13223, 430, -25398, 430, -25398, -1180, -16857, -307, 25764, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
20, 52, -1, 15, 1, 15, 0, -1, -9830, 12, 0, -1, -1024, 1, -32768, 0, 9830, 160, 1, -28, 360, -112, 1442, 3,
31133, -57, -24712, 0, -11989, 57, 10752, -6, 23743, 5, -18586, -57, -24712, -6, 23743, 4, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
100, 477, -1, 15, 1, 3, -16384, -1, -4572, 12, 0, -1, -1966, 1, -32768, 0, 7864, 0, 1, -4080, -256, -4080,
-256, 10, 12944, 0, 0, 10, 12944, 293, -5746, -264, -17086, 282, 10212, -275, -15067, -264, -17086, 40, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
800, 954, 23, 15, 1, 0, -32768, -1, -69, 14, 6554, -15, -6554, 7, 22938, 5, 19137, 0, 1, -30, -20744, -3665,
28433, 0, 5618, -3584, -8192, 0, -26399, 10977, -21383, -72, 27596, 263, 30420, -2983, -29831, -72, 27596, 30,
1, 1, -9175, 29, -15729, -2, 17302, -31, 0, 0, 15049, 15, -32768, -4, -6554, 145,
],
[
8, 284, -1, 15, 1, 3, -16384, -1, -8192, 0, 0, -1, -8937, 0, 0, 0, 0, 0, 1, -4057, 2971, -4057, 2971, 338,
5214, 0, 0, 4056, -2971, 28665, 0, -8, -10895, 57, 21627, -3584, -8192, -8, -10895, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
],
[
200, 477, -1, 15, 1, 0, -13797, -1, 26214, 14, 26214, -1, -16852, 0, 0, 0, 0, 0, 0, -4, 30423, -29, -18758, 5,
-22463, 0, 0, 28, 18758, 86, 2926, -11, -7397, 10, 21323, -85, -17949, -11, -7397, 50, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
],
[
500, 1923, 15, 15, 1, 0, 15124, -1, -21, 14, -29491, -15, 29491, 7, -32768, 2, 26214, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 5644, -25803, -1899, 25450, 4515, -20642, -2374, -17340, -1899, 25450, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 9, -5243, -6, 3146, 0,
],
[
500, 62, 15, 15, 1, 0, 15124, -1, -21, 14, -29491, -15, 29491, 2, 6554, 0, -27001, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 12285, 0, -738, -6554, 2948, 26214, -3072, -16384, -738, -6554, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,
-5243, -6, 3146, 0,
],
[
800, -1, 23, 15, 1, 0, 9362, -1, -93, 12, 29491, -1, -136, 7, 13107, 1, 25061, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, -9175, 29, -15729, -2, 17302, -31, 0, 0, 15049, 15, -32768, -4,
-6554, 145,
],
[
400, 238, -1, 15, 1, 0, 16384, -1, -337, 14, 6554, -1, -154, 5, -9830, 1, -23724, 15, 1, -300, 9867, -1200,
-26066, 86, 3191, -167, 12227, 0, 1822, 12285, 0, -7, -9339, 24, -28180, -3072, -16384, -7, -9339, 40, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
8, 1330, -1, 15, 1, 3, -16384, -1, -8192, 0, 0, -1, -8937, 0, 0, 0, 0, 0, 1, -4057, 2971, -4057, 2971, 338,
5214, 0, 0, 4056, -2971, 28665, 0, -3584, -8192, 28665, 0, -3584, -8192, -3584, -8192, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
50, 477, -1, 15, 1, 0, 24576, -1, -2048, 11, 16384, -1, -2633, 0, 0, 0, 0, 0, 1, -582, -10279, -4069, -6415,
54, 25814, -1077, -28428, 4, 9184, 28665, 0, -1577, 27853, 12612, -26214, -3584, -8192, -1577, 27853, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
[
700, 78, -1, 15, 1, 0, 24576, -1, -1229, 0, 0, -1, -9830, 0, 0, 0, 0, 0, 1, -3072, -16384, -3072, -16384, 10,
-9706, 1052, 30787, 0, -1848, 5644, -25803, 3612, -29621, 3612, -29621, -2374, -17340, -1519, 7252, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
];
fn pcm(samples: &[f32]) -> Pcm {
samples
.iter()
.flat_map(|&s| {
let v = (s.clamp(-1.0, 1.0) * 24_000.0) as i16;
[v, v]
})
.collect::<Vec<i16>>()
.into()
}
fn render(player: &mut GistPlayer, max: usize) -> Vec<f32> {
let mut out = Vec::new();
let mut buf = vec![0f32; RATE as usize / 50];
while player.is_playing() && out.len() < max {
player.generate_samples_into(&mut buf);
out.extend_from_slice(&buf);
}
out
}
pub fn effect(words: &[i16; 56], times: u32) -> Pcm {
let sound = GistSound::from(words);
let mut mix: Vec<f32> = Vec::new();
for n in 0..times.clamp(1, 50) as usize {
let mut p = GistPlayer::with_sample_rate(RATE);
p.play_sound(&sound, None, None);
let one = render(&mut p, RATE as usize * MAX_SECONDS);
let at = n * RATE as usize / 5;
if mix.len() < at + one.len() {
mix.resize(at + one.len(), 0.0);
}
for (i, s) in one.iter().enumerate() {
mix[at + i] = (mix[at + i] + s).clamp(-1.0, 1.0);
}
}
pcm(&mix)
}
pub fn note(words: &[i16; 56], volume: u8, pitch: u8, ticks: Option<u32>) -> Pcm {
let sound = GistSound::from(words);
let mut p = GistPlayer::with_sample_rate(RATE);
if p.play_sound_pitched(&sound, pitch as i16, Some(0), Some(volume.min(15) as i16), Some(1)).is_none() {
return pcm(&[]);
}
let held = ticks.map_or(2 * RATE as usize, |t| t as usize * RATE as usize / 200);
let mut out = vec![0f32; held.min(RATE as usize * MAX_SECONDS)];
p.generate_samples_into(&mut out);
p.stop_voice(0);
out.extend(render(&mut p, RATE as usize * 3));
pcm(&out)
}
pub fn dosound(data: &[u8]) -> Pcm {
let mut chip = Ym2149::with_clocks(2_000_000, RATE);
let mut out: Vec<f32> = Vec::new();
let tick = RATE as usize / 50;
let play = |chip: &mut Ym2149, out: &mut Vec<f32>, ticks: usize| {
for _ in 0..ticks * tick {
chip.clock();
out.push(chip.get_sample());
}
};
let (mut i, mut temp) = (0, 0u8);
while i < data.len() && out.len() < RATE as usize * MAX_SECONDS {
match data[i] {
r @ 0..=0x0F => {
let Some(&v) = data.get(i + 1) else { break };
chip.write_register(r, v);
i += 2;
}
0x80 => {
temp = data.get(i + 1).copied().unwrap_or(0);
i += 2;
}
0x81 => {
let (Some(&r), Some(&step), Some(&end)) = (data.get(i + 1), data.get(i + 2), data.get(i + 3)) else {
break;
};
for _ in 0..256 {
chip.write_register(r & 15, temp);
play(&mut chip, &mut out, 1);
if temp == end {
break;
}
temp = temp.wrapping_add(step);
}
i += 4;
}
0x82 => {
let n = data.get(i + 1).copied().unwrap_or(0);
if n == 0 {
break;
}
play(&mut chip, &mut out, n as usize);
i += 2;
}
_ => i += 1,
}
}
play(&mut chip, &mut out, 25);
pcm(&out)
}
pub fn midi_notes(data: &[u8]) -> Vec<(u8, u8)> {
let mut notes = Vec::new();
let mut status = 0u8;
let mut i = 0;
while i < data.len() {
let b = data[i];
if b & 0x80 != 0 {
status = b;
i += 1;
if b >= 0xF0 {
status = 0;
}
continue;
}
let len = match status & 0xF0 {
0x80 | 0x90 | 0xA0 | 0xB0 | 0xE0 => 2,
0xC0 | 0xD0 => 1,
_ => {
i += 1;
continue;
}
};
if status & 0xF0 == 0x90 && i + 1 < data.len() && data[i + 1] > 0 {
notes.push((data[i], data[i + 1]));
}
i += len;
}
notes
}
pub fn pluck(note: u8, velocity: u8) -> Pcm {
let f = 440.0 * 2f32.powf((note as f32 - 69.0) / 12.0);
let n = RATE as usize * 6 / 10;
let level = velocity.min(127) as f32 / 127.0 * 0.35;
let s: Vec<f32> = (0..n)
.map(|i| {
let t = i as f32 / RATE as f32;
let phase = (t * f).fract();
let wave = if phase < 0.5 { 1.0 } else { -1.0 } * 0.6 + (t * f * std::f32::consts::TAU).sin() * 0.4;
wave * level * (-t * 5.0).exp() * (1.0 - (-t * 400.0).exp())
})
.collect();
pcm(&s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn effects_notes_chip_and_midi() {
let loud = |p: &Pcm| p.iter().map(|&s| (s as i32).abs()).max().unwrap_or(0);
for (i, words) in EFFECTS.iter().enumerate() {
let p = effect(words, 1);
assert!(loud(&p) > 1000, "{} is silent", NAMES[i]);
assert!(p.len() / 2 <= RATE as usize * (MAX_SECONDS + 1), "{} never ends", NAMES[i]);
}
assert!(effect(&EFFECTS[2], 5).len() > effect(&EFFECTS[2], 1).len());
let n = note(&EFFECTS[8], 15, 60, Some(100));
assert!(n.len() / 2 >= RATE as usize / 2);
assert!(loud(&n) > 1000);
let d = dosound(&[0, 0x1C, 1, 0x01, 7, 0x3E, 8, 15, 0x82, 25, 8, 0, 0x82, 0]);
assert!(d.len() / 2 >= RATE as usize / 2 && loud(&d) > 1000);
assert_eq!(midi_notes(&[0x90, 60, 100, 64, 90, 0x80, 60, 0, 0x90, 67, 0]), [(60, 100), (64, 90)]);
assert!(loud(&pluck(60, 127)) > 1000);
}
}