I would like rust code for the following audio processing app:
Identical tracks get stacked on top of each other all the way to infinite layers (over 1000 googolplex) while each new layer gets time-shifted 1 milisecond, and the extra protruding ends as a result of this time-shifting get convolved back into the begining. The stacking should be done in batches of 10000 layers and after each batch or generation (infinite generations possible as long as the process has not been killed) should be deamplified by -42 db (0.0079432823). The amount of time-shifting between generations will be randomly determined somewhere between 9 miliseconds and 10 seconds. But time-shifting inside generations will always be 1 milisecond. the sample rate of the initial track and the resulting track after closing the app should be 48000 Hz. the input.wav and output.wav files can be 32 bit float or 64 bit float. And the audio quality should be at 32-bit float wav.
use hound::{WavReader, WavWriter};
use rand::Rng;
use std::env;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Instant;
const SAMPLE_RATE: u32 = 48000;
const BATCH_SIZE: usize = 10000;
const MIN_SHIFT_MS: f32 = 9.0;
const MAX_SHIFT_MS: f32 = 10000.0;
const DEAMPLIFICATION_FACTOR: f32 = 0.0079432823;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
eprintln!("Usage: {} <input_wav_file> <output_wav_file>", args[0]);
std::process::exit(1);
}
let input_path = &args[1];
let output_path = &args[2];
println!("Starting audio processing...");
println!("Input file: {}", input_path);
println!("Output file: {}", output_path);
// Print current working directory
if let Ok(current_dir) = env::current_dir() {
println!("Current working directory: {}", current_dir.display());
} else {
println!("Unable to get current working directory");
}
let start_time = Instant::now();
match process_audio(input_path, output_path) {
Ok(_) => println!("Processing completed successfully."),
Err(e) => {
eprintln!("Error during processing: {}", e);
std::process::exit(1);
}
}
let duration = start_time.elapsed();
println!("Total processing time: {:?}", duration);
}
fn process_audio(input_path: &str, output_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let input_path = Path::new(input_path);
println!("Attempting to open file: {}", input_path.display());
if !input_path.exists() {
return Err(format!("Input file does not exist: {}", input_path.display()).into());
}
let mut reader = WavReader::open(input_path)?;
println!("Successfully opened input file");
let spec = reader.spec();
println!("WAV Spec: {:?}", spec);
let mut samples: Vec<f32> = match reader.samples::<f32>().collect() {
Ok(samples) => samples,
Err(e) => {
eprintln!("Error reading samples: {}", e);
std::process::exit(1);
}
};
println!("Read {} samples", samples.len());
let is_processing = Arc::new(AtomicBool::new(true));
let is_processing_clone = Arc::clone(&is_processing);
let processing_thread = thread::spawn(move || {
let mut rng = rand::thread_rng();
let mut generation = 0;
while is_processing_clone.load(Ordering::SeqCst) {
// Process a batch of layers
for _ in 0..BATCH_SIZE {
let shift_samples = (0.001 * SAMPLE_RATE as f32) as usize; // 1 ms shift
let mut shifted_samples = samples.clone();
shifted_samples.rotate_right(shift_samples);
for (sample, shifted) in samples.iter_mut().zip(shifted_samples.iter()) {
*sample += *shifted;
}
}
// Deamplify
for sample in &mut samples {
*sample *= DEAMPLIFICATION_FACTOR;
}
// Random time shift between generations
let random_shift = rng.gen_range(MIN_SHIFT_MS..MAX_SHIFT_MS);
let shift_samples = (random_shift * SAMPLE_RATE as f32 / 1000.0) as usize;
samples.rotate_right(shift_samples);
generation += 1;
if generation % 100 == 0 {
println!("Completed generation {}", generation);
}
}
samples
});
// Wait for user input to stop processing
println!("Press Enter to stop processing and save the output...");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
is_processing.store(false, Ordering::SeqCst);
let processed_samples = processing_thread.join().unwrap();
// Write the processed audio to a new WAV file
println!("Writing output to: {}", output_path);
let mut writer = WavWriter::create(output_path, spec)?;
for &sample in &processed_samples {
writer.write_sample((sample * 32767.0) as i16)?;
}
writer.finalize()?;
println!("Output saved to: {}", output_path);
Ok(())
}
It compiled with no errors and seemed to run without any issues, but the resulting output file was very strange-looking after doing about 35 consecutive amplifications of +50 db. If you would like the output.wav file, I can attach it.
Actually, the one version with the above-stated issue is a version before the code provided above. The version that caused the issue above is the below code (so sorry):
use hound::{WavReader, WavWriter};
use rand::Rng;
use std::env;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Instant;
const SAMPLE_RATE: u32 = 48000;
const BATCH_SIZE: usize = 10000;
const MIN_SHIFT_MS: f32 = 9.0;
const MAX_SHIFT_MS: f32 = 10000.0;
const DEAMPLIFICATION_FACTOR: f32 = 0.0079432823;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
eprintln!("Usage: {} <input_wav_file> <output_wav_file>", args[0]);
std::process::exit(1);
}
let input_path = &args[1];
let output_path = &args[2];
println!("Starting audio processing...");
println!("Input file: {}", input_path);
println!("Output file: {}", output_path);
// Print current working directory
if let Ok(current_dir) = env::current_dir() {
println!("Current working directory: {}", current_dir.display());
} else {
println!("Unable to get current working directory");
}
let start_time = Instant::now();
match process_audio(input_path, output_path) {
Ok(_) => println!("Processing completed successfully."),
Err(e) => {
eprintln!("Error during processing: {}", e);
std::process::exit(1);
}
}
let duration = start_time.elapsed();
println!("Total processing time: {:?}", duration);
}
fn process_audio(input_path: &str, output_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let input_path = Path::new(input_path);
println!("Attempting to open file: {}", input_path.display());
if !input_path.exists() {
return Err(format!("Input file does not exist: {}", input_path.display()).into());
}
let mut reader = WavReader::open(input_path)?;
println!("Successfully opened input file");
let spec = reader.spec();
println!("WAV Spec: {:?}", spec);
let mut samples: Vec<f32> = match reader.samples::<f32>().collect() {
Ok(samples) => samples,
Err(e) => {
eprintln!("Error reading samples: {}", e);
std::process::exit(1);
}
};
println!("Read {} samples", samples.len());
let is_processing = Arc::new(AtomicBool::new(true));
let is_processing_clone = Arc::clone(&is_processing);
let processing_thread = thread::spawn(move || {
let mut rng = rand::thread_rng();
let mut generation = 0;
while is_processing_clone.load(Ordering::SeqCst) {
// Process a batch of layers
for _ in 0..BATCH_SIZE {
let shift_samples = (0.001 * SAMPLE_RATE as f32) as usize; // 1 ms shift
let mut shifted_samples = samples.clone();
shifted_samples.rotate_right(shift_samples);
for (sample, shifted) in samples.iter_mut().zip(shifted_samples.iter()) {
*sample += *shifted;
}
}
// Deamplify
for sample in &mut samples {
*sample *= DEAMPLIFICATION_FACTOR;
}
// Random time shift between generations
let random_shift = rng.gen_range(MIN_SHIFT_MS..MAX_SHIFT_MS);
let shift_samples = (random_shift * SAMPLE_RATE as f32 / 1000.0) as usize;
samples.rotate_right(shift_samples);
generation += 1;
if generation % 100 == 0 {
println!("Completed generation {}", generation);
}
}
samples
});
// Wait for user input to stop processing
println!("Press Enter to stop processing and save the output...");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
is_processing.store(false, Ordering::SeqCst);
let processed_samples = processing_thread.join().unwrap();
// Write the processed audio to a new WAV file
println!("Writing output to: {}", output_path);
let mut writer = WavWriter::create(output_path, spec)?;
for &sample in &processed_samples {
writer.write_sample((sample * 32767.0) as i16)?;
}
writer.finalize()?;
println!("Output saved to: {}", output_path);
Ok(())
}
How did you come up with the deamplification factor?
Assuming the input signal is a constant signal of 1.0, after each iteration in the batch the signal value will double. Meaning it will surpass the max allowed f32 value after about 128 iterations, but you are running 10000 of them. Are you sure you are aiming for this exponential behaviour? Even if you fix it to more stable or linear behaviour, I'm pretty sure you end up with noise.
It might be worthwhile to print some of the sample values after each iteration to check for divergence.
I haven't had a working version in the way explained in the main post yet. But I have done a Lisp version in Audacity audio editor's Nyquist Prompt without convolution and all super-generational deamplifications were done manually (one super generation was 50 normal generations), and -42 db works better than any other value I have tried. Every once in a while, I have imagined that I can stop the processing and make sure that the sound volume is at a proper level, just to be sure.
It happens to me all the time, as I work on many layers of identical tracks being stacked on top of each other while time-shifting and convolvement even things out.
I say, let's start fresh! Anyone, can make a program to layer in generations with a random number of seconds of time-shifting between layers and a larger random number of seconds of time-shifting between generations? The extra protruding tails should always be convolved back into the beginning, and we let the subconscious mind figure everything out.
That's not a constant signal of 1.0 - you wouldn't hear that.
I don't want to sound like an expert, I have only very basic knowledge about digital signal processing. Having said that: What you are doing looks to me like stacking random comb filters. Normally people try to avoid comb filtering.
Have you tried it with only one generation? Does it change the signal to sound hollow and nasal?
I noticed in your code that you use spec from the input file for the output file - which is ok, as you want to keep the format, but you also give output samples as i16 - so it will be 16bit audio.
And I would check for overflows writer.write_sample((sample * 32767.0) as i16)?;. Having samples > 1.0 doesn't give a pleasant sound.
Do you run this code with a debug or a release build?
Sure, I started out with Audacity audio editor and manually stacking clones of the original audio, but after several months, I realized what a huge task I was up against (HeavenS on Earth, and staunch opposition by the establishment). So then moved on to macros..., and now I am here. I don't know much about programming, but I know quite a bit about layering audio tracks with time-shifting and convolving the protruding ends.
You didn't say whether you're running the binary directly or with cargo run. Note that if you build it as above and then use cargo run, you'll run it with the debug (non-release) build. cargo run --release is needed to run the release version. (Or you can just build it like you said, and then run the binary directly.)
I just realized that release vs. debug doesn't make a difference in this case. If you overflow, the signal will be cut at 32767 resp. -32768, which will result in distortion.
You should make sure that all sample values stay between -1.0 and 1.0.
use std::env;
use std::fs::File;
use std::time::Duration;
use rand::Rng;
use rodio::{Decoder, OutputStream, Sink, Source};
use hound;
const LAYERS_PER_GENERATION: usize = 10000;
const TIME_SHIFT_MIN: f32 = 1.0;
const TIME_SHIFT_MAX: f32 = 9.0;
const DEAMPLIFICATION: f32 = -42.0; // Deamplification in dB
fn main() {
// Read input and output file paths from command-line arguments
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
println!("Usage: {} <input.wav> <output.wav>", args[0]);
return;
}
let input_path = &args[1];
let output_path = &args[2];
let layers_per_gen = LAYERS_PER_GENERATION;
let mut gen_count: usize = 0;
println!("Starting HeavenS on Earth subliminal audio processing...");
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
// Initialize the Decoder from the input file
let track = Decoder::new(File::open(input_path).unwrap()).unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
// Append the track to the sink initially (no need to clone, simply reinitialize later if needed)
sink.append(track);
loop {
gen_count += 1;
println!("Processing generation: {}", gen_count);
// Process generation with uniform time-shift
let mut track = Decoder::new(File::open(input_path).unwrap()).unwrap();
process_generation(&mut track, layers_per_gen);
// Apply deamplification after each generation
let amplified_track = deamplify_track(track, DEAMPLIFICATION);
// If the user presses Enter, break the loop and stop processing
if check_for_enter() {
println!("Processing stopped by user.");
break;
}
// Pass the amplified track for further processing or saving
save_to_wav(output_path, amplified_track.convert_samples::<f32>());
}
}
// Simulate generation processing with time-shift
fn process_generation(_track: &mut Decoder<File>, layers: usize) {
for _ in 0..layers {
let time_shift = random_delay(TIME_SHIFT_MIN, TIME_SHIFT_MAX);
std::thread::sleep(Duration::from_millis(time_shift as u64));
println!("Processed a layer with {} ms time-shift.", time_shift);
}
}
// Generate a random delay between min and max milliseconds
fn random_delay(min: f32, max: f32) -> f32 {
rand::thread_rng().gen_range(min..max)
}
// Deamplify the track and return an amplified version
fn deamplify_track(track: Decoder<File>, db: f32) -> impl Source<Item = i16> {
// Convert dB to linear amplitude scale
let attenuation_factor = 10f32.powf(db / 20.0);
println!("Deamplifying track by {} dB (factor: {}).", db, attenuation_factor);
// Apply the deamplification factor by adjusting the audio volume
track.amplify(attenuation_factor)
}
// Check for Enter press to stop processing
fn check_for_enter() -> bool {
println!("Press Enter to stop or wait for the next generation.");
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.trim().is_empty() // If Enter is pressed, return true
}
// Save the processed track to a 32-bit float WAV file
fn save_to_wav(output_path: &str, track: impl Source<Item = f32>) {
let spec = hound::WavSpec {
channels: 2, // Assuming stereo
sample_rate: 48000,
bits_per_sample: 32,
sample_format: hound::SampleFormat::Float,
};
let mut writer = hound::WavWriter::create(output_path, spec).unwrap();
// Save audio samples from the amplified track
for sample in track.convert_samples::<f32>() {
writer.write_sample(sample).unwrap();
}
writer.finalize().unwrap();
println!("Successfully saved the output to {}", output_path);
}
The TOML is:
[package]
name = "heavens_on_earth"
version = "0.1.0"
edition = "2021"
description = "A command-line program for subliminal audio processing"
license = "MIT"
repository = "https://github.com/your-username/heavens-on-earth"
[dependencies]
rand = "0.8"
rodio = "0.17"
hound = "3.4"
[package.metadata]
authors = ["Bryan J Benevolence <bryanjbenevolence@example.com>"]
When I compile the code I get no errors, but I run it and get no output.wav file.
The question wasn't whether you know what you're doing, but whether you tested your code with only one generation
I tried it without any stacking, just reading the input file and writing into the output file and already that doesn't work properly. The culprit is:
writer.write_sample((sample * 32767.0) as i16)?;
As already written before, you want to have a WAV file with 32bit floats, but you're recalculating and giving samples as i16 - I didn't look what conversions hound is doing but with
writer.write_sample(sample)?;
I get a correct output file.
The next thing is, that your batch size is too big. With your batch size a value for a sample can grow up to 2^10000. That's far too big for f32.
Btw. you can calculate your deamplification factor. You take the maximum sample value before and the maximum after and (before/after) is then the factor.