initial commit! going to pretend it's presentable

This commit is contained in:
Fluora 2021-06-15 13:11:13 -05:00
commit 544ccd7e88
33 changed files with 2430 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
bad
target
scripts
proto

57
README.md Normal file
View File

@ -0,0 +1,57 @@
# Fluora's Miscellaneous Digital Signal Toolbox
### Fluora
### 2019 onwards
### Public domain
---
### What's this?
I've made (and continue to keep making) a bunch of little Rust programs for debugging DSP projects and simulating
potential techniques. They all have a few things in common:
- They're really bad and have no real error handling.
- They operate exclusively on 32-bit big-endian floating-point samples, and don't expect to find headers.
- They do about one thing each, usually by reading a file and writing to another, but sometimes by writing back to the same file.
- They're very inefficient code, and only intended to be run on small files.
- They have very little documentation and I'm really sorry.
- None of them depend on anything. There are no crates here.
### What should I use these for?
You shouldn't use it at all. If you want to anyway, I can't stop you, but you should probably find a saner DSP toolbox
if you want to actually get work done.
### How do I compile them all at once?
You can run the build.sh script I included, which will try to compile everything in this directory that ends in ".rs", and put
all the resulting binaries in ./target/.
### What do they do?
Here's the fast version, covering everything included so far:
- `addmix` adds two signals, producing a combined output.
- `mulmix` multiplies two signals (like an RF mixer or ring modulator), generating harmonics at the sum and difference of frequencies.
- `bomp` is a plotter that generates simple images of signals that you can look at.
- `termplot` is another, worse plotter, which does something similar using rows of block characters in the terminal.
- `correlate` takes the correlation of two signals (multiply-sum for all possible time offsets).
- `correlate_cyclic` does the same, but makes the signals periodic.
- `fsinechirp` generates chirps (rising or falling tones) with linear change in frequency.
- `tsinechirp` generates chirps with linear change in period.
- `squarechirp` generates chirps using square waves.
- `normalize` multiplies signals by a constant value such that the maximum value becomes 1.
- `rectify` takes the absolute value of a signal, flipping negative values into positive values.
- `peakdetect` zeroes out all samples that are not local maxima.
- `splitbits` extracts the sign from samples, producing an output whose values are only 1 or -1.
- `upsample` increases the sample rate of a signal by an integer factor with linear interpolation.
- `gfilter` applies a gaussian low-pass filter.
- `sfilter` applies a sinc low-pass filter.
- `swindow` applies a sinc low-pass and high-pass filter, forming a rectangular bandpass filter.
- `worsen` adds a variable amount of random noise to a signal.
- `skew` simulates mismatches in sampling frequency and phase.
- `sft` is a naĩve, slow (O(n²)) implementation of a discrete Fourier transform. It's like an FFT, but instead of fast, it's slow.
- `waterfall` generates 2D frequency/time spectrum images with repeated Fourier transforms.
- `zadoffchu` generates Zadoff-Chu sequences, which are low-autocorrelation functions used in telecom.
- `unitvex` generates a sequence of complex unit vectors, using input data to set their angles.
- `vecsum` takes two signals and combines them with the Pythagorean theorem, e.g. to produce a power spectrum from a complex DFT.
- `dcst` simultaneously takes the discrete cosine transform (DCT) and discrete sine transform (DST) of a signal.
- `pad` adds arbitrary amounts of silence before and after a signal.
- `integrate` takes the integral of a signal.
- `differentiate` takes the derivative of a signal.
- `orthosine` generates orthogonal waveforms on an interval, i.e. OFDM carriers.
- `echospace` simulates reflections in a 3D volume.

11
build.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
if ! [ -d target ]; then
mkdir target
fi
for file in src/*.rs; do
if [ "$file" = "src/template.rs" ]; then continue; fi
echo $file
rustc -O $file --out-dir ./target/
done

105
src/addmix.rs Normal file
View File

@ -0,0 +1,105 @@
// addmix
// combines an arbitrary number of input files together into a single output file, with optional input level control.
// CPU demands: low.
// Memory requirements: 2x size of longest input file.
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::process::exit;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() < 3
{
eprintln!("usage: addmix <output.dat> <input_1.dat>:<level> <input_2.dat>:<level> ...");
return;
}
let output_filename = argv[1].clone();
let mut output_file = match File::create(&argv[1])
{
Err(reason) =>
{
eprintln!("addmix: could not open/create specified output file {} for writing: {}", argv[1], reason);
exit(1);
},
Ok(file) => file,
};
let mut input_files:Vec<(File,String,f32)> = Vec::new();
for i in 2..argv.len()
{
let mut new_filename = argv[i].clone();
let mut new_level:f32 = 1.0;
if let Some((filename,levelstring)) = argv[i].rsplit_once(':')
{
if let Ok(val) = levelstring.parse::<f32>()
{
// decide how to handle the filename:level vs just filename syntax by testing to see if what's after the colon
// actually looks like a number. if it doesn't, silently assume that the whole thing was supposed to be a colon-containing filename.
// if this wasn't the case, and the user meant to type a number, then they should be able to understand what happened from the
// "file not found" error message.
new_level = val;
new_filename = filename.to_string();
}
}
let new_file = match File::open(&new_filename)
{
Err(reason) =>
{
eprintln!("addmix: could not open specified input file {} for reading: {}", new_filename, reason);
exit(1);
},
Ok(file) => file,
};
input_files.push((new_file, new_filename, new_level));
}
let mut output_samples:Vec<f32> = Vec::new();
for (file, filename, level) in input_files.iter_mut()
{
let mut file_data:Vec<u8> = Vec::new();
match file.read_to_end(&mut file_data)
{
Err(reason) =>
{
eprintln!("addmix: could not read from input file {}: {}", filename, reason);
exit(1);
},
Ok(_) => (),
};
let file_length:usize = file_data.len()/4;
for _ in 0..file_length.saturating_sub(output_samples.len())
{
output_samples.push(0.0);
}
for (t,chunk) in file_data.chunks_exact(4).enumerate()
{
let mut sample_bytes:[u8;4] = [0x00; 4];
sample_bytes.copy_from_slice(chunk);
output_samples[t] += *level * f32::from_be_bytes(sample_bytes);
}
}
let mut output_data:Vec<u8> = Vec::with_capacity(output_samples.len() * 4);
for sample in output_samples.iter()
{
output_data.extend_from_slice(&sample.to_be_bytes());
}
match output_file.write_all(&output_data)
{
Err(reason) =>
{
eprintln!("addmix: could not write to output file {}: {}", output_filename, reason);
exit(1);
},
Ok(_) => (),
};
}

249
src/bomp.rs Normal file
View File

@ -0,0 +1,249 @@
// Name: bomp
// Description: bitmap plotter utility
// Why: generate viewable plots from f32be data files
// Processor usage: low
// Memory usage: loads all input files into memory at once
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::process::exit;
static OUTER_PADDING:usize = 16;
static INTERPLOT_PADDING:usize = 16;
static SIZE_ROUNDOFF:usize = 16;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() < 4
{
eprintln!("usage: bomp <yscale> <output.bmp> <input.dat> [<optional additional inputs>...]");
exit(1);
}
/*
yscale (integer):
the vertical scale of each plot, equal to the distance in pixels from zero to the plot's max value.
plots are thus 2*yscale+1 pixels high, plus padding.
output.bmp (bitmap file):
a classic microsoft bitmap file to which to write the uncompressed plot image.
if you want to use it elsewhere, i recommend converting it via ffmpeg to png.
inputs.dat (raw big-endian f32 data file):
simply a sequence of arbitrarily many f32 values in big-endian byte order.
you can specify an arbitrary number of input files, each of which will produce
its own parallel plot below the previous one on the resulting image. parallel plots
will use the same horizontal scale (always exactly one pixel per data value) and
vertical scale (automatically adjusted so that the overall maximum value between all
data sets matches the specified yscale value in pixel height).
*/
let yscale = match argv[1].parse::<usize>()
{
Ok(n) => n,
Err(_) =>
{
eprintln!("bomp: the first command-line argument needs to be an integer (number of vertical pixels per plot.)");
exit(1);
},
};
let output_filename:String = argv[2].clone();
let mut output_file = match File::create(&output_filename)
{
Ok(file) => file,
Err(why) =>
{
eprintln!("bomp: could not open {} for writing: {}", output_filename, why);
exit(1);
},
};
let mut input_files:Vec<(File, String)> = Vec::new();
for i in 3..argv.len()
{
let new_filename:String = argv[i].clone();
let new_file = match File::open(&argv[i])
{
Err(reason) =>
{
eprintln!("bomp: failed to open {} for reading: {}", new_filename, reason);
exit(1);
},
Ok(file) => file,
};
input_files.push((new_file, new_filename));
}
let mut signals:Vec<Vec<f32>> = Vec::new();
for (file,filename) in input_files.iter_mut()
{
let mut file_buffer:Vec<u8> = Vec::new();
match file.read_to_end(&mut file_buffer)
{
Err(reason) =>
{
eprintln!("bomp: could not read from input file {}: {}", filename, reason);
exit(1);
},
Ok(_) => (),
};
let mut new_signal:Vec<f32> = Vec::with_capacity(file_buffer.len()/4);
for chunk in file_buffer.chunks_exact(4)
{
let mut sample_bytes:[u8;4] = [0x00; 4];
sample_bytes.copy_from_slice(chunk);
new_signal.push(f32::from_be_bytes(sample_bytes));
}
signals.push(new_signal);
}
let mut abs_max:f32 = 0.0;
let mut signals_maxlen:usize = 0;
for signal in signals.iter()
{
signals_maxlen = signals_maxlen.max(signal.len());
for point in signal.iter()
{
abs_max = abs_max.max(point.abs());
}
}
let scale_factor = (yscale as f32) / abs_max;
eprintln!("peak: {}", abs_max);
for signal in signals.iter_mut()
{
for point in signal.iter_mut()
{
*point *= scale_factor;
}
}
let plot_height:usize = (yscale*2 + 1);
let plot_width:usize = signals_maxlen;
let signal_count:usize = signals.len();
let intersignal_count:usize = signals.len()-1;
// adjust the padding to make the image size a nice round number.
// why? i forgot, honestly. seems pretty pointless.
let mut lpad:usize = OUTER_PADDING;
let mut rpad:usize = OUTER_PADDING;
while (plot_width + lpad + rpad) % SIZE_ROUNDOFF != 0
{
if lpad <= rpad
{
lpad += 1;
} else {
rpad += 1;
}
}
let mut tpad:usize = OUTER_PADDING;
let mut bpad:usize = OUTER_PADDING;
while (plot_height*signal_count + intersignal_count*INTERPLOT_PADDING + tpad + bpad) % SIZE_ROUNDOFF != 0
{
if tpad <= bpad
{
tpad += 1;
} else {
bpad += 1;
}
}
let color_count:usize = 256;
let header_size:usize = 54 + color_count*4;
let image_width:usize = plot_width + lpad + rpad;
let image_height:usize = signal_count*plot_height + intersignal_count*INTERPLOT_PADDING + tpad + bpad;
let file_size:usize = image_height * image_width + 1078;
eprintln!("plots: {}", signal_count);
eprintln!("wide: {}", image_width);
eprintln!("high: {}", image_height);
eprintln!("size: {}", file_size);
let mut header:Vec<u8> = Vec::with_capacity(header_size);
header.extend_from_slice(&"BM".as_bytes()); // "signature"
header.extend_from_slice(&(file_size as u32).to_le_bytes()); // filesize
header.extend_from_slice(&0u32.to_le_bytes()); // forbidden field
header.extend_from_slice(&(header_size as u32).to_le_bytes()); // data offset
header.extend_from_slice(&40u32.to_le_bytes()); // infoheader size (constant)
header.extend_from_slice(&(image_width as u32).to_le_bytes()); // pixel width
header.extend_from_slice(&(image_height as u32).to_le_bytes()); // pixel height
header.extend_from_slice(&1u16.to_le_bytes()); // number of planes
header.extend_from_slice(&8u16.to_le_bytes()); // bits per pixel
header.extend_from_slice(&0u32.to_le_bytes()); // compression type (none)
header.extend_from_slice(&0u32.to_le_bytes()); // compressed image size (not applicable)
header.extend_from_slice(&1024u32.to_le_bytes()); // horizontal pixels/meter (arbitrary)
header.extend_from_slice(&1024u32.to_le_bytes()); // vertical pixels/meter (arbitrary)
header.extend_from_slice(&256u32.to_le_bytes()); // colors used (all of them)
header.extend_from_slice(&0u32.to_le_bytes()); // number of important colors (what does that even mean)
assert_eq!(header.len(), 54);
for i in 0x00..=0xFF
{
header.push(i); // red
header.push(i); // green
header.push(i); // blue
header.push(0x00); // ?????
}
match output_file.write_all(&header)
{
Err(reason) =>
{
eprintln!("bomp: write error on file {}: {}", output_filename, reason);
exit(1);
},
Ok(_) => (),
};
output_file.write_all(&vec![0x00; bpad*image_width]).expect("write error (lower padding)");
for (signal_number, signal) in signals.iter().enumerate().rev() // reversed because bmps go from bottom to top, for some reason
{
for y in -(yscale as isize)..=(yscale as isize)
{
let mut row_buffer:Vec<u8> = Vec::with_capacity(image_width);
row_buffer.extend_from_slice(&vec![0x00; lpad]);
for x in 0..signal.len()
{
if
(
(
signal[x] >= (y as f32)
&&
y >= 0
) || (
signal[x] <= (y as f32)
&&
y <= 0
)
) {
row_buffer.push(0xFF);
} else {
row_buffer.push(0x00);
}
}
row_buffer.extend_from_slice(&vec![0x00; signals_maxlen - signal.len() + rpad]);
output_file.write_all(&row_buffer).expect("write error (row)");
}
if signal_number > 0
{
output_file.write_all(&vec![0x00; image_width*INTERPLOT_PADDING]).expect("write error (interplot padding)");
}
}
output_file.write_all(&vec![0x00; tpad*image_width]).expect("write error (upper padding)");
}

88
src/correlate.rs Normal file
View File

@ -0,0 +1,88 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 5
{
eprintln!("usage: correlate <input_a.dat> <input_b.dat> <output_forward.dat> <output_reverse.dat>");
return;
}
let mut input_a = File::open(&argv[1]).unwrap();
let mut input_b = File::open(&argv[2]).unwrap();
let mut output_fwd = File::create(&argv[3]).unwrap();
let mut output_rev = File::create(&argv[4]).unwrap();
let mut data_a:Vec<f32> = Vec::new();
let mut data_b:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_a.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data_a.push(f32::from_be_bytes(buffer));
}
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_b.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data_b.push(f32::from_be_bytes(buffer));
}
let (longer, shorter) =
(
match data_a.len() > data_b.len()
{
true => (data_a, data_b),
false => (data_b, data_a),
}
);
let mut output_data_fwd:Vec<f32> = Vec::with_capacity(longer.len()+shorter.len());
let mut output_data_rev:Vec<f32> = Vec::with_capacity(longer.len()+shorter.len());
for offset in 0..=longer.len()+shorter.len()
{
let mut fwd_sum:f32 = 0.0;
let mut rev_sum:f32 = 0.0;
for (fi, ri) in
(
0..shorter.len()
).zip(
(0..shorter.len()).rev()
) {
fwd_sum += shorter[fi] * longer.get(offset+fi-shorter.len()).unwrap_or(&0.0);
rev_sum += shorter[ri] * longer.get(offset+fi-shorter.len()).unwrap_or(&0.0);
}
output_data_fwd.push(fwd_sum);
output_data_rev.push(rev_sum);
}
let mut output_buffer_fwd:Vec<u8> = Vec::with_capacity(output_data_fwd.len()*4);
for &point in output_data_fwd.iter()
{
output_buffer_fwd.extend_from_slice(&point.to_be_bytes());
}
let mut output_buffer_rev:Vec<u8> = Vec::with_capacity(output_data_rev.len()*4);
for &point in output_data_rev.iter()
{
output_buffer_rev.extend_from_slice(&point.to_be_bytes());
}
output_fwd.write_all(&output_buffer_fwd).unwrap();
output_rev.write_all(&output_buffer_rev).unwrap();
}

88
src/correlate_cyclic.rs Normal file
View File

@ -0,0 +1,88 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 5
{
eprintln!("correlate_cyclic <input_a.dat> <input_b.dat> <output_forward.dat> <output_reverse.dat>");
return;
}
let mut input_a = File::open(&argv[1]).unwrap();
let mut input_b = File::open(&argv[2]).unwrap();
let mut output_fwd = File::create(&argv[3]).unwrap();
let mut output_rev = File::create(&argv[4]).unwrap();
let mut data_a:Vec<f32> = Vec::new();
let mut data_b:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_a.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data_a.push(f32::from_be_bytes(buffer));
}
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_b.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data_b.push(f32::from_be_bytes(buffer));
}
let (longer, shorter) =
(
match data_a.len() > data_b.len()
{
true => (data_a, data_b),
false => (data_b, data_a),
}
);
let mut output_data_fwd:Vec<f32> = Vec::with_capacity(longer.len()*2);
let mut output_data_rev:Vec<f32> = Vec::with_capacity(longer.len()*2);
for offset in -(shorter.len() as isize)..=(longer.len() as isize)
{
let mut fwd_sum:f32 = 0.0;
let mut rev_sum:f32 = 0.0;
for (fi, ri) in
(
0..(shorter.len() as isize)
).zip(
(0..(shorter.len() as isize)).rev()
) {
fwd_sum += shorter[fi as usize] * longer[((offset+fi+(longer.len() as isize)) % (longer.len() as isize)) as usize];
rev_sum += shorter[ri as usize] * longer[((offset+fi+(longer.len() as isize)) % (longer.len() as isize)) as usize];
}
output_data_fwd.push(fwd_sum);
output_data_rev.push(rev_sum);
}
let mut output_buffer_fwd:Vec<u8> = Vec::with_capacity(output_data_fwd.len()*4);
for &point in output_data_fwd.iter()
{
output_buffer_fwd.extend_from_slice(&point.to_be_bytes());
}
let mut output_buffer_rev:Vec<u8> = Vec::with_capacity(output_data_rev.len()*4);
for &point in output_data_rev.iter()
{
output_buffer_rev.extend_from_slice(&point.to_be_bytes());
}
output_fwd.write_all(&output_buffer_fwd).unwrap();
output_rev.write_all(&output_buffer_rev).unwrap();
}

61
src/dcst.rs Normal file
View File

@ -0,0 +1,61 @@
#[allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 4
{
eprintln!("usage: dcst <input.dat> <sine-output.dat> <cos-output.dat");
return;
}
let mut signal_file = File::open(&argv[1]).unwrap();
let mut iout_file = File::create(&argv[2]).unwrap();
let mut qout_file = File::create(&argv[3]).unwrap();
let mut signal:Vec<f32> = Vec::new();
let mut signal_data:Vec<u8> = Vec::new();
signal_file.read_to_end(&mut signal_data).unwrap();
for chunk in signal_data.chunks_exact(4)
{
let mut samplebuffer:[u8;4] = [0x00; 4];
samplebuffer.copy_from_slice(chunk);
signal.push(f32::from_be_bytes(samplebuffer));
}
let size = signal.len();
let mut iout:Vec<f32> = Vec::with_capacity(size);
let mut qout:Vec<f32> = Vec::with_capacity(size);
for n in 0..size
{
let mut new_i:f32 = 0.0;
let mut new_q:f32 = 0.0;
for t in 0..size
{
let (ti, tq) = (-1.0*(n as f32)*PI*(t as f32) / (size as f32)).sin_cos();
new_i += signal[t] * ti;
new_q += signal[t] * tq;
}
new_i /= (size as f32).sqrt();
new_q /= (size as f32).sqrt();
iout.push(new_i);
qout.push(new_q);
}
let mut iout_data:Vec<u8> = Vec::with_capacity(size*4);
let mut qout_data:Vec<u8> = Vec::with_capacity(size*4);
for n in 0..size
{
iout_data.extend_from_slice(&iout[n].to_be_bytes());
qout_data.extend_from_slice(&qout[n].to_be_bytes());
}
iout_file.write_all(&iout_data).unwrap();
qout_file.write_all(&qout_data).unwrap();
}

42
src/differentiate.rs Normal file
View File

@ -0,0 +1,42 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 3
{
eprintln!("usage: differentiate <input.dat> <output.dat>");
return;
}
let mut input = File::open(&argv[1]).unwrap();
let mut data:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data.push(f32::from_be_bytes(buffer));
}
let mut output:Vec<f32> = Vec::with_capacity(data.len());
for i in 1..data.len()
{
output.push(data[i] - data[i-1]);
}
let mut output_file = File::create(&argv[2]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(output.len() * 4);
for &point in output.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output_file.write_all(&output_buffer).unwrap();
}

200
src/echospace.rs Normal file
View File

@ -0,0 +1,200 @@
#![allow(unused_parens)]
/*
echospace - rudimentary 3D echo simulator
accepts a signal, a transmitter position, a receiver position, and a scene matrix,
and emits a simulated received signal
! limitations !
- assumes transmitter and receiver to be isotropically radiant/sensitive
- does not simulate:
-- occlusion (hard)
-- secondary reflections (computationally intensive)
-- any frequency-dependent effects (needs frequency-domain operations, currently not implemented)
-- any nonlinear effects (significance unknown)
*/
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn distance
(
a: &[f32;3],
b: &[f32;3],
) ->
f32
{
(
(a[0] - b[0]).powi(2)
+
(a[1] - b[1]).powi(2)
+
(a[2] - b[2]).powi(2)
).sqrt()
}
fn skew
(
input: &[f32],
output: &mut [f32],
offset: f32,
) {
let skew_factor:f32 = offset.fract();
let output_slice:&mut [f32] = &mut output[0..input.len()+1];
for (i_point,o_point) in (input.iter()).zip(output_slice.iter_mut())
{
*o_point += i_point * (1.0 - skew_factor);
*o_point += i_point * skew_factor;
}
}
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 7
{
eprintln!("usage: echospace <txx>,<txy>,<txz> <rxx>,<rxy>,<rxz> <wave_speed> <input_signal.dat> <scene.3da> <output_signal.dat>");
return;
}
// argument type description
// <txx>,<txy>,<txz> f32,f32,f32 coordinates of the transmitter relative to the scene volume
// <rxx>,<rxy>,<rxz> f32,f32,f32 coordinates of the receiver relative to the scene volume
// <wavespeed> f32 speed of the wave in the medium, in points (3D scene coordinates) per signal sample
// <input_signal.dat> dat file signal emitted by the transmitter
// <scene.3da> 3da file 3D array file defining a reflectivity value at every point in the simulated volume (see 3da file definition)
// <output_signal.dat> dat file simulated signal detected by the receiver
let txpos_string:String = argv[1].clone();
let rxpos_string:String = argv[2].clone();
let wave_speed:f32 = argv[3].parse::<f32>().unwrap();
let mut input_file = File::open(&argv[4]).unwrap();
let mut scene_file = File::open(&argv[5]).unwrap();
let mut output_file = File::create(&argv[6]).unwrap();
let mut txpos:[f32;3] = [0.0, 0.0, 0.0];
let txpos_comps:Vec<&str> = txpos_string.split(',').collect();
for n in 0..3
{
txpos[n] = txpos_comps[n].parse::<f32>().unwrap();
}
let mut rxpos:[f32;3] = [0.0, 0.0, 0.0];
let rxpos_comps:Vec<&str> = rxpos_string.split(',').collect();
for n in 0..3
{
rxpos[n] = rxpos_comps[n].parse::<f32>().unwrap();
}
let mut input_data:Vec<u8> = Vec::new();
input_file.read_to_end(&mut input_data).unwrap();
let mut input_signal:Vec<f32> = Vec::with_capacity(input_data.len()/4);
for chunk in input_data.chunks_exact(4)
{
let mut sample_bytes:[u8;4] = [0x00; 4];
sample_bytes.copy_from_slice(chunk);
input_signal.push(f32::from_be_bytes(sample_bytes));
}
let mut scene_xsize_bytes:[u8;4] = [0x00; 4];
let mut scene_ysize_bytes:[u8;4] = [0x00; 4];
let mut scene_zsize_bytes:[u8;4] = [0x00; 4];
let mut scene_data:Vec<u8> = Vec::new();
scene_file.read_exact(&mut scene_xsize_bytes).unwrap();
scene_file.read_exact(&mut scene_ysize_bytes).unwrap();
scene_file.read_exact(&mut scene_zsize_bytes).unwrap();
scene_file.read_to_end(&mut scene_data).unwrap();
let scene_xsize:usize = u32::from_be_bytes(scene_xsize_bytes) as usize;
let scene_ysize:usize = u32::from_be_bytes(scene_ysize_bytes) as usize;
let scene_zsize:usize = u32::from_be_bytes(scene_zsize_bytes) as usize;
let mut scene:Vec<f32> = Vec::with_capacity(scene_data.len()/4);
for chunk in scene_data.chunks_exact(4)
{
let mut point_bytes:[u8;4] = [0x00; 4];
point_bytes.copy_from_slice(chunk);
scene.push(f32::from_be_bytes(point_bytes));
}
// scene[x][y][z] => scene[z*scene_ysize*scene_xsize + y*scene_xsize + x]
// scene[n] => scene[n % scene_xsize][(n/scene_xsize) % scene_ysize][n/(scene_xsize*scene_ysize)]
let scene_corners:[[f32;3];8] =
[
[0.0 , 0.0 , 0.0 ],
[scene_xsize as f32, 0.0 , 0.0 ],
[0.0 , scene_ysize as f32, 0.0 ],
[scene_xsize as f32, scene_ysize as f32, 0.0 ],
[0.0 , 0.0 , scene_zsize as f32],
[scene_xsize as f32, 0.0 , scene_zsize as f32],
[0.0 , scene_ysize as f32, scene_zsize as f32],
[scene_xsize as f32, scene_ysize as f32, scene_zsize as f32],
];
let mut max_flight_dist:f32 = 0.0;
for corner in scene_corners.iter()
{
max_flight_dist = max_flight_dist.max(distance(corner,&txpos) + distance(corner,&rxpos));
}
let max_flight_time = max_flight_dist*wave_speed;
eprintln!("maximum simulated path length: {} points", max_flight_dist.ceil());
eprintln!("maximum simulated path time: {} samples", max_flight_time.ceil());
let mut output_signal:Vec<f32> = vec![0.0; (max_flight_time.floor() as usize) + input_signal.len()+1];
let mut skewed_input:Vec<f32> = vec![0.0; input_signal.len()+1];
let tx_rx_flight_dist:f32 = distance(&txpos, &rxpos);
let tx_rx_flight_time:f32 = tx_rx_flight_dist*wave_speed;
let tx_rx_flight_samples:usize = tx_rx_flight_time.floor() as usize;
skew(&input_signal, &mut skewed_input, tx_rx_flight_time.fract());
for (i_point, o_point) in
(
skewed_input.iter()
).zip(
output_signal
[
tx_rx_flight_samples
..
tx_rx_flight_samples + skewed_input.len()
].iter_mut()
) {
*o_point += i_point;
}
for x in 0..scene_xsize {
for y in 0..scene_ysize {
for z in 0..scene_zsize {
let pointpos:[f32;3] = [x as f32, y as f32, z as f32];
let flight_dist:f32 = distance(&pointpos, &txpos) + distance(&pointpos, &rxpos);
let flight_time:f32 = flight_dist*wave_speed;
let attenuation:f32 = tx_rx_flight_dist.powi(2) / flight_dist.powi(2);
let sample_skew:f32 = flight_time.fract();
let flight_samples:usize = flight_time.floor() as usize;
let reflectivity:f32 = scene[z*scene_ysize*scene_xsize + y*scene_xsize + x];
skew(&input_signal, &mut skewed_input, sample_skew);
for (i_point, o_point) in
(
skewed_input.iter()
).zip(
output_signal
[
flight_samples
..
flight_samples + skewed_input.len()
].iter_mut()
) {
*o_point += reflectivity * attenuation * i_point;
}
}}}
let mut output_data:Vec<u8> = Vec::with_capacity(output_signal.len()*4);
for point in output_signal.iter()
{
output_data.extend_from_slice(&point.to_be_bytes());
}
// write the output file
output_file.write_all(&output_data).unwrap();
}

62
src/fsinechirp.rs Normal file
View File

@ -0,0 +1,62 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 6
{
eprintln!("no. <length> <start> <stop> <sin.dat> <cos.dat>");
return;
}
let length = argv[1].parse::<usize>().unwrap();
let start_freq = argv[2].parse::<f32>().unwrap();
let stop_freq = argv[3].parse::<f32>().unwrap();
let mut sin_file = File::create(&argv[4]).unwrap();
let mut cos_file = File::create(&argv[5]).unwrap();
let delta_freq =
(
(stop_freq - start_freq)
/
(
2.0
*
(length as f32)
)
);
let mut sin_buffer:Vec<u8> = Vec::with_capacity(length * 4);
let mut cos_buffer:Vec<u8> = Vec::with_capacity(length * 4);
for i in 0..length
{
sin_buffer.extend_from_slice(
&(
(i as f32) * PI
*
(start_freq + delta_freq * (i as f32))
/
(length as f32)
)
.sin()
.to_be_bytes()
);
cos_buffer.extend_from_slice(
&(
(i as f32) * PI
*
(start_freq + delta_freq * (i as f32))
/
(length as f32)
)
.cos()
.to_be_bytes()
);
}
sin_file.write_all(&sin_buffer).unwrap();
cos_file.write_all(&cos_buffer).unwrap();
}

91
src/gfilter.rs Normal file
View File

@ -0,0 +1,91 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn gauss
(
freq: f32,
input: f32,
) ->
f32
{
let stdev = (2.0 * PI * freq).recip();
(
-0.5 *
(
input
/
stdev
)
.powi(2)
)
.exp()
/
(
(2.0 * PI).sqrt()
*
stdev
)
}
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 3
{
eprintln!("usage: gfilter <frequency> <target.dat>");
return;
}
let filter_frequency =
(
argv[1]
.parse::<f32>()
.unwrap()
/ 2000.0
);
let mut input_file = File::open(&argv[2]).unwrap();
let mut data:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_file.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data.push(f32::from_be_bytes(buffer));
}
let mut filtered_data:Vec<f32> = Vec::with_capacity(data.len());
for shift in 0..data.len()
{
let mut data_sum:f32 = 0.0;
for x in 0..data.len()
{
data_sum +=
(
data[x]
*
gauss(
filter_frequency,
(x as f32) - (shift as f32)
)
);
}
filtered_data.push(data_sum);
}
let mut output_file = File::create(&argv[2]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(filtered_data.len() * 4);
for &point in filtered_data.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output_file.write_all(&output_buffer).unwrap();
}

44
src/integrate.rs Normal file
View File

@ -0,0 +1,44 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 3
{
eprintln!("usage: integrate <input.dat> <output.dat>");
return;
}
let mut input = File::open(&argv[1]).unwrap();
let mut data:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data.push(f32::from_be_bytes(buffer));
}
let mut integrated:Vec<f32> = Vec::with_capacity(data.len());
let mut val:f32 = 0.0;
for i in 0..data.len()
{
val += data[i];
integrated.push(val);
}
let mut output_file = File::create(&argv[2]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(integrated.len() * 4);
for &point in integrated.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output_file.write_all(&output_buffer).unwrap();
}

106
src/mulmix.rs Normal file
View File

@ -0,0 +1,106 @@
// mulmix
// combines an arbitrary number of input files together into a single output file by multiplication, with optional input level control.
// behaves like a ring modulator or RF mixer.
// CPU demands: low.
// Memory requirements: 3x size of longest input file.
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::process::exit;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() < 4
{
eprintln!("usage: mulmix <output.dat> <input_1.dat>:<level> <input_2.dat>:<level> ...");
return;
}
let output_filename = argv[1].clone();
let mut output_file = match File::create(&argv[1])
{
Err(reason) =>
{
eprintln!("mulmix: could not open/create specified output file {} for writing: {}", argv[1], reason);
exit(1);
},
Ok(file) => file,
};
let mut input_files:Vec<(File,String,f32)> = Vec::new();
for i in 2..argv.len()
{
let mut new_filename = argv[i].clone();
let mut new_level:f32 = 1.0;
if let Some((filename,levelstring)) = argv[i].rsplit_once(':')
{
if let Ok(val) = levelstring.parse::<f32>()
{
// decide how to handle the filename:level vs just filename syntax by testing to see if what's after the colon
// actually looks like a number. if it doesn't, silently assume that the whole thing was supposed to be a colon-containing filename.
// if this wasn't the case, and the user meant to type a number, then they should be able to understand what happened from the
// "file not found" error message.
new_level = val;
new_filename = filename.to_string();
}
}
let new_file = match File::open(&new_filename)
{
Err(reason) =>
{
eprintln!("mulmix: could not open specified input file {} for reading: {}", new_filename, reason);
exit(1);
},
Ok(file) => file,
};
input_files.push((new_file, new_filename, new_level));
}
let mut output_samples:Vec<f32> = Vec::new();
for (file, filename, level) in input_files.iter_mut()
{
let mut file_data:Vec<u8> = Vec::new();
match file.read_to_end(&mut file_data)
{
Err(reason) =>
{
eprintln!("mulmix: could not read from input file {}: {}", filename, reason);
exit(1);
},
Ok(_) => (),
};
let file_length:usize = file_data.len()/4;
for _ in 0..file_length.saturating_sub(output_samples.len())
{
output_samples.push(1.0);
}
for (t,chunk) in file_data.chunks_exact(4).enumerate()
{
let mut sample_bytes:[u8;4] = [0x00; 4];
sample_bytes.copy_from_slice(chunk);
output_samples[t] *= *level * f32::from_be_bytes(sample_bytes);
}
}
let mut output_data:Vec<u8> = Vec::with_capacity(output_samples.len() * 4);
for sample in output_samples.iter()
{
output_data.extend_from_slice(&sample.to_be_bytes());
}
match output_file.write_all(&output_data)
{
Err(reason) =>
{
eprintln!("mulmix: could not write to output file {}: {}", output_filename, reason);
exit(1);
},
Ok(_) => (),
};
}

59
src/normalize.rs Normal file
View File

@ -0,0 +1,59 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::process::exit;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 2
{
eprintln!("no. arguments: <target.dat>");
return;
}
let mut input = File::open(&argv[1]).unwrap();
let mut data:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data.push(f32::from_be_bytes(buffer));
}
let mut abs_max:f32 = 0.0;
for &point in data.iter()
{
if point.is_normal()
{
abs_max = abs_max.max(point.abs());
}
}
if
(
abs_max < std::f32::MIN_POSITIVE
) {
eprintln!("cannot normalize {}! it may be empty, it may contain one or more infinite or NaN values, or it may contain only values that are zero or nearly zero.", argv[1]);
exit(1);
}
for point in data.iter_mut()
{
*point /= abs_max;
}
let mut output = File::create(&argv[1]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(data.len() * 4);
for &point in data.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output.write_all(&output_buffer).unwrap();
}

36
src/orthosine.rs Normal file
View File

@ -0,0 +1,36 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 5
{
eprintln!("usage: orthosine <wavenumber> <size> <output_i.dat> <output_q.dat>");
return;
}
let wavenumber = argv[1].parse::<f32>().unwrap();
let size = argv[2].parse::<usize>().unwrap();
let mut idata = Vec::with_capacity(size);
let mut qdata = Vec::with_capacity(size);
for t in 0..size
{
let (i,q) = (wavenumber * PI * (t as f32) / (size as f32)).sin_cos();
idata.append(&mut i.to_be_bytes().to_vec());
qdata.append(&mut q.to_be_bytes().to_vec());
}
let mut output_i = File::create(&argv[3]).unwrap();
let mut output_q = File::create(&argv[4]).unwrap();
output_i.write_all(&idata).unwrap();
output_q.write_all(&qdata).unwrap();
}

36
src/pad.rs Normal file
View File

@ -0,0 +1,36 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 5
{
eprintln!("usage: pad <pre> <post> <input.dat> <output.dat>");
return;
}
let pre = argv[1].parse::<usize>().unwrap();
let post = argv[2].parse::<usize>().unwrap();
let mut input = File::open(&argv[3]).unwrap();
let mut input_data:Vec<u8> = Vec::new();
input.read_to_end(&mut input_data).unwrap();
let mut output_data:Vec<u8> = Vec::with_capacity(input_data.len() + pre*4 + post*4);
for _ in 0..pre
{
output_data.extend_from_slice(&0.0f32.to_be_bytes());
}
output_data.append(&mut input_data);
for _ in 0..post
{
output_data.extend_from_slice(&0.0f32.to_be_bytes());
}
let mut output = File::create(&argv[4]).unwrap();
output.write_all(&output_data).unwrap();
}

61
src/peakdetect.rs Normal file
View File

@ -0,0 +1,61 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 2
{
eprintln!("usage: peakdetect <target.dat>");
return;
}
let mut input_file = File::open(&argv[1]).unwrap();
let mut data:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_file.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data.push(f32::from_be_bytes(buffer));
}
let mut filtered_data:Vec<f32> = Vec::with_capacity(data.len());
filtered_data.push(0.0);
for i in 1..data.len()-1
{
let mut upper_radius:usize = 1;
let mut lower_radius:usize = 1;
while i.saturating_add(upper_radius) < (data.len()-1) && data[i] == data[i+upper_radius]
{
upper_radius += 1;
}
while i.saturating_sub(lower_radius) > 0 && data[i] == data[i-lower_radius]
{
lower_radius += 1;
}
if
(
(data[i-lower_radius].abs() < data[i].abs() && data[i+upper_radius].abs() < data[i].abs())
) {
filtered_data.push(data[i]);
} else {
filtered_data.push(0.0);
}
}
filtered_data.push(0.0);
let mut output_file = File::create(&argv[1]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(filtered_data.len() * 4);
for &point in filtered_data.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output_file.write_all(&output_buffer).unwrap();
}

50
src/rectify.rs Normal file
View File

@ -0,0 +1,50 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 2
{
eprintln!("no. arguments: <target.dat>");
return;
}
let mut target =
(
OpenOptions::new()
.read(true)
.write(true)
.append(false)
.truncate(false)
.create(false)
.create_new(false)
.open(&argv[1])
.unwrap()
);
let mut buffer:[u8;4] = [0x00;4];
for i in 0..
{
let seek_try = target.seek(SeekFrom::Start(i * 4));
if seek_try.is_err()
{
break;
}
let read_try = target.read_exact(&mut buffer);
if read_try.is_err()
{
break;
}
buffer[0] &= 0b0111_1111;
target.seek(SeekFrom::Start(i * 4)).unwrap();
target.write_all(&buffer).unwrap();
}
}

93
src/sfilter.rs Normal file
View File

@ -0,0 +1,93 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn sinc
(
x: f32,
) ->
f32
{
if x.abs() > std::f32::MIN_POSITIVE
{
(PI*x).sin() / (PI*x)
} else {
1.0
}
}
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 3
{
eprintln!("usage: sfilter <frequency> <target.dat>");
return;
}
/*
frequency (f32):
the frequency of the sinc function from 0.0 to 1000.0, where 0.0 is zero and 1000.0 is half the sample rate (the Nyquist limit).
*/
let filter_frequency =
(
argv[1]
.parse::<f32>()
.unwrap()
/ 2000.0 // later on, this will need to be normalized so that 1.0 equals the sample rate, so we divide by 2000 so that an input of 1000 becomes 0.5
);
let mut input_file = File::open(&argv[2]).unwrap();
let mut input_buffer:Vec<u8> = Vec::new();
input_file.read_to_end(&mut input_buffer).unwrap();
let mut data:Vec<f32> = Vec::new();
for chunk in input_buffer.chunks_exact(4)
{
let mut new_value:[u8;4] = [0x00;4];
new_value.copy_from_slice(chunk);
data.push(f32::from_be_bytes(new_value));
}
let mut filtered_data:Vec<f32> = Vec::with_capacity(data.len());
for shift in 0..data.len()
{
let s = shift as f32;
let mut convol_value:f32 = 0.0;
for x in 0..data.len()
{
let t = x as f32;
convol_value +=
(
data[x]
*
2.0
*
filter_frequency
*
sinc(
2.0
*
filter_frequency
*
(t - s)
)
);
}
filtered_data.push(convol_value);
}
let mut output_file = File::create(&argv[2]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(filtered_data.len() * 4);
for &point in filtered_data.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output_file.write_all(&output_buffer).unwrap();
}

74
src/sft.rs Normal file
View File

@ -0,0 +1,74 @@
#[allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 5
{
eprintln!("usage: sft <i-input.dat> <q-input.dat> <i-output.dat> <q-output.dat>");
return;
}
let mut iout_file = File::create(&argv[3]).unwrap();
let mut qout_file = File::create(&argv[4]).unwrap();
let mut isig_file = File::open(&argv[1]).unwrap();
let mut isig_data:Vec<u8> = Vec::new();
isig_file.read_to_end(&mut isig_data).unwrap();
let mut isig:Vec<f32> = Vec::new();
for chunk in isig_data.chunks_exact(4)
{
let mut samplebuffer:[u8;4] = [0x00; 4];
samplebuffer.copy_from_slice(chunk);
isig.push(f32::from_be_bytes(samplebuffer));
}
let mut qsig_file = File::open(&argv[2]).unwrap();
let mut qsig_data:Vec<u8> = Vec::new();
qsig_file.read_to_end(&mut qsig_data).unwrap();
let mut qsig:Vec<f32> = Vec::new();
for chunk in qsig_data.chunks_exact(4)
{
let mut samplebuffer:[u8;4] = [0x00; 4];
samplebuffer.copy_from_slice(chunk);
qsig.push(f32::from_be_bytes(samplebuffer));
}
let size = isig.len().min(qsig.len());
let mut iout:Vec<f32> = Vec::with_capacity(size);
let mut qout:Vec<f32> = Vec::with_capacity(size);
for n in 0..size
{
let mut new_i:f32 = 0.0;
let mut new_q:f32 = 0.0;
for t in 0..size
{
// (R+Bi)*(C+Di) = (AC-BD)+(AD+BC)i
let (tq, ti) = (-2.0*(n as f32)*PI*(t as f32) / (size as f32)).sin_cos();
new_i += isig[t]*ti - qsig[t]*tq;
new_q += isig[t]*tq + qsig[t]*ti;
}
new_i /= (size as f32).sqrt();
new_q /= (size as f32).sqrt();
iout.push(new_i);
qout.push(new_q);
}
let mut idata:Vec<u8> = Vec::with_capacity(size*4);
let mut qdata:Vec<u8> = Vec::with_capacity(size*4);
for n in 0..size
{
idata.extend_from_slice(&iout[n].to_be_bytes());
qdata.extend_from_slice(&qout[n].to_be_bytes());
}
iout_file.write_all(&idata).unwrap();
qout_file.write_all(&qdata).unwrap();
}

79
src/skew.rs Normal file
View File

@ -0,0 +1,79 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 4
{
eprintln!("usage: skew <offset‰> <mismatch‰> <target.dat>");
return;
}
// offset: permil phase offset in transmission from receiver
// mismatch: permil frequency error in transmission sample rate from receiver sample rate
let offset =
(
argv[1]
.parse::<f32>()
.unwrap()
/ 1000.0
);
let mismatch =
(
argv[2]
.parse::<f32>()
.unwrap()
.max(-999.0) // no closer to 0 than 1 permil
/ 1000.0
);
let mut input = File::open(&argv[3]).unwrap();
let mut data:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data.push(f32::from_be_bytes(buffer));
}
let output_length =
(
(
data.len() as f32
/
(1.0 + mismatch)
)
as usize
);
let mut result:Vec<f32> = Vec::with_capacity(output_length);
for i in 0..output_length
{
let skew = offset + (i as f32) * mismatch;
let shift = skew.floor() as usize;
let upper_misalign = skew.fract();
let lower_misalign = 1.0 - skew.fract();
result.push(
upper_misalign * data[(i + shift + 1) % data.len()]
+
lower_misalign * data[(i + shift + 0) % data.len()]
);
}
let mut output = File::create(&argv[3]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(data.len() * 4);
for &point in result.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output.write_all(&output_buffer).unwrap();
}

49
src/splitbits.rs Normal file
View File

@ -0,0 +1,49 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 3
{
eprintln!("usage: splitbits <input.dat> <output.dat>");
return;
}
// deal with command-line arguments first, before anything big happens
let mut input_file = File::open(&argv[1]).unwrap();
let mut output_file = File::create(&argv[2]).unwrap();
// read the input file
let mut input_data:Vec<u8> = Vec::new();
input_file.read_to_end(&mut input_data).unwrap();
// decode the input data
let mut input:Vec<f32> = Vec::with_capacity(input_data.len()/4);
for chunk in input_data.chunks_exact(4)
{
let mut samplebuffer:[u8;4] = [0x00; 4];
samplebuffer.copy_from_slice(chunk);
input.push(f32::from_be_bytes(samplebuffer));
}
// actually do the processing
let mut output:Vec<f32> = input;
for point in output.iter_mut()
{
*point /= point.abs();
}
// encode the output data
let mut output_data:Vec<u8> = Vec::with_capacity(output.len()*4);
for point in output.iter()
{
output_data.extend_from_slice(&point.to_be_bytes());
}
// write the output file
output_file.write_all(&output_data).unwrap();
}

26
src/squarechirp.rs Normal file
View File

@ -0,0 +1,26 @@
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 3
{
eprintln!("no. <iterations> <output.dat>");
return;
}
let n = argv[1].parse::<usize>().unwrap();
let mut output_file = File::create(&argv[2]).unwrap();
for i in 0..n
{
for _ in 0..2*i
{
output_file.write_all(&-1.0f32.to_be_bytes()).unwrap();
}
for _ in 0..2*i+1
{
output_file.write_all(&1.0f32.to_be_bytes()).unwrap();
}
}
}

106
src/swindow.rs Normal file
View File

@ -0,0 +1,106 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn sinc
(
x: f32,
) ->
f32
{
if x.abs() > std::f32::MIN_POSITIVE
{
(PI*x).sin() / (PI*x)
} else {
1.0
}
}
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 4
{
eprintln!("usage: swindow <low> <high> <target.dat>");
return;
}
let freq_lo =
(
argv[1]
.parse::<f32>()
.unwrap()
/ 2000.0
);
let freq_hi =
(
argv[2]
.parse::<f32>()
.unwrap()
/ 2000.0
);
let mut input_file = File::open(&argv[3]).unwrap();
let mut input_buffer:Vec<u8> = Vec::new();
input_file.read_to_end(&mut input_buffer).unwrap();
let mut data:Vec<f32> = Vec::new();
for chunk in input_buffer.chunks_exact(4)
{
let mut new_value:[u8;4] = [0x00;4];
new_value.copy_from_slice(chunk);
data.push(f32::from_be_bytes(new_value));
}
let mut filtered_data:Vec<f32> = Vec::with_capacity(data.len());
for shift in 0..data.len()
{
let s = shift as f32;
let mut value_hi:f32 = 0.0;
let mut value_lo:f32 = 0.0;
for x in 0..data.len()
{
let t = x as f32;
value_hi +=
(
data[x]
*
(2.0 * freq_hi)
*
sinc(
(2.0 * freq_hi)
*
(t - s)
)
);
value_lo +=
(
data[x]
*
(2.0 * freq_lo)
*
sinc(
(2.0 * freq_lo)
*
(t - s)
)
);
filtered_data.push(value_hi - value_lo);
}
}
let mut output_file = File::create(&argv[3]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(filtered_data.len() * 4);
for &point in filtered_data.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output_file.write_all(&output_buffer).unwrap();
}

96
src/termplot.rs Normal file
View File

@ -0,0 +1,96 @@
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 2
{
eprintln!("no. <input>");
return;
}
let mut input_file = File::open(&argv[1]).unwrap();
let mut file_string = String::new();
input_file.read_to_string(&mut file_string).unwrap();
let mut data:Vec<f32> = Vec::new();
for nstr in file_string.split(|c| c == '\r' || c == '\n' || c == ' ')
{
if let Ok(n) = nstr.parse::<f32>()
{
data.push(n);
}
}
let mut max_val = data[0];
let mut min_val = data[0];
for &point in data.iter()
{
max_val = max_val.max(point);
min_val = min_val.min(point);
}
let abs_max = max_val.abs().max(min_val.abs());
for &unnorm_point in data.iter()
{
let light_point = (50.0 * unnorm_point.abs() / abs_max).round() as usize;
let dark_point = 50 - light_point;
if unnorm_point > 0.0
{
for _ in 0..50
{
print!(" ");
}
//print!("┣");
print!("");
for _ in 0..light_point
{
//print!("━");
print!("");
}
for _ in 0..dark_point
{
print!(" ");
}
println!();
}
if unnorm_point < 0.0
{
for _ in 0..dark_point
{
print!(" ");
}
for _ in 0..light_point
{
//print!("━");
print!("");
}
//print!("┫");
print!("");
for _ in 0..50
{
print!(" ");
}
println!();
}
if unnorm_point == 0.0
{
for _ in 0..50
{
print!(" ");
}
print!("");
for _ in 0..50
{
print!(" ");
}
println!();
}
}
}

37
src/tsinechirp.rs Normal file
View File

@ -0,0 +1,37 @@
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
const TAU:f32 = std::f32::consts::PI*2.0;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 5
{
eprintln!("no. <length> <start> <stop> <output.dat>");
return;
}
let length = argv[1].parse::<usize>().unwrap();
let start_freq = 1.0 / argv[2].parse::<f32>().unwrap();
let stop_freq = 1.0 / argv[3].parse::<f32>().unwrap();
let mut output_file = File::create(&argv[4]).unwrap();
let delta_freq = (stop_freq-start_freq) / (length as f32);
let mut output_buffer:Vec<u8> = Vec::with_capacity(length * 4);
for i in 0..length
{
output_buffer.extend_from_slice(
&(
(i as f32) * TAU
/
(start_freq + delta_freq * (i as f32))
/
(length as f32)
)
.sin()
.to_be_bytes()
)
}
output_file.write_all(&output_buffer).unwrap();
}

44
src/unitvex.rs Normal file
View File

@ -0,0 +1,44 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 4
{
eprintln!("usage: unitvex <input.dat> <output_i.dat> <output_q.dat>");
return;
}
let mut infile = File::open(&argv[1]).unwrap();
let mut indata:Vec<u8> = Vec::new();
infile.read_to_end(&mut indata).unwrap();
let mut input:Vec<f32> = Vec::with_capacity(indata.len()/4);
for chunk in indata.chunks_exact(4)
{
let mut samplebuffer:[u8;4] = [0x00; 4];
samplebuffer.copy_from_slice(chunk);
input.push(f32::from_be_bytes(samplebuffer));
}
let mut idata:Vec<u8> = Vec::with_capacity(indata.len());
let mut qdata:Vec<u8> = Vec::with_capacity(indata.len());
for point in input.iter()
{
let new_i = (2.0*PI*point).cos();
let new_q = (2.0*PI*point).sin();
idata.append(&mut new_i.to_be_bytes().to_vec());
qdata.append(&mut new_q.to_be_bytes().to_vec());
}
let mut outfile_i = File::create(&argv[2]).unwrap();
let mut outfile_q = File::create(&argv[3]).unwrap();
outfile_i.write_all(&idata).unwrap();
outfile_q.write_all(&qdata).unwrap();
}

61
src/upsample.rs Normal file
View File

@ -0,0 +1,61 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 4
{
eprintln!("usage: upsample <factor> <input.dat> <output.dat>");
return;
}
// deal with command-line arguments first, before anything big happens
let factor = argv[1].parse::<usize>().unwrap();
let mut input_file = File::open(&argv[2]).unwrap();
let mut output_file = File::create(&argv[3]).unwrap();
// read the input file
let mut input_data:Vec<u8> = Vec::new();
input_file.read_to_end(&mut input_data).unwrap();
// decode the input data
let mut input:Vec<f32> = Vec::with_capacity(input_data.len()/4);
for chunk in input_data.chunks_exact(4)
{
let mut samplebuffer:[u8;4] = [0x00; 4];
samplebuffer.copy_from_slice(chunk);
input.push(f32::from_be_bytes(samplebuffer));
}
// actually do the processing
let output_size = (input.len()-1)*factor;
let mut output:Vec<f32> = Vec::with_capacity(output_size);
let ffac = factor as f32;
for t in 0..input.len()-1
{
for iu in 0..factor
{
let u = iu as f32;
output.push(
input[t] * (ffac-u)/ffac
+
input[t+1] * u/ffac
);
}
}
output.push(input[input.len()-1]);
// encode the output data
let mut output_data:Vec<u8> = Vec::with_capacity(output.len()*4);
for point in output.iter()
{
output_data.extend_from_slice(&point.to_be_bytes());
}
// write the output file
output_file.write_all(&output_data).unwrap();
}

67
src/vecsum.rs Normal file
View File

@ -0,0 +1,67 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 4
{
eprintln!("usage: vecsum <input_a.dat> <input_b.dat> <output.dat>");
return;
}
let mut input_a = File::open(&argv[1]).unwrap();
let mut data_a:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_a.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data_a.push(f32::from_be_bytes(buffer));
}
let mut input_b = File::open(&argv[2]).unwrap();
let mut data_b:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_b.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data_b.push(f32::from_be_bytes(buffer));
}
let mixed_len =
(
data_a.len()
).max(
data_b.len()
);
let mut data_mixed:Vec<f32> = Vec::with_capacity(mixed_len);
for i in 0..mixed_len
{
data_mixed.push(
(
(*data_a.get(i).unwrap_or(&0.0)).powi(2)
+
(*data_b.get(i).unwrap_or(&0.0)).powi(2)
).sqrt()
);
}
let mut output_file = File::create(&argv[3]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(mixed_len * 4);
for &point in data_mixed.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output_file.write_all(&output_buffer).unwrap();
}

108
src/waterfall.rs Normal file
View File

@ -0,0 +1,108 @@
#[allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn bmp_header
(
image_width: usize, image_height: usize
) ->
Vec<u8>
{
let file_width = image_width*image_height + 1078;
let mut header:Vec<u8> = Vec::with_capacity(1078);
header.extend_from_slice(&"BM".as_bytes()); // bimp...
header.extend_from_slice(&(file_width as u32).to_le_bytes()); // filewidth
header.extend_from_slice(&0u32.to_le_bytes()); // forbidden field
header.extend_from_slice(&1078u32.to_le_bytes()); // data offset
header.extend_from_slice(&40u32.to_le_bytes()); // infoheader width
header.extend_from_slice(&(image_width as u32).to_le_bytes()); // pixel width
header.extend_from_slice(&(image_height as u32).to_le_bytes()); // pixel height
header.extend_from_slice(&1u16.to_le_bytes()); // number of planes
header.extend_from_slice(&8u16.to_le_bytes()); // bits per pixel
header.extend_from_slice(&0u32.to_le_bytes()); // compression type
header.extend_from_slice(&0u32.to_le_bytes()); // compressed image width
header.extend_from_slice(&1024u32.to_le_bytes()); // horizontal pixels/meter
header.extend_from_slice(&1024u32.to_le_bytes()); // vertical pixels/meter
header.extend_from_slice(&256u32.to_le_bytes()); // colors used
header.extend_from_slice(&0u32.to_le_bytes()); // "number of important colors"
assert_eq!(header.len(), 54);
for i in 0x00..=0xFF
{
header.push(i); // red
header.push(i); // green
header.push(i); // blue
header.push(0x00); // ?????
}
return header;
}
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 5
{
eprintln!("usage: waterfall <slice width> <gain> <output.bmp> <input.dat>");
return;
}
let width = argv[1].parse::<usize>().unwrap();
let gain = argv[2].parse::<f32>().unwrap();
let mut plot_file = File::create(&argv[3]).unwrap();
let mut signal_file = File::open(&argv[4]).unwrap();
let mut signal_data:Vec<u8> = Vec::new();
signal_file.read_to_end(&mut signal_data).unwrap();
let mut image_output:Vec<Vec<u8>> = Vec::new();
let mut global_peak:f32 = 0.0;
for data_slice in signal_data.chunks_exact(width*4)
{
let mut signal:Vec<f32> = Vec::with_capacity(width);
for chunk in data_slice.chunks_exact(4)
{
let mut sample_bytes:[u8;4] = [0x00; 4];
sample_bytes.copy_from_slice(chunk);
signal.push(f32::from_be_bytes(sample_bytes));
}
let mut image_line:Vec<u8> = Vec::with_capacity(width);
for n in 0..width
{
let mut new_i:f32 = 0.0;
let mut new_q:f32 = 0.0;
for t in 0..width
{
let (tq, ti) = (-2.0*(n as f32)*PI*(t as f32) / (width as f32)).sin_cos();
new_i += signal[t] * ti;
new_q += signal[t] * tq;
}
new_i /= (width as f32).sqrt();
new_q /= (width as f32).sqrt();
let new_power:f32 = gain*(new_i.powi(2) + new_q.powi(2)).sqrt();
global_peak = global_peak.max(new_power);
let new_pixel:u8 = (new_power*255.0).floor().min(255.0).max(0.0) as u8;
image_line.push(new_pixel);
}
image_output.push(image_line);
}
let height = image_output.len();
eprintln!("width: {}", width);
eprintln!("height: {}", height);
eprintln!("max value: {}", global_peak);
let mut image_data:Vec<u8> = Vec::with_capacity(height*width+1078);
image_data.extend_from_slice(&bmp_header(width, height));
for line in image_output.iter_mut().rev()
{
image_data.append(line);
}
plot_file.write_all(&image_data).unwrap();
}

78
src/worsen.rs Normal file
View File

@ -0,0 +1,78 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
const RAND_DEVICE:&str = "/dev/urandom";
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 3
{
eprintln!("usage: worsen <SNR‰> <target.dat>");
return;
}
let snr =
(
(
argv[1]
.parse::<f32>()
.unwrap()
/ 1000.0
)
.max(0.0)
.min(1.0)
);
let mut input_signal = File::open(&argv[2]).unwrap();
let mut data_signal:Vec<f32> = Vec::new();
loop
{
let mut buffer:[u8;4] = [0x00;4];
match input_signal.read_exact(&mut buffer)
{
Ok(_) => (),
Err(_) => break,
};
data_signal.push(f32::from_be_bytes(buffer));
}
let mut input_noise = File::open(&RAND_DEVICE).unwrap();
let mut data_noise:Vec<f32> = Vec::new();
for _ in 0..data_signal.len()
{
let mut buffer:[u8;4] = [0x00;4];
input_noise.read_exact(&mut buffer).unwrap();
data_noise.push(
(
(i32::from_be_bytes(buffer) as f64)
/
(i32::max_value() as f64)
)
as f32
);
}
let signal_level = snr;
let noise_level = 1.0 - snr;
let mut data_mixed:Vec<f32> = Vec::with_capacity(data_signal.len());
for i in 0..data_signal.len()
{
data_mixed.push(
signal_level * data_signal[i]
+
noise_level * data_noise[i]
);
}
let mut output_file = File::create(&argv[2]).unwrap();
let mut output_buffer:Vec<u8> = Vec::with_capacity(data_signal.len() * 4);
for &point in data_mixed.iter()
{
output_buffer.extend_from_slice(&point.to_be_bytes());
}
output_file.write_all(&output_buffer).unwrap();
}

62
src/zadoffchu.rs Normal file
View File

@ -0,0 +1,62 @@
#![allow(unused_parens)]
use std::io::prelude::*;
use std::fs::File;
use std::env::args;
use std::f32::consts::PI;
fn main()
{
let argv = args().collect::<Vec<String>>();
if argv.len() != 6
{
eprintln!("usage: zadoffchu <u> <q> <length> <i.dat> <q.dat>");
return;
}
let param_u = argv[1].parse::<usize>().unwrap();
let param_q = argv[2].parse::<usize>().unwrap();
let param_l = argv[3].parse::<usize>().unwrap();
let mut sin_file = File::create(&argv[4]).unwrap();
let mut cos_file = File::create(&argv[5]).unwrap();
let u = param_u as f32;
let q = param_q as f32;
let l = param_l as f32;
let cf = l % 2.0;
let mut sin_buffer:Vec<u8> = Vec::with_capacity(param_l * 4);
let mut cos_buffer:Vec<u8> = Vec::with_capacity(param_l * 4);
for i in 0..param_l
{
let t = i as f32;
sin_buffer.extend_from_slice(
&(
(
-1.0*PI*u*t
) * (
t + cf + 2.0*q
) / (
l
)
)
.cos()
.to_be_bytes()
);
cos_buffer.extend_from_slice(
&(
(
-1.0*PI*u*t
) * (
t + cf + 2.0*q
) / (
l
)
)
.sin()
.to_be_bytes()
);
}
sin_file.write_all(&sin_buffer).unwrap();
cos_file.write_all(&cos_buffer).unwrap();
}