Skip to content

Instantly share code, notes, and snippets.

@scratchyone
Created March 18, 2020 16:32
Show Gist options
  • Save scratchyone/ee1805b6413fcd68560367498e0425d3 to your computer and use it in GitHub Desktop.
Save scratchyone/ee1805b6413fcd68560367498e0425d3 to your computer and use it in GitHub Desktop.
Midi -> Scratch
use midly::Smf;
use std::env;
use std::fs;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::Path;
#[derive(Eq, PartialEq)]
struct Note {
tick: i32,
key: i32,
length: i32,
instrument: i32,
}
#[derive(Clone)]
struct Channel {
instrument: i32,
}
fn get_file_as_byte_vec(filename: &String) -> Vec<u8> {
let mut f = File::open(&filename).expect("no file found");
let metadata = fs::metadata(&filename).expect("unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
f.read(&mut buffer).expect("buffer overflow");
buffer
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() == 1 {
panic!("Please pass the name of the midi file");
}
let path = format!("{}", args[args.len() - 1]);
println!("{}", path);
let file = get_file_as_byte_vec(&path);
let smf = Smf::parse(&file).unwrap();
if Path::new("notes.txt").exists() {
fs::remove_file("notes.txt").unwrap();
}
let mut notes_file = OpenOptions::new()
.create_new(true)
.write(true)
.append(true)
.open("notes.txt")
.unwrap();
if let midly::Timing::Metrical(time) = smf.header.timing {
println!("PPQ: {}", time.as_int());
}
let mut notes: Vec<Note> = vec![];
let mut channels: Vec<Channel> = vec![Channel { instrument: 1 }; 16];
for (i, track) in smf.tracks.iter().enumerate() {
//println!("track {} has {} events", i, track.len());
let mut tick = 0;
for item in track.iter() {
//println!("{:#?}", item.delta.as_int());
match item.kind {
midly::EventKind::Midi {
channel: channel,
message: message,
} => match message {
midly::MidiMessage::NoteOn { key: key, vel: vel } => {
tick += item.delta.as_int() as i32;
if vel.as_int() == 0 {
let mut last_index = 0;
let mut found = false;
for (index, item) in notes.iter().enumerate() {
if item.key == key.as_int().into() {
last_index = index;
found = true;
}
}
if found == true {
notes[last_index].length = tick - notes[last_index].tick;
}
} else {
//println!("{:#?}", message);
if channel.as_int() == 9 {
notes.push(Note {
key: key.as_int().into(),
tick: tick as i32,
length: 0,
instrument: 113,
});
} else {
notes.push(Note {
key: key.as_int().into(),
tick: tick as i32,
length: 0,
instrument: channels[channel.as_int() as usize].instrument,
});
}
}
}
midly::MidiMessage::NoteOff { key: key, vel: vel } => {
tick += item.delta.as_int() as i32;
let mut last_index = 0;
let mut found = false;
for (index, item) in notes.iter().enumerate() {
if item.key == key.as_int().into() {
last_index = index;
found = true;
}
}
if found == true {
notes[last_index].length = tick - notes[last_index].tick;
}
}
midly::MidiMessage::ProgramChange { program } => {
tick += item.delta.as_int() as i32;
channels[channel.as_int() as usize].instrument = program.as_int() as i32;
println!("{}: {}", channel.as_int() + 1, program.as_int());
}
_ => {
tick += item.delta.as_int() as i32;
} //println!("{:#?}", message),
},
midly::EventKind::Meta(meta) => match meta {
midly::MetaMessage::Tempo(tempo) => {
println!("tempo: {}", 60_000_000 / tempo.as_int())
}
_ => {} //println!("{:#?}", meta),
},
_ => {} //println!("{:#?}", item),
}
}
}
notes.sort_by_key(|a| a.tick);
let mut last_tick = 0;
for note in notes {
let instrument = match note.instrument + 1 {
1 | 2 | 3 | 4 | 7 | 8 => 1,
5 | 6 => 2,
11 => 17,
12 => 16,
13 => 19,
9 | 10 | 14 | 15 | 16 => 22,
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 => 3,
25 | 26 | 32 => 4,
27 | 28 | 29 | 30 | 31 => 5,
33 => 8,
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 => 6,
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 => 8,
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 => 15,
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 => 9,
65 | 66 | 67 | 68 | 69 | 70 => 11,
71 => 14,
72 => 10,
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 => 12,
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 => 20,
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 => 21,
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 => 21,
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 => 4,
113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 => 22,
_ => panic!("{} not found!", note.instrument),
};
if let Err(e) = writeln!(
notes_file,
"{}.{}.{}.{}",
note.key,
note.tick - last_tick,
note.length,
instrument
) {
eprintln!("Couldn't write to file: {}", e);
}
last_tick = note.tick;
}
}
@scratchyone
Copy link
Author

You need to import notes.txt into a scratch list and then you need to set the PPQ and tempo variables based on the output of the program

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment