Created
March 18, 2020 16:32
-
-
Save scratchyone/ee1805b6413fcd68560367498e0425d3 to your computer and use it in GitHub Desktop.
Midi -> Scratch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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