migrate pattern structures from previous projects.
This commit is contained in:
parent
b126a3105f
commit
82e6d51f55
|
@ -32,6 +32,7 @@ bincode = "1.3.3"
|
||||||
uuid = { version = "1.10.0", features = ["serde"] }
|
uuid = { version = "1.10.0", features = ["serde"] }
|
||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
rand = "0.8.5"
|
||||||
tauri-plugin-devtools = "2.0.0"
|
tauri-plugin-devtools = "2.0.0"
|
||||||
tauri-plugin-os = "2"
|
tauri-plugin-os = "2"
|
||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
|
|
13
src-tauri/src/canvas_model.rs
Normal file
13
src-tauri/src/canvas_model.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct CanvasPoint {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CanvasPoint {
|
||||||
|
pub fn new(x: f64, y: f64) -> Self {
|
||||||
|
CanvasPoint { x, y }
|
||||||
|
}
|
||||||
|
}
|
88
src-tauri/src/fraction.rs
Normal file
88
src-tauri/src/fraction.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
fmt::{self, Display, Formatter},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct Fraction {
|
||||||
|
pub numerator: i64,
|
||||||
|
pub denominator: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fraction {
|
||||||
|
pub fn new_from_float(numerator: f64, denominator: f64) -> Self {
|
||||||
|
if denominator == 0.0 {
|
||||||
|
panic!("denomitator cannot be zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算转换因子,以消除浮点数的小数部分
|
||||||
|
let scale_factor = 10i64
|
||||||
|
.pow(Self::decimal_places(numerator).max(Self::decimal_places(denominator)) as u32);
|
||||||
|
|
||||||
|
let int_numerator = (numerator * scale_factor as f64).round() as i64;
|
||||||
|
let int_denominator = (denominator * scale_factor as f64).round() as i64;
|
||||||
|
|
||||||
|
let gcd = Self::gcd(int_numerator, int_denominator);
|
||||||
|
Fraction {
|
||||||
|
numerator: int_numerator / gcd,
|
||||||
|
denominator: int_denominator / gcd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(numerator: i64, denominator: i64) -> Self {
|
||||||
|
if denominator == 0 {
|
||||||
|
panic!("denominator cannot be zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
let gcd = Self::gcd(numerator, denominator);
|
||||||
|
Fraction {
|
||||||
|
numerator: numerator / gcd,
|
||||||
|
denominator: denominator / gcd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decimal_places(value: f64) -> u32 {
|
||||||
|
let mut decimal_places = 0;
|
||||||
|
let mut scaled_value = value;
|
||||||
|
while scaled_value.fract() != 0.0 && decimal_places < 15 {
|
||||||
|
scaled_value *= 10.0;
|
||||||
|
decimal_places += 1;
|
||||||
|
}
|
||||||
|
decimal_places
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gcd(a: i64, b: i64) -> i64 {
|
||||||
|
let mut a = a.abs();
|
||||||
|
let mut b = b.abs();
|
||||||
|
while b != 0 {
|
||||||
|
let temp = b;
|
||||||
|
b = a % b;
|
||||||
|
a = temp;
|
||||||
|
}
|
||||||
|
a
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmp(&self, other: &Fraction) -> Ordering {
|
||||||
|
(self.numerator * other.denominator).cmp(&(other.numerator * self.denominator))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Fraction {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.cmp(other) == Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Fraction {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Fraction {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}/{}", self.numerator, self.denominator)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,10 +10,14 @@ use tauri::{
|
||||||
use tauri_plugin_dialog::DialogExt;
|
use tauri_plugin_dialog::DialogExt;
|
||||||
|
|
||||||
mod bluetooth;
|
mod bluetooth;
|
||||||
|
mod canvas_model;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod config_db;
|
mod config_db;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod fraction;
|
||||||
|
mod pattern;
|
||||||
mod playlist;
|
mod playlist;
|
||||||
|
mod protocols;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
|
73
src-tauri/src/pattern/algorithms.rs
Normal file
73
src-tauri/src/pattern/algorithms.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum PulseInterpolation {
|
||||||
|
Constant = 1,
|
||||||
|
JumpChange = 2,
|
||||||
|
Linear = 3,
|
||||||
|
Bilinear = 4,
|
||||||
|
Cubic = 5,
|
||||||
|
Bezier = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PulseInterpolation {
|
||||||
|
fn as_u8(&self) -> u8 {
|
||||||
|
self.clone() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str(&self) -> String {
|
||||||
|
self.as_u8().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for PulseInterpolation {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value.parse::<u8>() {
|
||||||
|
Ok(1) => Ok(PulseInterpolation::Constant),
|
||||||
|
Ok(2) => Ok(PulseInterpolation::JumpChange),
|
||||||
|
Ok(3) => Ok(PulseInterpolation::Linear),
|
||||||
|
Ok(4) => Ok(PulseInterpolation::Bilinear),
|
||||||
|
Ok(5) => Ok(PulseInterpolation::Cubic),
|
||||||
|
Ok(6) => Ok(PulseInterpolation::Bezier),
|
||||||
|
_ => Err(format!("Invalid Pulse Interpolation value: {}", value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum FrequencyChange {
|
||||||
|
Constant = 1,
|
||||||
|
Linear = 2,
|
||||||
|
EaseIn = 3,
|
||||||
|
EaseOut = 4,
|
||||||
|
Randomize = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrequencyChange {
|
||||||
|
fn as_u8(&self) -> u8 {
|
||||||
|
self.clone() as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str(&self) -> String {
|
||||||
|
self.as_u8().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for FrequencyChange {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value.parse::<u8>() {
|
||||||
|
Ok(1) => Ok(FrequencyChange::Constant),
|
||||||
|
Ok(2) => Ok(FrequencyChange::Linear),
|
||||||
|
Ok(3) => Ok(FrequencyChange::EaseIn),
|
||||||
|
Ok(4) => Ok(FrequencyChange::EaseOut),
|
||||||
|
Ok(5) => Ok(FrequencyChange::Randomize),
|
||||||
|
_ => Err(format!("Invalid Frequency Change value: {}", value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
201
src-tauri/src/pattern/frequency_shifting.rs
Normal file
201
src-tauri/src/pattern/frequency_shifting.rs
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
use rand::Rng;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::fraction::Fraction;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum FrequencyShifting {
|
||||||
|
Linear,
|
||||||
|
Quadratic,
|
||||||
|
Cubic,
|
||||||
|
Ease,
|
||||||
|
Pulsating,
|
||||||
|
Spiking,
|
||||||
|
Randomize,
|
||||||
|
Maniac,
|
||||||
|
Synchronized,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for FrequencyShifting {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(FrequencyShifting::Linear),
|
||||||
|
1 => Ok(FrequencyShifting::Quadratic),
|
||||||
|
2 => Ok(FrequencyShifting::Cubic),
|
||||||
|
3 => Ok(FrequencyShifting::Ease),
|
||||||
|
4 => Ok(FrequencyShifting::Pulsating),
|
||||||
|
5 => Ok(FrequencyShifting::Spiking),
|
||||||
|
6 => Ok(FrequencyShifting::Randomize),
|
||||||
|
7 => Ok(FrequencyShifting::Maniac),
|
||||||
|
8 => Ok(FrequencyShifting::Synchronized),
|
||||||
|
_ => Err(String::from("Unregconized shifting mode")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_frequency_shifting<'de, D>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<FrequencyShifting, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let value = u32::deserialize(deserializer)?;
|
||||||
|
FrequencyShifting::try_from(value).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shifting_frequency(
|
||||||
|
sample_tick: u32,
|
||||||
|
start_timing: u32,
|
||||||
|
start_pulse: &crate::pattern::Pulse,
|
||||||
|
end_pulse: &crate::pattern::Pulse,
|
||||||
|
) -> (u16, u16, u8) {
|
||||||
|
match end_pulse.frequency_shifting {
|
||||||
|
FrequencyShifting::Linear => bezier_shifting(
|
||||||
|
sample_tick,
|
||||||
|
start_timing,
|
||||||
|
start_pulse.frequency,
|
||||||
|
start_timing + start_pulse.duration,
|
||||||
|
end_pulse.frequency,
|
||||||
|
(0.0, 0.0),
|
||||||
|
(1.0, 1.0),
|
||||||
|
),
|
||||||
|
FrequencyShifting::Quadratic => bezier_shifting(
|
||||||
|
sample_tick,
|
||||||
|
start_timing,
|
||||||
|
start_pulse.frequency,
|
||||||
|
start_timing + start_pulse.duration,
|
||||||
|
end_pulse.frequency,
|
||||||
|
(1.0, 0.0),
|
||||||
|
(1.0, 1.0),
|
||||||
|
),
|
||||||
|
FrequencyShifting::Cubic => bezier_shifting(
|
||||||
|
sample_tick,
|
||||||
|
start_timing,
|
||||||
|
start_pulse.frequency,
|
||||||
|
start_timing + start_pulse.duration,
|
||||||
|
end_pulse.frequency,
|
||||||
|
(0.9, 0.1),
|
||||||
|
(1.0, 0.2),
|
||||||
|
),
|
||||||
|
FrequencyShifting::Ease => bezier_shifting(
|
||||||
|
sample_tick,
|
||||||
|
start_timing,
|
||||||
|
start_pulse.frequency,
|
||||||
|
start_timing + start_pulse.duration,
|
||||||
|
end_pulse.frequency,
|
||||||
|
(0.33, 0.0),
|
||||||
|
(0.67, 1.0),
|
||||||
|
),
|
||||||
|
FrequencyShifting::Pulsating => periodic_shifting(
|
||||||
|
sample_tick,
|
||||||
|
start_timing,
|
||||||
|
start_timing + start_pulse.duration,
|
||||||
|
start_pulse.frequency.min(end_pulse.frequency),
|
||||||
|
end_pulse.frequency.max(start_pulse.frequency),
|
||||||
|
),
|
||||||
|
FrequencyShifting::Spiking => periodic_shifting(
|
||||||
|
sample_tick,
|
||||||
|
start_timing,
|
||||||
|
start_timing + start_pulse.duration,
|
||||||
|
start_pulse.frequency.min(end_pulse.frequency),
|
||||||
|
start_pulse.frequency.max(end_pulse.frequency) * 2.0,
|
||||||
|
),
|
||||||
|
FrequencyShifting::Randomize => randomize_shifting(
|
||||||
|
start_pulse.frequency.min(end_pulse.frequency),
|
||||||
|
start_pulse.frequency.max(end_pulse.frequency),
|
||||||
|
),
|
||||||
|
FrequencyShifting::Maniac => randomize_shifting(1.0, 1000.0),
|
||||||
|
FrequencyShifting::Synchronized => bezier_shifting(
|
||||||
|
sample_tick,
|
||||||
|
start_timing,
|
||||||
|
start_pulse.frequency,
|
||||||
|
start_timing + start_pulse.duration,
|
||||||
|
end_pulse.frequency,
|
||||||
|
end_pulse.control_point_1.into(),
|
||||||
|
end_pulse.control_point_2.into(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_frequency_level(frequency: f64) -> u8 {
|
||||||
|
match frequency {
|
||||||
|
0.0..=150.0 => 1,
|
||||||
|
150.0..=300.0 => 2,
|
||||||
|
300.0..=600.0 => 3,
|
||||||
|
600.0..=800.0 => 4,
|
||||||
|
800.0.. => 5,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_frequency(frequency: f64) -> (u16, u16) {
|
||||||
|
let waveform_frequency = frequency * 1000_f64;
|
||||||
|
let x = (waveform_frequency.sqrt() * 15_f64).floor() as i64;
|
||||||
|
let y = (waveform_frequency - x as f64).floor() as i64;
|
||||||
|
let f = Fraction::new(x, y);
|
||||||
|
let output = u16::try_from(f.numerator).unwrap_or(u16::MAX);
|
||||||
|
let idle = u16::try_from(f.denominator).unwrap_or(u16::MAX);
|
||||||
|
(output, idle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bezier_shifting(
|
||||||
|
sample_tick: u32,
|
||||||
|
start_time: u32,
|
||||||
|
start_frequency: f64,
|
||||||
|
end_time: u32,
|
||||||
|
end_frequency: f64,
|
||||||
|
control_1: (f64, f64),
|
||||||
|
control_2: (f64, f64),
|
||||||
|
) -> (u16, u16, u8) {
|
||||||
|
let t = (sample_tick - start_time) as f64 / (end_time - start_time) as f64;
|
||||||
|
let u = 1.0 - t;
|
||||||
|
let tt = t * t;
|
||||||
|
let uu = u * u;
|
||||||
|
let uuu = uu * u;
|
||||||
|
let ttt = tt * t;
|
||||||
|
|
||||||
|
let p0 = (start_time as f64, start_frequency);
|
||||||
|
let p1 = control_1;
|
||||||
|
let p2 = control_2;
|
||||||
|
let p3 = (end_time as f64, end_frequency);
|
||||||
|
|
||||||
|
let frequency = uuu * p0.1 + 3.0 * uu * t * p1.1 + 3.0 * u * tt * p2.1 + ttt * p3.1;
|
||||||
|
|
||||||
|
let (x, y) = resolve_frequency(frequency);
|
||||||
|
let estimate_level = estimate_frequency_level(frequency);
|
||||||
|
(x, y, estimate_level)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn periodic_shifting(
|
||||||
|
sample_tick: u32,
|
||||||
|
start_time: u32,
|
||||||
|
_end_time: u32,
|
||||||
|
min_frequency: f64,
|
||||||
|
max_frequency: f64,
|
||||||
|
) -> (u16, u16, u8) {
|
||||||
|
let period_length = {
|
||||||
|
let mut l = sample_tick - start_time;
|
||||||
|
while l > 25 {
|
||||||
|
l -= 25;
|
||||||
|
}
|
||||||
|
l
|
||||||
|
};
|
||||||
|
let frequency = if (sample_tick - start_time) / period_length % 2 == 0 {
|
||||||
|
max_frequency
|
||||||
|
} else {
|
||||||
|
min_frequency
|
||||||
|
};
|
||||||
|
let (x, y) = resolve_frequency(frequency);
|
||||||
|
let estimate_level = estimate_frequency_level(frequency);
|
||||||
|
(x, y, estimate_level)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn randomize_shifting(min_frequency: f64, max_frequency: f64) -> (u16, u16, u8) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let frequency = rng.gen_range(min_frequency..=max_frequency);
|
||||||
|
let (x, y) = resolve_frequency(frequency);
|
||||||
|
let estimate_level = estimate_frequency_level(frequency);
|
||||||
|
(x, y, estimate_level)
|
||||||
|
}
|
66
src-tauri/src/pattern/mod.rs
Normal file
66
src-tauri/src/pattern/mod.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
mod algorithms;
|
||||||
|
mod frequency_shifting;
|
||||||
|
mod pulse;
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub use pulse::Pulse;
|
||||||
|
use pulse::{generate_pulse_sequence, RawPulse};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Pattern {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub smooth_repeat: bool,
|
||||||
|
pub pulses: VecDeque<Pulse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pattern {
|
||||||
|
pub fn add_pulse(&mut self, pulse: Pulse) {
|
||||||
|
self.pulses.push_back(pulse);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_pulse(&mut self, pulse_id: &Uuid) {
|
||||||
|
if let Some(index) = self.pulses.iter().position(|p| &p.id == pulse_id) {
|
||||||
|
self.pulses.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_forward(&mut self, pulse_id: &Uuid) {
|
||||||
|
if let Some(index) = self.pulses.iter().position(|p| &p.id == pulse_id) {
|
||||||
|
if index > 0 && index <= self.pulses.len() - 1 {
|
||||||
|
self.pulses.swap(index, index - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_backward(&mut self, pulse_id: &Uuid) {
|
||||||
|
if let Some(index) = self.pulses.iter().position(|p| &p.id == pulse_id) {
|
||||||
|
if index < self.pulses.len() - 1 {
|
||||||
|
self.pulses.swap(index, index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_smooth_repeat(&mut self, repeat: bool) {
|
||||||
|
self.smooth_repeat = repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_pulses(&self) -> Box<dyn Iterator<Item = RawPulse>> {
|
||||||
|
generate_pulse_sequence(self.pulses.clone(), self.smooth_repeat)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_duration(&self) -> u32 {
|
||||||
|
let mut duration = 0;
|
||||||
|
for (index, pulse) in self.pulses.iter().enumerate() {
|
||||||
|
if (index == self.pulses.len() - 1 && self.smooth_repeat)
|
||||||
|
|| index < self.pulses.len() - 1
|
||||||
|
{
|
||||||
|
duration += pulse.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
127
src-tauri/src/pattern/pulse.rs
Normal file
127
src-tauri/src/pattern/pulse.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::frequency_shifting::{self, deserialize_frequency_shifting, FrequencyShifting};
|
||||||
|
use crate::fraction::Fraction;
|
||||||
|
|
||||||
|
pub fn calculate_output_ratio(waveform_frequency: f64) -> Fraction {
|
||||||
|
let x = waveform_frequency.sqrt() * 15f64;
|
||||||
|
let y = waveform_frequency * 1000f64 - x;
|
||||||
|
Fraction::new_from_float(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct CanvasPoint {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<(f64, f64)> for CanvasPoint {
|
||||||
|
fn into(self) -> (f64, f64) {
|
||||||
|
(self.x, self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Pulse {
|
||||||
|
pub order: u32,
|
||||||
|
pub id: Uuid,
|
||||||
|
#[serde(default)]
|
||||||
|
pub duration: u32,
|
||||||
|
pub width: u32,
|
||||||
|
pub maniac: bool,
|
||||||
|
pub frequency: f64,
|
||||||
|
#[serde(deserialize_with = "deserialize_frequency_shifting")]
|
||||||
|
pub frequency_shifting: FrequencyShifting,
|
||||||
|
pub control_point_1: CanvasPoint,
|
||||||
|
pub control_point_2: CanvasPoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize)]
|
||||||
|
pub struct RawPulse {
|
||||||
|
pub tick_order: u32,
|
||||||
|
pub z: u16,
|
||||||
|
pub x: u16,
|
||||||
|
pub y: u16,
|
||||||
|
pub frequency_level: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bezier(
|
||||||
|
t: f64,
|
||||||
|
p0: CanvasPoint,
|
||||||
|
p1: CanvasPoint,
|
||||||
|
cp1: CanvasPoint,
|
||||||
|
cp2: CanvasPoint,
|
||||||
|
) -> CanvasPoint {
|
||||||
|
let mut p = CanvasPoint { x: t, y: 0.0 };
|
||||||
|
|
||||||
|
let t = (t - p0.x) / (p1.x - p0.x);
|
||||||
|
let u = 1.0 - t; // 1-t
|
||||||
|
let tt = t * t; // t^2
|
||||||
|
let uu = u * u; // (1-t)^2
|
||||||
|
let uuu = uu * u; // (1-t)^3
|
||||||
|
let ttt = tt * t; // t^3
|
||||||
|
|
||||||
|
p.y = uuu * p0.y; // (1-t)^3 * p0.y
|
||||||
|
p.y += 3.0 * uu * t * cp1.y; // 3 * (1-t)^2 * t * cp1.y
|
||||||
|
p.y += 3.0 * u * tt * cp2.y; // 3 * (1-t) * t^2 * cp2.y
|
||||||
|
p.y += ttt * p1.y; // t^3 * p1.y
|
||||||
|
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_pulse_sequence<P: IntoIterator<Item = Pulse>>(
|
||||||
|
pulse: P,
|
||||||
|
smooth_repeat: bool,
|
||||||
|
) -> Box<dyn Iterator<Item = RawPulse>> {
|
||||||
|
let sorted_pulses = pulse.into_iter().collect::<VecDeque<_>>();
|
||||||
|
|
||||||
|
let mut sample_tick = 0;
|
||||||
|
let mut timing = 0;
|
||||||
|
let mut sample_queue: VecDeque<(u32, u32, Pulse, Pulse)> = VecDeque::new();
|
||||||
|
let head_pulse = sorted_pulses.front();
|
||||||
|
let mut peekable_pulses = sorted_pulses.iter().peekable();
|
||||||
|
while let Some(p) = peekable_pulses.next() {
|
||||||
|
if let Some(next_pulse) = peekable_pulses.peek() {
|
||||||
|
while sample_tick < (timing + p.duration) {
|
||||||
|
sample_queue.push_back((sample_tick, timing, p.clone(), (*next_pulse).clone()));
|
||||||
|
sample_tick += 25;
|
||||||
|
}
|
||||||
|
timing += p.duration;
|
||||||
|
} else if smooth_repeat && head_pulse.is_some() {
|
||||||
|
sample_queue.push_back((sample_tick, timing, p.clone(), head_pulse.unwrap().clone()));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box::new(std::iter::from_fn(move || {
|
||||||
|
if let Some((tick, timing, prev, next)) = sample_queue.pop_front() {
|
||||||
|
let bezier_point = bezier(
|
||||||
|
tick as f64,
|
||||||
|
CanvasPoint {
|
||||||
|
x: timing as f64,
|
||||||
|
y: prev.frequency,
|
||||||
|
},
|
||||||
|
CanvasPoint {
|
||||||
|
x: (timing + prev.duration) as f64,
|
||||||
|
y: next.frequency,
|
||||||
|
},
|
||||||
|
prev.control_point_1,
|
||||||
|
prev.control_point_2,
|
||||||
|
);
|
||||||
|
let (x, y, frequency_level) =
|
||||||
|
frequency_shifting::shifting_frequency(tick, timing, &prev, &next);
|
||||||
|
return Some(RawPulse {
|
||||||
|
tick_order: tick,
|
||||||
|
z: bezier_point.y as u16,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
frequency_level,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}))
|
||||||
|
}
|
37
src-tauri/src/protocols/mod.rs
Normal file
37
src-tauri/src/protocols/mod.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use btleplug::platform::Peripheral;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum CoyoteProtocolError {
|
||||||
|
#[error("Failed to connect to peripheral")]
|
||||||
|
FailedToConnect,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum CoyoteChannel {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CoyoteProtocol {
|
||||||
|
fn identify(peripheral: &Peripheral) -> bool;
|
||||||
|
fn connected(&mut self, peripheral: &Peripheral) -> Result<(), CoyoteProtocolError>;
|
||||||
|
fn disconnect(&mut self) -> Result<(), CoyoteProtocolError>;
|
||||||
|
fn notify_battery_level<F>(&self, callback: F)
|
||||||
|
where
|
||||||
|
F: FnOnce(Option<i16>);
|
||||||
|
fn notify_strength_change<F>(&self, callback: F)
|
||||||
|
where
|
||||||
|
F: FnOnce((Option<i16>, Option<i16>));
|
||||||
|
fn set_output_strength(
|
||||||
|
&self,
|
||||||
|
channel: CoyoteChannel,
|
||||||
|
strength: i16,
|
||||||
|
) -> Result<(), CoyoteProtocolError>;
|
||||||
|
fn output(
|
||||||
|
&self,
|
||||||
|
channel: CoyoteChannel,
|
||||||
|
data: Vec<u8>,
|
||||||
|
maintain: bool,
|
||||||
|
) -> Result<(), CoyoteProtocolError>;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user