//! A basic output stream example, using an Output AudioUnit to generate a sine wave.

extern crate coreaudio;

use coreaudio::audio_unit::render_callback::{self, data};
use coreaudio::audio_unit::{AudioUnit, IOType, SampleFormat};
use std::f64::consts::PI;

struct SineWaveGenerator {
    time: f64,
    /// generated frequency in Hz
    freq: f64,
    /// magnitude of generated signal
    volume: f64,
}

impl SineWaveGenerator {
    fn new(freq: f64, volume: f64) -> Self {
        SineWaveGenerator {
            time: 0.,
            freq,
            volume,
        }
    }
}

impl Iterator for SineWaveGenerator {
    type Item = f32;
    fn next(&mut self) -> Option<f32> {
        self.time += 1. / 44_100.;
        let output = ((self.freq * self.time * PI * 2.).sin() * self.volume) as f32;
        Some(output)
    }
}

fn main() -> Result<(), coreaudio::Error> {
    #[cfg(any(target_os = "ios", target_os = "visionos", target_os = "tvos"))]
    unsafe {
        let session = objc2_avf_audio::AVAudioSession::sharedInstance();

        #[cfg(target_os = "tvos")]
        let _ = session.setCategory_error(
            objc2_avf_audio::AVAudioSessionCategoryPlayback.expect("Failed to get option"),
        );
        #[cfg(any(target_os = "ios", target_os = "tvos"))]
        let _ = session.setCategory_withOptions_error(
            objc2_avf_audio::AVAudioSessionCategoryPlayAndRecord.expect("Failed to get options"),
            objc2_avf_audio::AVAudioSessionCategoryOptions::DefaultToSpeaker,
        );
    }
    let frequency_hz = 440.;
    let volume = 0.15;
    let mut samples = SineWaveGenerator::new(frequency_hz, volume);

    // Construct an Output audio unit that delivers audio to the default output device.
    #[cfg(target_os = "macos")]
    let mut audio_unit = AudioUnit::new(IOType::DefaultOutput)?;

    #[cfg(any(target_os = "ios", target_os = "visionos", target_os = "tvos"))]
    let mut audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::RemoteIO)?;

    // iOS/tvOS/visionOS don't let you reconfigure an "initialized" audio unit, so uninitialize it
    #[cfg(any(target_os = "ios", target_os = "visionos", target_os = "tvos"))]
    audio_unit.uninitialize()?;

    // Read the input format. This is counterintuitive, but it's the format used when sending
    // audio data to the AudioUnit representing the output device. This is separate from the
    // format the AudioUnit later uses to send the data to the hardware device.
    let stream_format = audio_unit.output_stream_format()?;
    println!("{:#?}", &stream_format);

    // For this example, our sine wave expects `f32` data.
    assert!(SampleFormat::F32 == stream_format.sample_format);

    type Args = render_callback::Args<data::NonInterleaved<f32>>;
    audio_unit.set_render_callback(move |args| {
        let Args {
            num_frames,
            mut data,
            ..
        } = args;
        for i in 0..num_frames {
            let sample = samples.next().unwrap();
            for channel in data.channels_mut() {
                channel[i] = sample;
            }
        }
        Ok(())
    })?;
    #[cfg(any(target_os = "ios", target_os = "visionos", target_os = "tvos"))]
    audio_unit.initialize()?;

    audio_unit.start()?;
    println!("Audio started - playing 440Hz sine wave");

    std::thread::sleep(std::time::Duration::from_millis(10_000));

    Ok(())
}
