Compare commits
5 Commits
9aeb9ea3f4
...
main
Author | SHA1 | Date | |
---|---|---|---|
3c2f3365e8
|
|||
8a6e51c877
|
|||
5a3798732c
|
|||
7ef059fa5b
|
|||
fd5feaca3f
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,3 +8,6 @@ target/
|
|||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
# Direnv
|
||||||
|
.direnv/
|
@@ -1,20 +1,34 @@
|
|||||||
use cpal::{StreamConfig, traits::{DeviceTrait, HostTrait}};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub fn play_audio(signal: &[f32]) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn play_audio(signal: &[f32]) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let device = host.default_output_device()?.config();
|
let device = host.default_output_device().ok_or("No output device found")?;
|
||||||
let config = device.default_output_config()?.config();
|
let config = device.default_output_config()?.config();
|
||||||
|
|
||||||
|
let signal = Arc::new(Mutex::new(signal.to_vec()));
|
||||||
|
let singal_clone = Arc::clone(&signal);
|
||||||
|
let mut index = 0;
|
||||||
|
|
||||||
let stream = device.build_output_stream(
|
let stream = device.build_output_stream(
|
||||||
&config,
|
&config,
|
||||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||||
data.copy_from_slice(&signal[..data.len()]);
|
let signal = singal_clone.lock().unwrap();
|
||||||
|
|
||||||
|
for sample in data.iter_mut() {
|
||||||
|
*sample = if index < signal.len() {
|
||||||
|
signal[index]
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|err| eprintln!("Error: {:?}", err),
|
|err| eprintln!("Error: {:?}", err),
|
||||||
None
|
None
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
stream.play()?;
|
stream.play()?;
|
||||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
@@ -1,20 +1,43 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use dasp_signal::Signal;
|
|
||||||
|
|
||||||
const SAMPLE_RATE: f32 = 44100.0;
|
const SAMPLE_RATE: f32 = 44100.0;
|
||||||
const BAUD_RATE: f32 = 1200.0;
|
const BAUD_RATE: f32 = 1200.0;
|
||||||
const FREQ_MARK: f32 = 1200.0;
|
const FREQ_MARK: f32 = 1200.0;
|
||||||
const FREQ_SPACE: f32 = 2200.0;
|
const FREQ_SPACE: f32 = 2200.0;
|
||||||
|
|
||||||
pub fn fsk_modulate(bits: &[u8]) -> Vec<f32> {
|
pub fn fsk_modulate(data: &[u8]) -> Vec<f32> {
|
||||||
let samples_per_bit = (SAMPLE_RATE / BAUD_RATE) as usize;
|
let samples_per_bit = (SAMPLE_RATE / BAUD_RATE) as usize;
|
||||||
let mut signal = Vec::new();
|
let mut signal = Vec::new();
|
||||||
|
|
||||||
for &bit in bits {
|
for &byte in data {
|
||||||
let freq = if bit == 1 { FREQ_MARK } else { FREQ_SPACE };
|
for i in (0..8).rev() {
|
||||||
for i in 0..samples_per_bit {
|
let bit = (byte >> i) & 1;
|
||||||
let t = i as f32 / SAMPLE_RATE;
|
let freq = if bit == 1 { FREQ_MARK } else { FREQ_SPACE };
|
||||||
signal.push((2.0 * PI * freq * t).sin());
|
|
||||||
|
for j in 0..samples_per_bit {
|
||||||
|
let t = (signal.len() + j) as f32 / SAMPLE_RATE;
|
||||||
|
signal.push((2.0 * PI * freq * t).sin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signal
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn afsk_modulate(data: &[u8], carrier_freq: f32) -> Vec<f32> {
|
||||||
|
let samples_per_bit = (SAMPLE_RATE / BAUD_RATE) as usize;
|
||||||
|
let mut signal = Vec::new();
|
||||||
|
|
||||||
|
for &byte in data {
|
||||||
|
for i in (0..8).rev() {
|
||||||
|
let bit = (byte >> i) & 1;
|
||||||
|
let freq = if bit == 1 { FREQ_MARK } else { FREQ_SPACE };
|
||||||
|
|
||||||
|
for j in 0..samples_per_bit {
|
||||||
|
let t = (signal.len() + j) as f32 / SAMPLE_RATE;
|
||||||
|
let fsk_wave = (2.0 * PI * freq * t).sin();
|
||||||
|
let carrier_wave = (2.0 * PI * carrier_freq * t).sin();
|
||||||
|
signal.push(fsk_wave * carrier_wave); // Modulate FSK onto the carrier
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
signal
|
signal
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
use fsk::fsk_modulate;
|
use audio::play_audio;
|
||||||
|
use fsk::afsk_modulate;
|
||||||
use text_io::scan;
|
use text_io::scan;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
@@ -15,9 +16,14 @@ fn main() -> Result<(), io::Error> {
|
|||||||
match a {
|
match a {
|
||||||
1 => {
|
1 => {
|
||||||
println!("1 has been pressed! No test tone implemented yet");
|
println!("1 has been pressed! No test tone implemented yet");
|
||||||
let tone: signal;
|
let tone: Vec<f32>;
|
||||||
tone = fsk_modulate([0x25, 0x08, 0x00, 0x25, 0xA0]);
|
tone = afsk_modulate(&[
|
||||||
play_audio(tone);
|
0b10101010, 0b01010101, 0b10101010, 0b01010101, 0b10101010, 0b01010101, 0b10101010, 0b01010101, 0b10101010, 0b01010101,
|
||||||
|
0b1000101,0b11111111,0b1000101,0b11111111,0b11111111,0b1000101,0b11111111,0b11111111,0b1000101,0b11111111,0b11111111,
|
||||||
|
], 1700.0);
|
||||||
|
let repeated_tone: Vec<f32> = tone.iter().cloned().cycle().take(tone.len() * 10).collect();
|
||||||
|
|
||||||
|
play_audio(&repeated_tone);
|
||||||
return main();
|
return main();
|
||||||
},
|
},
|
||||||
0 => {
|
0 => {
|
||||||
|
28
cellular/src/amps.rs
Normal file
28
cellular/src/amps.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
enum DSPMode {
|
||||||
|
OFF, // Channel not active
|
||||||
|
AUDIO_RX_AUDIO_TX, // stream audio
|
||||||
|
AUDIO_RX_FRAME_TX, // stream audio and send frames
|
||||||
|
AUDIO_RX_SILENCE_TX, // stream audio and send silence
|
||||||
|
FRAME_RX_FRAME_TX, // send and receive frames
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AMPSChannelType {
|
||||||
|
CC, // control channel
|
||||||
|
PC, // paging channel
|
||||||
|
CC_PC, // combined control+paging
|
||||||
|
VC, // voice channel
|
||||||
|
CC_PC_VC, // combined control+paging+voice
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AMPSState {
|
||||||
|
NULL, // power off
|
||||||
|
IDLE, // channel not in use
|
||||||
|
BUSY, // channel busy (call in progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FSK_RX_Sync {
|
||||||
|
NONE, // not in sync, waiting for valid dotting sequence
|
||||||
|
DOTTING, // received a valid dotting sequence, check for sync sequence
|
||||||
|
POSITIVE, // valid sync, read all bits from the frame
|
||||||
|
NEGATIVE, // negative sync (high frequency deviation detected as low signal)
|
||||||
|
}
|
36
cellular/src/iir_filter.rs
Normal file
36
cellular/src/iir_filter.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
struct IIRFilter {
|
||||||
|
iter: i32,
|
||||||
|
a0: f64,
|
||||||
|
a1: f64,
|
||||||
|
a2: f64,
|
||||||
|
b1: f64,
|
||||||
|
b2: f64,
|
||||||
|
z1: f64,
|
||||||
|
z2: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IIRFilter {
|
||||||
|
pub fn lowpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn highpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bandpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notch_init(&mut self, frequency: f64, samplerate: i32, iterations: i32, Q: f64) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(&mut self, samples: Sample, length: i32) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_baseband(&mut self, filter: IIRFilter, baseband: f64, length: i32) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
45
cellular/src/libmobile_sender.rs
Normal file
45
cellular/src/libmobile_sender.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
enum PagingSignal {
|
||||||
|
NONE = 0,
|
||||||
|
TONE,
|
||||||
|
NOTONE,
|
||||||
|
POSITIVE,
|
||||||
|
NEGATIVE,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Sender {
|
||||||
|
// System Info
|
||||||
|
channel: char,
|
||||||
|
send_freq: f64,
|
||||||
|
recv_freq: f64,
|
||||||
|
|
||||||
|
// FM/AM levels
|
||||||
|
am: i32,
|
||||||
|
max_deviation: f64,
|
||||||
|
max_modulation: f64,
|
||||||
|
speech_deviation: f64,
|
||||||
|
modulation_index: f64,
|
||||||
|
max_display: f64,
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
audio: void,
|
||||||
|
device: char,
|
||||||
|
audio_open: void,
|
||||||
|
audio_start: i32,
|
||||||
|
audio_close: void,
|
||||||
|
audio_write: i32,
|
||||||
|
audio_get_tosend: i32,
|
||||||
|
samplerate: i32,
|
||||||
|
samplerate_state: SampleRate,
|
||||||
|
rx_gain: f64,
|
||||||
|
tx_gain: f64,
|
||||||
|
pre_emphasis: i32,
|
||||||
|
de_emphasis: i32,
|
||||||
|
epmphasis_state: EmphasisState,
|
||||||
|
|
||||||
|
// Loopback
|
||||||
|
loopback: i32,
|
||||||
|
|
||||||
|
// Record & Playback
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -2,6 +2,10 @@ use ferris_says::say;
|
|||||||
use std::io::{stdout, BufWriter};
|
use std::io::{stdout, BufWriter};
|
||||||
|
|
||||||
mod sdr;
|
mod sdr;
|
||||||
|
mod amps;
|
||||||
|
mod libmobile_sender;
|
||||||
|
mod iir_filter;
|
||||||
|
mod samplerate;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let stdout = stdout();
|
let stdout = stdout();
|
||||||
|
45
cellular/src/samplerate.rs
Normal file
45
cellular/src/samplerate.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
struct SampleRate {
|
||||||
|
factor: f64,
|
||||||
|
filter_cutoff: f64,
|
||||||
|
down: DownSampler,
|
||||||
|
up: UpSampler,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DownSampler {
|
||||||
|
lp: IIRFilter,
|
||||||
|
last_sample: Sample,
|
||||||
|
in_index: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UpSampler {
|
||||||
|
lp: IIRFilter,
|
||||||
|
current_sample: Sample,
|
||||||
|
last_sample: Sample,
|
||||||
|
in_index: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SampleRate {
|
||||||
|
pub fn init(&mut self, low_samplerate: f64, high_samplerate: f64, filter_cutoff: f64) -> Result<(), i32> {
|
||||||
|
// Implementation goes here
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
// or Err(error_code) if there's an error
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn downsample(&mut self, state: SampleRate, samples: Sample, input_num: i32) -> Result<(), i32> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upsample_input_num(&mut self, state: SampleRate, output_num: i32) -> Result<(), i32> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upsample_output_num(&mut self, state: SampleRate, input_num: i32) -> Result<(), i32> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upsample(&mut self, state: SampleRate, input: Sample, input_num: i32, output: Sample, output_num: i32) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
82
flake.lock
generated
Normal file
82
flake.lock
generated
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1742669843,
|
||||||
|
"narHash": "sha256-G5n+FOXLXcRx+3hCJ6Rt6ZQyF1zqQ0DL0sWAMn2Nk0w=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "1e5b653dff12029333a6546c11e108ede13052eb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1742870002,
|
||||||
|
"narHash": "sha256-eQnw8ufyLmrboODU8RKVNh2Mv7SACzdoFrRUV5zdNNE=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "b4c18f262dbebecb855136c1ed8047b99a9c75b6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
33
flake.nix
Normal file
33
flake.nix
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
rust-overlay = {
|
||||||
|
url = "github:oxalica/rust-overlay";
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
overlays = [ (import rust-overlay ) ];
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system overlays;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
with pkgs;
|
||||||
|
{
|
||||||
|
devShells.default = mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.bashInteractive
|
||||||
|
rust-bin.stable.latest.default
|
||||||
|
pkgs.pkg-config
|
||||||
|
pkgs.alsa-lib
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user