import random from data import Vocab, Control, Pitch, Duration, Polyphony, wavtool2midi import pytest import torch import json import math @pytest.mark.parametrize( "input, expected", [ (Pitch(0, 60, 28), "{C5 60 (28)}"), (Control(0, "pad"), "{pad}"), (Duration(0, 12, "1"), "{L1}"), (Polyphony(0, 2), "{P2}"), ], ) def test_vocab_reprs(input, expected): assert str(input) == expected def test_vocab(): v = Vocab(24, 96, 24, 48, 20, 127, 2, 1, 12, 4, 4 * 4 * 12, 4, 12) assert str(v.pitches[60][80]) == "{C5 60 (80)}" assert str(v.durations[12]) == "{L1}" assert str(v.durations[4]) == "{L1/3}" assert set(s.index for s in v.symbols) == set(range(v.N)) vocab_test_inputs = [ ( '{"notes":[{"pitch":48,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":49,"start":1,"end":2,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":2,"end":3,"velocity":0.7874015748031497,"lifted":true}]}', None, ), ( '{"notes":[{"pitch":48,"start":1,"end":2,"velocity":0.7874015748031497,"lifted":true},{"pitch":49,"start":2,"end":3,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":3,"end":4,"velocity":0.7874015748031497,"lifted":true}]}', '{"notes":[{"pitch":40,"start":1,"end":1.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":40,"start":1.75,"end":1.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":0.25,"end":0.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":0.75,"end":0.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":1.25,"end":1.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":42,"start":1.5,"end":1.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":0.5,"end":0.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":1,"end":1.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":0.875,"end":1.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":0.125,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":1.125,"end":2,"velocity":0.7874015748031497,"lifted":true},{"pitch":51,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":47,"start":2,"end":2.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":49,"start":2,"end":3,"velocity":0.7874015748031497,"lifted":true},{"pitch":54,"start":1.875,"end":2.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":39,"start":2,"end":2.125,"velocity":0.7874015748031497,"lifted":true}]}', ), ( '{"notes":[{"pitch":48,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":49,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true}]}', '{"notes":[{"pitch":46,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":46,"start":0.125,"end":0.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":46,"start":2.125,"end":2.5,"velocity":0.7874015748031497,"lifted":true},{"pitch":45,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":45,"start":1.875,"end":2.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":44,"start":0.25,"end":0.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":44,"start":0.5,"end":0.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":44,"start":1.375,"end":1.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":0.375,"end":0.5,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":1.125,"end":1.5,"velocity":0.7874015748031497,"lifted":true},{"pitch":35,"start":0,"end":0.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":35,"start":1,"end":1.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":36,"start":0.5,"end":0.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":0.75,"end":0.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":0.5,"end":0.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":51,"start":0.375,"end":0.5,"velocity":0.7874015748031497,"lifted":true},{"pitch":51,"start":0.625,"end":0.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":0.125,"end":0.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":53,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":53,"start":0.25,"end":0.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":0.625,"end":0.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":34,"start":1.5,"end":1.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":47,"start":2.25,"end":2.625,"velocity":0.7874015748031497,"lifted":true}]}', ), ( '{"notes":[{"pitch":51,"start":1.125,"end":2.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":49,"start":0.375,"end":1.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":0.75,"end":1.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":1.5,"end":2.5,"velocity":0.7874015748031497,"lifted":true}]}', None, ), ( [ '{"notes":[{"pitch":40,"start":1,"end":1.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":40,"start":1.75,"end":1.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":0.25,"end":0.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":0.75,"end":0.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":1.25,"end":1.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":42,"start":1.5,"end":1.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":0.5,"end":0.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":1,"end":1.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":0.875,"end":1.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":0.125,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":1.125,"end":2,"velocity":0.7874015748031497,"lifted":true},{"pitch":51,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":47,"start":2,"end":2.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":49,"start":2,"end":3,"velocity":0.7874015748031497,"lifted":true},{"pitch":54,"start":1.875,"end":2.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":39,"start":2,"end":2.125,"velocity":0.7874015748031497,"lifted":true}]}', '{"notes":[{"pitch":46,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":46,"start":0.125,"end":0.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":46,"start":2.125,"end":2.5,"velocity":0.7874015748031497,"lifted":true},{"pitch":45,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":45,"start":1.875,"end":2.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":44,"start":0.25,"end":0.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":44,"start":0.5,"end":0.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":44,"start":1.375,"end":1.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":0.375,"end":0.5,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":1.125,"end":1.5,"velocity":0.7874015748031497,"lifted":true},{"pitch":35,"start":0,"end":0.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":35,"start":1,"end":1.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":36,"start":0.5,"end":0.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":0.75,"end":0.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":0.5,"end":0.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":51,"start":0.375,"end":0.5,"velocity":0.7874015748031497,"lifted":true},{"pitch":51,"start":0.625,"end":0.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":0.125,"end":0.25,"velocity":0.7874015748031497,"lifted":true},{"pitch":53,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":53,"start":0.25,"end":0.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":0.625,"end":0.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":34,"start":1.5,"end":1.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":47,"start":2.25,"end":2.625,"velocity":0.7874015748031497,"lifted":true}]}', ], None, ), ( '{"notes":[{"pitch":1036,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":1037,"start":1,"end":2,"velocity":0.7874015748031497,"lifted":true}]}', None, ), ( '{"notes":[]}', '{"notes":[{"pitch":40,"start":1,"end":1.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":40,"start":1.75,"end":1.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":0.25,"end":0.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":0.75,"end":0.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":41,"start":1.25,"end":1.375,"velocity":0.7874015748031497,"lifted":true},{"pitch":42,"start":1.5,"end":1.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":0,"end":0.125,"velocity":0.7874015748031497,"lifted":true},{"pitch":43,"start":0.5,"end":0.625,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":48,"start":1,"end":1.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":50,"start":0.875,"end":1.875,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":0.125,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":52,"start":1.125,"end":2,"velocity":0.7874015748031497,"lifted":true},{"pitch":51,"start":0,"end":1,"velocity":0.7874015748031497,"lifted":true},{"pitch":47,"start":2,"end":2.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":49,"start":2,"end":3,"velocity":0.7874015748031497,"lifted":true},{"pitch":54,"start":1.875,"end":2.75,"velocity":0.7874015748031497,"lifted":true},{"pitch":39,"start":2,"end":2.125,"velocity":0.7874015748031497,"lifted":true}]}', ), ( '{"notes":[]}', '{"notes":[]}', ), ( '{"notes":[]}', None, ), ] @pytest.mark.parametrize("inputs,accompany", vocab_test_inputs) @pytest.mark.parametrize("append_in_place", [True, False]) @pytest.mark.parametrize("put_desc_anchor", [True, False]) def test_midi_tensors(inputs, accompany, append_in_place, put_desc_anchor): if isinstance(inputs, str): inputs = [inputs] midis = [wavtool2midi(json.loads(input)) for input in inputs] if put_desc_anchor: for midi in midis: if len(midi) > 0: some_note = random.choice(midi) some_note["descriptionAnchor"] = True accompany_midi = ( wavtool2midi(json.loads(accompany)) if accompany is not None else None ) v = Vocab(24, 96, 24, 48, 20, 127, 2, 1, 24 * 4, 4, 4 * 4 * 24, 4, 24) recovered_midis_a = [] recovered_keys_a = [] for midi in midis: key = random.randint(0, 23) x = v.midi_to_tensor(midi, key, accompany=accompany_midi, strict=True) est_len = v.fast_estimate_length(midi, accompany=accompany_midi) assert abs(est_len - x.shape[0]) <= 2 recovered_midi_a, recovered_key_a = v.tensor_to_midi(x) assert recovered_key_a == key recovered_midis_a.append(recovered_midi_a) recovered_keys_a.append(recovered_key_a) if not put_desc_anchor and len(recovered_midi_a) > 0: assert recovered_midi_a[0]["descriptionAnchor"] == True del recovered_midi_a[0]["descriptionAnchor"] assert recovered_midi_a == [ {k: note[k] for k in note.keys() if k not in ["offVelocity", "sourceTrack"]} for note in midi ] if accompany is not None: rec_acc, rec_key = v.tensor_to_midi(x, return_accompany=True) assert rec_key == key assert rec_acc == [ { k: note[k] for k in note.keys() if k not in ["offVelocity", "sourceTrack"] } for note in accompany_midi ] for splices in [ [1], [4], [1, 2, 3, 4], [3, 2, 4, 1], [8, 9, 2, 6, 7], [1] * 1000, [22, 23, 24], ]: symbolss = [] for midi in midis: symbols = v.midi_to_symbols(midi, allow_anchor=True) symbolss.append(symbols) symbols_len = max( symbols.shape[0] if symbols is not None else 0 for symbols in symbolss ) symbols = torch.zeros(len(midis), symbols_len, dtype=torch.long) for i, symbols_i in enumerate(symbolss): if symbols_i is not None: symbols[i, : symbols_i.shape[0]] = symbols_i ys = torch.tensor( [[[v.begin.index, 0, 0, 0, 0, 0, 0, 0, 0]]], dtype=torch.long, ).repeat(len(midis), 1 if not append_in_place else 1000, 1) seq_len = 1 for splice in splices + [math.inf]: if symbols.shape[1] == 0: break msplice = min(splice, symbols.shape[1]) symbols_pfx, symbols = symbols[:, :msplice], symbols[:, msplice:] if append_in_place: v.append_symbols(ys, symbols_pfx, seq_len=seq_len) seq_len += symbols_pfx.shape[1] else: ys = v.append_symbols(ys, symbols_pfx) for i, recovered_midi_a in enumerate(recovered_midis_a): recovered_midi_b = v.tensor_to_midi( ys[i, :seq_len] if append_in_place else ys[i] )[0] assert recovered_midi_a == recovered_midi_b v0 = Vocab(24, 96, 24, 48, 20, 127, 2, 1, 24 * 4, 4, 4 * 4 * 24, 4, 24) @pytest.mark.parametrize( "input", [ [[0, 0, 0, 0, 0, 0, 0, 0, 1]], [[0, 0, 0, 0, 1, 0, 0, 0, 1]], [[0, 0, 0, 0, 1, 0, 0, 0, 0]], [[0, 0, 0, 0, 0, 0, 0, 1, 0]], [[0, 0, 0, 1, 0, 0, 0, 0, 0]], [[0, 0, 0, 1, 0, 0, 0, 1, 0], [0, 0, 0, 2, 0, 0, 0, 2, 0]], [ # wrong polyphony symbol [v0.polyphony[2].index, 0, 0, 0, 0, 0, 0, 0, 0], [v0.pitches[50][126].index, 0, 0, 0, 0, 0, 0, 0, 0], [v0.durations[1].index, 0, 0, 0, 0, 0, 0, 0, 1], [v0.rest.index, 0, 0, 0, 1, 0, 0, 0, 1], [v0.durations[1].index, 0, 0, 0, 1, 0, 0, 1, 0], ], [ # wrong polyphony embed [v0.polyphony[2].index, 0, 0, 0, 0, 0, 0, 0, 0], [v0.pitches[50][126].index, 0, 0, 0, 0, 0, 0, 0, 0], [v0.durations[1].index, 0, 0, 0, 0, 0, 0, 0, 1], [v0.pitches[51][126].index, 0, 0, 0, 1, 0, 0, 0, 1], [v0.durations[1].index, 0, 0, 0, 1, 0, 0, 0, 3], [v0.rest.index, 0, 0, 0, 2, 0, 0, 0, 2], [v0.durations[1].index, 0, 0, 0, 2, 0, 0, 1, 0], ], ], ) def test_tensor_to_midi_asserts(input): with pytest.raises(Exception): v0.tensor_to_midi(torch.tensor(input, dtype=torch.long)) @pytest.mark.parametrize( "allow_anchor, input, expected", [ ( False, [ { "note": 60, "onBeat": 0, "offBeat": 1, "onVelocity": 20, "descriptionAnchor": True, } ], [[60, 20, 0, 24, False]], ), ( True, [ { "note": 60, "onBeat": 0.01, "offBeat": 0.99, "onVelocity": 126, "descriptionAnchor": True, } ], [[60, 126, 0, 24, True]], ), ( True, [{"note": 60, "onBeat": 0, "offBeat": 1.01, "onVelocity": 125}], [[60, 124, 0, 24, False]], ), ( True, [ {"note": 60, "onBeat": 0.5, "offBeat": 0.6, "onVelocity": 20}, {"note": 61, "onBeat": 0.625, "offBeat": 1.5, "onVelocity": 24}, { "note": 60, "onBeat": 0.5, "offBeat": 1.0, "onVelocity": 30, "descriptionAnchor": True, }, {"note": 61, "onBeat": 0.625, "offBeat": 1.0, "onVelocity": 36}, ], [[60, 20, 12, 14, True], [61, 36, 15, 24, False]], ), ( True, [ {"note": 60, "onBeat": 0, "offBeat": 0.5, "onVelocity": 40}, {"note": 60, "onBeat": 0.5, "offBeat": 1.0, "onVelocity": 50}, ], [[60, 40, 0, 12, False], [60, 50, 12, 24, False]], ), ( True, [ {"note": 60, "onBeat": 0, "offBeat": 1.0, "onVelocity": 80}, {"note": 60, "onBeat": 0.125, "offBeat": 0.5, "onVelocity": 90}, {"note": 60, "onBeat": 0.375, "offBeat": 0.75, "onVelocity": 100}, {"note": 60, "onBeat": 0.8, "offBeat": 0.95, "onVelocity": 110}, ], [ [60, 80, 0, 3, False], [60, 90, 3, 9, False], [60, 100, 9, 18, False], [60, 110, 19, 23, False], ], ), ( True, [ {"note": 60, "onBeat": 0.01, "offBeat": 0.51, "onVelocity": 21}, {"note": 60, "onBeat": 0.49, "offBeat": 0.99, "onVelocity": 127}, ], [[60, 20, 0, 12, False], [60, 127, 12, 24, False]], ), ], ) def test_quantize(allow_anchor, input, expected): v = Vocab(24, 96, 24, 48, 20, 127, 2, 1, 24 * 4, 4, 4 * 4 * 24, 4, 24) assert v.quantize_midi(input, allow_anchor=allow_anchor) == expected