Skip to content

Commit

Permalink
Merge pull request #7 from SolarLiner/duplex
Browse files Browse the repository at this point in the history
Initial duplex audio wrapper for single input/output stream pairs
  • Loading branch information
SolarLiner authored Nov 11, 2024
2 parents e2726b7 + 3b05ec6 commit fcb7268
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 43 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ log = "0.4.22"
ndarray = "0.15.6"
oneshot = "0.1.8"
thiserror = "1.0.63"
rtrb = "0.3.1"

[dev-dependencies]
anyhow = "1.0.86"
Expand Down
50 changes: 50 additions & 0 deletions examples/duplex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::util::sine::SineWave;
use anyhow::Result;
use interflow::duplex::AudioDuplexCallback;
use interflow::prelude::*;

mod util;

fn main() -> Result<()> {
let input = default_input_device();
let output = default_output_device();
let mut input_config = input.default_input_config().unwrap();
input_config.buffer_size_range = (Some(128), Some(512));
let mut output_config = output.default_output_config().unwrap();
output_config.buffer_size_range = (Some(128), Some(512));
let stream =
duplex::create_duplex_stream(input, input_config, output, output_config, RingMod::new())
.unwrap();
println!("Press Enter to stop");
std::io::stdin().read_line(&mut String::new())?;
stream.eject().unwrap();
Ok(())
}

struct RingMod {
carrier: SineWave,
}

impl RingMod {
fn new() -> Self {
Self {
carrier: SineWave::new(440.0),
}
}
}

impl AudioDuplexCallback for RingMod {
fn on_audio_data(
&mut self,
context: AudioCallbackContext,
input: AudioInput<f32>,
mut output: AudioOutput<f32>,
) {
let sr = context.stream_config.samplerate as f32;
for i in 0..output.buffer.num_samples() {
let inp = input.buffer.get_frame(i)[0];
let c = self.carrier.next_sample(sr);
output.buffer.set_mono(i, inp * c);
}
}
}
42 changes: 4 additions & 38 deletions examples/sine_wave.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,20 @@
use std::f32::consts::TAU;

use anyhow::Result;

use util::sine::SineWave;
use interflow::prelude::*;

mod util;

fn main() -> Result<()> {
env_logger::init();

let device = default_output_device();
println!("Using device {}", device.name());
let stream = device
.default_output_stream(SineWave {
frequency: 440.,
phase: 0.,
})
.default_output_stream(SineWave::new(440.0))
.unwrap();
println!("Press Enter to stop");
std::io::stdin().read_line(&mut String::new())?;
stream.eject().unwrap();
Ok(())
}

struct SineWave {
frequency: f32,
phase: f32,
}

impl AudioOutputCallback for SineWave {
fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput<f32>) {
eprintln!(
"Callback called, timestamp: {:2.3} s",
context.timestamp.as_seconds()
);
let sr = context.timestamp.samplerate as f32;
for i in 0..output.buffer.num_samples() {
output.buffer.set_mono(i, self.next_sample(sr));
}
// Reduce amplitude to not blow up speakers and ears
output.buffer.change_amplitude(0.125);
}
}

impl SineWave {
pub fn next_sample(&mut self, samplerate: f32) -> f32 {
let step = samplerate.recip() * self.frequency;
let y = (TAU * self.phase).sin();
self.phase += step;
if self.phase > 1. {
self.phase -= 1.;
}
y
}
}
1 change: 1 addition & 0 deletions examples/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod enumerate;
pub mod sine;
41 changes: 41 additions & 0 deletions examples/util/sine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use interflow::{AudioCallbackContext, AudioOutput, AudioOutputCallback};
use std::f32::consts::TAU;

pub struct SineWave {
pub frequency: f32,
pub phase: f32,
}

impl AudioOutputCallback for SineWave {
fn on_output_data(&mut self, context: AudioCallbackContext, mut output: AudioOutput<f32>) {
eprintln!(
"Callback called, timestamp: {:2.3} s",
context.timestamp.as_seconds()
);
let sr = context.timestamp.samplerate as f32;
for i in 0..output.buffer.num_samples() {
output.buffer.set_mono(i, self.next_sample(sr));
}
// Reduce amplitude to not blow up speakers and ears
output.buffer.change_amplitude(0.125);
}
}

impl SineWave {
pub fn new(frequency: f32) -> Self {
Self {
frequency,
phase: 0.0,
}
}

pub fn next_sample(&mut self, samplerate: f32) -> f32 {
let step = samplerate.recip() * self.frequency;
let y = (TAU * self.phase).sin();
self.phase += step;
if self.phase > 1. {
self.phase -= 1.;
}
y
}
}
15 changes: 15 additions & 0 deletions src/audio_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,21 @@ impl<S: DataMut> AudioBufferBase<S>
where
S::Elem: Clone,
{
/// Returns a mutable view over each channel of the frame at the given index.
///
/// # Arguments
///
/// * `sample`: Sample index for the frame to return.
///
/// # Panics
///
/// Panics if the sample index is out of range.
///
/// returns: ArrayBase<ViewRepr<&mut <S as RawData>::Elem>, Dim<[usize; 1]>>
pub fn get_frame_mut(&mut self, sample: usize) -> ArrayViewMut1<S::Elem> {
self.storage.column_mut(sample)
}

/// Sets audio data of a single frame, that is all channels at the specified sample index.
/// Panics when the sample is out of range.
pub fn set_frame<'a>(&mut self, sample: usize, data: impl AsArray<'a, S::Elem, Ix1>)
Expand Down
2 changes: 1 addition & 1 deletion src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub mod wasapi;
/// | **Platform** | **Driver** |
/// |:------------:|:----------:|
/// | Linux | ALSA |
/// | macOS | CoreAudio |
/// | macOS | CoreAudio |
/// | Windows | WASAPI |
#[cfg(any(os_alsa, os_coreaudio, os_wasapi))]
#[allow(clippy::needless_return)]
Expand Down
7 changes: 3 additions & 4 deletions src/backends/wasapi/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,16 +264,15 @@ impl<Callback: AudioInputCallback> AudioThread<Callback, Audio::IAudioCaptureCli
}

fn process(&mut self) -> Result<(), error::WasapiError> {
let frames_available = unsafe {
self.interface.GetNextPacketSize()? as usize
};
let frames_available = unsafe { self.interface.GetNextPacketSize()? as usize };
if frames_available == 0 {
return Ok(());
}
let Some(mut buffer) = AudioCaptureBuffer::<f32>::from_client(
&self.interface,
self.stream_config.channels.count(),
)? else {
)?
else {
eprintln!("Null buffer from WASAPI");
return Ok(());
};
Expand Down
Loading

0 comments on commit fcb7268

Please sign in to comment.