diff --git a/DryWetMidi.Tests/Interaction/GetObjects/GetObjectsUtilitiesTests.NotesAndRests.cs b/DryWetMidi.Tests/Interaction/GetObjects/GetObjectsUtilitiesTests.NotesAndRests.cs index a32ed86c5..213017576 100644 --- a/DryWetMidi.Tests/Interaction/GetObjects/GetObjectsUtilitiesTests.NotesAndRests.cs +++ b/DryWetMidi.Tests/Interaction/GetObjects/GetObjectsUtilitiesTests.NotesAndRests.cs @@ -15,53 +15,44 @@ public sealed partial class GetObjectsUtilitiesTests [TestCase(10, 2, 50, 50)] [TestCase(10, 10, 50, 100)] [TestCase(10, 2, 50, 100)] - public void GetObjects_NotesAndRests_NoSeparation_FromNotes( - byte channel1, - byte channel2, - byte noteNumber1, - byte noteNumber2) - { - GetObjects_NotesAndRests( - restSeparationPolicy: RestSeparationPolicy.NoSeparation, - inputObjects: new ITimedObject[] - { - new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 }, - new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = (FourBitNumber)channel1 }, - new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 }, - new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 }, - new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = (FourBitNumber)channel2 }, - new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 }, - new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 }, - new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 }, - new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 }, - new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 }, - }, - outputObjects: new ITimedObject[] - { - new Rest(0, 10, null, null), - new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 }, - new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = (FourBitNumber)channel1 }, - new Rest(130, 170, null, null), - new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 }, - new Rest(350, 650, null, null), - new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 }, - new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = (FourBitNumber)channel2 }, - new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 }, - new Rest(2300, 7700, null, null), - new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 }, - new Rest(11000, 89000, null, null), - new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 }, - new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 }, - new Rest(101000, 9000, null, null), - new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 }, - }); - } + public void GetObjects_NotesAndRests_NoSeparation_FromNotes(byte channel1, byte channel2, byte noteNumber1, byte noteNumber2) => GetObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.NoSeparation, + inputObjects: new ITimedObject[] + { + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 }, + }, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, null, null), + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Rest(130, 170, null, null), + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Rest(350, 650, null, null), + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + new Rest(2300, 7700, null, null), + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 }, + new Rest(11000, 89000, null, null), + new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 }, + new Rest(101000, 9000, null, null), + new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 }, + }); [TestCase(10, 10)] [TestCase(10, 50)] - public void GetObjects_NotesAndRests_SeparateByChannel_SingleChannel_FromNotes( - byte noteNumber1, - byte noteNumber2) + public void GetObjects_NotesAndRests_SeparateByChannel_SingleChannel_FromNotes(byte noteNumber1, byte noteNumber2) { var channel = (FourBitNumber)10; @@ -95,9 +86,7 @@ public void GetObjects_NotesAndRests_SeparateByChannel_SingleChannel_FromNotes( [TestCase(10, 10)] [TestCase(10, 50)] - public void GetObjects_NotesAndRests_SeparateByChannel_DifferentChannels_FromNotes( - byte noteNumber1, - byte noteNumber2) + public void GetObjects_NotesAndRests_SeparateByChannel_DifferentChannels_FromNotes(byte noteNumber1, byte noteNumber2) { var channel1 = (FourBitNumber)10; var channel2 = (FourBitNumber)2; @@ -131,9 +120,7 @@ public void GetObjects_NotesAndRests_SeparateByChannel_DifferentChannels_FromNot [TestCase(10, 10)] [TestCase(10, 5)] - public void GetObjects_NotesAndRests_SeparateByNoteNumber_SingleNoteNumber_FromNotes( - byte channel1, - byte channel2) + public void GetObjects_NotesAndRests_SeparateByNoteNumber_SingleNoteNumber_FromNotes(byte channel1, byte channel2) { var noteNumber = (SevenBitNumber)10; @@ -164,9 +151,7 @@ public void GetObjects_NotesAndRests_SeparateByNoteNumber_SingleNoteNumber_FromN [TestCase(10, 10)] [TestCase(10, 5)] - public void GetObjects_NotesAndRests_SeparateByNoteNumber_DifferentNoteNumbers_FromNotes( - byte channel1, - byte channel2) + public void GetObjects_NotesAndRests_SeparateByNoteNumber_DifferentNoteNumbers_FromNotes(byte channel1, byte channel2) { var noteNumber1 = (SevenBitNumber)10; var noteNumber2 = (SevenBitNumber)100; @@ -232,11 +217,7 @@ public void GetObjects_NotesAndRests_SeparateByChannelAndNoteNumber_FromNotes() [TestCase(10, 2, 50, 50)] [TestCase(10, 10, 50, 100)] [TestCase(10, 2, 50, 100)] - public void GetObjects_NotesAndRests_NoSeparation_FromNotesAndTimedEvents( - byte channel1, - byte channel2, - byte noteNumber1, - byte noteNumber2) + public void GetObjects_NotesAndRests_NoSeparation_FromNotesAndTimedEvents(byte channel1, byte channel2, byte noteNumber1, byte noteNumber2) { GetObjects_NotesAndRests( restSeparationPolicy: RestSeparationPolicy.NoSeparation, @@ -278,9 +259,7 @@ public void GetObjects_NotesAndRests_NoSeparation_FromNotesAndTimedEvents( [TestCase(10, 10)] [TestCase(10, 50)] - public void GetObjects_NotesAndRests_SeparateByChannel_SingleChannel_FromNotesAndTimedEvents( - byte noteNumber1, - byte noteNumber2) + public void GetObjects_NotesAndRests_SeparateByChannel_SingleChannel_FromNotesAndTimedEvents(byte noteNumber1, byte noteNumber2) { var channel = (FourBitNumber)10; @@ -318,9 +297,7 @@ public void GetObjects_NotesAndRests_SeparateByChannel_SingleChannel_FromNotesAn [TestCase(10, 10)] [TestCase(10, 50)] - public void GetObjects_NotesAndRests_SeparateByChannel_DifferentChannels_FromNotesAndTimedEvents( - byte noteNumber1, - byte noteNumber2) + public void GetObjects_NotesAndRests_SeparateByChannel_DifferentChannels_FromNotesAndTimedEvents(byte noteNumber1, byte noteNumber2) { var channel1 = (FourBitNumber)10; var channel2 = (FourBitNumber)2; @@ -356,9 +333,7 @@ public void GetObjects_NotesAndRests_SeparateByChannel_DifferentChannels_FromNot [TestCase(10, 10)] [TestCase(10, 5)] - public void GetObjects_NotesAndRests_SeparateByNoteNumber_SingleNoteNumber_FromNotesAndTimedEvents( - byte channel1, - byte channel2) + public void GetObjects_NotesAndRests_SeparateByNoteNumber_SingleNoteNumber_FromNotesAndTimedEvents(byte channel1, byte channel2) { var noteNumber = (SevenBitNumber)10; @@ -391,9 +366,7 @@ public void GetObjects_NotesAndRests_SeparateByNoteNumber_SingleNoteNumber_FromN [TestCase(10, 10)] [TestCase(10, 5)] - public void GetObjects_NotesAndRests_SeparateByNoteNumber_DifferentNoteNumbers_FromNotesAndTimedEvents( - byte channel1, - byte channel2) + public void GetObjects_NotesAndRests_SeparateByNoteNumber_DifferentNoteNumbers_FromNotesAndTimedEvents(byte channel1, byte channel2) { var noteNumber1 = (SevenBitNumber)10; var noteNumber2 = (SevenBitNumber)100; @@ -457,6 +430,418 @@ public void GetObjects_NotesAndRests_SeparateByChannelAndNoteNumber_FromNotesAnd }); } + [TestCase(10, 2, 50, 50)] + [TestCase(10, 10, 50, 100)] + [TestCase(10, 2, 50, 100)] + public void EnumerateObjects_NotesAndRests_NoSeparation_1(byte channel1, byte channel2, byte noteNumber1, byte noteNumber2) => EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.NoSeparation, + inputEvents: new ITimedObject[] + { + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, null, null), + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Rest(130, 170, null, null), + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Rest(350, 650, null, null), + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + new Rest(2300, 7700, null, null), + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 }, + new Rest(11000, 89000, null, null), + new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 }, + new Rest(101000, 9000, null, null), + new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 }, + }); + + [TestCase(10, 2, 50, 50)] + [TestCase(10, 10, 50, 100)] + [TestCase(10, 2, 50, 100)] + public void EnumerateObjects_NotesAndRests_NoSeparation_2(byte channel1, byte channel2, byte noteNumber1, byte noteNumber2) + { + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.NoSeparation, + inputEvents: new ITimedObject[] + { + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 }, + new TimedEvent(new NoteOnEvent((SevenBitNumber)noteNumber1, Note.DefaultVelocity) { Channel = (FourBitNumber)channel1 }, 30), + new TimedEvent(new NoteOffEvent((SevenBitNumber)noteNumber1, SevenBitNumber.MinValue) { Channel = (FourBitNumber)channel1 }, 130), + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new TimedEvent(new NoteOnEvent((SevenBitNumber)noteNumber2, Note.DefaultVelocity) { Channel = (FourBitNumber)channel2 }, 1200), + new TimedEvent(new NoteOffEvent((SevenBitNumber)noteNumber2, SevenBitNumber.MinValue) { Channel = (FourBitNumber)channel2 }, 1350), + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, null, null), + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Rest(130, 170, null, null), + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Rest(350, 650, null, null), + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + new Rest(2300, 7700, null, null), + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 }, + new Rest(11000, 89000, null, null), + new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 }, + new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 }, + new Rest(101000, 9000, null, null), + new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 }, + }); + } + + [TestCase(10, 50)] + public void EnumerateObjects_NotesAndRests_SeparateByChannel_SingleChannel_1(byte noteNumber1, byte noteNumber2) + { + var channel = (FourBitNumber)10; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByChannel, + inputEvents: new ITimedObject[] + { + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = channel }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel }, + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel }, + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = channel }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = channel }, + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = channel }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, channel, null), + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = channel }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel }, + new Rest(130, 170, channel, null), + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel }, + new Rest(350, 650, channel, null), + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = channel }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = channel }, + new Rest(2300, 7700, channel, null), + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = channel }, + }); + } + + [TestCase(10, 50)] + public void EnumerateObjects_NotesAndRests_SeparateByChannel_SingleChannel_2(byte noteNumber1, byte noteNumber2) + { + var channel = (FourBitNumber)10; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByChannel, + inputEvents: new ITimedObject[] + { + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = channel }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel }, + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel }, + new TimedEvent(new NoteOnEvent((SevenBitNumber)noteNumber1, Note.DefaultVelocity) { Channel = channel }, 1000), + new TimedEvent(new NoteOffEvent((SevenBitNumber)noteNumber1, SevenBitNumber.MinValue) { Channel = channel }, 1500), + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel }, + new TimedEvent(new NoteOnEvent((SevenBitNumber)noteNumber1, Note.DefaultVelocity) { Channel = channel }, 1300), + new TimedEvent(new NoteOffEvent((SevenBitNumber)noteNumber1, SevenBitNumber.MinValue) { Channel = channel }, 2300), + new TimedEvent(new NoteOnEvent((SevenBitNumber)noteNumber2, Note.DefaultVelocity) { Channel = channel }, 10000), + new TimedEvent(new NoteOffEvent((SevenBitNumber)noteNumber2, SevenBitNumber.MinValue) { Channel = channel }, 11000), + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, channel, null), + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = channel }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel }, + new Rest(130, 170, channel, null), + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel }, + new Rest(350, 650, channel, null), + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = channel }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = channel }, + new Rest(2300, 7700, channel, null), + new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = channel }, + }, + noteDetectionSettings: new NoteDetectionSettings { NoteStartDetectionPolicy = NoteStartDetectionPolicy.FirstNoteOn }); + } + + [TestCase(10, 10)] + [TestCase(10, 50)] + public void EnumerateObjects_NotesAndRests_SeparateByChannel_DifferentChannels_1(byte noteNumber1, byte noteNumber2) + { + var channel1 = (FourBitNumber)10; + var channel2 = (FourBitNumber)2; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByChannel, + inputEvents: new ITimedObject[] + { + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = channel1 }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel2 }, + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel1 }, + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = channel2 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel1 }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = channel2 }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, channel1, null), + new Rest(0, 30, channel2, null), + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = channel1 }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel2 }, + new Rest(110, 190, channel1, null), + new Rest(130, 870, channel2, null), + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel1 }, + new Rest(350, 850, channel1, null), + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = channel2 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel1 }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = channel2 }, + }); + } + + [TestCase(10, 10)] + [TestCase(10, 50)] + public void EnumerateObjects_NotesAndRests_SeparateByChannel_DifferentChannels_2(byte noteNumber1, byte noteNumber2) + { + var channel1 = (FourBitNumber)10; + var channel2 = (FourBitNumber)2; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByChannel, + inputEvents: new ITimedObject[] + { + new TimedEvent(new NoteOnEvent((SevenBitNumber)noteNumber1, Note.DefaultVelocity) { Channel = channel1 }, 10), + new TimedEvent(new NoteOffEvent((SevenBitNumber)noteNumber1, SevenBitNumber.MinValue) { Channel = channel1 }, 110), + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel2 }, + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel1 }, + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = channel2 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel1 }, + new TimedEvent(new NoteOnEvent((SevenBitNumber)noteNumber1, Note.DefaultVelocity) { Channel = channel2 }, 1300), + new TimedEvent(new NoteOffEvent((SevenBitNumber)noteNumber1, SevenBitNumber.MinValue) { Channel = channel2 }, 2300), + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, channel1, null), + new Rest(0, 30, channel2, null), + new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = channel1 }, + new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel2 }, + new Rest(110, 190, channel1, null), + new Rest(130, 870, channel2, null), + new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel1 }, + new Rest(350, 850, channel1, null), + new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = channel2 }, + new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel1 }, + new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = channel2 }, + }); + } + + [TestCase(10, 5)] + public void EnumerateObjects_NotesAndRests_SeparateByNoteNumber_SingleNoteNumber_1(byte channel1, byte channel2) + { + var noteNumber = (SevenBitNumber)10; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByNoteNumber, + inputEvents: new ITimedObject[] + { + new Note(noteNumber, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Note(noteNumber, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new Note(noteNumber, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, null, noteNumber), + new Note(noteNumber, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Rest(130, 170, null, noteNumber), + new Note(noteNumber, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Rest(350, 650, null, noteNumber), + new Note(noteNumber, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new Note(noteNumber, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + }); + } + + [TestCase(10, 5)] + public void EnumerateObjects_NotesAndRests_SeparateByNoteNumber_SingleNoteNumber_2(byte channel1, byte channel2) + { + var noteNumber = (SevenBitNumber)10; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByNoteNumber, + inputEvents: new ITimedObject[] + { + new Note(noteNumber, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 100, 30) { Channel = (FourBitNumber)channel1 }, + new TimedEvent(new NoteOnEvent(noteNumber, Note.DefaultVelocity) { Channel = (FourBitNumber)channel2 }, 300), + new TimedEvent(new NoteOffEvent(noteNumber, SevenBitNumber.MinValue) { Channel = (FourBitNumber)channel2 }, 350), + new TimedEvent(new NoteOnEvent(noteNumber, Note.DefaultVelocity) { Channel = (FourBitNumber)channel1 }, 1000), + new TimedEvent(new NoteOffEvent(noteNumber, SevenBitNumber.MinValue) { Channel = (FourBitNumber)channel1 }, 1500), + new Note(noteNumber, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, null, noteNumber), + new Note(noteNumber, 100, 10) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Rest(130, 170, null, noteNumber), + new Note(noteNumber, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Rest(350, 650, null, noteNumber), + new Note(noteNumber, 500, 1000) { Channel = (FourBitNumber)channel1 }, + new Note(noteNumber, 150, 1200) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber, 1000, 1300) { Channel = (FourBitNumber)channel1 }, + }); + } + + [TestCase(10, 5)] + public void EnumerateObjects_NotesAndRests_SeparateByNoteNumber_DifferentNoteNumbers_1(byte channel1, byte channel2) + { + var noteNumber1 = (SevenBitNumber)10; + var noteNumber2 = (SevenBitNumber)100; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByNoteNumber, + inputEvents: new ITimedObject[] + { + new Note(noteNumber1, 100, 0) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber2, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Note(noteNumber1, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber2, 500, 1000) { Channel = (FourBitNumber)channel1 }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 30, null, noteNumber2), + new Note(noteNumber1, 100, 0) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber2, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Rest(100, 200, null, noteNumber1), + new Rest(130, 870, null, noteNumber2), + new Note(noteNumber1, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber2, 500, 1000) { Channel = (FourBitNumber)channel1 }, + }); + } + + [TestCase(10, 10)] + [TestCase(10, 5)] + public void EnumerateObjects_NotesAndRests_SeparateByNoteNumber_DifferentNoteNumbers_2(byte channel1, byte channel2) + { + var noteNumber1 = (SevenBitNumber)10; + var noteNumber2 = (SevenBitNumber)100; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByNoteNumber, + inputEvents: new ITimedObject[] + { + new Note(noteNumber1, 100, 0) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber2, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Note(noteNumber1, 50, 300) { Channel = (FourBitNumber)channel2 }, + new TimedEvent(new NoteOnEvent(noteNumber2, Note.DefaultVelocity) { Channel = (FourBitNumber)channel1 }, 1000), + new TimedEvent(new NoteOffEvent(noteNumber2, SevenBitNumber.MinValue) { Channel = (FourBitNumber)channel1 }, 1500), + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 30, null, noteNumber2), + new Note(noteNumber1, 100, 0) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber2, 100, 30) { Channel = (FourBitNumber)channel1 }, + new Rest(100, 200, null, noteNumber1), + new Rest(130, 870, null, noteNumber2), + new Note(noteNumber1, 50, 300) { Channel = (FourBitNumber)channel2 }, + new Note(noteNumber2, 500, 1000) { Channel = (FourBitNumber)channel1 }, + }); + } + + [Test] + public void EnumerateObjects_NotesAndRests_SeparateByChannelAndNoteNumber_1() + { + var noteNumber1 = (SevenBitNumber)10; + var noteNumber2 = (SevenBitNumber)100; + var channel1 = (FourBitNumber)10; + var channel2 = (FourBitNumber)2; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByChannelAndNoteNumber, + inputEvents: new ITimedObject[] + { + new Note(noteNumber1, 100, 10) { Channel = channel1 }, + new Note(noteNumber2, 100, 30) { Channel = channel1 }, + new Note(noteNumber1, 50, 300) { Channel = channel2 }, + new Note(noteNumber2, 500, 1000) { Channel = channel2 }, + new Note(noteNumber1, 150, 1200) { Channel = channel1 }, + new Note(noteNumber2, 1000, 1300) { Channel = channel1 }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, channel1, noteNumber1), + new Rest(0, 30, channel1, noteNumber2), + new Rest(0, 300, channel2, noteNumber1), + new Rest(0, 1000, channel2, noteNumber2), + new Note(noteNumber1, 100, 10) { Channel = channel1 }, + new Note(noteNumber2, 100, 30) { Channel = channel1 }, + new Rest(110, 1090, channel1, noteNumber1), + new Rest(130, 1170, channel1, noteNumber2), + new Note(noteNumber1, 50, 300) { Channel = channel2 }, + new Note(noteNumber2, 500, 1000) { Channel = channel2 }, + new Note(noteNumber1, 150, 1200) { Channel = channel1 }, + new Note(noteNumber2, 1000, 1300) { Channel = channel1 }, + }); + } + + [Test] + public void EnumerateObjects_NotesAndRests_SeparateByChannelAndNoteNumber_2() + { + var noteNumber1 = (SevenBitNumber)10; + var noteNumber2 = (SevenBitNumber)100; + var channel1 = (FourBitNumber)10; + var channel2 = (FourBitNumber)2; + + EnumerateObjects_NotesAndRests( + restSeparationPolicy: RestSeparationPolicy.SeparateByChannelAndNoteNumber, + inputEvents: new ITimedObject[] + { + new TimedEvent(new NoteOnEvent(noteNumber1, Note.DefaultVelocity) { Channel = channel1 }, 10), + new TimedEvent(new NoteOffEvent(noteNumber1, SevenBitNumber.MinValue) { Channel = channel1 }, 110), + new Note(noteNumber2, 100, 30) { Channel = channel1 }, + new Note(noteNumber1, 50, 300) { Channel = channel2 }, + new Note(noteNumber2, 500, 1000) { Channel = channel2 }, + new Note(noteNumber1, 150, 1200) { Channel = channel1 }, + new Note(noteNumber2, 1000, 1300) { Channel = channel1 }, + }.ToTrackChunk().Events, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, channel1, noteNumber1), + new Rest(0, 30, channel1, noteNumber2), + new Rest(0, 300, channel2, noteNumber1), + new Rest(0, 1000, channel2, noteNumber2), + new Note(noteNumber1, 100, 10) { Channel = channel1 }, + new Note(noteNumber2, 100, 30) { Channel = channel1 }, + new Rest(110, 1090, channel1, noteNumber1), + new Rest(130, 1170, channel1, noteNumber2), + new Note(noteNumber1, 50, 300) { Channel = channel2 }, + new Note(noteNumber2, 500, 1000) { Channel = channel2 }, + new Note(noteNumber1, 150, 1200) { Channel = channel1 }, + new Note(noteNumber2, 1000, 1300) { Channel = channel1 }, + }); + } + #endregion #region Private methods @@ -481,6 +866,24 @@ private void GetObjects_NotesAndRests( }); } + private void EnumerateObjects_NotesAndRests( + RestSeparationPolicy restSeparationPolicy, + IEnumerable inputEvents, + IEnumerable outputObjects, + NoteDetectionSettings noteDetectionSettings = null) => + EnumerateObjects( + inputEvents, + outputObjects, + ObjectType.Note | ObjectType.Rest, + new ObjectDetectionSettings + { + RestDetectionSettings = new RestDetectionSettings + { + RestSeparationPolicy = restSeparationPolicy + }, + NoteDetectionSettings = noteDetectionSettings ?? new NoteDetectionSettings() + }); + #endregion } } diff --git a/DryWetMidi.Tests/Interaction/GetObjects/GetObjectsUtilitiesTests.Rests.cs b/DryWetMidi.Tests/Interaction/GetObjects/GetObjectsUtilitiesTests.Rests.cs index 91f58ff30..f3df87636 100644 --- a/DryWetMidi.Tests/Interaction/GetObjects/GetObjectsUtilitiesTests.Rests.cs +++ b/DryWetMidi.Tests/Interaction/GetObjects/GetObjectsUtilitiesTests.Rests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Melanchall.DryWetMidi.Common; using Melanchall.DryWetMidi.Core; using Melanchall.DryWetMidi.Interaction; @@ -406,6 +407,70 @@ public void GetObjects_Rests_SeparateByChannelAndNoteNumber_FromTimedEvents() }); } + [Test] + public void EnumerateObjects_Rests_NoSeparation_1() => EnumerateObjects_Rests( + restSeparationPolicy: RestSeparationPolicy.NoSeparation, + inputEvents: new MidiEvent[] + { + new NoteOnEvent(), + new NoteOffEvent { DeltaTime = 20 }, + }, + outputObjects: Array.Empty()); + + [Test] + public void EnumerateObjects_Rests_NoSeparation_2([Values(10, 100)] long restLength) => EnumerateObjects_Rests( + restSeparationPolicy: RestSeparationPolicy.NoSeparation, + inputEvents: new MidiEvent[] + { + new NoteOnEvent { DeltaTime = restLength }, + new NoteOffEvent { DeltaTime = 20 }, + }, + outputObjects: new ITimedObject[] + { + new Rest(0, restLength, null, null), + }); + + [Test] + public void EnumerateObjects_Rests_NoSeparation_3([Values(10, 100)] long restLength) => EnumerateObjects_Rests( + restSeparationPolicy: RestSeparationPolicy.NoSeparation, + inputEvents: new MidiEvent[] + { + new NoteOnEvent(), + new NoteOnEvent { DeltaTime = 10, Channel = (FourBitNumber)4 }, + new NoteOffEvent { DeltaTime = 10 }, + new NoteOffEvent { DeltaTime = 10, Channel = (FourBitNumber)4 }, + new NoteOnEvent { DeltaTime = restLength }, + new NoteOffEvent { DeltaTime = 10 }, + new NoteOnEvent { DeltaTime = 20, Channel = (FourBitNumber)4 }, + new NoteOffEvent { DeltaTime = 30, Channel = (FourBitNumber)4 }, + }, + outputObjects: new ITimedObject[] + { + new Rest(30, restLength, null, null), + new Rest(40 + restLength, 20, null, null), + }); + + [Test] + public void EnumerateObjects_Rests_SeparateByChannel([Values(10, 100)] long restLength) => EnumerateObjects_Rests( + restSeparationPolicy: RestSeparationPolicy.SeparateByChannel, + inputEvents: new MidiEvent[] + { + new NoteOnEvent(), + new NoteOnEvent { DeltaTime = 10, Channel = (FourBitNumber)4 }, + new NoteOffEvent { DeltaTime = 10 }, + new NoteOffEvent { DeltaTime = 10, Channel = (FourBitNumber)4 }, + new NoteOnEvent { DeltaTime = restLength }, + new NoteOffEvent { DeltaTime = 10 }, + new NoteOnEvent { DeltaTime = 20, Channel = (FourBitNumber)4 }, + new NoteOffEvent { DeltaTime = 30, Channel = (FourBitNumber)4 }, + }, + outputObjects: new ITimedObject[] + { + new Rest(0, 10, (FourBitNumber)4, null), + new Rest(20, 10 + restLength, (FourBitNumber)0, null), + new Rest(30, restLength + 30, (FourBitNumber)4, null), + }); + #endregion #region Private methods @@ -428,6 +493,24 @@ private void GetObjects_Rests( }); } + private void EnumerateObjects_Rests( + RestSeparationPolicy restSeparationPolicy, + IEnumerable inputEvents, + IEnumerable outputObjects) + { + EnumerateObjects( + inputEvents, + outputObjects, + ObjectType.Rest, + new ObjectDetectionSettings + { + RestDetectionSettings = new RestDetectionSettings + { + RestSeparationPolicy = restSeparationPolicy + } + }); + } + #endregion } } diff --git a/DryWetMidi/Core/MidiFile.cs b/DryWetMidi/Core/MidiFile.cs index 112a58082..fe506c3d2 100644 --- a/DryWetMidi/Core/MidiFile.cs +++ b/DryWetMidi/Core/MidiFile.cs @@ -129,12 +129,12 @@ public MidiFile(params MidiChunk[] chunks) public ChunksCollection Chunks { get; } = new ChunksCollection(); /// - /// Gets original format of the file was read or null if the current + /// Gets original format of the file that was read or null if the current /// was created by constructor. /// /// File format is unknown. /// Unable to get original format of the file. It means - /// the current was created via constructor rather than via Read method. + /// the current was created not as a result of reading the file. public MidiFileFormat OriginalFormat { get diff --git a/DryWetMidi/Interaction/GetObjects/GetObjectsUtilities.cs b/DryWetMidi/Interaction/GetObjects/GetObjectsUtilities.cs index 5a52c7bff..53f24150a 100644 --- a/DryWetMidi/Interaction/GetObjects/GetObjectsUtilities.cs +++ b/DryWetMidi/Interaction/GetObjects/GetObjectsUtilities.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime; +using System.Xml.Linq; using Melanchall.DryWetMidi.Common; using Melanchall.DryWetMidi.Core; @@ -85,15 +87,22 @@ public static IEnumerable EnumerateObjects( ObjectDetectionSettings settings = null) { ThrowIfArgument.IsNull(nameof(midiEvents), midiEvents); - ThrowIfArgument.DoesntSatisfyCondition( - nameof(objectType), - objectType, - t => !t.HasFlag(ObjectType.Rest), - "Rest object type specified."); - return midiEvents - .GetTimedEventsLazy(GetTimedEventDetectionSettings(objectType, settings)) - .EnumerateObjectsFromSortedTimedObjects(objectType, settings); + var effectiveObjectType = objectType.HasFlag(ObjectType.Rest) + ? objectType | ObjectType.Note + : objectType; + var objects = midiEvents + .GetTimedEventsLazy(GetTimedEventDetectionSettings(effectiveObjectType, settings)) + .EnumerateObjectsFromSortedTimedObjects(effectiveObjectType, settings); + + if (!objectType.HasFlag(ObjectType.Rest)) + return objects; + + var rests = GetSortedRestsFromObjects(objects, settings?.RestDetectionSettings); + if (objectType == ObjectType.Rest) + return rests; + + return EnumerateObjectsAndRests(objects, rests); } /// @@ -231,6 +240,89 @@ public static ICollection GetObjects( settings); } + private static IEnumerable EnumerateObjectsAndRests( + IEnumerable objects, + IEnumerable rests) + { + var objectsEnumerator = objects.GetEnumerator(); + var objectCanBeTaken = objectsEnumerator.MoveNext(); + + var restsEnumerator = rests.GetEnumerator(); + var restCanBeTaken = restsEnumerator.MoveNext(); + + while (objectCanBeTaken && restCanBeTaken) + { + var rest = restsEnumerator.Current; + var obj = objectsEnumerator.Current; + + if (obj.Time < rest.Time) + { + yield return obj; + objectCanBeTaken = objectsEnumerator.MoveNext(); + } + else + { + yield return rest; + restCanBeTaken = restsEnumerator.MoveNext(); + } + } + + while (objectCanBeTaken) + { + yield return objectsEnumerator.Current; + objectCanBeTaken = objectsEnumerator.MoveNext(); + } + + while (restCanBeTaken) + { + yield return restsEnumerator.Current; + restCanBeTaken = restsEnumerator.MoveNext(); + } + } + + private static ICollection GetSortedRestsFromObjects( + IEnumerable objects, + RestDetectionSettings restDetectionSettings) + { + restDetectionSettings = restDetectionSettings ?? new RestDetectionSettings(); + + var notesLastEndTimes = new Dictionary(); + var noteDescriptorProvider = NoteDescriptorProviders[restDetectionSettings.RestSeparationPolicy]; + var setRestChannel = SetRestChannel[restDetectionSettings.RestSeparationPolicy]; + var setRestNoteNumber = SetRestNoteNumber[restDetectionSettings.RestSeparationPolicy]; + + var rests = new List(); + + foreach (var obj in objects) + { + var note = obj as Note; + if (note == null) + continue; + + var noteDescriptor = noteDescriptorProvider(note); + + long lastEndTime; + notesLastEndTimes.TryGetValue(noteDescriptor, out lastEndTime); + + if (note.Time > lastEndTime) + { + var rest = new Rest( + lastEndTime, + note.Time - lastEndTime, + setRestChannel ? (FourBitNumber?)note.Channel : null, + setRestNoteNumber ? (SevenBitNumber?)note.NoteNumber : null); + + rests.Add(rest); + } + + notesLastEndTimes[noteDescriptor] = Math.Max(lastEndTime, note.EndTime); + } + + rests.Sort((r1, r2) => Math.Sign(r1.Time - r2.Time)); + + return rests; + } + private static bool TryProcessTimedEvent(TimedEvent timedEvent, List processedTimedObjects) { if (timedEvent == null) @@ -406,7 +498,6 @@ private static IEnumerable EnumerateObjectsFromSortedTimedObjects( { var getChords = objectType.HasFlag(ObjectType.Chord); var getNotes = objectType.HasFlag(ObjectType.Note); - var getRests = objectType.HasFlag(ObjectType.Rest); var getTimedEvents = objectType.HasFlag(ObjectType.TimedEvent); settings = settings ?? new ObjectDetectionSettings(); @@ -417,7 +508,7 @@ private static IEnumerable EnumerateObjectsFromSortedTimedObjects( var timedObjects = processedTimedObjects; - if (createNotes && (getChords || getNotes || getRests)) + if (createNotes && (getChords || getNotes)) { var notesAndTimedEvents = processedTimedObjects.GetNotesAndTimedEventsLazy(noteDetectionSettings, true);