Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MIDI light show: Process sustain pedal events #29

Merged
merged 5 commits into from
May 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion midi_light_show/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion midi_light_show/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "midi_light_show"
version = "0.2.2"
version = "0.2.3"
authors = ["Terkwood <38859656+Terkwood@users.noreply.github.com>"]
edition = "2018"

Expand Down
3 changes: 2 additions & 1 deletion midi_light_show/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ You need to start `fluidsynth` as a server, allow MIDI input, connect it to your
```sh
sudo apt-get install fluidsynth fluid-soundfont-gm libasound2-dev

fluidsynth -a alsa -i /usr/share/sounds/sf2/FluidR3_GM.sf2 --server
# -g flag is useful for boosting volume, try -g 2 if you need
fluidsynth -a alsa -i /usr/share/sounds/sf2/FluidR3_GM.sf2 -g 1 --server

aconnect -lio # and COUNT the number of MIDI related devices

Expand Down
30 changes: 3 additions & 27 deletions midi_light_show/examples/tone_test.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,18 @@
extern crate midir;
extern crate rimd;

use midir::MidiOutput;

use std::env::args;
use std::io::stdin;
use std::thread;
use std::time::Duration;

pub fn main() {
let notes: Vec<u8> = args().skip(1).map(|n| str::parse(&n).unwrap()).collect();

let midi_out = MidiOutput::new("Tone Test").unwrap();

let client = midir::MidiOutput::new("test example test").expect("c");
// Get an output port (read from console if multiple are available)
let output_device = match midi_out.port_count() {
0 => panic!("no output port found"),
1 => {
println!(
"Choosing the only available output port: {}",
midi_out.port_name(0).unwrap()
);
0
}
_ => {
println!("\nAvailable output ports:");
for i in 0..midi_out.port_count() {
println!("{}: {}", i, midi_out.port_name(i).unwrap());
}
println!("Please select output port: ");

let mut input = String::new();
stdin().read_line(&mut input).unwrap();
input.trim().parse().unwrap()
}
};
let output_port = &client.ports()[0];

let mut conn_out = midi_out.connect(output_device, "tone_test").unwrap();
let mut conn_out = client.connect(&output_port, "tone_test").unwrap();

const NOTE_ON_CHANNEL: u8 = 144;
const VELOCITY: u8 = 80;
Expand Down
109 changes: 93 additions & 16 deletions midi_light_show/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern crate rimd;
use log::{error, info, warn};
use midir::MidiOutput;
use rimd::{SMFError, TrackEvent, SMF};
use std::collections::HashSet;
use std::env;
use std::error::Error;
use std::path::Path;
Expand All @@ -18,7 +19,7 @@ mod light;

const DEFAULT_VEC_CAPACITY: usize = 133000;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Hash, Eq, PartialEq, Copy)]
pub enum ChannelEvent {
ChannelOn(u8),
ChannelOff(u8),
Expand All @@ -45,7 +46,7 @@ impl ChannelEvent {

/// These are read from the MIDI file and
/// will be used to produce audio.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Hash, Eq, PartialEq, Copy)]
pub struct NoteEvent {
channel_event: ChannelEvent,
time: u64,
Expand All @@ -54,21 +55,59 @@ pub struct NoteEvent {
velocity: u8,
}

#[derive(Clone)]
#[derive(Clone, Copy)]
pub struct TempoEvent {
time: u64,
vtime: u64,
_time: u64,
_vtime: u64,
micros_per_qnote: u64,
}

#[derive(Clone)]
#[derive(Clone, Copy)]
pub enum MidiEvent {
Note(NoteEvent),
Tempo(TempoEvent),
SustainPedal(SustainPedalEvent),
}

/// https://cecm.indiana.edu/etext/MIDI/chapter3_MIDI5.shtml
///
/// > In general, controller #'s 0-63 are reserved for continous-type
/// > data, such as volume, mod wheel, etc., controllers 64-121 have
/// > been reserved for switch-type controllers (i.e. on-off, up-down),
/// > such as the sustain pedal. Older conventions of switch values,
/// > ssuch as any data value over 0 = 'ON,' or
/// > recognizing only 0 = 'OFF' and 127 = 'ON' and ignoring the rest,
/// > have been replaced by the convention 0-63 = 'ON' and
/// > 64-127 = 'OFF.'
#[derive(Clone, Copy, Debug)]
pub struct SustainPedalEvent(pub PedalState);
const BREAD_CHANNEL: u8 = 0xb0;
const PEDAL_CONTROLLER: u8 = 0x40;

impl SustainPedalEvent {
pub fn new(data: &[u8]) -> Option<Self> {
match data {
&[BREAD_CHANNEL, PEDAL_CONTROLLER, v] => {
Some(SustainPedalEvent(if v < PEDAL_CONTROLLER {
PedalState::Off
} else {
PedalState::On
}))
}
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PedalState {
On,
Off,
}

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn main() {
env_logger::init();
info!("{}", VERSION);
let mut args: env::Args = env::args();
args.next();
let pathstr = &match args.next() {
Expand Down Expand Up @@ -191,6 +230,8 @@ fn transform_events(track_events: Vec<TrackEvent>) -> Vec<MidiEvent> {
velocity: msg.data[2],
};
events.push(MidiEvent::Note(e));
} else if let Some(pedal_event) = SustainPedalEvent::new(&msg.data) {
events.push(MidiEvent::SustainPedal(pedal_event));
} else {
// You can find fun and interesting things like Damper Pedal (sustain)
// Being turned on and off
Expand All @@ -207,8 +248,8 @@ fn transform_events(track_events: Vec<TrackEvent>) -> Vec<MidiEvent> {
data,
}),
} => events.push(MidiEvent::Tempo(TempoEvent {
time: time,
vtime: *vtime,
_time: time,
_vtime: *vtime,
micros_per_qnote: data_as_u64(&data),
})),
_ => (),
Expand All @@ -218,10 +259,13 @@ fn transform_events(track_events: Vec<TrackEvent>) -> Vec<MidiEvent> {
events
}

/// Process all of the MIDI events and send them to output_device
/// Each note calls `thread::sleep` based on its `vtime` attribute
/// and `delta_timing`
fn run(
output_device: usize,
notes: Vec<MidiEvent>,
division: DeltaTiming,
midi_events: Vec<MidiEvent>,
delta_timing: DeltaTiming,
midi_sender: channel::Sender<NoteEvent>,
) -> Result<(), Box<dyn Error>> {
let midi_out = MidiOutput::new("MIDI Magic Machine")?;
Expand All @@ -230,14 +274,14 @@ fn run(
let mut conn_out = midi_out.connect(port_number, "led_midi_show")?;

const DEFAULT_MICROS_PER_QNOTE: u64 = 681817;
let mut micros_per_tick = (DEFAULT_MICROS_PER_QNOTE as f32 / division.0 as f32) as u64;
let mut micros_per_tick = (DEFAULT_MICROS_PER_QNOTE as f32 / delta_timing.0 as f32) as u64;

println!("[ [ Show Starts Now ] ]");
{
// Define a new scope in which the closure `play_note` borrows conn_out, so it can be called easily
let mut play_note = |midi: MidiEvent| match midi {
// Define a new scope in which the closure `sleep_play` borrows conn_out, so it can be called easily
let mut sleep_play = |midi: &MidiEvent| match midi {
MidiEvent::Tempo(tempo_change) => {
let u = (tempo_change.micros_per_qnote as f32 / division.0 as f32) as u64;
let u = (tempo_change.micros_per_qnote as f32 / delta_timing.0 as f32) as u64;
info!("Update micros per tick: {}", u);
micros_per_tick = u;
}
Expand All @@ -253,10 +297,43 @@ fn run(
ChannelEvent::ChannelOff(c) => conn_out.send(&[c, note.note, note.velocity]),
};
}
MidiEvent::SustainPedal(p) => info!("Sustain pedal: {:?}", p),
};

for n in notes {
play_note(n)
let mut pedal_state = PedalState::Off;
let mut sustained = HashSet::new();

for n in midi_events {
match (&n, pedal_state) {
(MidiEvent::SustainPedal(SustainPedalEvent(PedalState::Off)), PedalState::On) => {
for s in sustained.drain() {
sleep_play(&MidiEvent::Note(s));
}
pedal_state = PedalState::Off;
}
(MidiEvent::SustainPedal(SustainPedalEvent(PedalState::On)), PedalState::Off) => {
pedal_state = PedalState::On;
}
(
MidiEvent::Note(NoteEvent {
channel_event: ChannelEvent::ChannelOff(c),
time,
vtime,
note,
velocity,
}),
PedalState::On,
) => {
sustained.insert(NoteEvent {
channel_event: ChannelEvent::ChannelOff(*c),
time: *time,
vtime: *vtime,
note: *note,
velocity: *velocity,
});
}
(n, _p) => sleep_play(&n),
}
}
}

Expand Down