Skip to content

Instantly share code, notes, and snippets.

@damncabbage
Last active October 28, 2019 04:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save damncabbage/08b85afb62f30667270bf1f1026abfb4 to your computer and use it in GitHub Desktop.
Save damncabbage/08b85afb62f30667270bf1f1026abfb4 to your computer and use it in GitHub Desktop.
Voyager Image, a crappy non-functional attempt. πŸ˜…
[package]
name = "voyager_images"
version = "0.1.0"
authors = ["Rob Howard <rob@robhoward.id.au>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hound = "3.4.0"
image = "0.22.3"
log = "0.4.8"
simple_logger = "1.0.1"
use std::f32;
use std::io;
use log::{error, info, debug};
use simple_logger;
use std::convert::{AsMut, TryInto};
use hound;
use image::bmp::*;
use image::ColorType;
use std::path::Path;
use std::io::{BufWriter, Write};
use std::fs::File;
fn main() {
simple_logger::init().unwrap();
let (wav, image) = ("./384kHzStereo.wav", "./384kHzStereo.bmp");
match &wav_to_image(wav, image) {
Err(e) => error!("ERROR: {}", e),
Ok(_result) => info!("Done!"),
}
}
// The 'real' main
fn wav_to_image(wav_filename: &str, img_filename: &str) -> io::Result<()> {
let lines = scaled_lines_from_wav(wav_filename, (0, 255))?;
let image = image_from_lines(lines, img_filename)?;
// TODO: write_image_to_file(image, img_filename)?;
// TODO: JUST WRITE IT OUT AS A BIG SHITTY IMAGE
// and then run `convert` on it.
Ok(())
}
fn scaled_lines_from_wav(filename: &str, rescale_range: (u8, u8)) -> io::Result<Vec<Vec<u8>>> {
let mut reader =
hound::WavReader::open(filename).expect(&["Failed to open WAV file: ", filename].concat());
// File info from afinfo:
// 384kHzStereo.wav
// File: 384kHzStereo.wav
// File type ID: WAVE
// Num Tracks: 1
// ----
// Data format: 2 ch, 384000 Hz, 'lpcm' (0x00000009) 32-bit little-endian float
// no channel layout.
// estimated duration: 473.856000 sec
// audio bytes: 1455685632
// audio packets: 181960704
// bit rate: 24576000 bits per second
// packet size upper bound: 8
// maximum packet size: 8
// audio data file offset: 88
// optimized
// source bit depth: F32
// ----
// File info from Hound:
// println!("{:?}", reader.spec());
// WavSpec { channels: 2, sample_rate: 384000, bits_per_sample: 32, sample_format: Float }
//
// Extra info:
// num_samples = 363_921_408
// start (before first 'buffer'): 00:00:15.674 sec, 6_023_017 samples
// length of each 'content' chunk: 0.006 sec, 2564 samples long
// length of each 'buffer' chunk: 0.002 sec, 649 sampless long
// TODO: Use a smarter 'picture frame'/buffer detection method so we don't have to fiddle with fixed
// number; each scan-line and buffer chunk is a different size.
let before_first_buffer_chunk = 6_019_836;
let content_chunk_length = 2_555;
let buffer_chunk_length = 643;
let before_last_buffer_chunk = 7_707_417;
let picture_length = before_last_buffer_chunk - before_first_buffer_chunk; // 1687581
let num_samples = reader.len() as u32;
// - Seek to where we think the first sample is starting (hardcoded)
reader.seek(before_first_buffer_chunk)?;
// - Figure out how long we want to grab a sample for (hardcoded)
// - Iterate for that amount, pulling out only the Left channel (the one with the calibration picture on it).
let samples = reader
.samples::<f32>()
.take((picture_length as usize) * 2)
// .skip(2)?
.fold((true, Vec::new()), |(is_left, mut amps), s| {
if is_left {
amps.push(s.unwrap());
}
(!is_left, amps)
})
.1;
let mut samples_iter = samples.iter();
let mut lines = Vec::new();
for _idx in 0..((picture_length / (content_chunk_length + buffer_chunk_length)) as u32) {
let line = &mut samples_iter;
let (min, max, line) = line
.skip(buffer_chunk_length as usize)
.take(content_chunk_length as usize)
.fold(
(f32::INFINITY, f32::NEG_INFINITY, Vec::new()),
|(mut min, mut max, mut samples), sample| {
if sample < &min {
min = *sample
}
if sample > &max {
max = *sample
}
samples.push(*sample);
(min, max, samples)
},
);
let rescaled_line = line.iter().map(|sample| {
let (rescale_from, rescale_to) = (rescale_range.0 as f32, rescale_range.1 as f32);
let rescaled = (sample - min) * (rescale_from - rescale_to) / (max - min) + rescale_from;
rescaled as u8
}).collect::<Vec<u8>>();
lines.push(rescaled_line);
}
println!("Samples: {} {:?} {:?}", num_samples, lines.len(), lines[0].len());
Ok(lines)
}
fn image_from_lines(lines: Vec<Vec<u8>>, filename: &str) -> io::Result<()> {
// TODO: Actually make things the right size.
// TODO: Not use a scattered bunch of constants everywhere I need to manually do the arithmetic on
// to get the array size for (ie. compile-time arithmetic).
// 525 high x 2564 wide, ie. picture_length high x content_chunk_length wide
let height: u32 = 528;
let width: u32 = 2564;
let mut picture = [0; 1353792];
for row_idx in 0..lines.len() {
let row_idx_base = row_idx * (width as usize);
let v_row = &lines[row_idx];
debug!("{}, {}, {}: {}", row_idx, row_idx_base, row_idx_base + v_row.len(), v_row.len());
for col_idx in 0..v_row.len() {
picture[row_idx_base + col_idx] = v_row[col_idx];
}
}
let path = Path::new(filename);
let file = &mut BufWriter::new(File::create(path)?);
let mut encoder = BMPEncoder::new(file);
encoder.encode(
&picture,
width,
height,
ColorType::Gray(8),
)?;
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment