Skip to content

Commit

Permalink
Turn custom sound recorder example into interactive graphical example
Browse files Browse the repository at this point in the history
Also make it showcase a lot more public items

Code cleanup later, I guess.
  • Loading branch information
crumblingstatue committed Oct 23, 2024
1 parent 3fcdee8 commit 8610e4c
Show file tree
Hide file tree
Showing 2 changed files with 241 additions and 65 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ required-features = ["graphics"]

[[example]]
name = "custom-sound-recorder"
required-features = ["audio"]
required-features = ["audio", "graphics"]

[[example]]
name = "cursor"
Expand Down
304 changes: 240 additions & 64 deletions examples/custom-sound-recorder.rs
Original file line number Diff line number Diff line change
@@ -1,85 +1,261 @@
use {
sfml::audio::{capture, SoundRecorder, SoundRecorderDriver},
std::{error::Error, fs::File, io::Write},
sfml::{
audio::{capture, Sound, SoundBuffer, SoundRecorder, SoundRecorderDriver},
graphics::{Color, Font, RectangleShape, RenderTarget, RenderWindow, Text, Transformable},
window::{Event, Key, Style},
},
std::{
error::Error,
sync::mpsc::{Receiver, Sender},
},
};

struct FileRecorder {
file: File,
include!("../example_common.rs");

struct MyRecorder {
sender: Sender<Vec<i16>>,
}

impl SoundRecorder for FileRecorder {
fn on_process_samples(&mut self, data: &[i16]) -> bool {
match self.file.write_all(unsafe {
std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2)
}) {
Ok(_) => true,
Err(e) => {
eprintln!("Error writing to file: {e}");
false
}
}
impl MyRecorder {
pub fn new() -> (Self, Receiver<Vec<i16>>) {
let (send, recv) = std::sync::mpsc::channel();
(Self { sender: send }, recv)
}
}

impl FileRecorder {
fn create(path: &str) -> std::io::Result<Self> {
let file = File::create(path)?;
Ok(Self { file })
impl SoundRecorder for MyRecorder {
fn on_process_samples(&mut self, samples: &[i16]) -> bool {
self.sender.send(samples.to_vec()).unwrap();
true
}
}

struct TextWriter<'font> {
text: Text<'font>,
x: f32,
y_cursor: f32,
font_size: u32,
}

impl<'font> TextWriter<'font> {
fn new(font: &'font Font, font_size: u32, x: f32, init_y: f32) -> Self {
Self {
text: Text::new("", font, font_size),
x,
y_cursor: init_y,
font_size,
}
}
fn write(&mut self, text: &str, rw: &mut RenderWindow) {
self.text.set_string(text);
self.text.set_position((self.x, self.y_cursor));
rw.draw(&self.text);
self.y_cursor += self.font_size as f32 + 4.0;
}
}

enum Mode {
Main,
SetDevice,
SetSampleRate,
SetChannelCount,
Export,
}

fn main() -> Result<(), Box<dyn Error>> {
example_ensure_right_working_dir();

assert!(
capture::is_available(),
"Sorry, audio capture is not supported by your system"
);
let default_device = capture::default_device();
let devices = capture::available_devices();
println!("{} recording devices available:", devices.len());
for (i, device) in devices.iter().enumerate() {
let def_str = if device == &*default_device {
" (default)"
} else {
""
};
println!("Device {i}: {device}{def_str}");
}
print!("Enter device and channel count: ");
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let mut tokens = input.trim().split_whitespace();
let device_idx = match tokens.next() {
Some(tok) => tok.parse::<usize>()?,
None => 0,
};
let channel_count = match tokens.next() {
Some(tok) => tok.parse::<u32>()?,
None => 1,
};
let mut fr = FileRecorder::create("hello.pcm")?;
let mut recorder = SoundRecorderDriver::new(&mut fr);
match devices.get(device_idx) {
Some(dev) => recorder.set_device(dev.to_str()?)?,
None => eprintln!("Invalid device index: {device_idx}"),
}
recorder.set_channel_count(channel_count);
recorder.start(44_100)?;
println!(
"Recorder properties:\n\
sample rate: {}\n\
channel count: {}\n\
device: {}",
recorder.sample_rate(),
recorder.channel_count(),
recorder.device()
);
let mut left = 5000;
while left > 0 {
std::thread::sleep(std::time::Duration::from_millis(100));
left -= 100;
print!("You have {left} left to record\r");
let _ = std::io::stdout().flush();
let mut rw = RenderWindow::new(
(800, 600),
"Custom sound recorder",
Style::CLOSE,
&Default::default(),
)?;
rw.set_vertical_sync_enabled(true);
let font = Font::from_file("sansation.ttf")?;
let (mut rec, recv) = MyRecorder::new();
let mut samp_buf = Vec::new();
let mut driver = SoundRecorderDriver::new(&mut rec);
let mut started = false;
let mut snd_buf = SoundBuffer::new()?;
let mut samp_accum = Vec::new();
let mut sound = Some(Sound::new());
let mut selected_dev_idx = 0;
let mut mode = Mode::Main;
let mut input_buf = String::new();
let mut desired_sample_rate = 44_100;
let mut desired_channel_count = 2;

while rw.is_open() {
while let Some(ev) = rw.poll_event() {
match ev {
Event::Closed => rw.close(),
Event::KeyPressed { code, .. } => match mode {
Mode::Main => match code {
Key::R => {
if started {
driver.stop();
sound = None;
snd_buf.load_from_samples(
&samp_accum[..],
desired_channel_count,
desired_sample_rate,
)?;
samp_accum.clear();
started = false;
} else {
driver.set_device(devices[selected_dev_idx].to_str()?)?;
driver.set_channel_count(desired_channel_count);
driver.start(desired_sample_rate)?;
started = true;
}
}
Key::P => {
if !started {
let sound = sound.insert(Sound::with_buffer(&snd_buf));
sound.play();
}
}
Key::D => mode = Mode::SetDevice,
Key::S => {
input_buf = desired_sample_rate.to_string();
mode = Mode::SetSampleRate;
}
Key::C => {
input_buf = desired_channel_count.to_string();
mode = Mode::SetChannelCount;
}
Key::E => {
input_buf = "export.wav".to_string();
mode = Mode::Export;
}
_ => {}
},
Mode::SetDevice => match code {
Key::Up => {
selected_dev_idx -= selected_dev_idx.saturating_sub(1);
}
Key::Down => {
if selected_dev_idx + 1 < devices.len() {
selected_dev_idx += 1;
}
}
Key::Enter | Key::Escape => {
mode = Mode::Main;
}
_ => {}
},
Mode::SetSampleRate => {
if code == Key::Enter {
desired_sample_rate = input_buf.parse()?;
mode = Mode::Main;
}
}
Mode::SetChannelCount => {
if code == Key::Enter {
desired_channel_count = input_buf.parse()?;
mode = Mode::Main;
}
}
Mode::Export => {
if code == Key::Enter {
snd_buf.save_to_file(&input_buf)?;
mode = Mode::Main;
}
}
},
Event::TextEntered { unicode } => match mode {
Mode::SetSampleRate | Mode::SetChannelCount => {
if unicode.is_ascii_digit() {
input_buf.push(unicode);
} else if unicode == 0x8 as char {
input_buf.pop();
}
}
Mode::Export => {
if !unicode.is_ascii_control() {
input_buf.push(unicode);
} else if unicode == 0x8 as char {
input_buf.pop();
}
}
Mode::Main | Mode::SetDevice => {}
},
_ => {}
}
}
if let Ok(samples) = recv.try_recv() {
samp_accum.extend_from_slice(&samples);
samp_buf = samples;
}
rw.clear(Color::rgb(10, 60, 40));
let mut writer = TextWriter::new(&font, 20, 0.0, 0.0);
macro_rules! w {
($($arg:tt)*) => {
writer.write(&format!($($arg)*), &mut rw);
}
}
match mode {
Mode::Main => {
w!("D - set device, S - set sample rate, C - set channel count, E - export");
let s = if started {
"Press R to stop recording"
} else {
"Press R to start recording. Press P to play the recording."
};
w!("{s}");
w!(
"{} @ {} Hz\n{} samples, {} channels, {} bytes recorded",
driver.device(),
driver.sample_rate(),
samp_buf.len(),
driver.channel_count(),
samp_accum.len() * 2,
);
let mut rect = RectangleShape::new();
for (i, &sample) in samp_buf.iter().enumerate() {
let ratio = samp_buf.len() as f32 / rw.size().x as f32;
rect.set_position((i as f32 / ratio, 300.0));
rect.set_size((2.0, sample as f32 / 48.0));
rw.draw(&rect);
}
}
Mode::SetDevice => {
for (i, dev) in devices.iter().enumerate() {
let default_str = if dev == &*default_device {
" (default)"
} else {
""
};
let color = if selected_dev_idx == i {
Color::YELLOW
} else {
Color::WHITE
};
writer.text.set_fill_color(color);
w!("{}: {}{default_str}", i + 1, dev.to_str()?);
}
}
Mode::SetSampleRate => {
w!("Enter desired sample rate");
w!("{input_buf}");
}
Mode::SetChannelCount => {
w!("Enter desired channel count");
w!("{input_buf}");
}
Mode::Export => {
w!("Enter filename to export as");
w!("{input_buf}");
}
}
rw.display();
}
Ok(())
}

0 comments on commit 8610e4c

Please sign in to comment.