// trriggy - a trigger/drum replacer in Rust
// Copyright (C) 2018 Harald Eilertsen <haraldei@anduin.net>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use clap::{self, crate_name, crate_authors, crate_version, crate_description};
use hound;
use std::io::Read;
use std::iter::FromIterator;
#[derive(Default)]
struct Trigger {
trigged: bool,
playing: bool,
acc: u32,
sample_pos: usize,
sample: Vec<i16>,
}
impl Trigger {
pub fn new<T: Read>(samples: hound::WavReader<T>) -> Trigger {
Trigger::default().read_samples(samples)
}
fn read_samples<T: Read>(mut self, mut reader: hound::WavReader<T>) -> Trigger {
self.sample = Vec::from_iter(reader.samples().map(|s| s.unwrap()));
self
}
pub fn process(&mut self, sample: i16) -> i16 {
let mut res = 0i16;
let abs = sample.abs() as u32;
let div = if (abs > self.acc) {
abs - self.acc
}
else {
0
};
self.acc = (self.acc + abs) / 2;
if !self.trigged && div > 5000 {
self.trigged = true;
self.playing = true;
self.sample_pos = 0;
}
else if self.trigged && self.acc < 250 {
self.trigged = false;
}
if self.playing {
res = self.sample[self.sample_pos];
self.sample_pos += 1;
}
if self.sample_pos == self.sample.len() {
self.playing = false;
}
res
}
}
fn print_wavspec<R: Read>(spec: &hound::WavSpec, r: &hound::WavReader<R>) {
println!(" channels: {}", spec.channels);
println!(" samplerate: {}", spec.sample_rate);
println!(" bits/sample: {}", spec.bits_per_sample);
println!(" sample format: {}", match spec.sample_format {
hound::SampleFormat::Int => "integer",
hound::SampleFormat::Float => "floating point",
});
println!(" duration: {} samples/{} sec/{} min",
r.duration(),
r.duration()/spec.sample_rate,
r.duration()/(spec.sample_rate * 60));
}
fn main() {
let m = clap::app_from_crate!()
.arg(clap::Arg::with_name("sample")
.short("s")
.long("sample")
.value_name("SAMPLE_FILE")
.help("The sample to replace when trigged.")
.takes_value(true)
.required(true))
.arg(clap::Arg::with_name("output")
.short("o")
.long("output")
.value_name("OUTPUT_FILE")
.help("Where to store the result.")
.takes_value(true))
.arg(clap::Arg::with_name("INPUT")
.help("The input file")
.required(true))
.get_matches();
let filename = m.value_of("INPUT").unwrap();
let mut reader = hound::WavReader::open(&filename).unwrap();
println!("Reading file {}:", &filename);
let spec = reader.spec();
print_wavspec(&spec, &reader);
let sample_filename = m.value_of("sample").unwrap();
println!("Using sample file: {}", sample_filename);
let mut sample_file = hound::WavReader::open(sample_filename).unwrap();
print_wavspec(&sample_file.spec(), &sample_file);
let output_filename = m.value_of("output").unwrap_or("output.wav");
println!("Writing to output: {}", output_filename);
let mut output = hound::WavWriter::create(output_filename, spec).unwrap();
let samples = reader.samples::<i16>();
let mut triggey = Trigger::new(sample_file);
samples
.map(|s| s.unwrap())
.for_each(|s| {
output.write_sample(triggey.process(s)).unwrap();
});
output.finalize().unwrap();
}