use super::{OplOperator, OplPatch};
use crate::{Error, Result};
mod readers;
mod starter;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct OplBankKey {
pub msb: u8,
pub lsb: u8,
pub program: u8,
pub percussion: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct OplInstrument {
pub patch: OplPatch,
pub second: Option<OplPatch>,
pub note_offsets: [i16; 2],
pub detune: i8,
pub fixed_note: Option<u8>,
pub velocity_offset: i8,
}
impl From<OplPatch> for OplInstrument {
fn from(patch: OplPatch) -> Self {
Self {
patch,
..Self::default()
}
}
}
impl OplInstrument {
fn validate(&self) -> Result<()> {
if self.fixed_note.is_some_and(|key| key > 127)
|| self.note_offsets.iter().any(|n| !(-127..=127).contains(n))
{
return Err(Error::Format(
"unsupported OPL instrument pitch or transposition",
));
}
for patch in [Some(self.patch), self.second].into_iter().flatten() {
if patch.feedback > 15 || patch.modulator.waveform > 7 || patch.carrier.waveform > 7 {
return Err(Error::Format("invalid OPL instrument register bits"));
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OplBankEntry {
pub key: OplBankKey,
pub name: String,
pub instrument: OplInstrument,
}
#[derive(Clone, Debug, Default)]
pub struct OplBank {
entries: Vec<OplBankEntry>,
pub(super) depth: u8,
}
impl OplBank {
pub fn new() -> Self {
Self::default()
}
pub fn starter() -> Self {
starter::bank()
}
pub fn insert(&mut self, entry: OplBankEntry) -> Result<()> {
if entry.key.msb > 127 || entry.key.lsb > 127 || entry.key.program > 127 {
return Err(Error::Format(
"OPL bank address must contain seven-bit MIDI values",
));
}
entry.instrument.validate()?;
match self.entries.binary_search_by_key(&entry.key, |e| e.key) {
Ok(index) => self.entries[index] = entry,
Err(index) => {
if self.entries.len() == 32_768 {
return Err(Error::TooLarge);
}
self.entries.insert(index, entry);
}
}
Ok(())
}
pub fn get(&self, key: OplBankKey) -> Option<&OplBankEntry> {
self.entries
.binary_search_by_key(&key, |e| e.key)
.ok()
.map(|i| &self.entries[i])
}
pub fn entries(&self) -> &[OplBankEntry] {
&self.entries
}
pub(super) fn resolve(&self, key: OplBankKey) -> Option<OplInstrument> {
self.get(key)
.or_else(|| {
self.get(OplBankKey {
msb: 0,
lsb: 0,
..key
})
})
.or_else(|| {
self.get(OplBankKey {
msb: 0,
lsb: 0,
program: if key.percussion { 36 } else { 0 },
..key
})
})
.map(|e| e.instrument)
}
}