PK!!superconf18_midibadge/__init__.pyPK!ީm "superconf18_midibadge/midistuff.pyimport copy import midi def read_midifile(fname): return midi.read_midifile(str(fname)) def get_track_info_from_file(fname): mf = midi.read_midifile(fname) tracks = {} for idx, el in enumerate(mf): if len(el) > 0: tone_count = sum([1 for event in el[1:] if type(event) == midi.events.NoteOnEvent], 0) else: tone_count = 0 if tone_count == 0: continue if hasattr(el[0], 'text'): name = el[0].text else: name = "unnamed track" tracks[idx] = { "name": name, "tones": tone_count, } return tracks def single_track_midi(mf, dest, track): if isinstance(mf, str): mf = midi.read_midifile(mf) pat = midi.Pattern() pat.append(mf[track]) midi.write_midifile(dest, pat) def three_track_midi(mf, dest, tr1, tr2, tr3): if isinstance(mf, str): mf = midi.read_midifile(mf) pat = midi.Pattern() pat.append(mf[tr1]) pat.append(mf[tr2]) pat.append(mf[tr3]) midi.write_midifile(dest, pat) def get_three_tracks_from_file(mf, t1, t2, t3): if isinstance(mf, str): mf = midi.read_midifile(mf) return [mf[t1], mf[t2], mf[t3]] def get_timeline_from_track(track, tick_multiplier=2): timestamp = 0 notes = [] for ev in track: # not sure why lots of midi files have multiple NoteOn events at the same time? if ev.tick == 0: continue timestamp += int(ev.tick * tick_multiplier) if type(ev) == midi.events.NoteOnEvent: if ev.data[1] == 0: # if the volume is set to zero we shouldn't play the tone, # setting the tone to 0 apparently achieves that notes.append((timestamp, 0)) else: notes.append((timestamp, ev.data[0])) # insert noop first entry if the first entry isn't at t=0 if notes[0][0] != 0: notes = [(0, 0), *notes] return notes def get_notes_from_timelines(timelines: list): timelines = copy.copy(timelines) last_timestamp = 0 notes = [] while sum(len(t) for t in timelines) > 0: next_timestamp = min(t[0][0] for t in timelines if len(t) > 0) if len(notes) == 0: next_note = [0, 0, 0, 0] else: # sub in duration for last note notes[-1][0] = next_timestamp - last_timestamp next_note = [0, *notes[-1][1:]] for i in range(len(timelines)): if len(timelines[i]) > 0 and timelines[i][0][0] == next_timestamp: next_track_note = timelines[i].pop(0) next_note[i+1] = next_track_note[1] notes.append(next_note) last_timestamp = next_timestamp # depending on the input tracks the beginning might be quiet, if so trim # it off if notes[0][0] == 0 and notes[0][1] == 0 and notes[0][2] == 0: notes = notes[1:] return notes # MIDI notes are numbered from 0 to 127 assigned to C-1 to G9. This # corresponds to a range of 8.175798916Hz to 12543.85395Hz (assuming equal # temperament and 440Hz A4) and extends beyond the 88 note piano range from # A0 to C8. # return [ # f"tune {ev.data[0]},{ev.data[0]},{ev.data[0]},1000" # for ev in track # if type(ev) == midi.events.NoteOnEvent # ] PK!'superconf18_midibadge/tools/__init__.pyPK!VV)superconf18_midibadge/tools/midi2basic.py#!/usr/bin/env python3 import argparse from pathlib import Path from ..midistuff import get_track_info_from_file from ..midistuff import read_midifile from ..midistuff import get_three_tracks_from_file from ..midistuff import get_timeline_from_track from ..midistuff import get_notes_from_timelines from ..uistuff import print_trackinfo from ..uistuff import input_tracklist def main(): parser = argparse.ArgumentParser( description="Creates BASIC file form three tracks of a MIDI file.") parser.add_argument("inpath", help="Input MIDI file") parser.add_argument("outpath", help="Output BASIC file") args = parser.parse_args() infile = Path(args.inpath) outfile = Path(args.outpath) trackinfo = get_track_info_from_file(str(infile)) print(f"These tracks in MIDI file {infile} contain sound:") print("") print_trackinfo(trackinfo) print("") print("") print("Select *exactly* three tracks to export into BASIC file!") tracklist = input_tracklist(validate_length=3) mf = read_midifile(str(infile)) tracks_from_file = get_three_tracks_from_file(str(infile), *tracklist) t1 = get_timeline_from_track(tracks_from_file[0]) t2 = get_timeline_from_track(tracks_from_file[1]) t3 = get_timeline_from_track(tracks_from_file[2]) notes = get_notes_from_timelines([t1, t2, t3]) with open(outfile, 'w') as out: for line, note in enumerate(notes): out.write(f"{(line+1)} tune {note[1]},{note[2]},{note[3]},{note[0]}\n") print(f"Wrote {len(notes)} notes to file {outfile}") if __name__ == "__main__": main() PK!%U^^)superconf18_midibadge/tools/midi3track.py#!/usr/bin/env python3 import argparse import os from pathlib import Path from ..midistuff import get_track_info_from_file from ..midistuff import read_midifile from ..midistuff import three_track_midi from ..uistuff import print_trackinfo from ..uistuff import input_tracklist def main(): parser = argparse.ArgumentParser( description="Extracts three tracks from a MIDI and creates a new MIDI file from it..") parser.add_argument("inpath", help="Input MIDI file") parser.add_argument("outpath", help="Output MIDI file") args = parser.parse_args() infile = Path(args.inpath) outfile = Path(args.outpath) trackinfo = get_track_info_from_file(str(infile)) print(f"These tracks in MIDI file {infile} contain sound:") print("") print_trackinfo(trackinfo) print("") print("") print("Select *exactly* three tracks to export into BASIC file!") tracklist = input_tracklist(validate_length=3) mf = read_midifile(str(infile)) three_track_midi(mf, str(outfile), *tracklist) print(f"Wrote file {outfile}") if __name__ == "__main__": main() PK!˒cc'superconf18_midibadge/tools/midiinfo.py#!/usr/bin/env python3 import argparse from pathlib import Path from ..midistuff import get_track_info_from_file from ..uistuff import print_trackinfo def main(): parser = argparse.ArgumentParser( description="Prints a table of tracks in the given MIDI file.") parser.add_argument("inpath", help="Input MIDI file") args = parser.parse_args() infile = Path(args.inpath) trackinfo = get_track_info_from_file(str(infile)) print(f"These tracks in MIDI file {infile} contain sound:") print("") print_trackinfo(trackinfo) if __name__ == "__main__": main() PK!G(superconf18_midibadge/tools/midisplit.py#!/usr/bin/env python3 import argparse import os from pathlib import Path from ..midistuff import get_track_info_from_file from ..midistuff import read_midifile from ..midistuff import single_track_midi from ..uistuff import print_trackinfo from ..uistuff import input_tracklist def main(): parser = argparse.ArgumentParser( description="Split a single MIDI file into multiple MIDI files, one for each track.") parser.add_argument( "inpath", help="Input MIDI file") parser.add_argument( "--out", dest="outpath", default=".", required=False, help="Output folder for single-track MIDI files (defaults to current directory)") args = parser.parse_args() infile = Path(args.inpath) inname = Path(args.inpath).stem # stem = file name without extension outfolder = Path(args.outpath) trackinfo = get_track_info_from_file(str(infile)) print(f"These tracks in MIDI file {infile} contain sound:") print("") print_trackinfo(trackinfo) print("") print("") tracklist = input_tracklist(default=list(trackinfo.keys())) os.makedirs(str(outfolder), exist_ok=True) mf = read_midifile(str(infile)) for track in tracklist: destfile = f"{outfolder}/{inname}-{track}-{trackinfo[track]['name'][:10]}.mid" single_track_midi(mf, destfile, track) print(f"Wrote file {destfile}") if __name__ == "__main__": main() PK!J,<:: superconf18_midibadge/uistuff.pydef print_trackinfo(trackinfo): print(" # Tones Track Name") print("--- ------ ------------------------------") for idx, track in trackinfo.items(): print(f"{str(idx).rjust(3)} {str(track['tones']).rjust(6)} {track['name'][:30]}") def input_tracklist(validate_length=None, default=None): default_info = " [press enter for all]" if default else "" tracklist = input( f"Select tracks as comma separated list, e.g. 2,6,7{default_info} ") tracklist = tracklist.strip().strip(',') if default and tracklist == "": tracklist = default else: tracklist = [int(el.strip()) for el in tracklist.split(',')] if validate_length: assert len(tracklist) == validate_length, f"Expected {validate_length} tracks, got {len(tracklist)}" return tracklist PK!H^6superconf18_midibadge-0.2.0.dist-info/entry_points.txtM @὇M$B!uıpCc$Vΐ ,YIӓl7f%eyA d! ::uBCvPK!&%Y..-superconf18_midibadge-0.2.0.dist-info/LICENSEMIT License Copyright (c) 2018 Jonas Neubert Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H\TT+superconf18_midibadge-0.2.0.dist-info/WHEEL 1 0 нR \I$ơ7.ZON `h6oi14m,b4>4ɛpK>X;baP>PK!HJg<: .superconf18_midibadge-0.2.0.dist-info/METADATAUSF~bgxIf,ٲl4!S0-d:,N;:XgZwhy-6B~>C0U:R2 ^!bqp[VE&(Qspʍ R1B4 aΣ;r`J p8[@"rdsUWrȬ-MfڏTѿU ߟݹPrXyP:6!\${rC2BvRL~SmQ .n/lN,5U ƻڐ)?SOf97F$)n!\.pRZc\Sg.WZ)sVԉھaF_1ٕx& nK̪"7Qožz_CRa^=34%y3%-J6%b ~aD2vp+I5l!%CnnnX)J ҽKr2nj75[W ( U]'A%1i)XT ORSqrG3&E9Vۄ ڈ,+*xYMd].cGa<+yQRJUtyv?Qwhr:51u `E2vz1(9lPGc 6h𑃏u%TxJ!`iiBi :pxHfICOu:myw$L>V’s0x_(&w#S!pA@Lg*)5LEɥhZvuIr$$z2"H ͼ:5{67S/|'-n厜r+.nz@wWy5Ky7?V[:@ekD1jlSTobL8Ui ^>QBLK9fV3pz\̚e3  n9~?jj&pgJEځ-;m{ikoPK!Hh7Y,superconf18_midibadge-0.2.0.dist-info/RECORDɎPE-`qo@4(J'WĤTMZ;;M܅uڻfQ%~z[43S_Rȕ 8G=[:Rauk7Dj o C!]ٳ=:DrG;ӌ*_`.{1qwYE?n2aK)5|6Ⱥ