use super::{
MusicCapabilities, MusicEvent, MusicHandle, MusicTarget, PlayOptions, Player, PreparedMusic,
validate_play,
};
use crate::{Error, OplChip, Result, Tick};
use std::sync::Arc;
mod restore;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(u8)]
#[non_exhaustive]
pub enum MusicDevice {
Opl2 = 0,
DualOpl2 = 1,
Opl3 = 2,
GeneralMidi = 3,
Mt32 = 4,
CustomMidi = 5,
Psg = 6,
Speaker = 7,
Native = 8,
}
impl MusicDevice {
pub(crate) const ALL: [Self; 9] = [
Self::Opl2,
Self::DualOpl2,
Self::Opl3,
Self::GeneralMidi,
Self::Mt32,
Self::CustomMidi,
Self::Psg,
Self::Speaker,
Self::Native,
];
pub(crate) fn from_code(code: u32) -> Option<Self> {
Self::ALL.get(code as usize).copied()
}
fn matches(self, target: MusicTarget) -> bool {
matches!(
(self, target),
(Self::Opl2, MusicTarget::Opl(OplChip::Opl2))
| (Self::DualOpl2, MusicTarget::Opl(OplChip::DualOpl2))
| (Self::Opl3, MusicTarget::Opl(OplChip::Opl3))
| (
Self::GeneralMidi | Self::Mt32 | Self::CustomMidi,
MusicTarget::Midi
)
| (Self::Psg, MusicTarget::Psg)
| (Self::Speaker, MusicTarget::Speaker)
| (Self::Native, MusicTarget::Native)
)
}
}
#[derive(Clone, Debug)]
pub(crate) struct Checkpoint {
pub at: Tick,
pub index: usize,
pub events: Vec<MusicEvent>,
}
#[derive(Clone, Debug)]
pub(crate) struct Arrangement {
pub device: MusicDevice,
pub music: PreparedMusic,
pub checkpoints: Vec<Checkpoint>,
}
#[derive(Clone, Debug, Default)]
pub struct Soundtrack {
pub(crate) arrangements: Vec<Arrangement>,
fallbacks: [Option<MusicDevice>; 9],
}
impl Soundtrack {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, device: MusicDevice, music: impl Into<PreparedMusic>) -> Result<()> {
let music = music.into();
if !device.matches(music.target()) {
return Err(Error::Config(
"soundtrack device does not match arrangement target",
));
}
if self.arrangements.iter().any(|a| {
a.device != device
&& (a.music.duration != music.duration || a.music.loop_start != music.loop_start)
}) {
return Err(Error::Config(
"soundtrack arrangements require equal durations and loop points",
));
}
let checkpoints = restore::checkpoints(&music, device)?;
let arrangement = Arrangement {
device,
music,
checkpoints,
};
match self
.arrangements
.binary_search_by_key(&device, |a| a.device)
{
Ok(i) => self.arrangements[i] = arrangement,
Err(i) => self.arrangements.insert(i, arrangement),
}
Ok(())
}
pub fn fallback(&mut self, from: MusicDevice, to: MusicDevice) -> Result<()> {
let mut next = Some(to);
for _ in 0..9 {
match next {
Some(device) if device == from => {
return Err(Error::Config("cyclic soundtrack fallback"));
}
Some(device) => next = self.fallbacks[device as usize],
None => {
self.fallbacks[from as usize] = Some(to);
return Ok(());
}
}
}
Err(Error::Config("cyclic soundtrack fallback"))
}
#[cfg(feature = "synthie")]
pub fn synthie(song: &synthie::pattern::Song, chip: OplChip, sample_rate: u32) -> Result<Self> {
super::synthie::soundtrack(song, chip, sample_rate)
}
pub fn arrangement(&self, device: MusicDevice) -> Option<&PreparedMusic> {
self.find(device).map(|a| &a.music)
}
pub(crate) fn find(&self, device: MusicDevice) -> Option<&Arrangement> {
self.arrangements
.binary_search_by_key(&device, |a| a.device)
.ok()
.map(|i| &self.arrangements[i])
}
pub(crate) fn select(
&self,
preferred: MusicDevice,
capabilities: MusicCapabilities,
) -> Option<MusicDevice> {
let mut device = preferred;
for _ in 0..9 {
if self.find(device).is_some_and(|a| {
capabilities.accepts(a.music.target)
&& (!matches!(
device,
MusicDevice::GeneralMidi | MusicDevice::Mt32 | MusicDevice::CustomMidi
) || capabilities.midi_device == Some(device))
}) {
return Some(device);
}
device = self.fallbacks[device as usize]?;
}
None
}
}
pub(crate) fn player(
soundtrack: Arc<Soundtrack>,
preferred: MusicDevice,
capabilities: MusicCapabilities,
rate: u32,
options: PlayOptions,
at: Tick,
last: Tick,
) -> Result<(MusicHandle, Player)> {
let device = soundtrack
.select(preferred, capabilities)
.ok_or(Error::Config(
"no usable soundtrack arrangement or configured fallback",
))?;
let music = soundtrack
.find(device)
.expect("selected arrangement")
.music
.clone();
validate_play(capabilities, &music, options, at)?;
let mut handle = MusicHandle::new(options.volume, rate);
let state = Arc::get_mut(&mut handle.state).expect("new handle");
state
.device
.store(device as u32, std::sync::atomic::Ordering::Relaxed);
for requested in MusicDevice::ALL {
state.device_routes[requested as usize] = soundtrack
.select(requested, capabilities)
.map_or(u8::MAX, |d| d as u8);
}
let mut player = Player::new(music, options, &handle, at, last);
player.soundtrack = Some(soundtrack);
Ok((handle, player))
}