def create_track_map(midi_instance: MIDIFile, tempo: int = 90): track_map = {} i = 0 for k, v in channel_map.items(): track = Track.create_track(midi_instance=midi_instance, track=i, channel=v, tempo=tempo, track_name=k) print('track = %d, channel = %d' % (i, v)) if v != 9: midi_instance.addProgramChange(i, i, 0, get_channel_program_int(v)) track_map[reversed_channel_map[v]] = track midi_instance.addControllerEvent( track=i, channel=v, time=0, controller_number=Track.PAN_CONTROLLER_NUMBER, parameter=channel_panning_map[k]) i += 1 return track_map
midiCmd >= 0xA0 and midiCmd <= 0xAF ): # polyphonic key pressure unsupported in MIDIUtil API :( # A0 11 01 00 00 00 00 A8 00 00 00 00 A5 83 00 00 debugPrint( "Polyphonic key pressure (unsupported) {}({})".format( midiCmd, hex(midiCmd))) s.read("bytes:15") elif (midiCmd >= 0xB0 and midiCmd <= 0xBF): # MIDI CC # B0 40 00 00 5D 9D 00 00 00 00 00 00 40 00 00 01 cc sustain off 00 40 ch 0 40 is cc val # B0 40 00 00 5D 9D 00 00 00 00 00 7F 40 00 00 01 cc sus on 7F 40 ch 0 # 0 (to 63) is off. 127 (to 64) is on. # B0 40 00 00 40 9A 00 00 00 00 00 00 01 00 00 01 cc mod wheel zero thisEvent = readTwoPartEvent(s) midiFileData.addControllerEvent(0, midiChl, thisEvent.time - baseTime, thisEvent.valueB, thisEvent.valueA) midiSection.bHasMIDI = True elif (midiCmd >= 0xC0 and midiCmd <= 0xCF): # Should be program change but don't think it is # C0 03 01 00 00 00 00 A8 00 00 00 00 A5 83 00 00 s.read("bytes:15") elif (midiCmd >= 0xD0 and midiCmd <= 0xDF): # channel pressure # D3 40 00 00 81 A1 00 00 00 00 00 00 00 00 00 01 channel pressure 0 # D5 40 00 00 C4 BA 00 00 00 00 00 1F 1F 00 00 01 channel pressure 1F thisEvent = readTwoPartEvent(s) if (thisEvent.valueA != thisEvent.valueB): quitWithError( "Pressure value A ({}) != Pressure value B ({})".
mf = MIDIFile(1, removeDuplicates=False) # MIDIFileArgs:(numTracks=1, removeDuplicates=True, deinterleave=True, adjust_origin=False, file_format=1, ticks_per_quarternote=960, eventtime_is_ticks=False) # Adds the tempo to the file mf.addTempo(track, time, tempo) # TempoArgs:(track, time, tempo) # ProgramChange -> Change the voice (instrument) of the pitch # Have to do it for each channel being used and they can be different mf.addProgramChange(track, 0, time, 0) mf.addProgramChange(track, 1, time, 0) mf.addProgramChange(track, 2, time, 0) # ProgramChangeArgs:(track, channel, time, program) # ControllerChange -> Controls various dynamics of pitch .i.e. mod wheel(1), pan(10), and sustain(64) mf.addControllerEvent(track, 0, time, 10, 0) mf.addControllerEvent(track, 1, time, 10, 0) mf.addControllerEvent(track, 2, time, 10, 127) # ControllerEventArgs(track, channel, time, controller_number, parameter) # Start adding music notes ----------------------------------------------------- time = 0 # Beginning note is a dotted quarter note mf.addNote(track, 0, 71, time + 0.5, enote, volume) # NoteArgs:(track,channel, pitch, time, duration, volume, annotation=None) # Begin the following notes after the dotted quarter note time = 1 # Channel 2 notes are lowest notes for i, pitch in enumerate(ch2): mf.addNote(track, 2, pitch, time, snote, volume)
class Pat2Midi: """ class to convert Pattern to Midi """ def __init__(self, num_tracks: int = 1, remove_duplicates: bool = True, deinterleave: bool = True, file_format: int = 1): """ :param num_tracks: number of tracks (default: 1) :param remove_duplicates: remove notes if they start at the same time on the same channel if they have the same pitch (default: True) :param deinterleave: clean up two note-ons with no note-off in between (default: True) :param file_format: 1 or 2 (default: 1) """ self.midiFile = MIDIFile(numTracks=num_tracks, removeDuplicates=remove_duplicates, deinterleave=deinterleave, adjust_origin=False, file_format=file_format) self.last_set_tempo = [Defaults.tempo for _ in range(16) ] # set every track to default tempo self.set_tempo(Defaults.tempo, 0) self.last_set_cc = [[None for _ in range(NO_OF_CONTROLLERS)] for _ in range(NO_OF_TRACKS)] self.note2midi = Note2Midi() def set_tempo(self, tempo=100, time=0): """ :param tempo: bpm (default: 100) :param time: time at which the tempo change should be inserted in the midi stream (default: 0) """ self.midiFile.addTempo(track=0, time=time, tempo=tempo) self.last_set_tempo[0] = tempo self.last_set_cc = [[None for _ in range(NO_OF_CONTROLLERS)] for _ in range(NO_OF_TRACKS)] def add_phrase(self, phrase: Phrase, track=0, channel=0, start_time=0): """ :param phrase: a Phrase containing patterns and animations :param track: default: 0 :param channel: default: 0 :param start_time: time at which the phrase should be inserted default: 0 :return: total duration of the inserted phrase """ for event in phrase: # set tempo events only if they changed since last time # handle note events if PP.NOTE in event: if event[PP.TEMPO] != self.last_set_tempo[track]: self.midiFile.addTempo( track, start_time + phrase.generated_duration(), event[PP.TEMPO]) self.last_set_tempo[track] = event[PP.TEMPO] # set notes always if isinstance(event[PP.NOTE], Pchord): for n in event[PP.NOTE].notes: try: intnote = int(n) except ValueError: intnote = self.note2midi.lookup(n) if intnote == REST: continue self.midiFile.addNote( track=track, channel=channel, pitch=intnote, time=start_time + phrase.generated_duration() + event[PP.LAG], duration=event[PP.DUR] * event[PP.PLAYEDDUR], volume=int(event[PP.VOL]), annotation=None) else: try: intnote = int(event[PP.NOTE]) except ValueError: intnote = self.note2midi.lookup(event[PP.NOTE]) if intnote == REST: continue self.midiFile.addNote( track=track, channel=channel, pitch=intnote, time=start_time + phrase.generated_duration() + event[PP.LAG], duration=event[PP.DUR] * event[PP.PLAYEDDUR], volume=int(event[PP.VOL]), annotation=None) self.handle_control_changes(channel, event, phrase, start_time, track) # handle controller events (only if they changed since last time) else: self.handle_control_changes(channel, event, phrase, start_time, track) return phrase.generated_duration() def handle_control_changes(self, channel, event, phrase, start_time, track): """ iterate over all control changes in the phrase and add them to the midi file :param channel: midi channel :param event: python dict containing phrase properties :param phrase: :param start_time: time offset :param track: midi track id :return: """ for cc in range(NO_OF_OFFICIAL_CONTROLLERS): if PP.ctrl_dur_key(cc) in event: time = start_time + phrase.generated_ctrl_duration(cc) value = event[PP.ctrl_val_key(cc)] if value is not None: self.midiFile.addControllerEvent(track=track, channel=channel, time=time, controller_number=cc, parameter=value) for cc in [MidiControlChanges.PitchWheel]: if PP.ctrl_dur_key(cc) in event: time = start_time + phrase.generated_ctrl_duration(cc) pwvalue = event[PP.ctrl_val_key(cc)] if pwvalue is not None: self.midiFile.addPitchWheelEvent(track=track, channel=channel, time=time, pitchWheelValue=pwvalue) def add_phrases(self, list_of_phrase, track=0, channel=0, start_time=0): """ :param list_of_phrase: a list of Phrase :param track: default: 0 :param channel: midi channel, deafult: 0 :param start_time: default: 0 :return: total duration of piece from begin until end of list of phrases """ time_delta = 0 for phrase in list_of_phrase: duration = self.add_phrase(phrase, track, channel, start_time + time_delta) time_delta += duration return start_time + time_delta def write(self, filename): """ write to midi file :param filename: filename """ try: with open(filename, "wb") as f: self.midiFile.writeFile(fileHandle=f) except Exception as e: print("we hit a SNAFU while writing to {0}: {1}".format( filename, e))