Skip to content

Commit 1d23c52

Browse files
committed
arpegiator
1 parent 431cd6c commit 1d23c52

37 files changed

+2315
-26
lines changed

Cargo.lock

Lines changed: 455 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ members = [
77
"filter_out_non_note",
88
"note_fan_out",
99
"midi_delay",
10-
"max_note_duration"
10+
"max_note_duration",
11+
"audio_data",
12+
"arpegiator",
13+
"arpegiator_pattern_receiver"
1114
]

arpegiator/Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "arpegiator"
3+
version = "0.1.0"
4+
authors = ["Vincent Alsteen <vincent.alsteen@gmail.com>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
vst = { git = "https://github.com/rustaudio/vst-rs" }
9+
util = { path = "../util" }
10+
build-info = "0.0.20"
11+
log = "0.4.11"
12+
num-traits = "0.2.14"
13+
itertools = "0.10.0"
14+
smol = "1.2.5"
15+
async-net = "1.5.0"
16+
bincode = "1.3.1"
17+
18+
[build-dependencies]
19+
build-info-build = "0.0.20"
20+
21+
[lib]
22+
name = "arpegiator"
23+
crate-type = ["cdylib", "lib"]

arpegiator/build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fn main() {
2+
// Calling `build_info_build::build_script` collects all data and makes it available to `build_info::build_info!`
3+
// and `build_info::format!` in the main program.
4+
build_info_build::build_script();
5+
}

arpegiator/src/change.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use core::cmp::{Ordering, PartialEq, PartialOrd};
2+
use core::option::Option;
3+
use crate::device::DeviceChange;
4+
use crate::pattern_device::PatternDeviceChange;
5+
use crate::timed_event::TimedEvent;
6+
7+
pub enum SourceChange {
8+
NoteChange(DeviceChange),
9+
PatternChange(PatternDeviceChange)
10+
}
11+
12+
13+
impl PartialOrd for SourceChange {
14+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
15+
// note come first, in order to start the pattern with the intended note
16+
match self {
17+
SourceChange::PatternChange(pattern) => {
18+
match other {
19+
SourceChange::PatternChange(other_pattern) => {
20+
pattern.partial_cmp(other_pattern)
21+
}
22+
SourceChange::NoteChange(_) => Option::Some(Ordering::Greater)
23+
}
24+
}
25+
SourceChange::NoteChange(note) => {
26+
match other {
27+
SourceChange::PatternChange(_) => Option::Some(Ordering::Less),
28+
SourceChange::NoteChange(other_note) => {
29+
note.partial_cmp(other_note)
30+
}
31+
}
32+
}
33+
}
34+
}
35+
}
36+
37+
38+
impl PartialEq for SourceChange {
39+
fn eq(&self, other: &Self) -> bool {
40+
match self {
41+
SourceChange::PatternChange(pattern) => {
42+
match other {
43+
SourceChange::PatternChange(other_pattern) => pattern.eq(other_pattern),
44+
SourceChange::NoteChange(_) => false
45+
}
46+
}
47+
SourceChange::NoteChange(note) => {
48+
match other {
49+
SourceChange::PatternChange(_) => false,
50+
SourceChange::NoteChange(other_note) => note.eq(other_note)
51+
}
52+
}
53+
}
54+
}
55+
}
56+
57+
58+
impl Ord for SourceChange {
59+
fn cmp(&self, other: &Self) -> Ordering {
60+
self.partial_cmp(other).unwrap()
61+
}
62+
}
63+
64+
impl Eq for SourceChange {
65+
66+
}
67+
68+
impl TimedEvent for SourceChange {
69+
fn timestamp(&self) -> usize {
70+
match self {
71+
SourceChange::NoteChange(note) => note.timestamp(),
72+
SourceChange::PatternChange(pattern) => pattern.timestamp()
73+
}
74+
}
75+
76+
fn id(&self) -> usize {
77+
match self {
78+
SourceChange::NoteChange(note) => note.id(),
79+
SourceChange::PatternChange(pattern) => pattern.id()
80+
}
81+
}
82+
}

arpegiator/src/device.rs

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
use log::info;
2+
use std::collections::HashMap;
3+
4+
use util::constants::TIMBRECC;
5+
use util::midi_message_type::MidiMessageType;
6+
7+
use crate::note::{CCIndex, Note, NoteIndex};
8+
use util::messages::CC;
9+
use crate::timed_event::TimedEvent;
10+
use std::cmp::Ordering;
11+
use util::midi_message_with_delta::MidiMessageWithDelta;
12+
13+
pub struct Device {
14+
pub notes: HashMap<NoteIndex, Note>,
15+
pub cc: HashMap<CCIndex, u8>,
16+
pub channels: [Channel; 16],
17+
pub note_index: usize,
18+
}
19+
20+
impl Default for Device {
21+
fn default() -> Self {
22+
Device {
23+
notes: Default::default(),
24+
cc: Default::default(),
25+
channels: [Channel {
26+
pressure: 0,
27+
pitchbend: 0,
28+
timbre: 0,
29+
}; 16],
30+
note_index: 0,
31+
}
32+
}
33+
}
34+
35+
#[derive(Copy, Clone, Debug)]
36+
pub struct Channel {
37+
pub pressure: u8,
38+
// in millisemitones
39+
pub pitchbend: i32,
40+
pub timbre: u8,
41+
}
42+
43+
pub enum Expression {
44+
Timbre,
45+
Pressure,
46+
PitchBend,
47+
}
48+
49+
pub enum DeviceChange {
50+
AddNote { time: usize, note: Note },
51+
RemoveNote { time: usize, note: Note },
52+
NoteExpressionChange { time: usize, expression: Expression, note: Note },
53+
ReplaceNote { time: usize, old_note: Note, new_note: Note },
54+
CCChange { time: usize, cc: CC },
55+
None { time: usize },
56+
}
57+
58+
59+
impl TimedEvent for DeviceChange {
60+
fn timestamp(&self) -> usize {
61+
match self {
62+
DeviceChange::AddNote { time, .. } => *time,
63+
DeviceChange::RemoveNote { time, .. } => *time,
64+
DeviceChange::NoteExpressionChange { time, .. } => *time,
65+
DeviceChange::ReplaceNote { time, .. } => *time,
66+
DeviceChange::CCChange { time, .. } => *time,
67+
DeviceChange::None { time, .. } => *time
68+
}
69+
}
70+
71+
fn id(&self) -> usize {
72+
// used to order events that happen at the same time. Doesn't matter on CCs, in any case they'll be sorted
73+
// by time already
74+
match self {
75+
DeviceChange::AddNote { note, .. } => note.id,
76+
DeviceChange::RemoveNote { note, .. } => note.id,
77+
DeviceChange::NoteExpressionChange { note, .. } => note.id,
78+
DeviceChange::ReplaceNote { new_note: note, .. } => note.id,
79+
DeviceChange::CCChange { .. } => 0,
80+
DeviceChange::None { .. } => 0
81+
}
82+
}
83+
}
84+
85+
impl PartialOrd for DeviceChange {
86+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
87+
let timestamp_cmp = self.timestamp().cmp(&other.timestamp());
88+
if timestamp_cmp == Ordering::Equal {
89+
Some(self.id().cmp(&other.id()))
90+
} else {
91+
Some(timestamp_cmp)
92+
}
93+
}
94+
}
95+
96+
impl PartialEq for DeviceChange {
97+
fn eq(&self, other: &Self) -> bool {
98+
self.id() == other.id()
99+
}
100+
}
101+
102+
103+
impl Device {
104+
pub fn update(&mut self,
105+
midi_message: MidiMessageWithDelta, current_time: usize, id: Option<usize>) -> DeviceChange {
106+
let time = current_time + midi_message.delta_frames as usize;
107+
108+
match MidiMessageType::from(&midi_message.data) {
109+
MidiMessageType::NoteOnMessage(note) => {
110+
let note_id = match id {
111+
None => {
112+
let note_id = self.note_index;
113+
self.note_index += 1;
114+
note_id
115+
}
116+
Some(id) => id
117+
};
118+
let index = NoteIndex { channel: note.channel, pitch: note.pitch };
119+
let new_note = Note {
120+
id: note_id,
121+
pressed_at: time,
122+
released_at: 0,
123+
channel: note.channel,
124+
pitch: note.pitch,
125+
velocity: note.velocity,
126+
velocity_off: 0,
127+
pressure: self.channels[note.channel as usize].pressure,
128+
timbre: self.channels[note.channel as usize].timbre,
129+
pitchbend: self.channels[note.channel as usize].pitchbend,
130+
};
131+
132+
match self.notes.insert(index, new_note) {
133+
None => {
134+
DeviceChange::AddNote { time, note: new_note }
135+
}
136+
Some(old_note) => {
137+
DeviceChange::ReplaceNote { time, old_note, new_note }
138+
}
139+
}
140+
}
141+
MidiMessageType::NoteOffMessage(note) => {
142+
let index = NoteIndex { channel: note.channel, pitch: note.pitch };
143+
144+
match self.notes.remove(&index) {
145+
None => {
146+
info!("Attempt to remove note, but it was not found {:02X?}", index);
147+
DeviceChange::None { time }
148+
}
149+
Some(mut old_note) => {
150+
//info!("Removed note {:02X?}", index);
151+
old_note.released_at = time;
152+
old_note.velocity_off = note.velocity;
153+
DeviceChange::RemoveNote { time, note: old_note }
154+
}
155+
}
156+
}
157+
MidiMessageType::CCMessage(cc) => {
158+
self.cc.insert(CCIndex { channel: cc.channel, index: cc.cc }, cc.value);
159+
if cc.cc == TIMBRECC {
160+
self.channels[cc.channel as usize].timbre = cc.value;
161+
for (_, note) in self.notes.iter_mut() {
162+
if note.channel == cc.channel {
163+
note.timbre = cc.value;
164+
// note: per design simplification, having several notes running on the same channel
165+
// is not supported. only the first note found on the channel is updated
166+
return DeviceChange::NoteExpressionChange {
167+
time,
168+
expression: Expression::Timbre,
169+
note: *note,
170+
};
171+
}
172+
}
173+
}
174+
DeviceChange::CCChange { time, cc }
175+
}
176+
MidiMessageType::PressureMessage(message) => {
177+
self.channels[message.channel as usize].pressure = message.value;
178+
for (_, note) in self.notes.iter_mut() {
179+
if note.channel == message.channel {
180+
// note: per design simplification, having several notes running on the same channel
181+
// is not supported. only the first note found on the channel is updated
182+
note.pressure = message.value;
183+
return DeviceChange::NoteExpressionChange {
184+
time,
185+
expression: Expression::Pressure,
186+
note: *note,
187+
};
188+
}
189+
}
190+
DeviceChange::None { time }
191+
}
192+
MidiMessageType::PitchBendMessage(message) => {
193+
self.channels[message.channel as usize].pitchbend = message.millisemitones;
194+
for (_, note) in self.notes.iter_mut() {
195+
if note.channel == message.channel {
196+
// note: per design simplification, having several notes running on the same channel
197+
// is not supported. only the first note found on the channel is updated
198+
note.pitchbend = message.millisemitones;
199+
return DeviceChange::NoteExpressionChange {
200+
time,
201+
expression: Expression::PitchBend,
202+
note: *note,
203+
};
204+
}
205+
}
206+
DeviceChange::None { time }
207+
}
208+
MidiMessageType::UnsupportedChannelMessage(_) => DeviceChange::None { time },
209+
MidiMessageType::Unsupported => DeviceChange::None { time }
210+
}
211+
}
212+
}

0 commit comments

Comments
 (0)