1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use crate::channel::Channel;
use super::{note::TickError, have_start_tick::{HaveBaseStartTick, HaveStartTick}, velocity::Velocity};
/// MIDI Control Change event (e.g., sustain pedal, soft pedal).
///
/// Represents a MIDI CC (Control Change) message at a specific time position.
/// Commonly used for pedal events like damper (sustain) and soft pedal.
///
/// # Examples
///
/// ```
/// # use klavier_core::ctrl_chg::CtrlChg;
/// # use klavier_core::velocity::Velocity;
/// # use klavier_core::channel::Channel;
/// // Sustain pedal on at tick 100
/// let sustain_on = CtrlChg::new(100, Velocity::new(127), Channel::new(0));
/// ```
#[derive(serde::Deserialize, serde::Serialize)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct CtrlChg {
/// The tick position where this control change occurs.
pub start_tick: u32,
/// The control value (0-127).
pub velocity: Velocity,
/// The MIDI channel.
pub channel: Channel,
}
impl CtrlChg {
/// Creates a new control change event.
///
/// # Arguments
///
/// * `start_tick` - The tick position for this event.
/// * `velocity` - The control value (0-127).
/// * `channel` - The MIDI channel.
pub fn new(start_tick: u32, velocity: Velocity, channel: Channel) -> Self {
Self { start_tick, velocity, channel }
}
/// Creates a new control change with adjusted tick position (for dragging).
///
/// # Arguments
///
/// * `tick_delta` - Amount to adjust the tick position.
pub fn drag(&self, tick_delta: i32) -> Self {
Self {
start_tick: (self.start_tick as i64 + tick_delta as i64) as u32,
..*self
}
}
/// Creates a new control change with adjusted tick position.
///
/// # Arguments
///
/// * `tick_delta` - Amount to adjust the tick position.
///
/// # Returns
///
/// - `Ok(CtrlChg)` - The control change with adjusted position.
/// - `Err(TickError)` - If the resulting tick would be negative.
pub fn with_tick_added(&self, tick_delta: i32) -> Result<Self, TickError> {
let tick = self.start_tick as i64 + tick_delta as i64;
if tick < 0 {
Err(TickError::Minus)
} else {
Ok(
Self {
start_tick: tick as u32,
..*self
}
)
}
}
}
impl HaveBaseStartTick for CtrlChg {
fn base_start_tick(&self) -> u32 {
self.start_tick
}
}
impl HaveStartTick for CtrlChg {
fn start_tick(&self) -> u32 {
self.start_tick
}
}
#[cfg(test)]
mod tests {
use crate::channel::Channel;
use crate::ctrl_chg::CtrlChg;
use crate::velocity::Velocity;
use serde_json::Value;
use serde_json::json;
#[test]
fn can_deserialize_ctrl_chg() {
let ctrl_chg: CtrlChg = serde_json::from_str(r#"
{
"start_tick": 123,
"velocity": 64,
"channel": 0
}"#).unwrap();
assert_eq!(ctrl_chg, CtrlChg {
start_tick: 123,
velocity: Velocity::new(64),
channel: Channel::default(),
});
}
#[test]
fn can_serialize_ctrl_chg() {
let json_str = serde_json::to_string(&CtrlChg {
start_tick: 123,
velocity: Velocity::new(64),
channel: Channel::new(1),
}).unwrap();
let json: Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(
json,
json!({
"start_tick": 123,
"velocity": 64,
"channel": 1
})
);
}
}