From f598fe6f89aa20efc94cc9d812494e896ffdb367 Mon Sep 17 00:00:00 2001 From: Alexander Rodionov Date: Sat, 20 Jan 2024 10:24:52 +0300 Subject: [PATCH] wip --- src/musiclib/midi/pitchbend.py | 4 +- tests/midi/data/notation/0/code.txt | 9 ++ tests/midi/data/notation/0/midi.json | 44 +++++++++ tests/midi/data/notation/1/code.txt | 9 ++ tests/midi/data/notation/1/midi.json | 40 ++++++++ tests/midi/notation_test.py | 132 ++++++++++++--------------- 6 files changed, 162 insertions(+), 76 deletions(-) create mode 100644 tests/midi/data/notation/0/code.txt create mode 100644 tests/midi/data/notation/0/midi.json create mode 100644 tests/midi/data/notation/1/code.txt create mode 100644 tests/midi/data/notation/1/midi.json diff --git a/src/musiclib/midi/pitchbend.py b/src/musiclib/midi/pitchbend.py index 7b507653..25d5f7ad 100644 --- a/src/musiclib/midi/pitchbend.py +++ b/src/musiclib/midi/pitchbend.py @@ -1,7 +1,7 @@ import bisect -import operator import dataclasses import itertools +import operator from typing import no_type_check import numpy as np @@ -72,7 +72,7 @@ def insert_pitch_pattern( notes=midi.notes, pitchbend=pitchbend, ticks_per_beat=midi.ticks_per_beat, - ) + ) def make_notes_pitchbends(midi: Midi) -> dict[MidiNote, list[MidiPitch]]: diff --git a/tests/midi/data/notation/0/code.txt b/tests/midi/data/notation/0/code.txt new file mode 100644 index 00000000..ab307e29 --- /dev/null +++ b/tests/midi/data/notation/0/code.txt @@ -0,0 +1,9 @@ +header +version 2.2.1 +root C3 +ticks_per_beat 96 + +flute 17 14 10 17 +flute 14 10 07 14 +piano 10 07 04 10 +bass 00 05 07 00 diff --git a/tests/midi/data/notation/0/midi.json b/tests/midi/data/notation/0/midi.json new file mode 100644 index 00000000..ebff4455 --- /dev/null +++ b/tests/midi/data/notation/0/midi.json @@ -0,0 +1,44 @@ +{ + "type": 1, + "ticks_per_beat": 96, + "tracks": [ + [ + {"type": "note_on", "time": 0, "channel": 0, "note": 48, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 48, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 53, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 53, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 55, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 55, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 48, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 48, "velocity": 100, "is_meta": false} + ], + [ + {"type": "note_on", "time": 0, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 64, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 0, "channel": 0, "note": 64, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 69, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 65, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 69, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 0, "channel": 0, "note": 65, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 62, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 0, "channel": 0, "note": 62, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 64, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 0, "channel": 0, "note": 64, "velocity": 100, "is_meta": false} + ], + [ + {"type": "note_on", "time": 0, "channel": 0, "note": 60, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 60, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 60, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 60, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 59, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 59, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 60, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 60, "velocity": 100, "is_meta": false} + ] + ] +} diff --git a/tests/midi/data/notation/1/code.txt b/tests/midi/data/notation/1/code.txt new file mode 100644 index 00000000..c25c56f6 --- /dev/null +++ b/tests/midi/data/notation/1/code.txt @@ -0,0 +1,9 @@ +header +version 2.2.1 +root C3 +ticks_per_beat 96 + +flute 17 14 10 20 +flute 14 10 07 17 +piano 10 -- -- 14 +bass 00 05 07 00 diff --git a/tests/midi/data/notation/1/midi.json b/tests/midi/data/notation/1/midi.json new file mode 100644 index 00000000..45f06937 --- /dev/null +++ b/tests/midi/data/notation/1/midi.json @@ -0,0 +1,40 @@ +{ + "type": 1, + "ticks_per_beat": 96, + "tracks": [ + [ + {"type": "note_on", "time": 0, "channel": 0, "note": 48, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 48, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 53, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 53, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 55, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 55, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 48, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 48, "velocity": 100, "is_meta": false} + ], + [ + {"type": "note_on", "time": 0, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 64, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 0, "channel": 0, "note": 64, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 69, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 65, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 69, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 0, "channel": 0, "note": 65, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 62, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 0, "channel": 0, "note": 62, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 72, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 67, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 72, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 0, "channel": 0, "note": 67, "velocity": 100, "is_meta": false} + ], + [ + {"type": "note_on", "time": 0, "channel": 0, "note": 60, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 288, "channel": 0, "note": 60, "velocity": 100, "is_meta": false}, + {"type": "note_on", "time": 0, "channel": 0, "note": 64, "velocity": 100, "is_meta": false}, + {"type": "note_off", "time": 96, "channel": 0, "note": 64, "velocity": 100, "is_meta": false} + ] + ] +} diff --git a/tests/midi/notation_test.py b/tests/midi/notation_test.py index 73e94b0f..59580907 100644 --- a/tests/midi/notation_test.py +++ b/tests/midi/notation_test.py @@ -1,77 +1,61 @@ +import json import pytest - +from pathlib import Path from musiclib.midi import notation from musiclib.midi.notation import IntervalEvent - - - -@pytest.mark.parametrize('code, name', [ - ('flute 1 2 3 4', 'flute'), - ('bass 9 8 -7 17 4 -12 0', 'bass'), -]) -def test_voice_name(code, name): - assert notation.Voice(code).name == name - - -@pytest.mark.parametrize('code, intervals', [ - ( - 'flute 1 2 3 4', - [ - IntervalEvent(interval=1, on=0, off=96), - IntervalEvent(interval=2, on=96, off=192), - IntervalEvent(interval=3, on=192, off=288), - IntervalEvent(interval=4, on=288, off=384), - ], - ), - ( - 'flute 15 -3 .. -10 26 17 28 -- 17 29 -15 27 -8 -5 25 23', - [ - IntervalEvent(interval=17, on=0, off=96), - IntervalEvent(interval=-3, on=96, off=288), - IntervalEvent(interval=-12, on=288, off=384), - IntervalEvent(interval=30, on=384, off=480), - IntervalEvent(interval=19, on=480, off=576), - IntervalEvent(interval=32, on=576, off=768), - IntervalEvent(interval=19, on=768, off=864), - IntervalEvent(interval=33, on=864, off=960), - IntervalEvent(interval=-17, on=960, off=1056), - IntervalEvent(interval=31, on=1056, off=1152), - IntervalEvent(interval=-8, on=1152, off=1248), - IntervalEvent(interval=-5, on=1248, off=1344), - IntervalEvent(interval=29, on=1344, off=1440), - IntervalEvent(interval=27, on=1440, off=1536), - ], - ), -]) -def test_voice(code, intervals): - assert notation.Voice(code).intervals == intervals - - -code = '''\ -header -version 2.2.1 -root C1 -ticks_per_beat 96 - -flute 13 29 13 26 22 22 -14 0 -1 4 2 19 11 27 -2 0 -flute 4 -7 -8 7 5 18 18 23 26 24 12 3 -1 12 -13 17 -piano 21 29 8 23 19 5 28 -8 26 16 -1 0 23 -7 27 25 -bass 9 8 -7 17 4 -12 0 -6 -10 14 25 6 -3 13 -1 29 - -modulation -root A0 - -flute 16 5 24 15 0 7 -6 7 4 -15 2 26 12 20 16 -11 -flute 15 -3 0 -10 26 17 28 28 17 29 -15 27 -8 -5 25 23 -piano 9 19 -11 -15 16 -8 12 -6 10 7 22 22 24 29 18 -4 -bass -13 28 7 5 8 1 16 9 -3 -11 -7 27 5 2 4 18 - -flutffsdfsdfsdf 16 5 24 15 0 7 -6 7 4 -15 2 26 12 20 16 -11 -flutesdfsdfsd -3 0 -10 26 17 28 28 17 29 -15 27 -8 -5 25 23 -pianosssssssssssssssssss 9 19 -11 -15 16 -8 12 -6 10 7 22 22 24 29 18 -4 -bass -13 28 7 5 8 1 16 9 -3 -11 -7 27 5 2 4 18 -''' - - -def test_notation(): - notation.Notation(code) +from musiclib.midi import parse + + +@pytest.mark.parametrize( + 'code, channel', [ + ('flute 1 2 3 4', 'flute'), + ('bass 9 8 -7 17 4 -12 0', 'bass'), + ], +) +def test_voice_channel(code, channel): + assert notation.Voice(code).channel == channel + + +@pytest.mark.parametrize( + 'code, interval_events', [ + ( + 'flute 1 2 3 4', + [ + IntervalEvent(interval=1, on=0, off=96), + IntervalEvent(interval=2, on=96, off=192), + IntervalEvent(interval=3, on=192, off=288), + IntervalEvent(interval=4, on=288, off=384), + ], + ), + ( + 'flute 15 -3 .. -10 26 17 28 -- 17 29 -15 27 -8 -5 25 23', + [ + IntervalEvent(interval=17, on=0, off=96), + IntervalEvent(interval=-3, on=96, off=288), + IntervalEvent(interval=-12, on=288, off=384), + IntervalEvent(interval=30, on=384, off=480), + IntervalEvent(interval=19, on=480, off=576), + IntervalEvent(interval=32, on=576, off=768), + IntervalEvent(interval=19, on=768, off=864), + IntervalEvent(interval=33, on=864, off=960), + IntervalEvent(interval=-17, on=960, off=1056), + IntervalEvent(interval=31, on=1056, off=1152), + IntervalEvent(interval=-8, on=1152, off=1248), + IntervalEvent(interval=-5, on=1248, off=1344), + IntervalEvent(interval=29, on=1344, off=1440), + IntervalEvent(interval=27, on=1440, off=1536), + ], + ), + ], +) +def test_voice(code, interval_events): + assert notation.Voice(code).interval_events == interval_events + + +@pytest.mark.parametrize('example', [0]) +def test_to_midi(example): + example_dir = Path(__file__).parent / f'data/notation/{example}' + code = (example_dir / 'code.txt').read_text() + with open(example_dir / 'midi.json') as f: + midi_dict = json.load(f) + assert parse.to_dict(notation.Notation(code).to_midi()) == midi_dict