migrate pattern structures from previous projects.

This commit is contained in:
Vixalie 2025-03-05 09:29:17 +08:00
parent b126a3105f
commit 82e6d51f55
9 changed files with 610 additions and 0 deletions

View File

@ -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"

View 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
View 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)
}
}

View File

@ -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)]

View 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)),
}
}
}

View 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)
}

View 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
}
}

View 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
}))
}

View 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>;
}