Skip to main content

rill_patchbay/
utils.rs

1//! # Утилиты для патчбэя
2//!
3//! Вспомогательные функции и структуры для работы с патчбэем:
4//! - Конвертеры значений
5//! - Утилиты для времени
6//! - Хелперы для тестирования
7
8use crate::automaton::Range;
9use crate::control::Transform;
10
11// =============================================================================
12// Конвертеры значений
13// =============================================================================
14
15/// Конвертер значений между разными шкалами
16#[derive(Debug, Clone)]
17pub struct ValueConverter {
18    /// Входной диапазон
19    input_range: Range,
20    /// Выходной диапазон
21    output_range: Range,
22    /// Тип преобразования
23    transform: Transform,
24}
25
26impl ValueConverter {
27    /// Создать новый конвертер
28    pub fn new(input_range: Range, output_range: Range, transform: Transform) -> Self {
29        Self {
30            input_range,
31            output_range,
32            transform,
33        }
34    }
35
36    /// Конвертировать значение
37    pub fn convert(&self, value: f64) -> f64 {
38        // Нормализуем входное значение
39        let norm = self.input_range.normalize(value);
40
41        // Применяем преобразование
42        let transformed = match self.transform {
43            Transform::Linear => norm,
44            Transform::Exponential => norm * norm,
45            Transform::Logarithmic => (1.0 + norm * 9.0).log10(),
46            Transform::Inverted => 1.0 - norm,
47            Transform::Custom(ref f) => f(norm as f32) as f64,
48        };
49
50        // Денормализуем в выходной диапазон
51        self.output_range.denormalize(transformed)
52    }
53
54    /// Конвертировать значение в обратном направлении
55    pub fn convert_inverse(&self, value: f64) -> f64 {
56        // Денормализуем в обратную сторону (приблизительно)
57        let norm = self.output_range.normalize(value);
58        self.input_range.denormalize(norm)
59    }
60}
61
62/// Преобразование MIDI value (0-127) в нормализованное значение (0.0-1.0)
63pub fn midi_to_normalized(midi: u8) -> f64 {
64    midi as f64 / 127.0
65}
66
67/// Преобразование нормализованного значения в MIDI value
68pub fn normalized_to_midi(norm: f64) -> u8 {
69    (norm.clamp(0.0, 1.0) * 127.0).round() as u8
70}
71
72/// Преобразование частоты в MIDI ноту
73pub fn freq_to_midi_note(freq: f64) -> f64 {
74    69.0 + 12.0 * (freq / 440.0).log2()
75}
76
77/// Преобразование MIDI ноты в частоту
78pub fn midi_note_to_freq(note: f64) -> f64 {
79    440.0 * 2.0_f64.powf((note - 69.0) / 12.0)
80}
81
82// =============================================================================
83// Утилиты для времени
84// =============================================================================
85
86/// Метроном для синхронизации с BPM
87#[derive(Debug, Clone)]
88pub struct Metronome {
89    /// BPM
90    bpm: f64,
91    /// Время последнего тика
92    last_tick: f64,
93    /// Время следующего тика
94    next_tick: f64,
95    /// Длительность четверти в секундах
96    quarter_duration: f64,
97}
98
99impl Metronome {
100    /// Создать новый метроном
101    pub fn new(bpm: f64) -> Self {
102        let quarter_duration = 60.0 / bpm;
103        Self {
104            bpm,
105            last_tick: 0.0,
106            next_tick: quarter_duration,
107            quarter_duration,
108        }
109    }
110
111    /// Обновить состояние и проверить, был ли тик
112    pub fn update(&mut self, time: f64) -> bool {
113        if time >= self.next_tick {
114            self.last_tick = self.next_tick;
115            self.next_tick += self.quarter_duration;
116            true
117        } else {
118            false
119        }
120    }
121
122    /// Получить текущую фазу (0.0-1.0) внутри четверти
123    pub fn phase(&self, time: f64) -> f64 {
124        ((time - self.last_tick) / self.quarter_duration).clamp(0.0, 1.0)
125    }
126
127    /// Установить новый BPM
128    pub fn set_bpm(&mut self, bpm: f64) {
129        self.bpm = bpm;
130        self.quarter_duration = 60.0 / bpm;
131        self.next_tick = self.last_tick + self.quarter_duration;
132    }
133
134    /// Сбросить метроном
135    pub fn reset(&mut self) {
136        self.last_tick = 0.0;
137        self.next_tick = self.quarter_duration;
138    }
139}
140
141/// Преобразование длительности ноты в секунды
142pub fn note_duration_to_seconds(note_type: NoteType, bpm: f64) -> f64 {
143    let quarter = 60.0 / bpm;
144    match note_type {
145        NoteType::Whole => quarter * 4.0,
146        NoteType::Half => quarter * 2.0,
147        NoteType::Quarter => quarter,
148        NoteType::Eighth => quarter / 2.0,
149        NoteType::Sixteenth => quarter / 4.0,
150        NoteType::ThirtySecond => quarter / 8.0,
151        NoteType::Dotted(n) => note_duration_to_seconds(*n, bpm) * 1.5,
152        NoteType::Triplet(n) => note_duration_to_seconds(*n, bpm) * 2.0 / 3.0,
153    }
154}
155
156/// Тип ноты
157#[derive(Debug, Clone)]
158pub enum NoteType {
159    Whole,
160    Half,
161    Quarter,
162    Eighth,
163    Sixteenth,
164    ThirtySecond,
165    Dotted(Box<NoteType>),
166    Triplet(Box<NoteType>),
167}
168
169// =============================================================================
170// Хелперы для тестирования
171// =============================================================================
172
173/// Запись событий для тестирования
174#[derive(Debug, Default)]
175pub struct EventRecorder {
176    /// Записанные события
177    events: Vec<RecordedEvent>,
178}
179
180/// Записанное событие
181#[derive(Debug, Clone)]
182pub struct RecordedEvent {
183    /// Время записи
184    pub time: f64,
185    /// Тип события
186    pub event_type: String,
187    /// Значение
188    pub value: f64,
189    /// Дополнительные данные
190    pub data: String,
191}
192
193impl EventRecorder {
194    /// Создать новый рекордер
195    pub fn new() -> Self {
196        Self { events: Vec::new() }
197    }
198
199    /// Записать событие
200    pub fn record(&mut self, time: f64, event_type: &str, value: f64, data: &str) {
201        self.events.push(RecordedEvent {
202            time,
203            event_type: event_type.to_string(),
204            value,
205            data: data.to_string(),
206        });
207    }
208
209    /// Получить все события
210    pub fn events(&self) -> &[RecordedEvent] {
211        &self.events
212    }
213
214    /// Очистить запись
215    pub fn clear(&mut self) {
216        self.events.clear();
217    }
218
219    /// Найти события по типу
220    pub fn find_by_type(&self, event_type: &str) -> Vec<&RecordedEvent> {
221        self.events
222            .iter()
223            .filter(|e| e.event_type == event_type)
224            .collect()
225    }
226}
227
228// =============================================================================
229// Генераторы тестовых сигналов
230// =============================================================================
231
232/// Генератор тестовых сигналов
233pub struct TestSignalGenerator {
234    /// Тип сигнала
235    signal_type: TestSignalType,
236    /// Параметры
237    params: TestSignalParams,
238}
239
240/// Тип тестового сигнала
241#[derive(Debug, Clone)]
242pub enum TestSignalType {
243    /// Синусоида
244    Sine,
245    /// Прямоугольный
246    Square,
247    /// Пилообразный
248    Saw,
249    /// Случайный шум
250    Noise,
251    /// Огибающая ADSR
252    Envelope,
253}
254
255/// Параметры тестового сигнала
256#[derive(Debug, Clone)]
257pub struct TestSignalParams {
258    /// Частота (Гц)
259    pub frequency: f64,
260    /// Амплитуда
261    pub amplitude: f64,
262    /// Смещение
263    pub offset: f64,
264    /// Длительность (секунды)
265    pub duration: f64,
266}
267
268impl TestSignalGenerator {
269    /// Создать новый генератор
270    pub fn new(signal_type: TestSignalType, params: TestSignalParams) -> Self {
271        Self {
272            signal_type,
273            params,
274        }
275    }
276
277    /// Генерировать значение в заданное время
278    pub fn generate(&self, time: f64) -> f64 {
279        if time > self.params.duration {
280            return 0.0;
281        }
282
283        match self.signal_type {
284            TestSignalType::Sine => {
285                let phase = 2.0 * std::f64::consts::PI * self.params.frequency * time;
286                self.params.offset + self.params.amplitude * phase.sin()
287            }
288
289            TestSignalType::Square => {
290                let phase = (self.params.frequency * time) % 1.0;
291                let value = if phase < 0.5 { 1.0 } else { -1.0 };
292                self.params.offset + self.params.amplitude * value
293            }
294
295            TestSignalType::Saw => {
296                let phase = (self.params.frequency * time) % 1.0;
297                let value = 2.0 * phase - 1.0;
298                self.params.offset + self.params.amplitude * value
299            }
300
301            TestSignalType::Noise => {
302                use rand::Rng;
303                let mut rng = rand::thread_rng();
304                self.params.offset + self.params.amplitude * (rng.gen::<f64>() * 2.0 - 1.0)
305            }
306
307            TestSignalType::Envelope => {
308                // Простая ADSR-подобная огибающая
309                let attack = 0.1;
310                let decay = 0.2;
311                let sustain = 0.7;
312                let release = 0.3;
313
314                if time < attack {
315                    (time / attack) * self.params.amplitude
316                } else if time < attack + decay {
317                    (1.0 - (1.0 - sustain) * ((time - attack) / decay)) * self.params.amplitude
318                } else if time < self.params.duration - release {
319                    sustain * self.params.amplitude
320                } else {
321                    let rel_time = time - (self.params.duration - release);
322                    (sustain * (1.0 - rel_time / release)) * self.params.amplitude
323                }
324            }
325        }
326    }
327}
328
329// =============================================================================
330// Тесты
331// =============================================================================
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    #[test]
338    fn test_value_converter() {
339        let converter = ValueConverter::new(
340            Range::new(0.0, 127.0),
341            Range::new(0.0, 1.0),
342            Transform::Linear,
343        );
344
345        let result = converter.convert(64.0);
346        assert!((result - 0.5).abs() < 0.01);
347    }
348
349    #[test]
350    fn test_metronome() {
351        let mut metro = Metronome::new(120.0); // 120 BPM = 0.5 сек на четверть
352
353        assert!(!metro.update(0.2));
354        assert!(metro.update(0.6));
355        assert!((metro.phase(0.6) - 0.2).abs() < 0.01);
356    }
357
358    #[test]
359    fn test_test_signal() {
360        let params = TestSignalParams {
361            frequency: 1.0,
362            amplitude: 1.0,
363            offset: 0.0,
364            duration: 2.0,
365        };
366
367        let gen = TestSignalGenerator::new(TestSignalType::Sine, params);
368        let val = gen.generate(0.25);
369        assert!((val - 1.0).abs() < 0.01);
370    }
371}