new library for midi processing
This commit is contained in:
parent
acb3024341
commit
7d5cd31bdd
676
project/midi_processing.py
Normal file
676
project/midi_processing.py
Normal file
@ -0,0 +1,676 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
# In[1]:
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import numpy as np
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import pretty_midi as pm
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
# In[98]:
|
||||||
|
|
||||||
|
|
||||||
|
TODO = '''
|
||||||
|
TODO: put methods of data extraction for seq2seq arangment model to multitrack class [DONE]
|
||||||
|
TODO: make functions for data extraction for seq2seq model for riff/melody generation [DONE]
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
# In[367]:
|
||||||
|
|
||||||
|
|
||||||
|
# '''return a dictionary with tracks indexes grouped by instrument class'''
|
||||||
|
# tracks = file.tracks
|
||||||
|
# names = [track.name for track in tracks]
|
||||||
|
# uniqe_instruemnts = set(names)
|
||||||
|
# tracks_by_instrument = dict()
|
||||||
|
# for key in uniqe_instruemnts:
|
||||||
|
# tracks_by_instrument[key] = []
|
||||||
|
|
||||||
|
# for i, track in enumerate(tracks):
|
||||||
|
# tracks_by_instrument[track.name].append(i)
|
||||||
|
|
||||||
|
# tracks_by_instrument
|
||||||
|
|
||||||
|
|
||||||
|
# In[368]:
|
||||||
|
|
||||||
|
|
||||||
|
# def get_posible_pairs(instrument_x, instrument_y):
|
||||||
|
# '''it takes two lists, and return a list of tuples with every posible 2-element combination
|
||||||
|
# parameters:
|
||||||
|
# -----------
|
||||||
|
# instrument_x, instrument_y : string {'Guitar','Bass','Drums'}
|
||||||
|
# a string that represent a instrument class you want to look for in midi file.
|
||||||
|
|
||||||
|
# returns:
|
||||||
|
# ----------
|
||||||
|
# pairs: list of tuples
|
||||||
|
# a list of posible 2-element combination of two lists
|
||||||
|
# '''
|
||||||
|
# x_indexes = tracks_by_instrument[instrument_x]
|
||||||
|
# y_indexes = tracks_by_instrument[instrument_y]
|
||||||
|
# pairs = []
|
||||||
|
# # pairs = [(x,y) for x in x_indexes for y in y_indexes]
|
||||||
|
|
||||||
|
# for x in x_indexes:
|
||||||
|
# for y in y_indexes:
|
||||||
|
# pairs.append((x,y))
|
||||||
|
|
||||||
|
# return pairs
|
||||||
|
|
||||||
|
|
||||||
|
# In[369]:
|
||||||
|
|
||||||
|
|
||||||
|
# def get_common_bars_for_every_possible_pair(pairs)
|
||||||
|
# ''' for every possible pair of given instrument classes
|
||||||
|
# returns common bars from multitrack'''
|
||||||
|
# x_bars = []
|
||||||
|
# y_bars = []
|
||||||
|
# for x_track_index, y_track_index in pairs:
|
||||||
|
# _x_bars, _y_bars = get_common_bars(file.tracks[x_track_index], file.tracks[y_track_index])
|
||||||
|
# x_bars.extend(_x_bars)
|
||||||
|
# y_bars.extend(_y_bars)
|
||||||
|
|
||||||
|
# return x_bars, y_bars
|
||||||
|
|
||||||
|
|
||||||
|
# In[370]:
|
||||||
|
|
||||||
|
|
||||||
|
# def get_data_seq2seq_arrangment(self, bars_in_seq):
|
||||||
|
# ## This is the end of extracting data from midis to seq2seq arranging network.
|
||||||
|
# '''this method is returning a sequances of given lenth by rolling this lists of x and y for arrangemt generation'''
|
||||||
|
# x_seq = []
|
||||||
|
# y_seq = []
|
||||||
|
|
||||||
|
# for i in range(len(x_bars) - bars_in_seq + 1):
|
||||||
|
# x_seq_to_add = [note for bar in x_bars[i:i+bars_in_seq] for note in bar ]
|
||||||
|
# y_seq_to_add = [note for bar in y_bars[i:i+bars_in_seq] for note in bar ]
|
||||||
|
# x_seq.append(x_seq_to_add)
|
||||||
|
# y_seq.append(y_seq_to_add)
|
||||||
|
|
||||||
|
# len(x_seq), len(y_seq)
|
||||||
|
# # get_bar_len(y_seq[0])
|
||||||
|
|
||||||
|
|
||||||
|
# In[371]:
|
||||||
|
|
||||||
|
|
||||||
|
# def get_track_by_instrument(self):
|
||||||
|
# '''return a dictionary with tracks indexes grouped by instrument class'''
|
||||||
|
# tracks = self.tracks
|
||||||
|
# names = [track.name for track in tracks]
|
||||||
|
# uniqe_instruemnts = set(names)
|
||||||
|
# tracks_by_instrument = dict()
|
||||||
|
# for key in uniqe_instruemnts:
|
||||||
|
# tracks_by_instrument[key] = []
|
||||||
|
|
||||||
|
# for i, track in enumerate(tracks):
|
||||||
|
# tracks_by_instrument[track.name].append(i)
|
||||||
|
|
||||||
|
# return tracks_by_instrument
|
||||||
|
|
||||||
|
|
||||||
|
# In[372]:
|
||||||
|
|
||||||
|
|
||||||
|
# def get_data_seq2seq_melody(self,instrument_class, x_seq_len=4)
|
||||||
|
# '''return a list of bars with content for every track with given instrument class for melody generaiton'''
|
||||||
|
|
||||||
|
# instrument_tracks = tracks_by_instrument[instrument_class]
|
||||||
|
|
||||||
|
# for track_index in instrument_tracks:
|
||||||
|
# # make below as function: get_bars_with_content
|
||||||
|
# bars = file.tracks[track_index].stream_to_bars()
|
||||||
|
# bars_indexes_with_content = get_bar_indexes_with_content(bars)
|
||||||
|
# bars_with_content = [bars[i] for i in get_bar_indexes_with_content(bars)]
|
||||||
|
|
||||||
|
# # make below as function: get_sequances_from_bars (for seq2seq melody generator)
|
||||||
|
# x_seq = []
|
||||||
|
# y_bar = []
|
||||||
|
# for i in range(len(bars_with_content)-seq_len-1):
|
||||||
|
# _x_seq = bars_with_content[i:i+seq_len]
|
||||||
|
# _y_bar = bars_with_content[i+seq_len]
|
||||||
|
# x_seq.append(_x_seq)
|
||||||
|
# y_bar.append(_y_bar)
|
||||||
|
|
||||||
|
|
||||||
|
# len(x_seq), len(y_bar)
|
||||||
|
# # print( ' x:' ,x_seq[1],'\n', 'y: ', y_bar[1],'\n', 'seq: ',bars_with_content[1:6])
|
||||||
|
|
||||||
|
|
||||||
|
# In[15]:
|
||||||
|
|
||||||
|
|
||||||
|
def get_bar_indexes_with_content(bars):
|
||||||
|
'''this method is looking for non-empty bars in the tracks bars
|
||||||
|
the empty bar consist of only rest notes.
|
||||||
|
returns: a set of bars indexes with notes
|
||||||
|
'''
|
||||||
|
bars_indexes_with_content = set()
|
||||||
|
for i, bar in enumerate(bars):
|
||||||
|
if bar_has_content(bar):
|
||||||
|
bars_indexes_with_content.add(i)
|
||||||
|
|
||||||
|
return bars_indexes_with_content
|
||||||
|
|
||||||
|
|
||||||
|
# In[4]:
|
||||||
|
|
||||||
|
|
||||||
|
def get_bars_with_content(bars):
|
||||||
|
'''this method is looking for non-empty bars in the tracks bars
|
||||||
|
the empty bar consist of only rest notes.
|
||||||
|
returns: a set of bars with notes
|
||||||
|
'''
|
||||||
|
bars_with_content = []
|
||||||
|
for bar in bars:
|
||||||
|
if bar_has_content(bar):
|
||||||
|
bars_with_content.append(bar)
|
||||||
|
|
||||||
|
return bars_with_content
|
||||||
|
|
||||||
|
|
||||||
|
# In[5]:
|
||||||
|
|
||||||
|
|
||||||
|
def get_common_bars(track_x,track_y):
|
||||||
|
'''return common bars, for two tracks is song
|
||||||
|
return X_train, y_train list of
|
||||||
|
'''
|
||||||
|
bars_x = track_x.stream_to_bars()
|
||||||
|
bars_y = track_y.stream_to_bars()
|
||||||
|
bwc_x = get_bar_indexes_with_content(bars_x)
|
||||||
|
bwc_y = get_bar_indexes_with_content(bars_y)
|
||||||
|
|
||||||
|
common_bars = bwc_x.intersection(bwc_y)
|
||||||
|
common_bars_x = [bars_x[i] for i in common_bars]
|
||||||
|
common_bars_y = [bars_y[i] for i in common_bars]
|
||||||
|
return common_bars_x, common_bars_y
|
||||||
|
|
||||||
|
|
||||||
|
# In[6]:
|
||||||
|
|
||||||
|
|
||||||
|
def get_bar_len(bar):
|
||||||
|
"""calculate a lenth of a bar
|
||||||
|
parameters:
|
||||||
|
bar : list
|
||||||
|
list of "notes", tuples like (pitches, len)
|
||||||
|
"""
|
||||||
|
time = 0
|
||||||
|
for note in bar:
|
||||||
|
time += note[1]
|
||||||
|
return time
|
||||||
|
|
||||||
|
|
||||||
|
# In[7]:
|
||||||
|
|
||||||
|
|
||||||
|
def bar_has_content(bar):
|
||||||
|
'''check if bar has any musical information, more accurate
|
||||||
|
it checks if in a bar is any non-rest event like note, or chord
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
-----------
|
||||||
|
bar: list
|
||||||
|
list of notes
|
||||||
|
|
||||||
|
return:
|
||||||
|
-------
|
||||||
|
bool:
|
||||||
|
True if bas has concent and False of doesn't
|
||||||
|
'''
|
||||||
|
bar_notes = len(bar)
|
||||||
|
count_rest = 0
|
||||||
|
for note in bar:
|
||||||
|
if note[0] == (-1,):
|
||||||
|
count_rest += 1
|
||||||
|
if count_rest == bar_notes:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# In[8]:
|
||||||
|
|
||||||
|
|
||||||
|
def round_to_sixteenth_note(x, base=0.25):
|
||||||
|
'''round value to closest multiplication by base
|
||||||
|
in default to 0.25 witch is sisteenth note accuracy
|
||||||
|
'''
|
||||||
|
|
||||||
|
return base * round(x/base)
|
||||||
|
|
||||||
|
|
||||||
|
# In[9]:
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pretty_midi_instrument(instrument, resolution, time_to_tick, key_offset):
|
||||||
|
''' arguments: a prettyMidi instrument object
|
||||||
|
return: a custom SingleTrack object
|
||||||
|
'''
|
||||||
|
|
||||||
|
first_tick = None
|
||||||
|
prev_tick = 0
|
||||||
|
prev_note_lenth = 0
|
||||||
|
max_rest_len = 4.0
|
||||||
|
|
||||||
|
notes = defaultdict(lambda:[set(), set()])
|
||||||
|
for note in instrument.notes:
|
||||||
|
if first_tick == None:
|
||||||
|
# first_tick = round_to_sixteenth_note(time_to_tick(note.start)/resolution)
|
||||||
|
first_tick = 0
|
||||||
|
|
||||||
|
tick = round_to_sixteenth_note(time_to_tick(note.start)/resolution)
|
||||||
|
# add rest if needed
|
||||||
|
if prev_tick != None:
|
||||||
|
act_tick = prev_tick + prev_note_lenth
|
||||||
|
if act_tick < tick:
|
||||||
|
rest_lenth = tick - act_tick
|
||||||
|
while rest_lenth > max_rest_len:
|
||||||
|
notes[act_tick] = [{-1},{max_rest_len}]
|
||||||
|
act_tick += max_rest_len
|
||||||
|
rest_lenth -= max_rest_len
|
||||||
|
notes[act_tick] = [{-1},{rest_lenth}]
|
||||||
|
|
||||||
|
note_lenth = round_to_sixteenth_note(time_to_tick(note.end-note.start)/resolution)
|
||||||
|
|
||||||
|
if -1 in notes[tick][0]:
|
||||||
|
notes[tick] = [set(), set()]
|
||||||
|
|
||||||
|
if instrument.is_drum:
|
||||||
|
notes[tick][0].add(note.pitch)
|
||||||
|
else:
|
||||||
|
notes[tick][0].add(note.pitch+key_offset)
|
||||||
|
notes[tick][1].add(note_lenth)
|
||||||
|
|
||||||
|
prev_tick = tick
|
||||||
|
prev_note_lenth = note_lenth
|
||||||
|
|
||||||
|
notes = [(tuple(e[0]), max(e[1])) for e in notes.values()]
|
||||||
|
|
||||||
|
name = 'Drums' if instrument.is_drum else pm.program_to_instrument_class(instrument.program)
|
||||||
|
return SingleTrack(name, instrument.program, instrument.is_drum, Stream(first_tick,notes) )
|
||||||
|
|
||||||
|
|
||||||
|
# In[10]:
|
||||||
|
|
||||||
|
|
||||||
|
def remove_duplicated_sequences(xy_tuple):
|
||||||
|
x = xy_tuple[0]
|
||||||
|
y = xy_tuple[1]
|
||||||
|
x_freeze = [tuple(seq) for seq in x]
|
||||||
|
y_freeze = [tuple(seq) for seq in y]
|
||||||
|
unique_data = list(set(zip(x_freeze,y_freeze)))
|
||||||
|
x_unique = [seq[0] for seq in unique_data]
|
||||||
|
y_unique = [seq[1] for seq in unique_data]
|
||||||
|
return x_unique, y_unique
|
||||||
|
|
||||||
|
|
||||||
|
# In[11]:
|
||||||
|
|
||||||
|
|
||||||
|
class Stream():
|
||||||
|
|
||||||
|
def __init__ (self, first_tick, notes):
|
||||||
|
self.notes = notes
|
||||||
|
self.first_tick = first_tick
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Stream object with {} musical events>'.format(len(self.notes))
|
||||||
|
|
||||||
|
|
||||||
|
# In[12]:
|
||||||
|
|
||||||
|
|
||||||
|
class SingleTrack():
|
||||||
|
'''class of single track in midi file encoded from pretty midi library
|
||||||
|
|
||||||
|
atributes:
|
||||||
|
----------
|
||||||
|
name: str
|
||||||
|
name of instrument class
|
||||||
|
program: int
|
||||||
|
midi instrument program
|
||||||
|
is_drum: bool
|
||||||
|
True if this track is drums track, False otherwise
|
||||||
|
stream:
|
||||||
|
Stream object of encoded music events (chords or notes)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, name=None, program=None, is_drum=None, stream=None):
|
||||||
|
self.name = name
|
||||||
|
self.program = program
|
||||||
|
self.is_drum = is_drum
|
||||||
|
self.stream = stream
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<SingleTrack object. Name:{}, Program:{}, is_drum:{}>".format(self.name, self.program, self.is_drum)
|
||||||
|
|
||||||
|
def to_pretty_midi_instrument(self, tempo=100):
|
||||||
|
'''is create a pretty midi Instrument object from self.stream.notes sequance
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
-----------
|
||||||
|
self: SingleTrack object
|
||||||
|
|
||||||
|
return:
|
||||||
|
-------
|
||||||
|
track: PrettyMIDI.Instrument object
|
||||||
|
'''
|
||||||
|
|
||||||
|
tempo_strech = 100/tempo
|
||||||
|
track = pm.Instrument(program=self.program, is_drum=self.is_drum, name=self.name)
|
||||||
|
time = self.stream.first_tick * tempo_strech
|
||||||
|
for note in self.stream.notes:
|
||||||
|
note_pitch = note[0]
|
||||||
|
note_len = note[1] * tempo_strech
|
||||||
|
for pitch in note_pitch:
|
||||||
|
# if note is a rest (pause)
|
||||||
|
if pitch == -1:
|
||||||
|
break
|
||||||
|
event = pm.Note(velocity=100, pitch=pitch, start=time, end=time+note_len)
|
||||||
|
track.notes.append(event)
|
||||||
|
time = time + note_len
|
||||||
|
|
||||||
|
return track
|
||||||
|
|
||||||
|
def stream_to_bars(self, beat_per_bar=4):
|
||||||
|
'''it takes notes and split it into equaly time distibuted sequances
|
||||||
|
if note is between bars, the note is splited into two notes, with time sum equal to the note between bars.
|
||||||
|
arguments:
|
||||||
|
stream: list of "notes"
|
||||||
|
return:
|
||||||
|
bars: list: list of lists of notes, every list has equal time. in musical context it returns bars
|
||||||
|
'''
|
||||||
|
# TODO: if last bar of sequance has less notes to has time equal given bar lenth it is left shorter
|
||||||
|
# fill the rest of bar with rests
|
||||||
|
notes = self.stream.notes
|
||||||
|
bars = []
|
||||||
|
time = 0
|
||||||
|
bar_index = 0
|
||||||
|
add_tail = False
|
||||||
|
note_pitch = lambda note: note[0]
|
||||||
|
note_len = lambda note: note[1]
|
||||||
|
for note in notes:
|
||||||
|
try:
|
||||||
|
temp = bars[bar_index]
|
||||||
|
except IndexError:
|
||||||
|
bars.append([])
|
||||||
|
|
||||||
|
if add_tail:
|
||||||
|
bars[bar_index].append(tail_note)
|
||||||
|
time += note_len(tail_note)
|
||||||
|
add_tail = False
|
||||||
|
|
||||||
|
time += note_len(note)
|
||||||
|
|
||||||
|
if time == beat_per_bar:
|
||||||
|
bars[bar_index].append(note)
|
||||||
|
time = 0
|
||||||
|
bar_index += 1
|
||||||
|
|
||||||
|
elif time > beat_per_bar: # if note is between bars
|
||||||
|
between_bars_note_len = note_len(note)
|
||||||
|
tail_note_len = time - beat_per_bar
|
||||||
|
leading_note_len = between_bars_note_len - tail_note_len
|
||||||
|
|
||||||
|
leading_note = (note_pitch(note), leading_note_len)
|
||||||
|
bars[bar_index].append(leading_note)
|
||||||
|
tail_note = (note_pitch(note), tail_note_len)
|
||||||
|
|
||||||
|
add_tail = True
|
||||||
|
time = 0
|
||||||
|
bar_index += 1
|
||||||
|
else:
|
||||||
|
bars[bar_index].append(note)
|
||||||
|
|
||||||
|
return bars
|
||||||
|
|
||||||
|
|
||||||
|
# In[99]:
|
||||||
|
|
||||||
|
|
||||||
|
class MultiTrack():
|
||||||
|
'''Class that represent one midi file
|
||||||
|
atributes:
|
||||||
|
pm_obj : PrettyMIDI class object of this midi file
|
||||||
|
res: resolution of midi
|
||||||
|
time_to_tick: function that coverts miliseconds to ticks. it depends on midi resolution for every midi
|
||||||
|
name: path to midi file
|
||||||
|
tracks: a list of SingleTrack objects
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, path=None, tempo=100):
|
||||||
|
self.tempo = tempo
|
||||||
|
self.pm_obj = pm.PrettyMIDI(path, initial_tempo=self.tempo)
|
||||||
|
self.res = self.pm_obj.resolution
|
||||||
|
self.time_to_tick = self.pm_obj.time_to_tick
|
||||||
|
self.name = path
|
||||||
|
self.tracks = [parse_pretty_midi_instrument(instrument, self.res, self.time_to_tick, self.get_pitch_offset_to_C() ) for instrument in self.pm_obj.instruments]
|
||||||
|
self.tracks_by_instrument = self.get_track_by_instrument()
|
||||||
|
|
||||||
|
def get_multiseq(self):
|
||||||
|
'''tracks: list of SingleTrack objects
|
||||||
|
reaturn a dictionary of sequences for every sequence in SingleTrack
|
||||||
|
'''
|
||||||
|
|
||||||
|
multiseq_indexes = set([key for music_track in self.tracks for key in music_track.seq])
|
||||||
|
multiseq = dict()
|
||||||
|
|
||||||
|
for seq_id in multiseq_indexes:
|
||||||
|
multiseq[seq_id] = []
|
||||||
|
|
||||||
|
for single_track in self.tracks:
|
||||||
|
for key, value in single_track.seq.items():
|
||||||
|
multiseq[key].append((single_track.name,value))
|
||||||
|
|
||||||
|
return multiseq
|
||||||
|
|
||||||
|
def get_pitch_offset_to_C(self):
|
||||||
|
'''to get better train resoult without augmenting midis to all posible keys
|
||||||
|
we assumed that most frequent note is the rootnote of song then calculate
|
||||||
|
the offset of semitones to move song key to C.
|
||||||
|
|
||||||
|
You should ADD this offset to note pitch to get it right
|
||||||
|
'''
|
||||||
|
|
||||||
|
hist = self.pm_obj.get_pitch_class_histogram()
|
||||||
|
offset = np.argmax(hist)
|
||||||
|
if offset > 6:
|
||||||
|
return 12-offset
|
||||||
|
else:
|
||||||
|
return -offset
|
||||||
|
|
||||||
|
def save(self, path):
|
||||||
|
midi_file = pm.PrettyMIDI()
|
||||||
|
for track in self.tracks:
|
||||||
|
midi_file.instruments.append(track.to_pretty_midi_instrument(self.tempo))
|
||||||
|
midi_file.write(path)
|
||||||
|
return midi_file
|
||||||
|
|
||||||
|
def get_track_by_instrument(self):
|
||||||
|
'''return a dictionary with tracks indexes grouped by instrument class'''
|
||||||
|
tracks = self.tracks
|
||||||
|
names = [track.name for track in tracks]
|
||||||
|
uniqe_instruemnts = set(names)
|
||||||
|
tracks_by_instrument = dict()
|
||||||
|
for key in uniqe_instruemnts:
|
||||||
|
tracks_by_instrument[key] = []
|
||||||
|
|
||||||
|
for i, track in enumerate(tracks):
|
||||||
|
tracks_by_instrument[track.name].append(i)
|
||||||
|
|
||||||
|
return tracks_by_instrument
|
||||||
|
|
||||||
|
def get_common_bars_for_every_possible_pair(self, x_instrument, y_instrument):
|
||||||
|
''' for every possible pair of given instrument classes
|
||||||
|
returns common bars from multitrack'''
|
||||||
|
x_bars = []
|
||||||
|
y_bars = []
|
||||||
|
pairs = self.get_posible_pairs(x_instrument, y_instrument)
|
||||||
|
for x_track_index, y_track_index in pairs:
|
||||||
|
_x_bars, _y_bars = get_common_bars(self.tracks[x_track_index], self.tracks[y_track_index])
|
||||||
|
x_bars.extend(_x_bars)
|
||||||
|
y_bars.extend(_y_bars)
|
||||||
|
|
||||||
|
return x_bars, y_bars
|
||||||
|
|
||||||
|
def get_data_seq2seq_arrangment(self, x_instrument, y_instrument, bars_in_seq=4):
|
||||||
|
'''this method is returning a sequances of given lenth by rolling this lists of x and y for arrangemt generation'''
|
||||||
|
x_seq = []
|
||||||
|
y_seq = []
|
||||||
|
x_bars, y_bars = self.get_common_bars_for_every_possible_pair(x_instrument, y_instrument)
|
||||||
|
|
||||||
|
for i in range(len(x_bars) - bars_in_seq + 1):
|
||||||
|
x_seq_to_add = [note for bar in x_bars[i:i+bars_in_seq] for note in bar ]
|
||||||
|
y_seq_to_add = [note for bar in y_bars[i:i+bars_in_seq] for note in bar ]
|
||||||
|
x_seq.append(x_seq_to_add)
|
||||||
|
y_seq.append(y_seq_to_add)
|
||||||
|
|
||||||
|
return x_seq, y_seq
|
||||||
|
|
||||||
|
def get_data_seq2seq_melody(self,instrument_class, x_seq_len=4):
|
||||||
|
'''return a list of bars with content for every track with given instrument class for melody generaiton'''
|
||||||
|
|
||||||
|
instrument_tracks = self.tracks_by_instrument[instrument_class]
|
||||||
|
|
||||||
|
for track_index in instrument_tracks:
|
||||||
|
bars = self.tracks[track_index].stream_to_bars()
|
||||||
|
bars_indexes_with_content = get_bar_indexes_with_content(bars)
|
||||||
|
bars_with_content = [bars[i] for i in get_bar_indexes_with_content(bars)]
|
||||||
|
|
||||||
|
x_seq = []
|
||||||
|
y_seq = []
|
||||||
|
for i in range(len(bars_with_content)-x_seq_len-1):
|
||||||
|
_x_seq = [note for bar in bars_with_content[i:i+x_seq_len] for note in bar]
|
||||||
|
_y_bar = bars_with_content[i+x_seq_len]
|
||||||
|
x_seq.append(_x_seq)
|
||||||
|
y_seq.append(_y_bar)
|
||||||
|
|
||||||
|
return x_seq, y_seq
|
||||||
|
|
||||||
|
def get_posible_pairs(self, instrument_x, instrument_y):
|
||||||
|
'''it takes two lists, and return a list of tuples with every posible 2-element combination
|
||||||
|
parameters:
|
||||||
|
-----------
|
||||||
|
instrument_x, instrument_y : string {'Guitar','Bass','Drums'}
|
||||||
|
a string that represent a instrument class you want to look for in midi file.
|
||||||
|
|
||||||
|
returns:
|
||||||
|
----------
|
||||||
|
pairs: list of tuples
|
||||||
|
a list of posible 2-element combination of two lists
|
||||||
|
'''
|
||||||
|
x_indexes = self.tracks_by_instrument[instrument_x]
|
||||||
|
y_indexes = self.tracks_by_instrument[instrument_y]
|
||||||
|
# pairs = []
|
||||||
|
pairs = [(x,y) for x in x_indexes for y in y_indexes]
|
||||||
|
|
||||||
|
# for x in x_indexes:
|
||||||
|
# for y in y_indexes:
|
||||||
|
# pairs.append((x,y))
|
||||||
|
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
def show_map(self):
|
||||||
|
print(self.name)
|
||||||
|
print()
|
||||||
|
for track in self.tracks:
|
||||||
|
bars = track.stream_to_bars(4)
|
||||||
|
track_str = ''
|
||||||
|
for bar in bars:
|
||||||
|
if bar_has_content(bar):
|
||||||
|
track_str += '█'
|
||||||
|
else:
|
||||||
|
track_str += '_'
|
||||||
|
|
||||||
|
print(track.name[:4],':', track_str)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# In[104]:
|
||||||
|
|
||||||
|
|
||||||
|
def extract_data(midi_folder_path=None, how=None, instrument=None, remove_duplicates=True):
|
||||||
|
'''extract musical data from midis in given folder, to x_train, y_train lists on sequences
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
-----------
|
||||||
|
midi_folder_path : string
|
||||||
|
a path to directory where midi files are stored
|
||||||
|
how : string {'melody','arrangment'}
|
||||||
|
- if melody: function extract data of one instrument,
|
||||||
|
and return lists of x and y that x is actual sequance of 4 bars
|
||||||
|
and y is next bar
|
||||||
|
- if arrangment: function extract data of two instruments and
|
||||||
|
returns a lists of x and y that x is one instrument sequence,
|
||||||
|
and y is coresponing sequance to x, played by second instrument
|
||||||
|
instrument: string or tuple of two strings
|
||||||
|
this parameter is used to specify a instrument class, or classes that you wanted
|
||||||
|
to extract from midi files.
|
||||||
|
|
||||||
|
if how='melody': string
|
||||||
|
if how='arrangment' : (string_x, string_y)
|
||||||
|
|
||||||
|
return:
|
||||||
|
-------
|
||||||
|
x_train, y_train - tuple of coresponding lists of x_train and y_train data for training set
|
||||||
|
|
||||||
|
notes:
|
||||||
|
------
|
||||||
|
extracted data is transposed to the key od C
|
||||||
|
duplicated x,y pairs are removed
|
||||||
|
'''
|
||||||
|
if how not in {'melody','arrangment'}:
|
||||||
|
raise ValueError('how parameter must by one of {melody,arrangment} ')
|
||||||
|
|
||||||
|
x_train = []
|
||||||
|
y_train = []
|
||||||
|
|
||||||
|
for directory, subdirectories, files in os.walk(midi_folder_path):
|
||||||
|
for midi_file in tqdm(files):
|
||||||
|
midi_file_path = os.path.join(directory, midi_file)
|
||||||
|
try:
|
||||||
|
mt = MultiTrack(midi_file_path)
|
||||||
|
if how=='melody':
|
||||||
|
x ,y = mt.get_data_seq2seq_melody(instrument)
|
||||||
|
if how=='arrangment':
|
||||||
|
x ,y = mt.get_data_seq2seq_arrangment(instrument[0], instrument[1])
|
||||||
|
x_train.extend(x)
|
||||||
|
y_train.extend(y)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if remove_duplicates:
|
||||||
|
x_train, y_train = remove_duplicated_sequences((x_train, y_train))
|
||||||
|
|
||||||
|
return x_train , y_train
|
||||||
|
|
||||||
|
|
||||||
|
# In[109]:
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
'''extract data from midis
|
||||||
|
'''
|
||||||
|
x_train, y_train = extract_data(midi_folder_path='WhiteStripes', how='arrangment', instrument=('Guitar','Bass'))
|
||||||
|
pickle.dump((x_train, y_train), open('Guitar_to_Bass_data.pkl','wb'))
|
||||||
|
return x_train, y_train
|
||||||
|
|
||||||
|
|
||||||
|
# In[107]:
|
||||||
|
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
main()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user