use crate::{
AudioEngine, DosAudio, Error, Frame, MusicHandle, PlayOptions, PreparedMusic, Result,
SoundConfig, Tick,
};
use std::time::Duration;
pub struct Wav {
rate: u32,
frames: Vec<Frame>,
}
impl Wav {
pub fn sample_rate(&self) -> u32 {
self.rate
}
pub fn frames(&self) -> &[Frame] {
&self.frames
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let length = self
.frames
.len()
.checked_mul(4)
.and_then(|n| u32::try_from(n).ok())
.ok_or(Error::TooLarge)?;
let riff_length = length.checked_add(36).ok_or(Error::TooLarge)?;
let mut bytes = Vec::with_capacity(
usize::try_from(length)
.map_err(|_| Error::TooLarge)?
.checked_add(44)
.ok_or(Error::TooLarge)?,
);
bytes.extend(b"RIFF");
bytes.extend(riff_length.to_le_bytes());
bytes.extend(b"WAVEfmt ");
bytes.extend(16u32.to_le_bytes());
bytes.extend(1u16.to_le_bytes());
bytes.extend(2u16.to_le_bytes());
bytes.extend(self.rate.to_le_bytes());
bytes.extend((self.rate * 4).to_le_bytes());
bytes.extend(4u16.to_le_bytes());
bytes.extend(16u16.to_le_bytes());
bytes.extend(b"data");
bytes.extend(length.to_le_bytes());
for frame in &self.frames {
for &value in frame {
let pcm = (value.clamp(-1.0, 1.0) * 32767.0).round() as i16;
bytes.extend(pcm.to_le_bytes());
}
}
Ok(bytes)
}
}
pub struct OfflineMachine {
control: DosAudio,
engine: AudioEngine,
frames: Vec<Frame>,
}
impl OfflineMachine {
pub fn new(config: SoundConfig, sample_rate: u32) -> Result<Self> {
let (control, engine) =
DosAudio::split(config.sample_rate(sample_rate).latency(Tick::ZERO))?;
Ok(Self {
control,
engine,
frames: Vec::new(),
})
}
pub fn now(&self) -> Tick {
self.control.now()
}
pub fn inb(&mut self, port: u16) -> u8 {
self.control.inb(port)
}
pub fn outb(&mut self, port: u16, value: u8) -> Result<()> {
self.control.try_outb_at(self.now(), port, value)
}
#[cfg(feature = "sb")]
pub fn next_dma_tick(&self) -> Option<Tick> {
self.control.next_dma_tick()
}
#[cfg(feature = "sb")]
pub fn dma_write(&mut self, channel: crate::DmaChannel, value: u8) -> Result<()> {
self.control.try_dma_write_at(self.now(), channel, value)
}
#[cfg(feature = "sb")]
pub fn dma_write16(&mut self, channel: crate::DmaChannel, value: u16) -> Result<()> {
self.control.try_dma_write16_at(self.now(), channel, value)
}
#[cfg(feature = "sb")]
pub fn attach_dma(&mut self, memory: std::sync::Arc<crate::ConventionalMemory>) -> Result<()> {
self.control.attach_dma(memory)
}
#[cfg(feature = "sb")]
pub fn poll_dma_completion(&mut self) -> Option<crate::DmaCompletion> {
self.control.poll_dma_completion()
}
#[cfg(feature = "sb")]
pub fn next_dma_block_tick(&self) -> Option<Tick> {
self.control.next_dma_block_tick()
}
#[cfg(feature = "sb")]
pub fn irq_request(&self) -> Option<crate::IrqRequest> {
crate::IoDevice::irq_request(&self.control, self.now())
}
pub fn sound(&mut self, hz: f64) -> Result<()> {
self.control.sound(hz)
}
pub fn nosound(&mut self) -> Result<()> {
self.control.nosound()
}
pub fn play(&mut self, sequence: impl Into<PreparedMusic>) -> Result<MusicHandle> {
self.engine
.play_at(sequence, PlayOptions::default(), self.now())
}
pub fn play_soundtrack(
&mut self,
soundtrack: std::sync::Arc<crate::Soundtrack>,
preferred: crate::MusicDevice,
options: crate::PlayOptions,
) -> Result<MusicHandle> {
self.engine.play_soundtrack(soundtrack, preferred, options)
}
#[cfg(feature = "sb")]
pub fn play_pcm(
&mut self,
samples: impl Into<std::sync::Arc<[u8]>>,
sample_rate: u32,
) -> Result<crate::SfxHandle> {
self.control.play_pcm(samples, sample_rate)
}
pub fn play_speaker(
&mut self,
music: impl Into<PreparedMusic>,
options: PlayOptions,
) -> Result<crate::SpeakerHandle> {
self.engine.play_speaker(music, options)
}
pub fn advance_to(&mut self, at: Tick) -> Result<()> {
if at < self.now() {
return Err(Error::BackwardsTime);
}
let target = u128::from(at.0)
* u128::from(self.engine.sample_rate())
* u128::from(Tick::HZ_DENOMINATOR)
/ u128::from(Tick::HZ_NUMERATOR);
let target = u64::try_from(target).map_err(|_| Error::TooLarge)?;
let extra = target.saturating_sub(self.engine.stats().frames);
let extra = usize::try_from(extra).map_err(|_| Error::TooLarge)?;
if self
.frames
.len()
.checked_add(extra)
.ok_or(Error::TooLarge)?
> (u32::MAX as usize - 36) / 4
{
return Err(Error::TooLarge);
}
loop {
#[cfg(feature = "sb")]
let next = self.control.next_pcm_tick().map_or(at, |next| next.min(at));
#[cfg(not(feature = "sb"))]
let next = at;
self.render_until(next);
self.control.try_advance_to(next)?;
if next == at {
break;
}
}
Ok(())
}
fn render_until(&mut self, at: Tick) {
let target = u128::from(at.0)
* u128::from(self.engine.sample_rate())
* u128::from(Tick::HZ_DENOMINATOR)
/ u128::from(Tick::HZ_NUMERATOR);
let mut buffer = [[0.0; 2]; 512];
let mut remaining = (target as u64).saturating_sub(self.engine.stats().frames);
while remaining > 0 {
let count = remaining.min(buffer.len() as u64) as usize;
self.engine.render(&mut buffer[..count]);
self.frames.extend_from_slice(&buffer[..count]);
remaining -= count as u64;
}
}
pub fn render_for(&mut self, duration: Duration) -> Result<()> {
let delta = Tick::checked_from_duration(duration).ok_or(Error::TooLarge)?;
self.advance_to(self.now().checked_add(delta).ok_or(Error::TooLarge)?)
}
pub fn render_to_end(mut self) -> Result<Wav> {
#[cfg(feature = "sb")]
let pcm_end = self.control.pcm_end();
#[cfg(not(feature = "sb"))]
let pcm_end = None;
let until = self
.engine
.music_end()?
.into_iter()
.chain(pcm_end)
.chain(self.engine.speaker_end()?)
.max()
.ok_or(Error::Config("no music or PCM buffer is installed"))?
.checked_add(Tick::from_micros(250_000))
.ok_or(Error::TooLarge)?;
self.advance_to(until.max(self.now()))?;
Ok(self.finish())
}
pub fn finish(self) -> Wav {
Wav {
rate: self.engine.sample_rate(),
frames: self.frames,
}
}
}