cicadenade/src/main.rs

301 lines
8.3 KiB
Rust

#![allow(unused_parens)]
use std::io::prelude::*;
use std::io::ErrorKind;
use std::io::BufReader;
use std::fs::File;
use std::time::Duration;
use std::time::Instant;
use std::process::exit;
extern crate lapp;
mod chirp;
mod gaussian;
mod posi;
mod enmodulation;
mod demodulation;
fn main
() {
let args =
(
lapp::parse_args(
"
Threnodyne is program for transmitting and receiving data in chirp-sequence spread-spectrum form using your computer's audio card.
Chirps can provide substantial resistance to unfavorable channel conditions, which makes this program \
potentially suitable for transmitting data over channels intended for narrowband voice communication.
Audio input/output is in 16-bit little-endian integer sample format.
Data input/output is arbitrary binary data, which can be text, file contents, or whatever else.
Usage:
<mode> (string) - Either 'e' to encode/enmodulate, or 'd' to decode/demodulate.
-i, --input (default stdin) - Filelike object from which to read input.
-o, --output (default stdout) - Filelike object into which to write output.
-s, --sample-rate (default 48000.0) - Sample rate in Hertz, used for calculating modulation frequencies
-b, --bit-rate (default 50.0) - Data rate, in chirps (bits) per second. Be aware that lower values will increase system load.
-f, --center-frequency (default 4000.0) - Center frequency of the signal in Hertz.
-d, --deviation (default 2000.0) - Maximum range traversed above and below the center frequency by the signal.
-t, --squelch (default 0.4) - Minimum signal match degree for demodulation, from 0.0 (accept all) to 1.0 (ignore all).
-a, --ascii-only - When demodulating, suppress all output that is not an ASCII character. Possibly useful if searching for text on a noisy channel.
-g, --debug (default 0) - If specified when demodulating, emits every nth correlation value in f32 form to threnodyne_rise.dat and threnodyne_fall.dat.
"
)
);
let mode = args.get_string("mode");
let mut input_reader = BufReader::new(args.get_infile("input"));
let mut output_file = args.get_outfile("output");
let arg_samplerate = args.get_float("sample-rate");
let arg_bitrate= args.get_float("bit-rate");
let arg_centerfreq = args.get_float("center-frequency");
let arg_deviation = args.get_float("deviation");
let arg_squelch = args.get_float("squelch");
let arg_ascii = args.get_bool("ascii-only");
let arg_debug = args.get_integer("debug");
if !(
&["e", "d"].contains(&mode.as_str())
) {
eprintln!("threnodyne: please specify either 'e' to encode/enmodulate or 'd' to decode/demodulate.");
exit(1);
}
if arg_samplerate <= 0.0
{
eprintln!("threnodyne: the sample rate must be a positive, nonzero number ({} won't do)", arg_samplerate);
exit(1);
}
if arg_bitrate < 0.0
{
eprintln!("threnodyne: the bitrate must be a positive number ({} won't do)", arg_bitrate);
exit(1);
}
if arg_bitrate > arg_samplerate
{
eprintln!("threnodyne: the bitrate cannot be larger than the sample rate (a bitrate of {} won't do when the sample rate is {})", arg_bitrate, arg_samplerate);
exit(1);
}
if arg_centerfreq < 0.0
{
eprintln!("threnodyne: the center frequency must be a positive number ({} won't do)", arg_centerfreq);
exit(1);
}
if arg_deviation < 0.0
{
eprintln!("threnodyne: the frequency deviation must be a positive number ({} won't do)", arg_deviation);
exit(1);
}
if arg_deviation > arg_centerfreq
{
eprintln!("threnodyne: the frequency deviation cannot be larger than the center frequency (a deviation of {} won't do when the center frequency is {})", arg_deviation, arg_centerfreq);
exit(1);
}
if arg_squelch < 0.0
{
eprintln!("threnodyne: the squelch threshold must be at least 0.0 ({} won't do)", arg_squelch);
exit(1);
}
let symbol_len = arg_samplerate / arg_bitrate;
let symbol_size = symbol_len.round() as usize;
let center_freq = 2.0 * arg_centerfreq / arg_samplerate;
let deviation = 2.0 * arg_deviation / arg_samplerate;
if mode.as_str() == "e"
{
let enmodulator = enmodulation::Enmodulator::new(center_freq, deviation, symbol_len);
let mut output_buffer:Vec<u8> = Vec::with_capacity(symbol_size * 2);
let wall_start = Instant::now();
let mut process_time = Duration::new(0,0);
let mut samples_processed:usize = 0;
let mut bytes_processed:usize = 0;
loop
{
let mut input_buffer:[u8;1] = [0x00];
let byte = match input_reader.read_exact(&mut input_buffer)
{
Err(why) if why.kind() == ErrorKind::UnexpectedEof => break,
Err(why) =>
{
eprintln!("threnodyne: input read error: {}", why);
exit(1);
},
Ok(_) => input_buffer[0],
};
let iter_time = Instant::now();
let signal = enmodulator.process(byte);
output_buffer.clear();
for &sample in signal.iter()
{
output_buffer.extend_from_slice(
&(
(sample * 32767.0) as i16
)
.to_le_bytes()
);
}
process_time += iter_time.elapsed();
bytes_processed += 1;
samples_processed += signal.len();
match output_file.write_all(&output_buffer)
{
Err(why) =>
{
eprintln!("threnodyne: output write error: {}", why);
exit(1);
},
Ok(_) => (),
};
}
eprintln!();
eprintln!("samples processed: {}", samples_processed);
eprintln!("wall time: {} ms", wall_start.elapsed().as_millis());
eprintln!("process time: {} ms", process_time.as_millis());
eprintln!("process rate: {} samples/s, {} bytes/s", samples_processed as f32 / process_time.as_secs_f32(), bytes_processed as f32 / process_time.as_secs_f32());
}
if mode.as_str() == "d"
{
let mut demodulator = demodulation::Demodulator::new(center_freq, deviation, symbol_len);
demodulator.set_squelch(arg_squelch);
let wall_start = Instant::now();
let mut process_time = Duration::new(0,0);
let mut samples_processed:usize = 0;
let mut bytes_processed:usize = 0;
let mut debug_files:Option<(File,File)> = None;
if arg_debug > 0
{
debug_files =
Some(
(
match File::create("threnodyne_rise.dat")
{
Ok(file) => file,
Err(why) =>
{
eprintln!("threnodyne: could not create debug output file for rising correlation: {}", why);
exit(1);
},
},
match File::create("threnodyne_fall.dat")
{
Ok(file) => file,
Err(why) =>
{
eprintln!("threnodyne: could not create debug output file for falling correlation: {}", why);
exit(1);
},
},
)
);
}
let mut ending:bool = false;
let mut ending_counter:usize = symbol_len as usize;
loop
{
let mut sample_bytes:[u8;2] = [0x00, 0x00];
if !ending
{
match input_reader.read_exact(&mut sample_bytes)
{
Err(why) if why.kind() == ErrorKind::UnexpectedEof =>
{
ending = true
},
Err(why) =>
{
eprintln!("threnodyne: input read error: {}", why);
exit(1);
},
Ok(_) => (),
}
}
else if ending_counter > 0
{
ending_counter -= 1;
}
else
{
break;
}
let iter_time = Instant::now();
let sample =
(
i16::from_le_bytes(sample_bytes) as f32
/
32767.0
);
let demod_result = demodulator.process(sample);
if let Some((risefile, fallfile)) = &mut debug_files
{
if samples_processed % (arg_debug as usize) == 0
{
let (riseval, fallval) = demodulator.dump_correl();
risefile.write_all(&riseval.to_be_bytes()).expect("write failed on rise debug file");
fallfile.write_all(&fallval.to_be_bytes()).expect("write failed on fall debug file");
}
}
process_time += iter_time.elapsed();
samples_processed += 1;
if let Some(byte) = demod_result
{
bytes_processed += 1;
if byte.is_ascii() || !arg_ascii
{
match output_file.write_all(&[byte])
{
Err(why) =>
{
eprintln!("threnodyne: output write error: {}", why);
exit(1);
},
Ok(_) => (),
};
let _ = output_file.flush();
}
}
}
eprintln!();
eprintln!("samples processed: {}", samples_processed);
eprintln!("bytes processed: {}", bytes_processed);
eprintln!("wall time: {} ms", wall_start.elapsed().as_millis());
eprintln!("process time: {} ms", process_time.as_millis());
eprintln!("process rate: {} samples/s, {} bytes/s", samples_processed as f32 / process_time.as_secs_f32(), bytes_processed as f32 / process_time.as_secs_f32());
}
}