import bisect from collections import defaultdict from copy import copy from functools import partial import music21 import numpy as np import scipy.signal as sig import torch import torch.nn.functional as F from scipy import interpolate from torch import nn def convLowPass(x, kernel=101, returnNumpy=False): kernel = int((kernel // 2) * 2 + 1) weights = nn.Parameter(torch.tensor([1 / kernel for i in range(kernel)]).view(1, 1, kernel)) convSmooth = nn.Conv1d( in_channels=1, out_channels=1, kernel_size=kernel, padding=kernel // 2, padding_mode="reflect", bias=False, ) convSmooth.weight = weights convSmooth.requires_grad = False # distortedAlignmentSmooth = convSmooth(torch.flip(torch.tensor(distortedAlignment).view(1,1,-1).float(), [2])) if not torch.is_tensor(x): x = torch.tensor(x) y = convSmooth(x.view(1, 1, -1).float()) if returnNumpy is True: return y.squeeze().detach().numpy() return y def convHighPass(x, kernel=3, returnNumpy=False): # kernel = int((kernel//2)*2 + 1) weights = nn.Parameter(torch.tensor([-1.0, 1.0, -1.0]).view(1, 1, kernel)).float() convSmooth = nn.Conv1d( in_channels=1, out_channels=1, kernel_size=kernel, padding=kernel // 2, padding_mode="reflect", bias=False, ) convSmooth.weight = weights convSmooth.requires_grad = False # distortedAlignmentSmooth = convSmooth(torch.flip(torch.tensor(distortedAlignment).view(1,1,-1).float(), [2])) if not torch.is_tensor(x): x = torch.tensor(x) y = convSmooth(x.view(1, 1, -1).float()) if returnNumpy is True: return y.squeeze().detach().numpy() return y def find(condition): (res,) = np.nonzero(np.ravel(condition)) return res def lowpass_butter(signal, cutoff, Fs, order=6): nyq = 0.5 * Fs normal_cutoff = cutoff / nyq b, a = sig.butter(order, normal_cutoff, btype="low", analog=False) signal = sig.filtfilt(b, a, signal) return signal def highpassButter(signal, cutoff, Fs, order=6): nyq = 0.5 * Fs normal_cutoff = cutoff / nyq b, a = sig.butter(order, normal_cutoff, btype="high", analog=False) signal = sig.filtfilt(b, a, signal) return signal def bandpassButter(signal, cutoffMin, cutoffMax, Fs, order=6): nyq = 0.5 * Fs # normal_cutoff = cutoff / nyq b, a = sig.butter(order, [cutoffMin / nyq, cutoffMax / nyq], btype="band", analog=False) signal = sig.filtfilt(b, a, signal) return signal def custom1(pinakas, emptyVal=-10): telos = 0 arxi = 0 isActive = False for slot in range(len(pinakas) - 1, -1, -1): if pinakas[slot] == emptyVal: if not isActive: telos = slot + 1 arxi = slot isActive = 1 else: arxi = slot else: if not isActive: pass else: # arxi = slot pinakas[arxi:telos] = pinakas[slot] isActive = 0 # print(pinakas) return pinakas # b, a = butter_lowpass(cutoff=0.1, fs=480, order=3) def getDirs(midisHoldsRests, mode="linear"): midisHoldsRests = np.array(midisHoldsRests) scoreLen = len(midisHoldsRests) # input here is the whole score not just a measure onsetInds = np.where(np.logical_and(midisHoldsRests != 128, midisHoldsRests != 129) == True)[0] # onsetInds = np.where(midisHolds!=128)[0] midiPoints = midisHoldsRests[onsetInds] onsetInds = np.insert(onsetInds, len(onsetInds), scoreLen - 1) midiPoints = np.insert(midiPoints, len(midiPoints), midiPoints[-1]) dirPoints = np.insert(np.diff(midiPoints), 0, 0) # dirs = np.zeros(24)-10 # for i, ind in enumerate(onsetInds): # dirs[ind] = dirPoints[i] # dirs = custom2Fixed(dirs, -10) # either non causal zero hold dirs = None if mode == "zero": funcZeroHold = interpolate.interp1d( x=np.sort(scoreLen - 1 - onsetInds), y=np.flip(dirPoints), kind="zero", fill_value="extrapolate", assume_sorted=True, ) dirs = np.flip(funcZeroHold(np.arange(scoreLen))) # or just linear elif mode == "linear": # print("AAA") funcLinear = interpolate.interp1d( x=onsetInds, y=dirPoints, kind="linear", fill_value="extrapolate", assume_sorted=True, ) dirs = funcLinear(np.arange(scoreLen)) return dirs def parseMeasureFull(measure, dirDims, condDims): quantDivsReg = (3, 4) samplesPerQuarterReg = np.lcm(*quantDivsReg) samplesPerQuarterIrreg = 6 fails = [] datasetDict = defaultdict(list) curveSamples = 504 # ts = list(score[0][0].getElementsByClass("TimeSignature")) ts = [music21.meter.TimeSignature("4/4")] ts_string = f"{ts[0].numerator}/{ts[0].denominator}" measureQuarterLength = ts[0].beatCount * ts[0].beatDuration.quarterLength beatsNum = 1 * ts[0].beatCount beatQuarterLength = ts[0].beatDuration.quarterLength gridIrreg34 = np.array( [a + b for a in range(4) for b in [0, 1 / 4, 1 / 3, 2 / 4, 2 / 3, 3 / 4]], ) gridIrreg34 = np.append(gridIrreg34, 4.0) # reggular grids for any ts # TODO be carefull, (3,4) quantizes the grid to 12 per quarter. Meaning that # the smallest posible position is 1/12 and not 1/4 # it is better to make sure that music21.quantize has be done in (3,4) before this step # however music21.quantize introduces rests between notes that has to be fixed gridReg34 = np.linspace( 0.0, measureQuarterLength, int(measureQuarterLength * samplesPerQuarterReg) + 1, ) regGrid34Dict = defaultdict( partial(returnZeros, int(samplesPerQuarterReg * measureQuarterLength)), ) # regGrid68Dict = defaultdict(partial(self.returnZeros, int(self.samplesPerQuarterHigh*measureQuarterLength))) irregGrid34Dict = defaultdict(partial(returnZeros, int(samplesPerQuarterIrreg * 4))) floatGridDict = defaultdict(list) curveDict = defaultdict(list) localOffsetsMap = measure.flat.notesAndRests.offsetMap() ####################################################### #################### REMOVE RESTS ##################### ####################################################### restsMappingInds = restToNotesMapping(localOffsetsMap) for ind, entry in enumerate(localOffsetsMap): # check if currentPosition + dur exceeds the measure limits # if no, popa, # if yes, then, split it. Basically just change the duration, # and add the residual to a queue, # on the next loop the first entry should have offset > 0 # and the queue should have 1 element. endTime = entry.endTime entryNoRest = localOffsetsMap[restsMappingInds[ind]] midi = getElementDetails(entry) midiNoRest = getElementDetails(entryNoRest) dur = endTime - entry.offset if ts_string == "4/4": ##################### irregular inds ##################### startIndIrr = np.argmin(np.abs(gridIrreg34 - entry.offset)) stopIndIrr = np.argmin(np.abs(gridIrreg34 - endTime)) # basic midi vectors irregGrid34Dict["midis"][startIndIrr:stopIndIrr] = midi irregGrid34Dict["midisNoRest"][startIndIrr:stopIndIrr] = midiNoRest # onsets irregGrid34Dict["onsets"][startIndIrr] = 1 ##################### regularLow inds ####################### startIndReg = np.argmin(np.abs(gridReg34 - entry.offset)) stopIndReg = np.argmin(np.abs(gridReg34 - endTime)) # basic midi vectors regGrid34Dict["midis"][startIndReg:stopIndReg] = midi regGrid34Dict["midisNoRest"][startIndReg:stopIndReg] = midiNoRest # onsets regGrid34Dict["onsets"][startIndReg] = 1 # regGrid34Dict['diatonics'][startIndRegL:stopIndRegL] = diatonic # regGrid34Dict['cpcs'][startIndRegL:stopIndRegL] = cpc # regGrid34Dict['dpcs'][startIndRegL:stopIndRegL] = dpc ######################## floatGrid ######################### floatGridDict["midis"].append(midi) floatGridDict["midisNoRest"].append(midiNoRest) floatGridDict["relDur"].append(dur / measureQuarterLength) floatGridDict["relPos"].append(entry.offset / measureQuarterLength) assert entry.offset / measureQuarterLength <= 1 if ts_string == "4/4": # onsets irregGrid34Dict["onsets"][irregGrid34Dict["onsets"] == -10] = 0 # midis vector with holds (128) and no rests. I.e quarter C4 --> 60 128 128 128 128 128 # irregGrid34Dict['midisHoldsNoRest'] = irregGrid34Dict['midisNoRest'] * irregGrid34Dict['onsets'] irregGrid34Dict["midisHoldsNoRest"] = copy(irregGrid34Dict["midisNoRest"]) irregGrid34Dict["midisHoldsNoRest"][irregGrid34Dict["onsets"] == 0] = 128 # midis vector with holds (128) and rests (129) irregGrid34Dict["midisHolds"] = copy(irregGrid34Dict["midis"]) irregGrid34Dict["midisHolds"][irregGrid34Dict["onsets"] == 0] = 128 # rhythm vector # same as onsets but with rests. I.e 0 -> hold, 1 -> hit, 2 -> rest irregGrid34Dict["rhythm"] = copy(irregGrid34Dict["onsets"]) # We need 2 in the rest holds alss. That's why we use 'midis' and not 'midisHolds' irregGrid34Dict["rhythm"][ irregGrid34Dict["midis"] == 129 ] = 2 # 2 is the rhythm symbol for rest ######################################################## ############ Old code for dirs ######################### ######################################################## # # for directions. get onset positions. # # then get midis at these positions # # from onset0 to onset1, set midi1-mid0 # onsetInds = np.where(irregGrid34Dict['onsets']==1)[0] # # onsetInds = np.append(onsetInds, len(regGrid68Dict['onsets'])) # midiPoints = irregGrid34Dict['midisNoRest'][onsetInds] # dirPoints = np.insert(np.diff(midiPoints),0,0) # for i, ind in enumerate(onsetInds): # irregGrid34Dict['dirs'][ind] = dirPoints[i] # irregGrid34Dict['dirs'] = custom2(irregGrid34Dict['dirs'], -10) ######################################################## ################ New code for dirs ##################### ######################################################## irregGrid34Dict["dirs"] = getDirs(irregGrid34Dict["midisHoldsNoRest"], mode="linear") # quantize dirs # comment this out. I calculate it during training. and also it is wrong anyway # irregGrid34Dict['dirs3'] = np.sign(irregGrid34Dict['dirs']) # irregGrid34Dict['dirs5'] = np.clip(irregGrid34Dict['dirs'] //6, -2,2) # irregGrid34Dict['dirs7'] = np.clip(irregGrid34Dict['dirs'] //4, -3,3) # irregGrid34Dict['dirs9'] = np.clip(irregGrid34Dict['dirs'] //3, -4,4) # quantize dirs amp = dirDims // 2 quantDirs = ( np.clip( np.sign(irregGrid34Dict["dirs"]) * np.ceil(np.abs(irregGrid34Dict["dirs"] / (12 / amp))), -amp, amp, ) + amp ) irregGrid34Dict[f"dirs{dirDims}OH"] = onehot(quantDirs, vocabSize=dirDims) # cond offset condCenters = np.linspace(-12, 12, condDims) midiMean = np.mean(irregGrid34Dict["midisNoRest"]) - 71 cond = np.argmin(np.abs(np.array(condCenters) - midiMean)) irregGrid34Dict["cond"] = np.array([cond]) irregGrid34Dict["condOH"] = onehot(irregGrid34Dict["cond"], vocabSize=condDims) irregGrid34Dict["midisHoldsOH"] = onehot(irregGrid34Dict["midisHolds"], vocabSize=130) irregGrid34Dict["midisHoldsNoRestOH"] = onehot( irregGrid34Dict["midisHoldsNoRest"], vocabSize=130, ) irregGrid34Dict["rhythmOH"] = onehot(irregGrid34Dict["rhythm"], vocabSize=3) cpc = np.mod(irregGrid34Dict["midisHoldsNoRest"], 12) + 1 cpc[irregGrid34Dict["midisHoldsNoRest"] == 128] = 0 irregGrid34Dict["cpcsHoldsNoRest"] = cpc irregGrid34Dict["cpcsHoldsNoRestOH"] = onehot( irregGrid34Dict["cpcsHoldsNoRest"], vocabSize=13, ) if np.sum(irregGrid34Dict["midisHolds"] > 129) > 0: assert False # print(f"midi > 129 {idx}") # onsets regGrid34Dict["onsets"][regGrid34Dict["onsets"] == -10] = 0 # midis vector with holds (128) and no rests. I.e quarter C4 --> 60 128 128 128 128 128 # regGrid34Dict['midisHoldsNoRest'] = regGrid34Dict['midisNoRest'] * regGrid34Dict['onsets'] regGrid34Dict["midisHoldsNoRest"] = copy(regGrid34Dict["midisNoRest"]) regGrid34Dict["midisHoldsNoRest"][regGrid34Dict["onsets"] == 0] = 128 # midis vector with holds (128) and rests (129) regGrid34Dict["midisHolds"] = copy(regGrid34Dict["midis"]) regGrid34Dict["midisHolds"][regGrid34Dict["onsets"] == 0] = 128 # rhythm vector # same as onsets but with rests. I.e 0 -> hold, 1 -> hit, 2 -> rest regGrid34Dict["rhythm"] = copy(regGrid34Dict["onsets"]) # We need 2 in the rest holds alss. That's why we use 'midis' and not 'midisHolds' regGrid34Dict["rhythm"][regGrid34Dict["midis"] == 129] = 2 # 2 is the rhythm symbol for rest if np.sum(regGrid34Dict["midisHolds"] > 129) > 0: assert False # print(f"midi > 129 {idx}") # note onet density vector is calculated only for regular grid onsetInds = np.where(regGrid34Dict["onsets"] == 1)[0] onsetInds = np.append(onsetInds, len(regGrid34Dict["onsets"])) for k, ind in enumerate(onsetInds[:-1]): regGrid34Dict["dursZH"][ind] = onsetInds[k + 1] - ind regGrid34Dict["dursZH"] = custom1(regGrid34Dict["dursZH"], -10) try: assert np.count_nonzero(regGrid34Dict["dursZH"] == -10) == 0 except: assert False # regGrid68Dict['onsetDensity'][regGrid68Dict['onsetDensity']==-10]=0 # dursZH relative to beat dur / regGrid34Dict["dursZHBeatRel"] = regGrid34Dict["dursZH"] / ( beatQuarterLength * samplesPerQuarterReg ) # dursZH relative to measure dur / min = 0.00... max = 1 regGrid34Dict["dursZHMeasureRel"] = regGrid34Dict["dursZH"] / ( measureQuarterLength * samplesPerQuarterReg ) # dursZH relative to quarter dur / min = 1/12 max = (around 6 (for 6/4 or 12/8)) regGrid34Dict["dursZHQuarterRel"] = regGrid34Dict["dursZH"] / ( beatQuarterLength * samplesPerQuarterReg ) ################################################################## ######################### ONSET DENSITY ########################## ################################################################## # # 1) onset density using absolute durations # regGrid34Dict['onsetDensityAbs'] = -regGrid34Dict['dursZH'] # min 1/(12*4) --> max 1 # # 2) relative to beat durations # regGrid34Dict['onsetDensityBeatRel'] = 1/(regGrid34Dict['dursZH']/(beatQuarterLength*self.samplesPerQuarterReg)) # min 1 max 48/4 # # 3) relative to measure durations # regGrid34Dict['onsetDensityMeasureRel'] = 1/regGrid34Dict['dursZH']/(measureQuarterLength*self.samplesPerQuarterReg) # min 1 max 48 # # 4) excess density (and relative) I.e 1) 4 8 8 and 2) 2 4 4 .. will give the same. # # it's like regGrid34Dict["onsetDensity"] = 1 - regGrid34Dict["dursZHMeasureRel"] # side1 = convLowPass(regGrid34Dict['onsetDensity'], self.samplesPerQuarterReg/4) # TODO kernel size = 16th note, or maybe 8th # side2 = convLowPass(torch.fliplr(relDursGridUp[:,0,:])[0,:], self.samplesPerQuarterReg/4) # TODO kernel size = 16th note, or maybe 8th # upsample to match shape with the curve relDursGridUp = F.upsample_nearest( torch.tensor(regGrid34Dict["onsetDensity"]).view(1, 1, -1), curveSamples, ) side1 = convLowPass( relDursGridUp[0, 0, :], samplesPerQuarterReg / 4 * (curveSamples / len(regGrid34Dict["onsetDensity"])), ) side2 = convLowPass( torch.fliplr(relDursGridUp[:, 0, :])[0, :], samplesPerQuarterReg / 4 * (curveSamples / len(regGrid34Dict["onsetDensity"])), ) filteredOnsetDensity = torch.max(side1[:, 0, :], torch.fliplr(side2[:, 0, :])) curveDict["onsetDensityLP"] = filteredOnsetDensity.detach().numpy()[0, :] # curveDict['curve' ] = [] #################################################################### ###################### A BASIC CURVE ############################### #################################################################### xPoints = np.where(regGrid34Dict["onsets"] == 1)[0] yPoints = regGrid34Dict["midisNoRest"][xPoints] xPoints = np.append(xPoints, len(regGrid34Dict["onsets"]) - 1) yPoints = np.append(yPoints, yPoints[-1]) # funcZeroHold = interpolate.interp1d(x=xPoints, y=yPoints, # kind='zero', # fill_value='extrapolate', # assume_sorted=True) # funcAkima = interpolate.Akima1DInterpolator(xPoints, yPoints) newX = np.linspace(0, len(regGrid34Dict["midisNoRest"]) - 1, curveSamples) # squareCurve = funcZeroHold(newX) # curve = funcAkima(list(newX)) # # now in some cases, Akima gives huge range # # maybe use a) smooth b) linear + low pass # currentRange = np.max(curve) - np.min(curve) # squareRange = np.max(midis) - np.min(midis) # if currentRange > squareRange + 3 : # yPoints = np.array(yPoints) #+ np.random.randn(len(yPoints)) # xPoints = np.clip(np.array(xPoints) + 6*np.random.randn(len(xPoints)), 0,47) # print(yPoints) # print(xPoints) funcLinear = interpolate.interp1d( x=xPoints, y=yPoints, kind="linear", fill_value="extrapolate", assume_sorted=True, ) # funcSmooth = SmoothInterp(list(xPoints),list(yPoints) ,0.5,0.5, curveSamples, True) curve = funcLinear(list(newX)) # curve = funcSmooth(list(newX)) curve = convLowPass( curve, kernel=samplesPerQuarterReg / 2 * (curveSamples / len(regGrid34Dict["onsetDensity"])), returnNumpy=True, ) curveDict["curve"] = curve # + 0.1*np.random.randn(len(curve)) # zero hold inteprolation for -10s # generally some post-processing fixes have to be done here # irregGrid34Dict['onsets'][irregGrid34Dict['onsets'] == -10] = 0 # regGrid34Dict['onsets'][regGrid34Dict['onsets'] == -10] = 0 # regGrid68Dict['onsets'][regGrid68Dict['onsets'] == -10] = 0 try: assert np.count_nonzero(regGrid34Dict["midis"] == -10) == 0 except: assert False # tempMeasureDict = {} # measureFloatDicts.append(dict(floatGridDict)) # measureReg34Dicts.append(dict(regGrid34Dict)) # # measureReg68Dicts.append(regGrid68Dict) # measureIrreg34Dicts.append(dict(irregGrid34Dict)) # measureCurveDicts.append(dict(curveDict)) if ts_string != "4/4": irregGrid34Dict = {} tempMeasureDict = { "floatGrid": dict(floatGridDict), "regGrid34": dict(regGrid34Dict), "irregGrid34": dict(irregGrid34Dict), "curveData": dict(curveDict), } return tempMeasureDict def restToNotesMapping(offsetsMap): restInds = [] noteInds = [] fixedInds = [] length = len(offsetsMap) for ind, entry in enumerate(offsetsMap): if isinstance(entry.element, music21.note.Rest): restInds.append(ind) else: noteInds.append(ind) # [0,1,2,3,4,5] # [0, 2, 3, 5] + [1,4] # [1, 4, 4, 4] # [1,1,4,4,4,4] lookup = [noteInds[min(bisect.bisect(noteInds, i), len(noteInds) - 1)] for i in restInds] for i in range(length): if i in restInds: fixedInds.append(lookup.pop(0)) else: fixedInds.append(i) return fixedInds def returnZeros(s): return np.zeros(s) - 10 def onehot(d, vocabSize=130): d = d.astype(int) midi_vec = np.zeros((len(d), vocabSize)) k = np.arange(len(d)) midi_vec[k, d] = 1 return midi_vec def getElementDetails(entry): # when replacRests = True then, # replace rest with previous midi note # if self.lastElement = None : # self.lastElement = entry.element if isinstance(entry.element, music21.note.Note): midi = entry.element.pitch.midi diatonic = entry.element.pitch.diatonicNoteNum cpc = entry.element.pitch.pitchClass + 1 # +1 because 0 is for rest dpc = (cpc - 1) // 2 + 1 if entry.element.pitch.accidental is None: acc = 0 else: acc = entry.element.pitch.accidental.alter elif isinstance(entry.element, music21.chord.Chord): # pick a random note randNote = entry.element.notes[0] midi = randNote.pitch.midi diatonic = randNote.pitch.diatonicNoteNum cpc = randNote.pitch.pitchClass + 1 # +1 because 0 is for rest dpc = (cpc - 1) // 2 + 1 if randNote.pitch.accidental is None: acc = 0 else: acc = randNote.pitch.accidental.alter elif isinstance(entry.element, music21.note.Rest): midi = 129 # for rest # diatonic = 0 # cpc = 0 # dpc = 0 # acc = 0 return midi # , diatonic, cpc, dpc, acc