ada

git clone git://archive.git.mtrnord.blog/MTRNord/ada.git
Log | Files | Refs

commit 22ae97b9b4c53545b7456d56386e0bccd00c513e
Author: Marcel <mtrnord1@gmail.com>
Date:   Thu, 18 Jun 2020 22:16:18 +0200

Initial commit

Took 5 hours 32 minutes

Diffstat:
A.idea/.gitignore | 8++++++++
A.idea/misc.xml | 7+++++++
A.idea/modules.xml | 9+++++++++
A.idea/vcs.xml | 7+++++++
Aada.iml | 13+++++++++++++
Asrc/analyzer_state.rs | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/config.rs | 17+++++++++++++++++
Asrc/data.rs | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/errors.rs | 18++++++++++++++++++
Asrc/start_time.rs | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 303 insertions(+), 0 deletions(-)

diff --git a/.idea/.gitignore b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectRootManager"> + <output url="file://$PROJECT_DIR$/out" /> + </component> +</project> +\ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/ada.iml" filepath="$PROJECT_DIR$/ada.iml" /> + </modules> + </component> +</project> +\ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> +\ No newline at end of file diff --git a/ada.iml b/ada.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="RUST_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + <excludeFolder url="file://$MODULE_DIR$/target" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> +\ No newline at end of file diff --git a/src/analyzer_state.rs b/src/analyzer_state.rs @@ -0,0 +1,41 @@ +use std::mem; +use std::sync::{Arc, Mutex, Once}; + +#[derive(Clone, Debug)] +pub struct AnalyzerState { + running: Arc<Mutex<bool>>, +} + +impl AnalyzerState { + pub(crate) fn singleton() -> Self { + // Initialize it to a null value + static mut ANALYZER_STATE_SINGLETON: *const AnalyzerState = 0 as *const AnalyzerState; + static ANALYZER_STATE_ONCE: Once = Once::new(); + + unsafe { + ANALYZER_STATE_ONCE.call_once(|| { + // Make it + let singleton = AnalyzerState { + running: Arc::new(Mutex::new(false)), + }; + + // Put it in the heap so it can outlive this call + ANALYZER_STATE_SINGLETON = mem::transmute(Box::new(singleton)); + }); + + // Now we give out a copy of the data that is safe to use concurrently. + (*ANALYZER_STATE_SINGLETON).clone() + } + } + + pub fn get(&self) -> bool { + let running = self.running.lock().unwrap(); + let data = running.clone(); + data + } + + pub fn set(&mut self, new_value: bool) { + let mut state = self.running.lock().expect("Could not lock mutex"); + mem::replace(&mut *state, new_value.clone()); + } +} diff --git a/src/config.rs b/src/config.rs @@ -0,0 +1,17 @@ +use crate::errors::ConfigError; +use async_std::fs; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +pub struct Config { + pub model_path: String, + pub model_name: String, +} + +impl Config { + pub async fn load(filepath: String) -> Result<Self, ConfigError> { + let contents = fs::read(filepath).await?; + let config: Self = serde_yaml::from_slice(&contents)?; + Ok(config) + } +} diff --git a/src/data.rs b/src/data.rs @@ -0,0 +1,132 @@ +use std::mem; +use std::pin::Pin; +use std::sync::{Arc, Mutex, Once}; + +use async_std::stream::Stream; +use async_std::task::Context; +use audrey::read::Reader; +use audrey::sample::interpolate::{Converter, Linear}; +use audrey::sample::signal::{from_iter, Signal}; +use futures::task::Poll; + +use crate::start_time::StartTime; +use crate::SAMPLE_RATE; +use std::io::{Cursor, Seek, SeekFrom}; + +#[derive(Clone, Debug)] +pub struct Data { + buffer: Arc<Mutex<Vec<i16>>>, +} + +fn packets_to_wav(data: Vec<i16>) -> Cursor<Vec<u8>> { + println!("Make WAV"); + let wavspec = hound::WavSpec { + channels: 1, + sample_rate: 48_000, + bits_per_sample: 16, + sample_format: hound::SampleFormat::Int, + }; + + let mew_data = Vec::<u8>::new(); + let mut cursor = Cursor::new(mew_data); + + let mut writer = hound::WavWriter::new(&mut cursor, wavspec).unwrap(); + let mut i16_writer = writer.get_i16_writer((data.clone().len()) as u32); + + for sample in data.clone().into_iter().step_by(1) { + i16_writer.write_sample(sample); + } + + i16_writer.flush().unwrap(); + drop(writer); + cursor.seek(SeekFrom::Start(0)).unwrap(); + println!("Return WAV"); + cursor +} + +/// Interpolate the wav to the sample rate used by the deepspeech model. +fn interpolate<F>(wav: F) -> Vec<i16> +where + F: std::io::Read, + F: std::io::Seek, +{ + println!("interpolate WAV"); + let mut reader = Reader::new(wav).unwrap(); + let description = reader.description(); + + let audio_buffer: Vec<_> = if description.sample_rate() == SAMPLE_RATE { + reader.samples().map(|s| s.unwrap()).collect() + } else { + let interpolator = Linear::new([0i16], [0]); + let conv = Converter::from_hz_to_hz( + from_iter(reader.samples::<i16>().map(|s| [s.unwrap()])), + interpolator, + description.sample_rate() as f64, + SAMPLE_RATE as f64, + ); + conv.until_exhausted().map(|v| v[0]).collect() + }; + println!("return interpolated WAV"); + audio_buffer +} + +impl Stream for Data { + type Item = Vec<i16>; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { + let buffer = self.buffer.lock().unwrap(); + let data = buffer.clone(); + + let start_time_struct = StartTime::singleton(); + let ready = start_time_struct.get(); + if !data.is_empty() && ready { + let audio_buffer = packets_to_wav(data.clone()); + let audio_buffer = interpolate(audio_buffer); + Poll::Ready(Some(audio_buffer)) + } else { + // Hack to keep alive + Poll::Ready(Some(Vec::new())) + } + } +} + +impl Data { + pub(crate) fn singleton() -> Data { + // Initialize it to a null value + static mut DATA_SINGLETON: *const Data = 0 as *const Data; + static DATA_ONCE: Once = Once::new(); + + unsafe { + DATA_ONCE.call_once(|| { + // Make it + let singleton = Data { + buffer: Arc::new(Mutex::new(Vec::new())), + }; + + // Put it in the heap so it can outlive this call + DATA_SINGLETON = mem::transmute(Box::new(singleton)); + }); + + // Now we give out a copy of the data that is safe to use concurrently. + (*DATA_SINGLETON).clone() + } + } + + pub fn reset(&mut self) { + let mut state = self.buffer.lock().expect("Could not lock mutex"); + let data = Vec::new(); + mem::replace(&mut *state, data.clone()); + } + + pub fn add(&mut self, buf: cpal::UnknownTypeInputBuffer) { + let mut state = self.buffer.lock().expect("Could not lock mutex"); + match buf { + cpal::UnknownTypeInputBuffer::I16(buffer) => { + let mut local_state = state.clone(); + local_state.extend_from_slice(&*buffer); + mem::replace(&mut *state, local_state.clone()); + } + _ => panic!("Unexpected buffer type"), + } + } +} diff --git a/src/errors.rs b/src/errors.rs @@ -0,0 +1,18 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ConfigError { + /// Represents all cases of `serde_yaml::Error`. + #[error(transparent)] + SerdeError(#[from] serde_yaml::Error), + + /// Represents all other cases of `std::io::Error`. + #[error(transparent)] + IOError(#[from] std::io::Error), +} + +#[derive(Error, Debug)] +pub enum SpeechError { + #[error("Model couldn't be loaded")] + ModelNotFound, +} diff --git a/src/start_time.rs b/src/start_time.rs @@ -0,0 +1,51 @@ +use std::mem; +use std::sync::{Arc, Mutex, Once}; +use std::time::{Duration, Instant}; + +#[derive(Clone, Debug)] +pub struct StartTime { + start: Arc<Mutex<Option<Instant>>>, +} + +impl StartTime { + pub(crate) fn singleton() -> StartTime { + // Initialize it to a null value + static mut START_TIME_SINGLETON: *const StartTime = 0 as *const StartTime; + static START_TIME_ONCE: Once = Once::new(); + + unsafe { + START_TIME_ONCE.call_once(|| { + // Make it + let singleton = StartTime { + start: Arc::new(Mutex::new(None)), + }; + + // Put it in the heap so it can outlive this call + START_TIME_SINGLETON = mem::transmute(Box::new(singleton)); + }); + + // Now we give out a copy of the data that is safe to use concurrently. + (*START_TIME_SINGLETON).clone() + } + } + + pub fn get(&self) -> bool { + let running = self.start.lock().unwrap(); + let data = running.clone(); + match data { + None => false, + Some(v) => { + if Instant::now().duration_since(v) >= Duration::from_secs(3) { + true + } else { + false + } + } + } + } + + pub fn set(&mut self, time: Instant) { + let mut state = self.start.lock().expect("Could not lock mutex"); + mem::replace(&mut *state, Some(time.clone())); + } +}