diff --git a/DryWetMidi.Tests/Common/RedBlackTree/RedBlackTreeTests.cs b/DryWetMidi.Tests/Common/RedBlackTree/RedBlackTreeTests.cs new file mode 100644 index 000000000..139bd5c13 --- /dev/null +++ b/DryWetMidi.Tests/Common/RedBlackTree/RedBlackTreeTests.cs @@ -0,0 +1,285 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using System; +using Melanchall.DryWetMidi.Common; +using System.Drawing; +using Melanchall.DryWetMidi.Tests.Multimedia; + +namespace Melanchall.DryWetMidi.Tests.Common +{ + [TestFixture] + public sealed class RedBlackTreeTests + { + private sealed class KeyValue : IEquatable + { + public KeyValue(int key, string value) + { + Key = key; + Value = value; + } + + public int Key { get; set; } + + public string Value { get; set; } + + public bool Equals(KeyValue other) => + ReferenceEquals(this, other); + } + + [Test] + public void Enumerate_Empty() + { + var tree = new RedBlackTree(); + var enumerated = tree.ToArray(); + + CollectionAssert.IsEmpty(enumerated, "Enumerated collection is not empty."); + } + + [Test] + public void Enumerate([Values(0, 1, 2, 3, 4, 5, 10, 100, 1000, 10000)] int count) + { + var random = DryWetMidi.Common.Random.Instance; + var data = Enumerable.Range(0, count).Select(_ => random.Next(1000)).ToArray(); + + var tree = new RedBlackTree(data, d => d); + var enumerated = tree.ToArray(); + + CollectionAssert.AreEqual( + data.OrderBy(d => d).ToArray(), + enumerated, + "Enumerated collection is invalid."); + } + + [Test] + public void InsertOne([Values(0, 10, 40, -2, 1000, 12, 2, 9, 11)] int value) + { + var data = new[] { 2, 3, 1, 1, 10, 100, 50, 45, 0 }; + var tree = new RedBlackTree(data, d => d); + + CheckAscendingOrder(tree.ToArray()); + + tree.Add(value, value); + CheckAscendingOrder(tree.ToArray()); + } + + [Test] + public void InsertMultiple() + { + var data = new[] { 2, 3, 1, 1, 10, 100, 50, 45, 0 }.ToList(); + var tree = new RedBlackTree(data, d => d); + + CheckAscendingOrder(tree.ToArray()); + + var dataToAdd = new[] { 0, 10, 40, -2, 1000, 12, 2, 9, 11 }; + + foreach (var d in dataToAdd) + { + tree.Add(d, d); + CheckAscendingOrder(tree.ToArray()); + } + } + + [Test] + public void DeleteOne([Values(500, 100, 2, 4, 6, 9, 10, 700, 701, 702, 45, 44, 43)] int value) + { + var data = Enumerable.Range(0, 1000).ToList(); + var tree = new RedBlackTree(data, d => d); + + CheckAscendingOrder(tree.ToArray()); + + tree.Delete(tree.GetFirstNode(value)); + CheckAscendingOrder(tree.ToArray()); + } + + [Test] + public void DeleteMultiple() + { + var data = Enumerable.Range(0, 1000).ToList(); + var tree = new RedBlackTree(data, d => d); + + CheckAscendingOrder(tree.ToArray()); + + var dataToDelete = new[] { 500, 100, 2, 4, 6, 9, 10, 700, 701, 702, 45, 44, 43 }; + + foreach (var d in dataToDelete) + { + tree.Delete(tree.GetFirstNode(d)); + CheckAscendingOrder(tree.ToArray()); + } + } + + [Test] + public void GetNextNode([Values(1, 2, 4, 8, 16, 32, 64, 128, 10, 100, 1000)] int count) + { + var data = Enumerable.Range(0, count).ToArray(); + var tree = new RedBlackTree(data, d => d); + + for (var i = 0; i < data.Length; i++) + { + var node = tree.GetFirstNode(i); + var values = EnumerateViaGetNextNode(tree, node).ToArray(); + CheckAscendingOrder(values); + } + } + + [Test] + public void GetPreviousNode([Values(1, 2, 4, 8, 16, 32, 64, 128, 10, 100, 1000)] int count) + { + var data = Enumerable.Range(0, count).ToArray(); + var tree = new RedBlackTree(data, d => d); + + for (var i = 0; i < data.Length; i++) + { + var node = tree.GetFirstNode(i); + var values = EnumerateViaGetPreviousNode(tree, node).ToArray(); + CheckDescendingOrder(values); + } + } + + [Test] + public void GetValues() + { + var aValue = new KeyValue(1, "A"); + var bValue = new KeyValue(2, "B"); + var cValue = new KeyValue(3, "C"); + var dValue = new KeyValue(-3, "D"); + var eValue = new KeyValue(1, "E"); + var fValue = new KeyValue(0, "F"); + var gValue = new KeyValue(10, "G"); + var hValue = new KeyValue(1, "H"); + var iValue = new KeyValue(2, "I"); + + var data = new[] + { + aValue, + bValue, + cValue, + dValue, + eValue, + fValue, + gValue, + hValue, + iValue, + }; + var tree = new RedBlackTree(data, d => d.Key); + + var values1 = tree.GetValues(1); + CollectionAssert.AreEqual(new[] { aValue, eValue, hValue }, values1, "Invalid values by key 1."); + + var values2 = tree.GetValues(2); + CollectionAssert.AreEqual(new[] { bValue, iValue }, values2, "Invalid values by key 2."); + + var values3 = tree.GetValues(3); + CollectionAssert.AreEqual(new[] { cValue }, values3, "Invalid values by key 3."); + } + + [Test] + public void Clone([Values(1, 2, 4, 8, 16, 32, 64, 128, 10, 100, 1000, 10000)] int count) + { + var data = Enumerable.Range(0, count).ToArray(); + + var tree = new RedBlackTree(data, d => d); + var treeElements = tree.ToArray(); + + var treeClone = tree.Clone(); + var treeCloneElements = treeClone.ToArray(); + + CollectionAssert.AreEqual(data, treeElements, "Original tree elements are invalid."); + CollectionAssert.AreEqual(data, treeCloneElements, "Tree clone elements are invalid."); + CollectionAssert.AreEqual(treeElements, treeCloneElements, "Tree clone elements aren't equal to original ones."); + + var treeNodes = tree.EnumerateNodes().ToArray(); + var treeCloneNodes = treeClone.EnumerateNodes().ToArray(); + var nodesIntersection = treeNodes.Intersect(treeCloneNodes).ToArray(); + CollectionAssert.IsEmpty(nodesIntersection, "There are the same nodes."); + } + + [Test] + public void GetLastNodeBelowThreshold_NoRepeats([Values(1, 2, 4, 8, 16, 32, 64, 128)] int count) + { + var data = Enumerable.Range(0, count).ToArray(); + var tree = new RedBlackTree(data, d => d); + + for (var i = 0; i < count; i++) + { + var result = tree.GetLastNodeBelowThreshold(i); + if (i == 0) + Assert.IsNull(result, $"Invalid result for {i}."); + else + Assert.AreEqual(i - 1, result.Key, $"Invalid result for {i}."); + } + } + + [Test] + public void GetLastNodeBelowThreshold_Repeats() + { + var tree = new RedBlackTree(new[] { 1, 1, 2, 2, 2, 3, 4, 4, 4, 5 }, d => d); + + var result = tree.GetLastNodeBelowThreshold(1); + Assert.IsNull(result, "Invalid result for 1."); + + void Check(int threshold, int expectedResult, int[] expectedPreviousValues) + { + var node = tree.GetLastNodeBelowThreshold(threshold); + var previousValues = EnumerateViaGetPreviousNode(tree, node).ToArray(); + Assert.AreEqual(expectedResult, node.Value, $"Invalid result for {threshold}."); + CollectionAssert.AreEqual( + expectedPreviousValues, + previousValues, + $"Invalid previous values list for {threshold}."); + } + + Check(2, 1, new[] { 1, 1 }); + Check(3, 2, new[] { 2, 2, 2, 1, 1 }); + Check(4, 3, new[] { 3, 2, 2, 2, 1, 1 }); + Check(5, 4, new[] { 4, 4, 4, 3, 2, 2, 2, 1, 1 }); + Check(6, 5, new[] { 5, 4, 4, 4, 3, 2, 2, 2, 1, 1 }); + Check(10, 5, new[] { 5, 4, 4, 4, 3, 2, 2, 2, 1, 1 }); + } + + [Test] + public void GetLastNodeBelowThreshold_InMiddle() + { + var tree = new RedBlackTree(new[] { 0, 5500 }, d => d); + + var result = tree.GetLastNodeBelowThreshold(700); + } + + private static IEnumerable EnumerateViaGetNextNode(RedBlackTree tree, RedBlackTreeNode node) + where TKey : IComparable + where TValue : IEquatable + { + do + { + yield return node.Value; + } + while ((node = tree.GetNextNode(node)) != null); + } + + private static IEnumerable EnumerateViaGetPreviousNode(RedBlackTree tree, RedBlackTreeNode node) + { + do + { + yield return node.Value; + } + while ((node = tree.GetPreviousNode(node)) != null); + } + + private static void CheckAscendingOrder(int[] values) + { + for (var i = 0; i < values.Length - 1; i++) + { + Assert.GreaterOrEqual(values[i + 1], values[i], $"Ascending order is broken on index {i}."); + } + } + + private static void CheckDescendingOrder(int[] values) + { + for (var i = 0; i < values.Length - 1; i++) + { + Assert.LessOrEqual(values[i + 1], values[i], $"Descending order is broken on index {i}."); + } + } + } +} diff --git a/DryWetMidi.Tests/Interaction/TimedObject/ObservableTimedObjectsCollectionTests.cs b/DryWetMidi.Tests/Interaction/TimedObject/ObservableTimedObjectsCollectionTests.cs new file mode 100644 index 000000000..a4dc165f9 --- /dev/null +++ b/DryWetMidi.Tests/Interaction/TimedObject/ObservableTimedObjectsCollectionTests.cs @@ -0,0 +1,735 @@ +using Melanchall.DryWetMidi.Common; +using Melanchall.DryWetMidi.Core; +using Melanchall.DryWetMidi.Interaction; +using Melanchall.DryWetMidi.Tests.Utilities; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Melanchall.DryWetMidi.Tests.Interaction +{ + [TestFixture] + public sealed class ObservableTimedObjectsCollectionTests + { + #region Test methods + + [Test] + public void Add_FromEmptyCollection_Single() + { + var newObject = new Note((SevenBitNumber)90, 100, 10); + + Add_FromEmptyCollection( + new[] { newObject }, + collection => collection.Add(newObject)); + } + + [Test] + public void Add_FromEmptyCollection_Multiple() + { + var newObject = new ITimedObject[] + { + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + + Add_FromEmptyCollection( + newObject, + collection => collection.Add(newObject)); + } + + [Test] + public void Add_FromEmptyCollection_Batch() + { + var newObjects = new ITimedObject[] + { + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + + Add_FromEmptyCollection( + newObjects, + collection => collection.ChangeCollection(() => collection.Add(newObjects))); + } + + [Test] + public void Add_FromPreFilledCollection_Single_1() + { + var initialObjects = new[] { new TimedEvent(new TextEvent("B"), 20) }; + var newObjects = new Note((SevenBitNumber)90, 100, 10); + + Add_FromPreFilledCollection( + initialObjects, + new[] { newObjects }, + collection => collection.Add(newObjects)); + } + + [Test] + public void Add_FromPreFilledCollection_Single_2() + { + var initialObjects = new[] + { + new TimedEvent(new TextEvent("B"), 20), + new TimedEvent(new NoteOnEvent()), + new TimedEvent(new NoteOffEvent(), 400), + }; + var newObjects = new Note((SevenBitNumber)90, 100, 10); + + Add_FromPreFilledCollection( + initialObjects, + new[] { newObjects }, + collection => collection.Add(newObjects)); + } + + [Test] + public void Add_FromPreFilledCollection_Multiple_1() + { + var initialObjects = new[] { new TimedEvent(new TextEvent("B"), 20) }; + var newObjects = new ITimedObject[] + { + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + + Add_FromPreFilledCollection( + initialObjects, + newObjects, + collection => collection.Add(newObjects)); + } + + [Test] + public void Add_FromPreFilledCollection_Multiple_2() + { + var initialObjects = new[] + { + new TimedEvent(new TextEvent("B"), 20), + new TimedEvent(new NoteOnEvent()), + new TimedEvent(new NoteOffEvent(), 400), + }; + var newObjects = new ITimedObject[] + { + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + + Add_FromPreFilledCollection( + initialObjects, + newObjects, + collection => collection.Add(newObjects)); + } + + [Test] + public void Add_FromPreFilledCollection_Batch_1() + { + var initialObjects = new[] { new TimedEvent(new TextEvent("B"), 20) }; + var newObjects = new ITimedObject[] + { + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + + Add_FromPreFilledCollection( + initialObjects, + newObjects, + collection => collection.ChangeCollection(() => collection.Add(newObjects))); + } + + [Test] + public void Add_FromPreFilledCollection_Batch_2() + { + var initialObjects = new[] + { + new TimedEvent(new TextEvent("B"), 20), + new TimedEvent(new NoteOnEvent()), + new TimedEvent(new NoteOffEvent(), 400), + }; + var newObjects = new ITimedObject[] + { + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + + Add_FromPreFilledCollection( + initialObjects, + newObjects, + collection => collection.ChangeCollection(() => collection.Add(newObjects))); + } + + [Test] + public void Remove_FromEmptyCollection_Single() + { + var oldObject = new TimedEvent(new TextEvent("B"), 20); + + Remove( + Array.Empty(), + new[] { oldObject }, + collection => collection.Remove(oldObject)); + } + + [Test] + public void Remove_FromPreFilledCollection_Single_1() + { + var initialObjects = new[] + { + new TimedEvent(new TextEvent("B"), 20), + }; + + Remove( + initialObjects, + initialObjects, + collection => collection.Remove(initialObjects.First())); + } + + [Test] + public void Remove_FromPreFilledCollection_Single_2() + { + var initialObjects = new[] + { + new TimedEvent(new TextEvent("B"), 20), + new TimedEvent(new NoteOnEvent()), + new TimedEvent(new NoteOffEvent(), 400), + }; + var oldObject = initialObjects[DryWetMidi.Common.Random.Instance.Next(initialObjects.Length)]; + + Remove( + initialObjects, + new[] { oldObject }, + collection => collection.Remove(oldObject)); + } + + [Test] + public void Remove_FromPreFilledCollection_Multiple([Values(0, 1, 2, 3)] int count) + { + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + var oldObjects = initialObjects.Take(count).ToArray(); + + Remove( + initialObjects, + oldObjects, + collection => collection.Remove(oldObjects)); + } + + [Test] + public void Remove_FromPreFilledCollection_Batch([Values(0, 1, 2, 3)] int count) + { + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + var oldObjects = initialObjects.Take(count).ToArray(); + + Remove( + initialObjects, + oldObjects, + collection => collection.ChangeCollection(() => collection.Remove(oldObjects))); + } + + [Test] + public void ChangeObject_Single() + { + var objectToChange = new TimedEvent(new TextEvent("A"), 5); + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + objectToChange, + }; + + ChangeObject( + initialObjects, + collection => collection.ChangeObject(objectToChange, obj => ((TextEvent)((TimedEvent)obj).Event).Text = "C"), + new[] { objectToChange }, + new[] { new TimedEvent(new TextEvent("C"), 5) }); + } + + [Test] + public void ChangeObject_Single_Time() + { + var objectToChange = new TimedEvent(new TextEvent("A"), 5); + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + objectToChange, + }; + + ChangeObject( + initialObjects, + collection => collection.ChangeObject(objectToChange, obj => obj.Time = 100), + new[] { objectToChange }, + new[] { new TimedEvent(new TextEvent("A"), 100) }); + } + + [Test] + public void ChangeObject_Single_Batch() + { + var objectToChange = new TimedEvent(new TextEvent("A"), 5); + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + objectToChange, + }; + + ChangeObject( + initialObjects, + collection => collection.ChangeCollection(() => collection.ChangeObject(objectToChange, obj => ((TextEvent)((TimedEvent)obj).Event).Text = "C")), + new[] { objectToChange }, + new[] { new TimedEvent(new TextEvent("C"), 5) }); + } + + [Test] + public void ChangeObject_Single_Time_Batch() + { + var objectToChange = new TimedEvent(new TextEvent("A"), 5); + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + objectToChange, + }; + + ChangeObject( + initialObjects, + collection => collection.ChangeCollection(() => collection.ChangeObject(objectToChange, obj => obj.Time = 100)), + new[] { objectToChange }, + new[] { new TimedEvent(new TextEvent("A"), 100) }); + } + + [Test] + public void ChangeCollection_General([Values(0, 1, 2, 3)] int deepLevel) + { + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }; + + var collection = new ObservableTimedObjectsCollection(initialObjects); + + var eventsArgs = new List(); + collection.CollectionChanged += (_, e) => eventsArgs.Add(e); + + var objectToAdd1 = new TimedEvent(new TextEvent("C"), 15); + var objectToAdd2 = new Note((SevenBitNumber)80, 200, 20); + var objectsToAdd = new ITimedObject[] + { + objectToAdd1, + objectToAdd2, + }; + var objectToAdd3 = new Chord( + new Note((SevenBitNumber)70, 20, 23), + new Note((SevenBitNumber)60, 20, 24)); + + Action action = () => collection.ChangeCollection(() => + { + collection.Add(objectsToAdd); + collection.Remove(new TimedEvent(new NoteOnEvent())); + collection.Add(objectToAdd3); + collection.Remove(objectToAdd2); + collection.ChangeObject( + objectToAdd3, + obj => + { + obj.Time = 100; + ((Chord)obj).Notes.First().Velocity = (SevenBitNumber)50; + }); + }); + + for (var i = 0; i < deepLevel; i++) + { + var a = action; + action = () => collection.ChangeCollection(a); + } + + action(); + + Assert.AreEqual(1, eventsArgs.Count, "Invalid events args count."); + + var eventArgs = eventsArgs.Single(); + + CheckCollectionIsNullOrEmpty(eventArgs.RemovedObjects, "There are removed objects."); + + var addedObjects = eventArgs.AddedObjects; + CollectionAssert.AreEquivalent( + new ITimedObject[] + { + objectToAdd1, + objectToAdd3, + }, + addedObjects, + "Invalid added objects."); + + var changedObjects = eventArgs.ChangedObjects; + CollectionAssert.AreEqual( + new ITimedObject[] + { + objectToAdd3, + }, + changedObjects.Select(o => o.TimedObject), + "Invalid changed objects."); + + var changedObject = changedObjects.Single(); + MidiAsserts.AreEqual( + new Chord( + new Note((SevenBitNumber)70, 20, 100) { Velocity = (SevenBitNumber)50 }, + new Note((SevenBitNumber)60, 20, 101)), + changedObject.TimedObject, + "Invalid changed object."); + Assert.AreEqual(23, changedObject.OldTime, "Invalid old time of changed object."); + } + + [Test] + public void ChangeCollection_AddAfterAdd() + { + var collection = new ObservableTimedObjectsCollection(); + + var eventsArgs = new List(); + collection.CollectionChanged += (_, e) => eventsArgs.Add(e); + + var objectToAdd1 = new TimedEvent(new TextEvent("C"), 15); + var objectToAdd2 = new Note((SevenBitNumber)80, 200, 20); + + collection.ChangeCollection(() => + { + collection.Add(objectToAdd1); + collection.Add(objectToAdd2); + }); + + Assert.AreEqual(1, eventsArgs.Count, "Invalid events args count."); + + var eventArgs = eventsArgs.Single(); + CheckCollectionIsNullOrEmpty(eventArgs.RemovedObjects, "There are removed objects."); + CheckCollectionIsNullOrEmpty(eventArgs.ChangedObjects, "There are changed objects."); + + var addedObjects = eventArgs.AddedObjects; + CollectionAssert.AreEquivalent( + new ITimedObject[] + { + objectToAdd1, + objectToAdd2, + }, + addedObjects, + "Invalid added objects."); + } + + [Test] + public void ChangeCollection_AddAndRemoveSame() + { + var collection = new ObservableTimedObjectsCollection(new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }); + + var eventsArgs = new List(); + collection.CollectionChanged += (_, e) => eventsArgs.Add(e); + + var objectToAdd = new TimedEvent(new TextEvent("C"), 15); + + collection.ChangeCollection(() => + { + collection.Add(objectToAdd); + collection.Remove(objectToAdd); + }); + + Assert.AreEqual(0, eventsArgs.Count, "Invalid events args count."); + } + + [Test] + public void ChangeCollection_AddAndRemoveAndAddSame() + { + var collection = new ObservableTimedObjectsCollection(new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }); + + var eventsArgs = new List(); + collection.CollectionChanged += (_, e) => eventsArgs.Add(e); + + var objectToAdd = new TimedEvent(new TextEvent("C"), 15); + + collection.ChangeCollection(() => + { + collection.Add(objectToAdd); + collection.Remove(objectToAdd); + collection.Add(objectToAdd); + }); + + Assert.AreEqual(1, eventsArgs.Count, "Invalid events args count."); + + var eventArgs = eventsArgs.Single(); + + CheckCollectionIsNullOrEmpty(eventArgs.RemovedObjects, "There are removed objects."); + CheckCollectionIsNullOrEmpty(eventArgs.ChangedObjects, "There are changed objects."); + + var addedObjects = eventArgs.AddedObjects; + CollectionAssert.AreEquivalent( + new ITimedObject[] + { + objectToAdd, + }, + addedObjects, + "Invalid added objects."); + } + + [Test] + public void ChangeCollection_AddAndChangeAndRemoveSame() + { + var collection = new ObservableTimedObjectsCollection(new ITimedObject[] + { + new TimedEvent(new TextEvent("B"), 20), + new Note((SevenBitNumber)90, 100, 10), + new TimedEvent(new TextEvent("A"), 5), + }); + + var eventsArgs = new List(); + collection.CollectionChanged += (_, e) => eventsArgs.Add(e); + + var objectToAdd = new TimedEvent(new TextEvent("C"), 15); + + collection.ChangeCollection(() => + { + collection.Add(objectToAdd); + collection.ChangeObject(objectToAdd, obj => obj.Time = 20); + collection.Remove(objectToAdd); + }); + + Assert.AreEqual(0, eventsArgs.Count, "Invalid events args count."); + } + + [Test] + public void ChangeCollection_AddAndRemoveSome() + { + var collection = new ObservableTimedObjectsCollection(); + + var eventsArgs = new List(); + collection.CollectionChanged += (_, e) => eventsArgs.Add(e); + + var objectToAdd1 = new TimedEvent(new TextEvent("C"), 15); + var objectToAdd2 = new Note((SevenBitNumber)80, 200, 20); + + collection.ChangeCollection(() => + { + collection.Add(objectToAdd1, objectToAdd2); + collection.Remove(objectToAdd1); + }); + + Assert.AreEqual(1, eventsArgs.Count, "Invalid events args count."); + + var eventArgs = eventsArgs.Single(); + + CheckCollectionIsNullOrEmpty(eventArgs.RemovedObjects, "There are removed objects."); + CheckCollectionIsNullOrEmpty(eventArgs.ChangedObjects, "There are changed objects."); + + var addedObjects = eventArgs.AddedObjects; + CollectionAssert.AreEquivalent( + new ITimedObject[] + { + objectToAdd2, + }, + addedObjects, + "Invalid added objects."); + } + + [Test] + public void ChangeCollection_ChangeAndChangeSame() + { + var objectToChange = new TimedEvent(new TextEvent("C"), 15); + var collection = new ObservableTimedObjectsCollection(new[] { objectToChange }); + + var eventsArgs = new List(); + collection.CollectionChanged += (_, e) => eventsArgs.Add(e); + + collection.ChangeCollection(() => + { + collection.ChangeObject(objectToChange, obj => obj.Time = 20); + collection.ChangeObject(objectToChange, obj => obj.Time = 30); + collection.ChangeObject(objectToChange, obj => ((TextEvent)((TimedEvent)obj).Event).Text = "ABC"); + }); + + Assert.AreEqual(1, eventsArgs.Count, "Invalid events args count."); + + var eventArgs = eventsArgs.Single(); + + CheckCollectionIsNullOrEmpty(eventArgs.RemovedObjects, "There are removed objects."); + CheckCollectionIsNullOrEmpty(eventArgs.AddedObjects, "There are added objects."); + + var changedObjects = eventArgs.ChangedObjects; + CollectionAssert.AreEqual(new[] { objectToChange }, changedObjects.Select(o => o.TimedObject), "Invalid changed objects."); + + var changedObject = eventArgs.ChangedObjects.Single(); + MidiAsserts.AreEqual( + new TimedEvent(new TextEvent("ABC"), 30), + changedObject.TimedObject, + "Invalid changed object"); + Assert.AreEqual(15, changedObject.OldTime, "Invalid old time of changed object."); + } + + #endregion + + #region Private methods + + private void ChangeObject( + IEnumerable initialObjects, + Action change, + IEnumerable updatedObjects, + IEnumerable updatedObjectsWithNewValues) + { + Dictionary oldTimes = default; + ICollection oldObjects = default; + + CheckObservableTimedObjectsCollection_FromPreFilled( + initialObjects, + change, + args => + { + Assert.AreEqual(1, args.Length, "Invalid events args count."); + + var eventArgs = args.FirstOrDefault(); + CheckCollectionIsNullOrEmpty(eventArgs?.AddedObjects, "There are added objects."); + CheckCollectionIsNullOrEmpty(eventArgs?.RemovedObjects, "There are removed objects."); + + var changedObjects = eventArgs.ChangedObjects; + CollectionAssert.AreEqual(updatedObjects, changedObjects.Select(o => o.TimedObject), "Invalid changed objects."); + CollectionAssert.AreEqual(updatedObjects, oldTimes.Keys, "Invalid changed objects via old timed map."); + MidiAsserts.AreEqual(updatedObjectsWithNewValues, changedObjects.Select(obj => obj.TimedObject).ToArray(), "Invalid changed objects via new-values objects."); + + var changedObjectsOldTimes = changedObjects.Select(obj => obj.OldTime).ToArray(); + Assert.AreEqual(changedObjects.Count, changedObjectsOldTimes.Length, "Invalid changed objects old times count."); + Assert.AreEqual(updatedObjects.Count(), changedObjectsOldTimes.Length, "Invalid changed objects old times count."); + + CollectionAssert.AreEquivalent(oldTimes.Values, changedObjectsOldTimes, "Invalid changed objects old times."); + }, + collection => CollectionAssert.AreEquivalent(oldObjects, collection, "Invalid collection after change."), + collection => + { + oldObjects = collection.ToArray(); + oldTimes = collection + .Where(updatedObjects.Contains) + .ToDictionary( + obj => obj, + obj => obj.Time); + }); + } + + private void Remove( + IEnumerable initialObjects, + IEnumerable oldObjects, + Action remove) => CheckObservableTimedObjectsCollection_FromPreFilled( + initialObjects, + remove, + args => + { + if (initialObjects.Any() && oldObjects.Any()) + Assert.AreEqual(1, args.Length, "Invalid events args count."); + + var eventArgs = args.FirstOrDefault(); + CheckCollectionIsNullOrEmpty(eventArgs?.AddedObjects, "There are added objects."); + CheckCollectionIsNullOrEmpty(eventArgs?.ChangedObjects, "There are changed objects."); + + if (initialObjects.Any() && oldObjects.Any()) + { + var removedObjects = eventArgs.RemovedObjects; + CollectionAssert.AreEqual(oldObjects, removedObjects, "Invalid removed objects."); + } + else + CheckCollectionIsNullOrEmpty(eventArgs?.RemovedObjects, "There are removed objects."); + }, + collection => CollectionAssert.AreEqual(initialObjects.Except(oldObjects).OrderBy(o => o.Time), collection, "Invalid collection after change.")); + + private void Add_FromEmptyCollection( + IEnumerable timedObjects, + Action add) => CheckObservableTimedObjectsCollection_FromEmpty( + add, + args => + { + Assert.AreEqual(1, args.Length, "Invalid events args count."); + + var eventArgs = args.First(); + CheckCollectionIsNullOrEmpty(eventArgs.RemovedObjects, "There are removed objects."); + CheckCollectionIsNullOrEmpty(eventArgs.ChangedObjects, "There are changed objects."); + + var addedObjects = eventArgs.AddedObjects; + CollectionAssert.AreEqual(timedObjects, addedObjects, "Invalid added objects."); + }, + collection => CollectionAssert.AreEqual(timedObjects.OrderBy(o => o.Time), collection, "Invalid collection after change.")); + + private void Add_FromPreFilledCollection( + IEnumerable initialObjects, + IEnumerable newObjects, + Action add) => CheckObservableTimedObjectsCollection_FromPreFilled( + initialObjects, + add, + args => + { + Assert.AreEqual(1, args.Length, "Invalid events args count."); + + var eventArgs = args.First(); + CheckCollectionIsNullOrEmpty(eventArgs.RemovedObjects, "There are removed objects."); + CheckCollectionIsNullOrEmpty(eventArgs.ChangedObjects, "There are changed objects."); + + var addedObjects = eventArgs.AddedObjects; + CollectionAssert.AreEqual(newObjects, addedObjects, "Invalid added objects."); + }, + collection => CollectionAssert.AreEqual(initialObjects.Concat(newObjects).OrderBy(o => o.Time), collection, "Invalid collection after change.")); + + private void CheckObservableTimedObjectsCollection_FromEmpty( + Action changeCollection, + Action checkEventsData, + Action checkCollection) => + CheckObservableTimedObjectsCollection( + () => new ObservableTimedObjectsCollection(), + changeCollection, + checkEventsData, + checkCollection); + + private void CheckObservableTimedObjectsCollection_FromPreFilled( + IEnumerable initialObjects, + Action changeCollection, + Action checkEventsData, + Action checkCollection, + Action beforeChangeCollection = null) => + CheckObservableTimedObjectsCollection( + () => new ObservableTimedObjectsCollection(initialObjects), + changeCollection, + checkEventsData, + checkCollection, + beforeChangeCollection); + + private void CheckObservableTimedObjectsCollection( + Func createCollection, + Action changeCollection, + Action checkEventsData, + Action checkCollection, + Action beforeChangeCollection = null) + { + var eventsArgs = new List(); + + var collection = createCollection(); + collection.CollectionChanged += (_, e) => eventsArgs.Add(e); + + beforeChangeCollection?.Invoke(collection); + changeCollection(collection); + + checkEventsData(eventsArgs.ToArray()); + checkCollection(collection); + } + + private void CheckCollectionIsNullOrEmpty(ICollection collection, string message) => + Assert.IsTrue(collection == null || collection.Count == 0, message); + + #endregion + } +} diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Asserts.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Asserts.cs index c7890d834..e2ee61254 100644 --- a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Asserts.cs +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Asserts.cs @@ -704,6 +704,9 @@ private void CompareSentReceivedEvents( { var currentTime = TimeSpan.Zero; + Assert.AreEqual(expectedEvents.Count, receivedEvents.Count, "Invalid received events count."); + Assert.AreEqual(expectedEvents.Count, sentEvents.Count, "Invalid sent events count."); + for (var i = 0; i < sentEvents.Count; i++) { var sentEvent = sentEvents[i]; @@ -745,7 +748,8 @@ private void CompareSentReceivedEvents( private void CompareReceivedEvents( IReadOnlyList receivedEvents, - IReadOnlyList expectedReceivedEvents) + IReadOnlyList expectedReceivedEvents, + TimeSpan? sendReceiveTimeDelta = null) { Assert.AreEqual( expectedReceivedEvents.Count, @@ -755,20 +759,34 @@ private void CompareReceivedEvents( $"{Environment.NewLine}Expected events:{Environment.NewLine}" + string.Join(Environment.NewLine, expectedReceivedEvents)); - for (var i = 0; i < receivedEvents.Count; i++) + var equalityCheckSettings = new MidiEventEqualityCheckSettings { CompareDeltaTimes = false }; + var timeDelta = sendReceiveTimeDelta ?? SendReceiveUtilities.MaximumEventSendReceiveDelay; + + var actualEventsList = receivedEvents.ToList(); + var notReceivedEvents = new List(); + + foreach (var expectedReceivedEvent in expectedReceivedEvents) { - var receivedEvent = receivedEvents[i]; - var expectedReceivedEvent = expectedReceivedEvents[i]; + var actualEvent = actualEventsList.FirstOrDefault(e => + { + if (!MidiEvent.Equals(expectedReceivedEvent.Event, e.Event, equalityCheckSettings)) + return false; - MidiAsserts.AreEqual(expectedReceivedEvent.Event, receivedEvent.Event, false, $"Received event [{receivedEvent.Event}] doesn't match expected one [{expectedReceivedEvent.Event}]."); + var expectedTime = expectedReceivedEvent.Time; + var offsetFromExpectedTime = (e.Time - expectedTime).Duration(); - var expectedTime = expectedReceivedEvent.Time; - var offsetFromExpectedTime = (receivedEvent.Time - expectedTime).Duration(); - Assert.LessOrEqual( - offsetFromExpectedTime, - SendReceiveUtilities.MaximumEventSendReceiveDelay, - $"Event was received at wrong time ({receivedEvent.Time}; expected is {expectedTime})."); + return offsetFromExpectedTime <= timeDelta; + }); + + if (actualEvent == null) + notReceivedEvents.Add(expectedReceivedEvent); + else + actualEventsList.Remove(actualEvent); } + + CollectionAssert.IsEmpty( + notReceivedEvents, + $"Following events was not received:{Environment.NewLine}{string.Join(Environment.NewLine, notReceivedEvents)}"); } private static void CheckCurrentTime(Playback playback, TimeSpan expectedCurrentTime, string afterPlaybackAction) diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Metadata.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Metadata.cs index a1bbe07f7..4b7017153 100644 --- a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Metadata.cs +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Metadata.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Melanchall.DryWetMidi.Common; using Melanchall.DryWetMidi.Core; using Melanchall.DryWetMidi.Multimedia; @@ -68,6 +67,11 @@ public TimedEventWithTrackChunkIndex(MidiEvent midiEvent, long time, int trackCh } public object Metadata { get; set; } + + public override ITimedObject Clone() + { + return new TimedEventWithTrackChunkIndex(Event.Clone(), Time, (int)Metadata); + } } #endregion @@ -744,7 +748,7 @@ public void CheckPlaybackMetadata_TrackPitchValue_FromAfterPitchBend_ToBeforePit expectedMetadata: new (MidiEvent, object)[] { (new PitchBendEvent(pitchValue) { Channel = (FourBitNumber)4 }, 0), - (new PitchBendEvent(SevenBitNumber.MinValue) { Channel = (FourBitNumber)4 }, null), + (new PitchBendEvent() { Channel = (FourBitNumber)4 }, null), (new PitchBendEvent(pitchValue) { Channel = (FourBitNumber)4 }, 0), (new NoteOffEvent(), 1) }); @@ -1764,13 +1768,17 @@ public void CheckPlaybackMetadata_NoteCallback_MoveBack() afterResume: (context, playback) => CheckCurrentTime(playback, stopAfter - stepAfterStop, "stopped"), runningAfterResume: new[] { - Tuple.Create(firstAfterResumeDelay, (context, playback) => CheckCurrentTime(playback, stopAfter + firstAfterResumeDelay - stepAfterStop, "resumed")), + Tuple.Create(firstAfterResumeDelay, (context, playback) => + { + Assert.IsTrue(playback.IsRunning, "Playback is not running after resumed."); + CheckCurrentTime(playback, stopAfter + firstAfterResumeDelay - stepAfterStop, "resumed on first span"); + }), Tuple.Create(secondAfterResumeDelay, (context, playback) => { playback.MoveBack((MetricTimeSpan)stepAfterResumed); - CheckCurrentTime(playback, stopAfter + firstAfterResumeDelay + secondAfterResumeDelay - stepAfterStop - stepAfterResumed, "resumed"); + CheckCurrentTime(playback, stopAfter + firstAfterResumeDelay + secondAfterResumeDelay - stepAfterStop - stepAfterResumed, "resumed on second span"); }), - Tuple.Create(thirdAfterResumeDelay, (context, playback) => CheckCurrentTime(playback, stopAfter + firstAfterResumeDelay + secondAfterResumeDelay + thirdAfterResumeDelay - stepAfterStop - stepAfterResumed, "resumed")) + Tuple.Create(thirdAfterResumeDelay, (context, playback) => CheckCurrentTime(playback, stopAfter + firstAfterResumeDelay + secondAfterResumeDelay + thirdAfterResumeDelay - stepAfterStop - stepAfterResumed, "resumed on third span")) }, expectedMetadata: new (MidiEvent, object)[] { diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Add.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Add.cs new file mode 100644 index 000000000..ad467817f --- /dev/null +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Add.cs @@ -0,0 +1,2584 @@ +using Melanchall.DryWetMidi.Common; +using Melanchall.DryWetMidi.Core; +using Melanchall.DryWetMidi.Interaction; +using NUnit.Framework; +using System; +using System.Linq; + +namespace Melanchall.DryWetMidi.Tests.Multimedia +{ + [TestFixture] + public sealed partial class PlaybackTests + { + #region Test methods + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddEventWithinNote_AfterCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddEventWithinNote_AfterCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddEventWithinNote_AfterCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddEventWithinNote_BeforeCurrentTime() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddNotesInAdvance() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap), + }; + + var objectToAdd1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var objectToAdd2 = new Note((SevenBitNumber)60) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var objectToAdd3 = new Note((SevenBitNumber)70) { Channel = (FourBitNumber)4 } + .SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 150), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(200, + (playback, collection) => collection.Add(objectToAdd2)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd3)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)60, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)60, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(850)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_AfterCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_AfterCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_AfterCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_AfterCurrentTime_4() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_BeforeCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_BeforeCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_BeforeCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_MoveToTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => + { + collection.Add(objectToAdd); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddProgramChangeWithinNote_MoveToTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + new PlaybackChanger(100, + (playback, collection) => + { + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 400)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_AfterCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_AfterCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_AfterCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_AfterCurrentTime_4() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_BeforeCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_BeforeCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_BeforeCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_MoveToTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, (playback, collection) => + { + collection.Add(objectToAdd); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddPitchBendWithinNote_MoveToTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + new PlaybackChanger(100, + (playback, collection) => + { + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 400)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_AfterCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_AfterCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_AfterCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_AfterCurrentTime_4() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_AfterCurrentTime_5() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap); + var objectToAdd3 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)40, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 800), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2, objectToAdd3)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)40, (SevenBitNumber)70), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_BeforeCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_BeforeCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_BeforeCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_MoveToTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => + { + collection.Add(objectToAdd); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddControlChangeWithinNote_MoveToTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + new PlaybackChanger(100, + (playback, collection) => + { + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 400)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddNote_1([Values(1.0, 0.5, 0.1)] double scaleFactor) + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, (int)(1000 * scaleFactor)), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger((int)(400 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd); + CheckDuration(TimeSpan.FromMilliseconds((int)(1000 * scaleFactor)), playback); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(400 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(800 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1000 * scaleFactor))), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddNote_2([Values(1.0, 0.5, 0.1)] double scaleFactor) + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, (int)(1000 * scaleFactor)), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap); + var objectToAdd2 = new Note((SevenBitNumber)50) { Channel = (FourBitNumber)6 } + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(350 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger((int)(400 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + CheckDuration(TimeSpan.FromMilliseconds((int)(1000 * scaleFactor)), playback); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(400 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds((int)(400 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(800 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds((int)(850 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1000 * scaleFactor))), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddNote_3([Values(1.0, 0.5, 0.1)] double scaleFactor) + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(600 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + var objectToAdd2 = new Note((SevenBitNumber)80) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(1000 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger((int)(200 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd1); + CheckDuration(TimeSpan.FromMilliseconds((int)(900 * scaleFactor)), playback); + + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, (int)(700 * scaleFactor))); + }), + new PlaybackChanger((int)(100 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd2); + CheckDuration(TimeSpan.FromMilliseconds((int)(1300 * scaleFactor)), playback); + + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, (int)(1100 * scaleFactor))); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(200 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(200 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(300 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(300 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(500 * scaleFactor))), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddNote_4([Values(1.0, 0.5, 0.1)] double scaleFactor) + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(600 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + var objectToAdd2 = new Note((SevenBitNumber)80) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(1000 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger((int)(200 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + CheckDuration(TimeSpan.FromMilliseconds((int)(1300 * scaleFactor)), playback); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(500 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(600 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(900 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(1000 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1300 * scaleFactor))), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddNote_5() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddNote_6() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 1000), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddEventWithinNote_AfterCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddEventWithinNote_AfterCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddEventWithinNote_AfterCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(1300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddEventWithinNote_BeforeOrEqualToCurrentTime() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(1000 + 300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(2000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddNotesInAdvance() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 900), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap), + }; + + var objectToAdd1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var objectToAdd2 = new Note((SevenBitNumber)60) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var objectToAdd3 = new Note((SevenBitNumber)70) { Channel = (FourBitNumber)4 } + .SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 150), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(200, + (playback, collection) => collection.Add(objectToAdd2)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd3)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)60, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)60, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(850)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)60, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)60, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1850)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(2000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_AfterCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_AfterCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_AfterCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_AfterCurrentTime_4() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_BeforeCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_BeforeCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_BeforeCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_MoveToTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => + { + collection.Add(objectToAdd); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 500)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddProgramChangeWithinNote_MoveToTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + new PlaybackChanger(100, + (playback, collection) => + { + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 400)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1200)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(1500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(1800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(2200)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_AfterCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_AfterCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_AfterCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_AfterCurrentTime_4() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_BeforeCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_BeforeCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_BeforeCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_MoveToTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, (playback, collection) => + { + collection.Add(objectToAdd); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 400)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddPitchBendWithinNote_MoveToTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + new PlaybackChanger(100, + (playback, collection) => + { + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 400)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1200)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(1500)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(1800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(2200)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_AfterCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_AfterCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_AfterCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_AfterCurrentTime_4() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1)), + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_AfterCurrentTime_5() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToAdd3 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)40, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2, objectToAdd3)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)40, (SevenBitNumber)70), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)40, (SevenBitNumber)70), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_BeforeCurrentTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_BeforeCurrentTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_BeforeCurrentTime_3() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.Add(objectToAdd1, objectToAdd2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_MoveToTime_1() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => + { + collection.Add(objectToAdd); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 400)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddControlChangeWithinNote_MoveToTime_2() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap); + var objectToAdd2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 700)); + }), + new PlaybackChanger(100, + (playback, collection) => + { + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 400)); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1200)), + + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1200)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)50), TimeSpan.FromMilliseconds(1500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)50, (SevenBitNumber)70), TimeSpan.FromMilliseconds(1800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(2200)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddNote_1([Values(1.0, 0.5, 0.1)] double scaleFactor) + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(100 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger((int)(200 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd); + CheckDuration(TimeSpan.FromMilliseconds((int)(500 * scaleFactor)), playback); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(200 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(400 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(500 * scaleFactor))), + + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(500 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(600 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(900 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1000 * scaleFactor))), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddNote_2([Values(1.0, 0.5, 0.1)] double scaleFactor) + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, (int)(1000 * scaleFactor)), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap); + var objectToAdd2 = new Note((SevenBitNumber)50) { Channel = (FourBitNumber)6 } + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(350 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger((int)(400 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + CheckDuration(TimeSpan.FromMilliseconds((int)(1000 * scaleFactor)), playback); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(400 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds((int)(400 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(800 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds((int)(850 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1000 * scaleFactor))), + + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(1000 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(1300 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds((int)(1350 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1800 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds((int)(1850 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(2000 * scaleFactor))), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddNote_3([Values(1.0, 0.5, 0.1)] double scaleFactor) + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(600 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + var objectToAdd2 = new Note((SevenBitNumber)80) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(1000 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger((int)(200 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd1); + CheckDuration(TimeSpan.FromMilliseconds((int)(900 * scaleFactor)), playback); + + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, (int)(700 * scaleFactor))); + }), + new PlaybackChanger((int)(100 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd2); + CheckDuration(TimeSpan.FromMilliseconds((int)(1300 * scaleFactor)), playback); + + playback.MoveToTime(new MetricTimeSpan(0, 0, 0, (int)(1100 * scaleFactor))); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(200 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(200 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(300 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(300 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(500 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(500 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1000 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(1100 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1400 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(1500 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1800 * scaleFactor))), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddNote_4([Values(1.0, 0.5, 0.1)] double scaleFactor) + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, (int)(500 * scaleFactor)), OnTheFlyChecksTempoMap), + }; + var objectToAdd1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(600 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + var objectToAdd2 = new Note((SevenBitNumber)80) + .SetTime(new MetricTimeSpan(0, 0, 0, (int)(1000 * scaleFactor)), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, (int)(300 * scaleFactor)), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger((int)(200 * scaleFactor), + (playback, collection) => + { + collection.Add(objectToAdd1, objectToAdd2); + CheckDuration(TimeSpan.FromMilliseconds((int)(1300 * scaleFactor)), playback); + }), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(500 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(600 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(900 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(1000 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1300 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(1300 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(1800 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(1900 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(2200 * scaleFactor))), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds((int)(2300 * scaleFactor))), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((int)(2600 * scaleFactor))), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddNote_5() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_AddNote_6() + { + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + }; + var objectToAdd = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 1000), OnTheFlyChecksTempoMap); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(100, + (playback, collection) => collection.Add(objectToAdd)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(2000)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + #endregion + } +} diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.ChangeObject.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.ChangeObject.cs new file mode 100644 index 000000000..c90bc3487 --- /dev/null +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.ChangeObject.cs @@ -0,0 +1,2288 @@ +using Melanchall.DryWetMidi.Common; +using Melanchall.DryWetMidi.Core; +using Melanchall.DryWetMidi.Interaction; +using NUnit.Framework; +using System; +using System.Linq; + +namespace Melanchall.DryWetMidi.Tests.Multimedia +{ + [TestFixture] + public sealed partial class PlaybackTests + { + #region Test methods + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_1() + { + + var objectToChange = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 1000), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_2() + { + var objectToChange = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_ProgramChange_1() + { + var objectToChange = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + ((ProgramChangeEvent)((TimedEvent)obj).Event).ProgramNumber = (SevenBitNumber)71; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)71), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_ProgramChange_2() + { + var objectToChange1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_ProgramChange_3() + { + var objectToChange = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)80)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + new Note((SevenBitNumber)50).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap).SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 800), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)80), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ProgramChangeEvent(), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)80), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(900)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_ProgramChange_4() + { + var objectToChange1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_PitchBend_1() + { + var objectToChange = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + ((PitchBendEvent)((TimedEvent)obj).Event).PitchValue = 7100; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7100), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_PitchBend_2() + { + var objectToChange1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_PitchBend_3() + { + var objectToChange = new TimedEvent(new PitchBendEvent(8000)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + new Note((SevenBitNumber)50).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap).SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 800), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(8000), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new PitchBendEvent(), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(8000), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(900)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_PitchBend_4() + { + var objectToChange1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_ControlChange_1() + { + var objectToChange = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + ((ControlChangeEvent)((TimedEvent)obj).Event).ControlValue = (SevenBitNumber)71; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)71), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_ControlChange_2() + { + var objectToChange1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_ControlChange_3() + { + var objectToChange = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)80)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + new Note((SevenBitNumber)50).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap).SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 800), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)80), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)0), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)80), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(900)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_ControlChange_4() + { + var objectToChange1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_TrackNotes_1() + { + // ----[ | ]---- + // | + // v v v v | v v v + // | + // ------[ | ]-- + + var tempoMap = TempoMap.Default; + + var objectToChange = new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_TrackNotes_2() + { + // -[ | ]------- + // | + // v v v|v v v v v + // | + // -----|-[ ]- + + var tempoMap = TempoMap.Default; + + var objectToChange = new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + ((Note)obj).Channel = (FourBitNumber)4; + ((Note)obj).NoteNumber = (SevenBitNumber)71; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)71, Note.DefaultVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)71, Note.DefaultOffVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_TrackNotes_3() + { + // -------[ | ]- + // | + // v v v v v | v v + // | + // -[ ]--|---- + + var tempoMap = TempoMap.Default; + + var objectToChange = new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(), OnTheFlyChecksTempoMap); + ((Note)obj).Velocity = (SevenBitNumber)30; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_TrackNotes_4() + { + // ----------|-[ ]- + // | + // v v v v v | v v v v + // | + // -[ ]--|--------- + + var tempoMap = TempoMap.Default; + + var objectToChange = new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(350, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + ((Note)obj).Velocity = (SevenBitNumber)30; + })), + }, + expectedReceivedEvents: new ReceivedEvent[] + { + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_TrackNotes_5() + { + // -[ 1| ]--------------- + // -----|---[ 2 ]------- + // | + // v v v|v v v v v v v v v + // | + // -----|-[ 1 ]--------- + // -----|---[ 2 ]------- + // | + // +-----+ + // | + // -------[ 1| ]--------- + // ---------[ |2 ]------- + // | + // v v v v v v|v v v v v v + // | + // -------[ 1| ]--------- + // -----------|---[ 2 ]- + // | + // +-------+ + // | + // -------[ 1 ]-----|--- + // ----------- ---[ 2| ]- + // | + // v v v v v v v v v v|v v + // | + // -------[ 1 ]-----|--- + // -------[ 2 ]-----|--- + + + var tempoMap = TempoMap.Default; + + var objectToChange1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToChange2 = new Note((SevenBitNumber)60) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + ((Note)obj).Velocity = (SevenBitNumber)30; + })), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + ((Note)obj).NoteNumber = (SevenBitNumber)80; + })), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + ((Note)obj).Channel = (FourBitNumber)5; + playback.MoveToStart(); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, (SevenBitNumber)30), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)60, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)60, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, (SevenBitNumber)30), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity) { Channel = (FourBitNumber)5 }, TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity) { Channel = (FourBitNumber)5 }, TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_Chord_1() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + var note3 = new Note((SevenBitNumber)90); + + var objectToChange = new Chord(note1, note2, note3); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(50, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Remove(note1); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_Chord_2() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + var note3 = new Note((SevenBitNumber)90) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(50, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Remove(note2); + chord.Notes.Add(note3); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_Chord_3() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(50, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes + .Last() + .SetTime(new MetricTimeSpan(0, 0, 0, 150), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(150)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(350)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_Chord_4() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(50, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Clear(); + })), + }, + expectedReceivedEvents: new ReceivedEvent[] + { + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_Chord_5() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + var note3 = new Note((SevenBitNumber)90); + + var objectToChange = new Chord(note1, note2, note3); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Remove(note1); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_Chord_6() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + var note3 = new Note((SevenBitNumber)90) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Remove(note2); + chord.Notes.Add(note3); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_Chord_7() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes + .Last() + .SetTime(new MetricTimeSpan(0, 0, 0, 150), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(350)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ChangeObject_Chord_8() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Clear(); + })), + }, + expectedReceivedEvents: new ReceivedEvent[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_1() + { + + var objectToChange = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_2() + { + var objectToChange = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_ProgramChange_1() + { + var objectToChange = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + ((ProgramChangeEvent)((TimedEvent)obj).Event).ProgramNumber = (SevenBitNumber)71; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)71), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)71), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_ProgramChange_2() + { + var objectToChange1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)50), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_ProgramChange_3() + { + var objectToChange = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)80)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + new Note((SevenBitNumber)50).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap).SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 800), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)80), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ProgramChangeEvent(), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)80), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1600)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)80), TimeSpan.FromMilliseconds(1700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1800)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_ProgramChange_4() + { + var objectToChange1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_PitchBend_1() + { + var objectToChange = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + ((PitchBendEvent)((TimedEvent)obj).Event).PitchValue = 7100; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7100), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(7100), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_PitchBend_2() + { + var objectToChange1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new PitchBendEvent(5000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(5000), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_PitchBend_3() + { + var objectToChange = new TimedEvent(new PitchBendEvent(8000)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + new Note((SevenBitNumber)50).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap).SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 800), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(8000), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new PitchBendEvent(), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(8000), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1600)), + new ReceivedEvent(new PitchBendEvent(8000), TimeSpan.FromMilliseconds(1700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1800)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_PitchBend_4() + { + var objectToChange1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new PitchBendEvent(7000) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_ControlChange_1() + { + var objectToChange = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + ((ControlChangeEvent)((TimedEvent)obj).Event).ControlValue = (SevenBitNumber)71; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)71), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)71), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_ControlChange_2() + { + var objectToChange1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)50)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)50), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)50), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_ControlChange_3() + { + var objectToChange = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)80)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + new Note((SevenBitNumber)50).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap).SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap), + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 800), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)80), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)0), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)80), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1600)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)80), TimeSpan.FromMilliseconds(1700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1800)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_ControlChange_4() + { + var objectToChange1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var objectToChange2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap))), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)5, (SevenBitNumber)70), TimeSpan.FromMilliseconds(900)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_TrackNotes_1() + { + // ----[ | ]---- + // | + // v v v v | v v v + // | + // ------[ | ]-- + + var tempoMap = TempoMap.Default; + + var objectToChange = new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap))), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_TrackNotes_2() + { + // -[ | ]------- + // | + // v v v|v v v v v + // | + // -----|-[ ]- + + var tempoMap = TempoMap.Default; + + var objectToChange = new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + ((Note)obj).Channel = (FourBitNumber)4; + ((Note)obj).NoteNumber = (SevenBitNumber)71; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)71, Note.DefaultVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)71, Note.DefaultOffVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)71, Note.DefaultVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)71, Note.DefaultOffVelocity) { Channel = (FourBitNumber)4 }, TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_TrackNotes_3() + { + // -------[ | ]- + // | + // v v v v v | v v + // | + // -[ ]--|---- + + var tempoMap = TempoMap.Default; + + var objectToChange = new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(), OnTheFlyChecksTempoMap); + ((Note)obj).Velocity = (SevenBitNumber)30; + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, (SevenBitNumber)30), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_TrackNotes_4() + { + // ----------|-[ ]- + // | + // v v v v v | v v v v + // | + // -[ ]--|--------- + + var tempoMap = TempoMap.Default; + + var objectToChange = new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(350, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + ((Note)obj).Velocity = (SevenBitNumber)30; + })), + }, + expectedReceivedEvents: new ReceivedEvent[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, (SevenBitNumber)30), TimeSpan.FromMilliseconds(450)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(650)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_TrackNotes_5() + { + // -[ 1| ]--------------- + // -----|---[ 2 ]------- + // | + // v v v|v v v v v v v v v + // | + // -----|-[ 1 ]--------- + // -----|---[ 2 ]------- + // | + // +-----+ + // | + // -------[ 1| ]--------- + // ---------[ |2 ]------- + // | + // v v v v v v|v v v v v v + // | + // -------[ 1| ]--------- + // -----------|---[ 2 ]- + // | + // +-------+ + // | + // -------[ 1 ]-----|--- + // ----------- ---[ 2| ]- + // | + // v v v v v v v v v v|v v + // | + // -------[ 1 ]-----|--- + // -------[ 2 ]-----|--- + + + var tempoMap = TempoMap.Default; + + var objectToChange1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToChange2 = new Note((SevenBitNumber)60) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange1, + objectToChange2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange1, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + ((Note)obj).Velocity = (SevenBitNumber)30; + })), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + ((Note)obj).NoteNumber = (SevenBitNumber)80; + })), + new PlaybackChanger(300, + (playback, collection) => collection.ChangeObject( + objectToChange2, + obj => + { + obj.SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + ((Note)obj).Channel = (FourBitNumber)5; + playback.MoveToStart(); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, (SevenBitNumber)30), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)60, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)60, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, (SevenBitNumber)30), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity) { Channel = (FourBitNumber)5 }, TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity) { Channel = (FourBitNumber)5 }, TimeSpan.FromMilliseconds(1400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, (SevenBitNumber)30), TimeSpan.FromMilliseconds(1700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity) { Channel = (FourBitNumber)5 }, TimeSpan.FromMilliseconds(1700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(2000)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity) { Channel = (FourBitNumber)5 }, TimeSpan.FromMilliseconds(2000)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_Chord_1() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + var note3 = new Note((SevenBitNumber)90); + + var objectToChange = new Chord(note1, note2, note3); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(50, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Remove(note1); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_Chord_2() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + var note3 = new Note((SevenBitNumber)90) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(50, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Remove(note2); + chord.Notes.Add(note3); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_Chord_3() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(50, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes + .Last() + .SetTime(new MetricTimeSpan(0, 0, 0, 150), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(150)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(350)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(550)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(750)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_Chord_4() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(50, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Clear(); + })), + }, + expectedReceivedEvents: new ReceivedEvent[] + { + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_Chord_5() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + var note3 = new Note((SevenBitNumber)90); + + var objectToChange = new Chord(note1, note2, note3); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Remove(note1); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_Chord_6() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + var note3 = new Note((SevenBitNumber)90) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Remove(note2); + chord.Notes.Add(note3); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)90, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)90, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_Chord_7() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes + .Last() + .SetTime(new MetricTimeSpan(0, 0, 0, 150), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + })), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(350)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(550)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(750)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_ChangeObject_Chord_8() + { + var note1 = new Note((SevenBitNumber)70); + var note2 = new Note((SevenBitNumber)50); + + var objectToChange = new Chord(note1, note2); + + objectToChange + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToChange, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.ChangeObject( + objectToChange, + obj => + { + var chord = (Chord)obj; + chord.Notes.Clear(); + })), + }, + expectedReceivedEvents: new ReceivedEvent[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + #endregion + } +} diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Common.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Common.cs new file mode 100644 index 000000000..076ffe3b2 --- /dev/null +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Common.cs @@ -0,0 +1,146 @@ +using Melanchall.DryWetMidi.Tests.Common; +using NUnit.Framework; +using System.Collections.Generic; +using System; +using Melanchall.DryWetMidi.Multimedia; +using Melanchall.DryWetMidi.Interaction; +using System.Linq; +using System.Diagnostics; +using Melanchall.DryWetMidi.Core; + +namespace Melanchall.DryWetMidi.Tests.Multimedia +{ + [TestFixture] + public sealed partial class PlaybackTests + { + #region Nested classes + + private sealed class PlaybackChanger + { + public PlaybackChanger( + int periodMs, + Action action) + { + PeriodMs = periodMs; + Action = action; + } + + public int PeriodMs { get; } + + public Action Action { get; } + + public override string ToString() => + $"After {PeriodMs} ms"; + } + + private sealed class EventsCollectingDevice : IOutputDevice + { + public event EventHandler EventSent; + + private readonly Stopwatch _stopwatch = new Stopwatch(); + + public List ReceivedEvents { get; } = new List(); + + public void StartCollecting() + { + _stopwatch.Start(); + } + + public void PrepareForEventsSending() + { + } + + public void SendEvent(MidiEvent midiEvent) + { + ReceivedEvents.Add(new ReceivedEvent(midiEvent, _stopwatch.Elapsed)); + EventSent?.Invoke(this, new MidiEventSentEventArgs(midiEvent)); + } + + public void Dispose() + { + _stopwatch.Stop(); + } + } + + #endregion + + #region Constants + + private static readonly TempoMap OnTheFlyChecksTempoMap = new TempoMap(new TicksPerQuarterNoteTimeDivision(500)); + private const int OnTheFlyChecksRetriesNumber = 5; + + #endregion + + #region Private methods + + private void CheckPlaybackDataChangesOnTheFly( + ICollection initialObjects, + PlaybackChanger[] actions, + ICollection expectedReceivedEvents, + Action setupPlayback = null, + int? repeatsCount = null) + { + var collection = new ObservableTimedObjectsCollection(initialObjects); + + using (var outputDevice = new EventsCollectingDevice()) + using (var playback = new Playback(collection, OnTheFlyChecksTempoMap, outputDevice)) + { + setupPlayback?.Invoke(playback); + + if (repeatsCount > 0) + playback.Loop = true; + + var actualRepeatsCount = 0; + playback.RepeatStarted += (_, __) => + { + actualRepeatsCount++; + if (actualRepeatsCount == repeatsCount) + playback.Loop = false; + }; + + var actionsExecutedCount = 0; + + playback.Start(); + outputDevice.StartCollecting(); + + foreach (var action in actions) + { + var stopwatch = Stopwatch.StartNew(); + + while (stopwatch.ElapsedMilliseconds < action.PeriodMs) + { + } + + action.Action(playback, collection); + actionsExecutedCount++; + } + + var timeout = TimeSpan.FromSeconds(10); + var stopped = WaitOperations.Wait(() => !playback.IsRunning, timeout); + Assert.IsTrue(stopped, $"Playback is running after {timeout}."); + + WaitOperations.Wait(SendReceiveUtilities.MaximumEventSendReceiveDelay); + + Assert.AreEqual( + actions.Length, + actionsExecutedCount, + "Invalid number of actions executed."); + + CompareReceivedEvents( + outputDevice.ReceivedEvents, + expectedReceivedEvents.ToList(), + sendReceiveTimeDelta: TimeSpan.FromMilliseconds(10)); + } + } + + private void CheckDuration( + TimeSpan expectedDuration, + Playback playback) => + Assert.Less( + (expectedDuration - (TimeSpan)playback.GetDuration()).Duration(), + TimeSpan.FromMilliseconds(4), + "Invalid duration after note added."); + + #endregion + } +} diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Complex.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Complex.cs new file mode 100644 index 000000000..5060759b1 --- /dev/null +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Complex.cs @@ -0,0 +1,477 @@ +using Melanchall.DryWetMidi.Common; +using Melanchall.DryWetMidi.Core; +using Melanchall.DryWetMidi.Interaction; +using Melanchall.DryWetMidi.Multimedia; +using NUnit.Framework; +using System; +using System.Linq; + +namespace Melanchall.DryWetMidi.Tests.Multimedia +{ + [TestFixture] + public sealed partial class PlaybackTests + { + #region Test methods + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddAtAdvanceByOne( + [Values(1, 2, 3, 4, 8, 16, 17, 32, 50, 51, 64)] int notesCount, + [Values(0, 10)] int gapMs) + { + var noteLengthMs = 20; + var lastEventTime = notesCount * noteLengthMs + 20 + notesCount * gapMs; + + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("END")) + .SetTime(new MetricTimeSpan(0, 0, 0, lastEventTime), OnTheFlyChecksTempoMap), + }; + + var objectsToAdd = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 10 + n * gapMs), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + var actions = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new PlaybackChanger(n == 0 ? 0 : noteLengthMs, + (playback, collection) => collection.Add(objectsToAdd[n]))) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: actions, + expectedReceivedEvents: SevenBitNumber + .Values + .Take(notesCount) + .SelectMany(n => new[] + { + new ReceivedEvent(new NoteOnEvent(n, Note.DefaultVelocity), TimeSpan.FromMilliseconds(n * noteLengthMs + 10 + n * gapMs)), + new ReceivedEvent(new NoteOffEvent(n, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((n + 1) * noteLengthMs + 10 + n * gapMs)), + }) + .Concat(new[] + { + new ReceivedEvent(new TextEvent("END"), TimeSpan.FromMilliseconds(lastEventTime)), + }) + .ToArray(), + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddAtAdvanceByOne_WithEndMovesBehindCurrentTime( + [Values(1, 2, 3, 4, 8, 16, 17, 32, 50, 51, 64)] int notesCount) + { + var noteLengthMs = 20; + var lastEventTime = notesCount * noteLengthMs + 20; + + var endObject = new TimedEvent(new TextEvent("END")) + .SetTime(new MetricTimeSpan(0, 0, 0, lastEventTime), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + endObject, + }; + + var objectsToAdd = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 10), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + var actions = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new PlaybackChanger(n == 0 ? 0 : noteLengthMs, + (playback, collection) => collection.ChangeCollection(() => + { + collection.Add(objectsToAdd[n]); + collection.ChangeObject(endObject, obj => obj + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 15), OnTheFlyChecksTempoMap)); + }))) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: actions, + expectedReceivedEvents: SevenBitNumber + .Values + .Take(notesCount) + .SelectMany(n => new[] + { + new ReceivedEvent(new NoteOnEvent(n, Note.DefaultVelocity), TimeSpan.FromMilliseconds(n * noteLengthMs + 10)), + new ReceivedEvent(new TextEvent("END"), TimeSpan.FromMilliseconds((n + 1) * noteLengthMs + 5)), + new ReceivedEvent(new NoteOffEvent(n, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((n + 1) * noteLengthMs + 10)), + }) + .ToArray(), + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddAtAdvanceByOneWithOverlapping( + [Values(1, 2, 3, 4, 8, 16, 17, 32, 50, 51, 64)] int notesCount) + { + var overlappedMs = 5; + var noteLengthMs = 20; + var lastEventTime = notesCount * noteLengthMs + 20 - notesCount * overlappedMs; + + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("END")) + .SetTime(new MetricTimeSpan(0, 0, 0, lastEventTime), OnTheFlyChecksTempoMap), + }; + + var objectsToAdd = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 10 - n * overlappedMs), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + var actions = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new PlaybackChanger(n == 0 ? 0 : noteLengthMs - n * overlappedMs, + (playback, collection) => collection.Add(objectsToAdd[n]))) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: actions, + expectedReceivedEvents: SevenBitNumber + .Values + .Take(notesCount) + .SelectMany(n => new[] + { + new ReceivedEvent(new NoteOnEvent(n, Note.DefaultVelocity), TimeSpan.FromMilliseconds(n * noteLengthMs + 10 - n * overlappedMs)), + new ReceivedEvent(new NoteOffEvent(n, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((n + 1) * noteLengthMs + 10 - n * overlappedMs)), + }) + .OrderBy(e => e.Time) + .Concat(new[] + { + new ReceivedEvent(new TextEvent("END"), TimeSpan.FromMilliseconds(lastEventTime)), + }) + .ToArray(), + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_BatchAdd_1( + [Values(1, 2, 3, 4, 8, 16, 17, 32, 50, 51, 64)] int notesCount) + { + var noteLengthMs = 20; + var lastEventTime = notesCount * noteLengthMs + 20; + + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("END")) + .SetTime(new MetricTimeSpan(0, 0, 0, lastEventTime), OnTheFlyChecksTempoMap), + }; + + var objectsToAdd = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 10), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(0, + (playback, collection) => collection.Add(objectsToAdd)), + }, + expectedReceivedEvents: SevenBitNumber + .Values + .Take(notesCount) + .SelectMany(n => new[] + { + new ReceivedEvent(new NoteOnEvent(n, Note.DefaultVelocity), TimeSpan.FromMilliseconds(n * noteLengthMs + 10)), + new ReceivedEvent(new NoteOffEvent(n, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((n + 1) * noteLengthMs + 10)), + }) + .Concat(new[] + { + new ReceivedEvent(new TextEvent("END"), TimeSpan.FromMilliseconds(lastEventTime)), + }) + .ToArray(), + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_BatchAdd_2( + [Values(8, 16, 17, 20, 32)] int notesCount, + [Values(20, 40, 60, 100)] int addAtMs) + { + var noteLengthMs = 20; + var lastEventTime = notesCount * noteLengthMs + 20; + + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("END")) + .SetTime(new MetricTimeSpan(0, 0, 0, lastEventTime), OnTheFlyChecksTempoMap), + }; + + var objectsToAdd = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 10), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + var receivedEvents = SevenBitNumber + .Values + .Take(notesCount) + .SelectMany(n => new[] + { + new ReceivedEvent(new NoteOnEvent(n, Note.DefaultVelocity), TimeSpan.FromMilliseconds(n * noteLengthMs + 10)), + new ReceivedEvent(new NoteOffEvent(n, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((n + 1) * noteLengthMs + 10)), + }) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(addAtMs, + (playback, collection) => collection.Add(objectsToAdd)), + }, + expectedReceivedEvents: + new[] + { + new ReceivedEvent( + objectsToAdd.AtTime(new MetricTimeSpan(0, 0, 0, addAtMs), OnTheFlyChecksTempoMap, LengthedObjectPart.Entire).First().GetTimedNoteOnEvent().Event, + TimeSpan.FromMilliseconds(addAtMs)), + } + .Concat(receivedEvents + .SkipWhile(e => e.Time < TimeSpan.FromMilliseconds(addAtMs)) + .Concat(new[] + { + new ReceivedEvent(new TextEvent("END"), TimeSpan.FromMilliseconds(lastEventTime)), + })) + .ToArray(), + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveByOne( + [Values(1, 2, 3, 4, 8, 16, 17, 32, 50, 51, 64)] int notesCount) + { + var noteLengthMs = 20; + + var objectsToRemove = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 10), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + var actions = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new PlaybackChanger(noteLengthMs, + (playback, collection) => collection.Remove(objectsToRemove[n]))) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: objectsToRemove, + actions: actions, + expectedReceivedEvents: SevenBitNumber + .Values + .Take(notesCount) + .SelectMany(n => new[] + { + new ReceivedEvent(new NoteOnEvent(n, Note.DefaultVelocity), TimeSpan.FromMilliseconds(n * noteLengthMs + 10)), + new ReceivedEvent(new NoteOffEvent(n, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(n * noteLengthMs + 20)), + }) + .ToArray(), + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveAtAdvance( + [Values(1, 2, 3, 4, 8, 16, 17, 32, 50, 51, 64)] int notesCount) + { + var noteLengthMs = 40; + + var objectsToRemove = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 40), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + var actions = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new PlaybackChanger(n == 0 ? 20 : noteLengthMs, + (playback, collection) => collection.Remove(objectsToRemove[n]))) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: objectsToRemove, + actions: actions, + expectedReceivedEvents: Array.Empty(), + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_ShiftNoteAtAdvance() + { + var noteLengthMs = 40; + var noteNumber = (SevenBitNumber)70; + + var objectToShift = new Note(noteNumber) + .SetTime(new MetricTimeSpan(0, 0, 0, 20), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap); + + var actions = Enumerable + .Range(0, 10) + .Select(n => new PlaybackChanger(n == 0 ? 10 : noteLengthMs, + (playback, collection) => collection.ChangeObject(objectToShift, obj => obj + .SetTime(new MetricTimeSpan(0, 0, 0, (n + 1) * noteLengthMs + 20), OnTheFlyChecksTempoMap)))) + .ToArray(); + + var lastMs = 10 * noteLengthMs + 20; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: new[] { objectToShift }, + actions: actions, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent(noteNumber, Note.DefaultVelocity), TimeSpan.FromMilliseconds(lastMs)), + new ReceivedEvent(new NoteOffEvent(noteNumber, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(lastMs + noteLengthMs)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddAndRemoveAtAdvanceByOne( + [Values(1, 2, 3, 4, 8, 16, 17, 32, 50, 51, 64)] int notesCount, + [Values(0, 10)] int gapMs, + [Values] bool viaChangeCollection) + { + var noteLengthMs = 20; + var lastEventTime = notesCount * noteLengthMs + 20 + notesCount * gapMs; + + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("END")) + .SetTime(new MetricTimeSpan(0, 0, 0, lastEventTime), OnTheFlyChecksTempoMap), + }; + + var objectsToAdd = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 10 + n * gapMs), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + var action = viaChangeCollection + ? new Action((playback, collection, n) => collection.ChangeCollection(() => + { + collection.Add(objectsToAdd[n]); + collection.Remove(objectsToAdd[n]); + })) + : (playback, collection, n) => + { + collection.Add(objectsToAdd[n]); + collection.Remove(objectsToAdd[n]); + }; + + var actions = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new PlaybackChanger(n == 0 ? 0 : noteLengthMs, + (playback, collection) => action(playback, collection, n))) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: actions, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new TextEvent("END"), TimeSpan.FromMilliseconds(lastEventTime)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_AddAtAdvanceAndRemovePastByOne( + [Values(1, 2, 3, 4, 8, 16, 17, 32, 50, 51, 64)] int notesCount) + { + var noteLengthMs = 20; + var lastEventTime = notesCount * noteLengthMs + 20; + + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("END")) + .SetTime(new MetricTimeSpan(0, 0, 0, lastEventTime), OnTheFlyChecksTempoMap), + }; + + var objectsToAdd = SevenBitNumber + .Values + .Take(notesCount) + .Select(n => new Note(n) + .SetTime(new MetricTimeSpan(0, 0, 0, n * noteLengthMs + 10), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, noteLengthMs), OnTheFlyChecksTempoMap)) + .ToArray(); + + var actions = SevenBitNumber + .Values + .Take(notesCount) + .SelectMany(n => new[] + { + new PlaybackChanger(n == 0 ? 0 : noteLengthMs, + (playback, collection) => + { + collection.Add(objectsToAdd[n]); + + if (n >= 2) + collection.Remove(objectsToAdd[n - 2]); + }), + }) + .ToArray(); + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: actions, + expectedReceivedEvents: SevenBitNumber + .Values + .Take(notesCount) + .SelectMany(n => new[] + { + new ReceivedEvent(new NoteOnEvent(n, Note.DefaultVelocity), TimeSpan.FromMilliseconds(n * noteLengthMs + 10)), + new ReceivedEvent(new NoteOffEvent(n, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds((n + 1) * noteLengthMs + 10)), + }) + .Concat(new[] + { + new ReceivedEvent(new TextEvent("END"), TimeSpan.FromMilliseconds(lastEventTime)), + }) + .ToArray(), + setupPlayback: playback => playback.TrackNotes = true); + } + + #endregion + } +} diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Remove.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Remove.cs new file mode 100644 index 000000000..8b4deaf0e --- /dev/null +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.ObservableCollection.Remove.cs @@ -0,0 +1,1860 @@ +using Melanchall.DryWetMidi.Common; +using Melanchall.DryWetMidi.Core; +using Melanchall.DryWetMidi.Interaction; +using NUnit.Framework; +using System; +using System.Linq; + +namespace Melanchall.DryWetMidi.Tests.Multimedia +{ + [TestFixture] + public sealed partial class PlaybackTests + { + #region Test methods + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveEventWithinNote_AfterCurrentTime() + { + var objectToRemove = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveEventWithinNote_BeforeCurrentTime() + { + var objectToRemove = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 1), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveLastActiveEvent() + { + var objectToRemove = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("A")), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(0)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveAtSameTime_AfterCurrentTime([Values(0, 1, 2)] int indexToRemove) + { + var objectToRemove1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove3 = new TimedEvent(new TextEvent("C")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var objectsToRemove = new ITimedObject[] + { + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectsToRemove[indexToRemove])), + }, + expectedReceivedEvents: + new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(0)), + } + .Concat( + objectsToRemove + .Except(new[] { objectsToRemove[indexToRemove] }) + .Select(o => new ReceivedEvent(((TimedEvent)o).Event, TimeSpan.FromMilliseconds(300)))) + .Concat(new[] + { + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + }) + .ToArray()); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveAtSameTime_BeforeCurrentTime([Values(0, 1, 2)] int indexToRemove) + { + var objectToRemove1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove3 = new TimedEvent(new TextEvent("C")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var objectsToRemove = new ITimedObject[] + { + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Remove(objectsToRemove[indexToRemove])), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveProgramChange_AfterCurrentTime_1() + { + var objectToRemove = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)7)).SetTime(new MetricTimeSpan(0, 0, 0, 250), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveProgramChange_AfterCurrentTime_2() + { + var objectToRemove = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)7)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveProgramChange_BeforeCurrentTime_1() + { + var objectToRemove = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)7)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)7), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ProgramChangeEvent(), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveProgramChange_BeforeCurrentTime_2() + { + var objectToRemove1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)7)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)8)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)7), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)8), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)7), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new ProgramChangeEvent(), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackProgram = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemovePitchBend_AfterCurrentTime_1() + { + var objectToRemove = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 250), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemovePitchBend_AfterCurrentTime_2() + { + var objectToRemove = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemovePitchBend_BeforeCurrentTime_1() + { + var objectToRemove = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new PitchBendEvent(), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemovePitchBend_BeforeCurrentTime_2() + { + var objectToRemove1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new PitchBendEvent(8000)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new PitchBendEvent(8000), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new PitchBendEvent(), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackPitchValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveControlChange_AfterCurrentTime_1() + { + var objectToRemove = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)20)).SetTime(new MetricTimeSpan(0, 0, 0, 250), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveControlChange_AfterCurrentTime_2() + { + var objectToRemove = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)20)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveControlChange_BeforeCurrentTime_1() + { + var objectToRemove = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)20)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)20), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveControlChange_BeforeCurrentTime_2() + { + var objectToRemove1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)80)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)80), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveControlChange_BeforeCurrentTime_3() + { + var objectToRemove1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)8, (SevenBitNumber)80)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)8, (SevenBitNumber)80), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)8, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveControlChange_BeforeCurrentTime_4() + { + var objectToRemove1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)80) { Channel = (FourBitNumber)6 }).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)80) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackControlValue = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveNotes_1() + { + var objectToRemove = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(600, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveNotes_2() + { + var objectToRemove = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(450, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveNotes_3() + { + var objectToRemove = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveNotes_4() + { + var objectToRemove1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new Note((SevenBitNumber)60) + .SetTime(new MetricTimeSpan(0, 0, 0, 450), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove1)), + new PlaybackChanger(300, + (playback, collection) => collection.Remove(objectToRemove2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)60, Note.DefaultVelocity), TimeSpan.FromMilliseconds(450)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)60, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveNotes_5() + { + var objectToRemove = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_RemoveNotes_6() + { + var objectToRemove1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + var objectToRemove2 = new Note((SevenBitNumber)80) + .SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(700, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + }, + setupPlayback: playback => playback.TrackNotes = true); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveEventWithinNote_AfterCurrentTime() + { + var objectToRemove = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveEventWithinNote_BeforeCurrentTime() + { + var objectToRemove = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(300, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveLastActiveEvent() + { + var objectToRemove = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + var initialObjects = new ITimedObject[] + { + new TimedEvent(new TextEvent("A")), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(0)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(200)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveAtSameTime_AfterCurrentTime_1() + { + var objectToRemove1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove3 = new TimedEvent(new TextEvent("C")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(0)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveAtSameTime_AfterCurrentTime_2() + { + var objectToRemove1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove3 = new TimedEvent(new TextEvent("C")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(0)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveAtSameTime_AfterCurrentTime_3() + { + var objectToRemove1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove3 = new TimedEvent(new TextEvent("C")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove3)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(0)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1000)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveAtSameTime_BeforeCurrentTime_1() + { + var objectToRemove1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove3 = new TimedEvent(new TextEvent("C")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveAtSameTime_BeforeCurrentTime_2() + { + var objectToRemove1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove3 = new TimedEvent(new TextEvent("C")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Remove(objectToRemove2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveAtSameTime_BeforeCurrentTime_3() + { + var objectToRemove1 = new TimedEvent(new TextEvent("A")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new TextEvent("B")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove3 = new TimedEvent(new TextEvent("C")).SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70).SetLength(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + objectToRemove3, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(500, + (playback, collection) => collection.Remove(objectToRemove3)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.Zero), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new TextEvent("C"), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new TextEvent("A"), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new TextEvent("B"), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveProgramChange_AfterCurrentTime_1() + { + var objectToRemove = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)7)).SetTime(new MetricTimeSpan(0, 0, 0, 250), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveProgramChange_AfterCurrentTime_2() + { + var objectToRemove = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)7)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveProgramChange_BeforeCurrentTime_1() + { + var objectToRemove = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)7)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)7), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ProgramChangeEvent(), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveProgramChange_BeforeCurrentTime_2() + { + var objectToRemove1 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)7)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new ProgramChangeEvent((SevenBitNumber)8)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)7), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)8), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ProgramChangeEvent((SevenBitNumber)7), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new ProgramChangeEvent(), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackProgram = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemovePitchBend_AfterCurrentTime_1() + { + var objectToRemove = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 250), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemovePitchBend_AfterCurrentTime_2() + { + var objectToRemove = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemovePitchBend_BeforeCurrentTime_1() + { + var objectToRemove = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new PitchBendEvent(), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemovePitchBend_BeforeCurrentTime_2() + { + var objectToRemove1 = new TimedEvent(new PitchBendEvent(7000)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new PitchBendEvent(8000)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new PitchBendEvent(8000), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new PitchBendEvent(7000), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new PitchBendEvent(), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackPitchValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveControlChange_AfterCurrentTime_1() + { + var objectToRemove = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)20)).SetTime(new MetricTimeSpan(0, 0, 0, 250), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveControlChange_AfterCurrentTime_2() + { + var objectToRemove = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)20)).SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(400, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveControlChange_BeforeCurrentTime_1() + { + var objectToRemove = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)20)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)20), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveControlChange_BeforeCurrentTime_2() + { + var objectToRemove1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)80)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)80), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveControlChange_BeforeCurrentTime_3() + { + var objectToRemove1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)8, (SevenBitNumber)80)).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)8, (SevenBitNumber)80), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)8, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveControlChange_BeforeCurrentTime_4() + { + var objectToRemove1 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 50), OnTheFlyChecksTempoMap); + var objectToRemove2 = new TimedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)80) { Channel = (FourBitNumber)6 }).SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove2)), + new PlaybackChanger(100, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)70), TimeSpan.FromMilliseconds(50)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, (SevenBitNumber)80) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue) { Channel = (FourBitNumber)6 }, TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new ControlChangeEvent((SevenBitNumber)7, SevenBitNumber.MinValue), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackControlValue = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveNotes_1() + { + var objectToRemove = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(600, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveNotes_2() + { + var objectToRemove = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(450, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(300)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(400)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(750)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(850)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveNotes_3() + { + var objectToRemove = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 500), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap), + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1400)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveNotes_4() + { + var objectToRemove1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap); + var objectToRemove2 = new Note((SevenBitNumber)60) + .SetTime(new MetricTimeSpan(0, 0, 0, 450), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 700), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove1)), + new PlaybackChanger(300, + (playback, collection) => collection.Remove(objectToRemove2)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)60, Note.DefaultVelocity), TimeSpan.FromMilliseconds(450)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)60, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1500)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveNotes_5() + { + var objectToRemove = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 100), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 400), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + objectToRemove, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(200, + (playback, collection) => collection.Remove(objectToRemove)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(100)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(200)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + [Retry(OnTheFlyChecksRetriesNumber)] + [Test] + public void CheckPlaybackDataChangesOnTheFly_Loop_RemoveNotes_6() + { + var objectToRemove1 = new Note((SevenBitNumber)50) + .SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + var objectToRemove2 = new Note((SevenBitNumber)80) + .SetTime(new MetricTimeSpan(0, 0, 0, 600), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap); + + var initialObjects = new ITimedObject[] + { + new Note((SevenBitNumber)70) + .SetTime(new MetricTimeSpan(0, 0, 0, 200), OnTheFlyChecksTempoMap) + .SetLength(new MetricTimeSpan(0, 0, 0, 300), OnTheFlyChecksTempoMap), + objectToRemove1, + objectToRemove2, + }; + + CheckPlaybackDataChangesOnTheFly( + initialObjects: initialObjects, + actions: new[] + { + new PlaybackChanger(700, + (playback, collection) => collection.Remove(objectToRemove1)), + }, + expectedReceivedEvents: new[] + { + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(200)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(500)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)50, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds(600)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)50, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(700)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(800)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)70, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1000)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)70, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1300)), + new ReceivedEvent(new NoteOnEvent((SevenBitNumber)80, Note.DefaultVelocity), TimeSpan.FromMilliseconds(1400)), + new ReceivedEvent(new NoteOffEvent((SevenBitNumber)80, Note.DefaultOffVelocity), TimeSpan.FromMilliseconds(1600)), + }, + setupPlayback: playback => playback.TrackNotes = true, + repeatsCount: 1); + } + + #endregion + } +} diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.TrackPitchValue.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.TrackPitchValue.cs index 4d185f6d1..e8ff38ffe 100644 --- a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.TrackPitchValue.cs +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.TrackPitchValue.cs @@ -222,7 +222,7 @@ public void TrackPitchValue_FromAfterPitchBend_ToBeforePitchBend(bool useOutputD eventsWillBeSent: new[] { new EventToSend(new PitchBendEvent(pitchValue) { Channel = (FourBitNumber)4 }, pitchBendTime), - new EventToSend(new PitchBendEvent(SevenBitNumber.MinValue) { Channel = (FourBitNumber)4 }, moveFrom - pitchBendTime), + new EventToSend(new PitchBendEvent() { Channel = (FourBitNumber)4 }, moveFrom - pitchBendTime), new EventToSend(new PitchBendEvent(pitchValue) { Channel = (FourBitNumber)4 }, pitchBendTime - moveTo), new EventToSend(new NoteOffEvent(), noteOffTime - pitchBendTime) }, @@ -343,7 +343,7 @@ public void TrackPitchValue_EnableInMiddle_FromAfterPitchBend_ToBeforePitchBend( eventsWillBeSent: new[] { new EventToSend(new PitchBendEvent(pitchValue) { Channel = (FourBitNumber)4 }, pitchBendTime), - new EventToSend(new PitchBendEvent(SevenBitNumber.MinValue) { Channel = (FourBitNumber)4 }, moveFrom - pitchBendTime + enableAfter), + new EventToSend(new PitchBendEvent() { Channel = (FourBitNumber)4 }, moveFrom - pitchBendTime + enableAfter), new EventToSend(new PitchBendEvent(pitchValue) { Channel = (FourBitNumber)4 }, pitchBendTime - (moveTo + enableAfter)), new EventToSend(new NoteOffEvent(), noteOffTime - pitchBendTime) }, diff --git a/DryWetMidi/Common/RedBlackTree/RedBlackTree.cs b/DryWetMidi/Common/RedBlackTree/RedBlackTree.cs new file mode 100644 index 000000000..a5b5f3fc9 --- /dev/null +++ b/DryWetMidi/Common/RedBlackTree/RedBlackTree.cs @@ -0,0 +1,502 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Melanchall.DryWetMidi.Common +{ + internal sealed class RedBlackTree : IEnumerable + where TKey : IComparable + where TValue : IEquatable + { + #region Fields + + private RedBlackTreeNode _root = RedBlackTreeNode.Void; + + #endregion + + #region Constructor + + public RedBlackTree() + { + } + + public RedBlackTree(IEnumerable values, Func keySelector) + { + foreach (var v in values) + { + Add(keySelector(v), v); + } + } + + #endregion + + #region Methods + + public RedBlackTreeNode GetRoot() + { + return _root; + } + + public RedBlackTree Clone() + { + return new RedBlackTree + { + _root = _root.Clone(), + }; + } + + public IEnumerable> EnumerateNodes() + { + var node = GetMinimumNode(); + if (IsVoid(node)) + yield break; + + do + { + yield return node; + } + while ((node = GetNextNode(node)) != null); + } + + public RedBlackTreeNode GetFirstNode(TKey key) + { + var node = _root; + + while (!IsVoid(node) && key.CompareTo(node.Key) != 0) + { + if (key.CompareTo(node.Key) < 0) + node = node.Left; + else + node = node.Right; + } + + return !IsVoid(node) ? node : null; + } + + public IEnumerable> SearchNodes(TKey key) + { + // TODO: optimize + + var queue = new Queue>(); + queue.Enqueue(GetFirstNode(key)); + + var visited = new HashSet>(); + + while (queue.Count > 0) + { + var node = queue.Dequeue(); + if (IsVoid(node) || node.Key.CompareTo(key) != 0 || !visited.Add(node)) + continue; + + yield return node; + + queue.Enqueue(GetNextNode(node)); + queue.Enqueue(GetPreviousNode(node)); + } + } + + public IEnumerable GetValues(TKey key) + { + return SearchNodes(key).Select(n => n.Value); + } + + public RedBlackTreeNode GetFirstNode(TKey key, TValue value) + { + return SearchNodes(key).FirstOrDefault(n => n.Value.Equals(value)); + } + + public RedBlackTreeNode GetMinimumNode() + { + return GetMinimumNode(_root); + } + + public RedBlackTreeNode GetMinimumNode(RedBlackTreeNode node) + { + while (!IsVoid(node?.Left)) + node = node.Left; + return node; + } + + public RedBlackTreeNode GetMaximumNode() + { + return GetMaximumNode(_root); + } + + public RedBlackTreeNode GetMaximumNode(RedBlackTreeNode node) + { + while (!IsVoid(node?.Right)) + node = node.Right; + return node; + } + + public RedBlackTreeNode GetNextNode(RedBlackTreeNode node) + { + var right = node.Right; + if (!IsVoid(right)) + return GetMinimumNode(right); + + var nextNode = node.Parent; + + while (!IsVoid(nextNode)) + { + if (node == nextNode.Left) + return nextNode; + + node = nextNode; + nextNode = node.Parent; + } + + return IsVoid(nextNode) ? null : nextNode; + } + + public RedBlackTreeNode GetPreviousNode(RedBlackTreeNode node) + { + var left = node.Left; + if (!IsVoid(left)) + return GetMaximumNode(left); + + var previousNode = node.Parent; + + while (!IsVoid(previousNode)) + { + if (node == previousNode.Right) + return previousNode; + + node = previousNode; + previousNode = node.Parent; + } + + return IsVoid(previousNode) ? null : previousNode; + } + + public RedBlackTreeNode Add(TKey key, TValue value) + { + var node = new RedBlackTreeNode(key, value, null); + Insert(node); + return node; + } + + public void Delete(RedBlackTreeNode node) + { + if (IsVoid(node)) + return; + + RedBlackTreeNode x = null; + var y = node; + var yOriginalColor = y.Color; + if (node.Left == RedBlackTreeNode.Void) + { + x = node.Right; + Transplant(node, node.Right); + } + else if (node.Right == RedBlackTreeNode.Void) + { + x = node.Left; + Transplant(node, node.Left); + } + else + { + y = GetMinimumNode(node.Right); + yOriginalColor = y.Color; + x = y.Right; + if (y != node.Right) + { + Transplant(y, y.Right); + y.Right = node.Right; + y.Right.Parent = y; + } + else + x.Parent = y; + Transplant(node, y); + y.Left = node.Left; + y.Left.Parent = y; + y.Color = node.Color; + } + if (yOriginalColor == RedBlackTreeNodeColor.Black) + DeleteFixup(x); + } + + public RedBlackTreeNode GetLastNodeBelowThreshold(TKey threshold) + { + return GetLastNodeBelowThreshold( + threshold, + node => node.Key); + } + + public RedBlackTreeNode GetLastNodeBelowThreshold( + TValueKey threshold, + Func, TValueKey> keySelector) + where TValueKey : IComparable + { + return GetLastNodeBelowThreshold(threshold, keySelector, _root); + } + + public RedBlackTreeNode GetLastNodeBelowThreshold( + TValueKey threshold, + Func, TValueKey> keySelector, + RedBlackTreeNode node) + where TValueKey : IComparable + { + if (IsVoid(node)) + return null; + + while (true) + { + RedBlackTreeNode nextNode = null; + if (threshold.CompareTo(keySelector(node)) < 0) + { + if (IsVoid(node.Left)) + { + var prev = GetPreviousNode(node); + while (!IsVoid(prev) && keySelector(prev).CompareTo(threshold) == 0) + prev = GetPreviousNode(prev); + + return IsVoid(prev) + ? null + : prev; + } + + nextNode = node.Left; + } + else if (threshold.CompareTo(keySelector(node)) > 0) + { + if (IsVoid(node.Right)) + return node; + + nextNode = node.Right; + } + else + { + var prev = GetPreviousNode(node); + while (!IsVoid(prev) && keySelector(prev).CompareTo(threshold) == 0) + prev = GetPreviousNode(prev); + + return IsVoid(prev) + ? null + : prev; + } + + node = nextNode; + if (IsVoid(node)) + return null; + } + } + + private bool IsVoid(RedBlackTreeNode node) + { + return node == null || node == RedBlackTreeNode.Void; + } + + private void Transplant(RedBlackTreeNode u, RedBlackTreeNode v) + { + if (u.Parent == RedBlackTreeNode.Void) + _root = v; + else if (u == u.Parent.Left) + u.Parent.Left = v; + else + u.Parent.Right = v; + v.Parent = u.Parent; + } + + private void DeleteFixup(RedBlackTreeNode x) + { + while (x != _root && x.Color == RedBlackTreeNodeColor.Black) + { + if (x == x.Parent.Left) + { + var w = x.Parent.Right; + if (w.Color == RedBlackTreeNodeColor.Red) + { + w.Color = RedBlackTreeNodeColor.Black; + x.Parent.Color = RedBlackTreeNodeColor.Red; + LeftRotate(x.Parent); + w = x.Parent.Right; + } + if (w.Left.Color == RedBlackTreeNodeColor.Black && w.Right.Color == RedBlackTreeNodeColor.Black) + { + w.Color = RedBlackTreeNodeColor.Red; + x = x.Parent; + } + else + { + if (w.Right.Color == RedBlackTreeNodeColor.Black) + { + w.Left.Color = RedBlackTreeNodeColor.Black; + w.Color = RedBlackTreeNodeColor.Red; + RightRotate(w); + w = x.Parent.Right; + } + w.Color = x.Parent.Color; + x.Parent.Color = RedBlackTreeNodeColor.Black; + w.Right.Color = RedBlackTreeNodeColor.Black; + LeftRotate(x.Parent); + x = _root; + } + } + else + { + var w = x.Parent.Left; + if (w.Color == RedBlackTreeNodeColor.Red) + { + w.Color = RedBlackTreeNodeColor.Black; + x.Parent.Color = RedBlackTreeNodeColor.Red; + RightRotate(x.Parent); + w = x.Parent.Left; + } + if (w.Right.Color == RedBlackTreeNodeColor.Black && w.Left.Color == RedBlackTreeNodeColor.Black) + { + w.Color = RedBlackTreeNodeColor.Red; + x = x.Parent; + } + else + { + if (w.Left.Color == RedBlackTreeNodeColor.Black) + { + w.Right.Color = RedBlackTreeNodeColor.Black; + w.Color = RedBlackTreeNodeColor.Red; + LeftRotate(w); + w = x.Parent.Left; + } + w.Color = x.Parent.Color; + x.Parent.Color = RedBlackTreeNodeColor.Black; + w.Left.Color = RedBlackTreeNodeColor.Black; + RightRotate(x.Parent); + x = _root; + } + } + } + x.Color = RedBlackTreeNodeColor.Black; + } + + private void Insert(RedBlackTreeNode z) + { + var x = _root; + var y = RedBlackTreeNode.Void; + while (x != RedBlackTreeNode.Void) + { + y = x; + if (z.Key.CompareTo(x.Key) < 0) + x = x.Left; + else + x = x.Right; + } + z.Parent = y; + if (y == RedBlackTreeNode.Void) + _root = z; + else if (z.Key.CompareTo(y.Key) < 0) + y.Left = z; + else + y.Right = z; + z.Left = RedBlackTreeNode.Void; + z.Right = RedBlackTreeNode.Void; + z.Color = RedBlackTreeNodeColor.Red; + InsertFixup(z); + } + + private void InsertFixup(RedBlackTreeNode z) + { + while (z.Parent.Color == RedBlackTreeNodeColor.Red) + { + if (z.Parent == z.Parent.Parent.Left) + { + var y = z.Parent.Parent.Right; + if (y.Color == RedBlackTreeNodeColor.Red) + { + z.Parent.Color = RedBlackTreeNodeColor.Black; + y.Color = RedBlackTreeNodeColor.Black; + z.Parent.Parent.Color = RedBlackTreeNodeColor.Red; + z = z.Parent.Parent; + } + else + { + if (z == z.Parent.Right) + { + z = z.Parent; + LeftRotate(z); + } + z.Parent.Color = RedBlackTreeNodeColor.Black; + z.Parent.Parent.Color = RedBlackTreeNodeColor.Red; + RightRotate(z.Parent.Parent); + } + } + else + { + var y = z.Parent.Parent.Left; + if (y.Color == RedBlackTreeNodeColor.Red) + { + z.Parent.Color = RedBlackTreeNodeColor.Black; + y.Color = RedBlackTreeNodeColor.Black; + z.Parent.Parent.Color = RedBlackTreeNodeColor.Red; + z = z.Parent.Parent; + } + else + { + if (z == z.Parent.Left) + { + z = z.Parent; + RightRotate(z); + } + z.Parent.Color = RedBlackTreeNodeColor.Black; + z.Parent.Parent.Color = RedBlackTreeNodeColor.Red; + LeftRotate(z.Parent.Parent); + } + } + } + _root.Color = RedBlackTreeNodeColor.Black; + } + + private void LeftRotate(RedBlackTreeNode x) + { + var y = x.Right; + x.Right = y.Left; + if (y.Left != RedBlackTreeNode.Void) + y.Left.Parent = x; + y.Parent = x.Parent; + if (x.Parent == RedBlackTreeNode.Void) + _root = y; + else if (x == x.Parent.Left) + x.Parent.Left = y; + else + x.Parent.Right = y; + y.Left = x; + x.Parent = y; + } + + private void RightRotate(RedBlackTreeNode x) + { + var y = x.Left; + x.Left = y.Right; + if (y.Right != RedBlackTreeNode.Void) + y.Right.Parent = x; + y.Parent = x.Parent; + if (x.Parent == RedBlackTreeNode.Void) + _root = y; + else if (x == x.Parent.Right) + x.Parent.Right = y; + else + x.Parent.Left = y; + y.Right = x; + x.Parent = y; + } + + #endregion + + #region IEnumerable + + public IEnumerator GetEnumerator() + { + foreach (var node in EnumerateNodes()) + { + yield return node.Value; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } +} diff --git a/DryWetMidi/Common/RedBlackTree/RedBlackTreeNode.cs b/DryWetMidi/Common/RedBlackTree/RedBlackTreeNode.cs new file mode 100644 index 000000000..43fb47bae --- /dev/null +++ b/DryWetMidi/Common/RedBlackTree/RedBlackTreeNode.cs @@ -0,0 +1,50 @@ +using System; + +namespace Melanchall.DryWetMidi.Common +{ + internal sealed class RedBlackTreeNode + where TKey : IComparable + { + public static readonly RedBlackTreeNode Void = new RedBlackTreeNode(default(TKey), default(TValue), null); + + public RedBlackTreeNode(TKey key, TValue value, RedBlackTreeNode parent) + { + Key = key; + Value = value; + Parent = parent; + } + + public TKey Key { get; set; } + + public TValue Value { get; set; } + + public RedBlackTreeNode Left { get; set; } + + public RedBlackTreeNode Right { get; set; } + + public RedBlackTreeNode Parent { get; set; } + + public RedBlackTreeNodeColor Color { get; set; } = RedBlackTreeNodeColor.Black; + + public RedBlackTreeNode Clone() + { + if (this == Void) + return Void; + + var node = new RedBlackTreeNode(Key, Value, Parent) + { + Color = Color, + Left = Left.Clone(), + Right = Right.Clone(), + }; + + node.Left.Parent = node.Right.Parent = node; + return node; + } + + public override string ToString() + { + return this != Void ? $"{Key}: {Value}" : ""; + } + } +} diff --git a/DryWetMidi/Common/RedBlackTree/RedBlackTreeNodeColor.cs b/DryWetMidi/Common/RedBlackTree/RedBlackTreeNodeColor.cs new file mode 100644 index 000000000..a2387aaaa --- /dev/null +++ b/DryWetMidi/Common/RedBlackTree/RedBlackTreeNodeColor.cs @@ -0,0 +1,8 @@ +namespace Melanchall.DryWetMidi.Common +{ + internal enum RedBlackTreeNodeColor + { + Red, + Black, + } +} diff --git a/DryWetMidi/Interaction/TimedObject/ChangedTimedObject.cs b/DryWetMidi/Interaction/TimedObject/ChangedTimedObject.cs new file mode 100644 index 000000000..c02029b1e --- /dev/null +++ b/DryWetMidi/Interaction/TimedObject/ChangedTimedObject.cs @@ -0,0 +1,56 @@ +namespace Melanchall.DryWetMidi.Interaction +{ + public sealed class ChangedTimedObject + { + #region Constructor + + public ChangedTimedObject( + ITimedObject timedObject, + long oldTime) + { + TimedObject = timedObject; + OldTime = oldTime; + } + + #endregion + + #region Properties + + public ITimedObject TimedObject { get; } + + public long OldTime { get; } + + #endregion + + #region Overrides + + public override bool Equals(object obj) + { + var changedTimedObject = obj as ChangedTimedObject; + if (changedTimedObject == null) + return false; + + return + object.ReferenceEquals(TimedObject, changedTimedObject.TimedObject) && + OldTime == changedTimedObject.OldTime; + } + + public override int GetHashCode() + { + unchecked + { + var result = 17; + result = result * 23 + TimedObject.GetHashCode(); + result = result * 23 + OldTime.GetHashCode(); + return result; + } + } + + public override string ToString() + { + return $"{OldTime}: {TimedObject}"; + } + + #endregion + } +} diff --git a/DryWetMidi/Interaction/TimedObject/IObservableTimedObjectsCollection.cs b/DryWetMidi/Interaction/TimedObject/IObservableTimedObjectsCollection.cs new file mode 100644 index 000000000..e5276bd6e --- /dev/null +++ b/DryWetMidi/Interaction/TimedObject/IObservableTimedObjectsCollection.cs @@ -0,0 +1,13 @@ +using System; + +namespace Melanchall.DryWetMidi.Interaction +{ + public interface IObservableTimedObjectsCollection + { + #region Events + + event EventHandler CollectionChanged; + + #endregion + } +} diff --git a/DryWetMidi/Interaction/TimedObject/ObservableTimedObjectsCollection.cs b/DryWetMidi/Interaction/TimedObject/ObservableTimedObjectsCollection.cs new file mode 100644 index 000000000..0cad7a577 --- /dev/null +++ b/DryWetMidi/Interaction/TimedObject/ObservableTimedObjectsCollection.cs @@ -0,0 +1,292 @@ +using Melanchall.DryWetMidi.Common; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Melanchall.DryWetMidi.Interaction +{ + public sealed class ObservableTimedObjectsCollection : IObservableTimedObjectsCollection, IEnumerable + { + #region Events + + public event EventHandler CollectionChanged; + + #endregion + + #region Fields + + private readonly List _objects = new List(); + private ICollection _collectionChangedEventsArgs; + + #endregion + + #region Constructor + + public ObservableTimedObjectsCollection() + { + } + + public ObservableTimedObjectsCollection(IEnumerable timedObjects) + { + ThrowIfArgument.IsNull(nameof(timedObjects), timedObjects); + + Add(timedObjects); + } + + #endregion + + #region Properties + + public int Count => _objects.Count; + + #endregion + + #region Methods + + public void ChangeCollection(Action change) + { + ThrowIfArgument.IsNull(nameof(change), change); + + var deepChange = _collectionChangedEventsArgs != null; + _collectionChangedEventsArgs = _collectionChangedEventsArgs ?? new List(); + + try + { + change(); + + if (!deepChange) + OnCollectionChanged(_collectionChangedEventsArgs); + } + finally + { + if (!deepChange) + _collectionChangedEventsArgs = null; + } + } + + public void ChangeObject(ITimedObject timedObject, Action change) + { + ThrowIfArgument.IsNull(nameof(timedObject), timedObject); + ThrowIfArgument.IsNull(nameof(change), change); + + var oldTime = timedObject.Time; + change(timedObject); + HandleObjectChanged(timedObject, oldTime); + } + + public void Add(IEnumerable timedObjects) + { + ThrowIfArgument.IsNull(nameof(timedObjects), timedObjects); + + var addedObjects = timedObjects.Where(o => o != null).ToList(); + _objects.AddRange(addedObjects); + + HandleObjectsAdded(addedObjects); + } + + public void Add(params ITimedObject[] timedObjects) + { + ThrowIfArgument.IsNull(nameof(timedObjects), timedObjects); + + Add((IEnumerable)timedObjects); + } + + public bool Remove(IEnumerable objects) + { + ThrowIfArgument.IsNull(nameof(objects), objects); + + var removedObjects = new List(); + + foreach (var obj in objects) + { + if (obj != null && _objects.Remove(obj)) + removedObjects.Add(obj); + } + + HandleObjectsRemoved(removedObjects); + + return removedObjects.Any(); + } + + public bool Remove(params ITimedObject[] objects) + { + ThrowIfArgument.IsNull(nameof(objects), objects); + + return Remove((IEnumerable)objects); + } + + // TODO: test + public void Clear() + { + var removedObjects = _objects.ToList(); + _objects.Clear(); + + HandleObjectsRemoved(removedObjects); + } + + private void HandleObjectsAdded(IEnumerable addedObjects) + { + var eventsArgs = _collectionChangedEventsArgs ?? new List(); + var eventArgs = new ObservableTimedObjectsCollectionChangedEventArgs(); + + var eventArgsAddedObjects = eventArgs.AddedObjects; + if (eventArgsAddedObjects == null) + eventArgs.AddedObjects = eventArgsAddedObjects = new List(); + + foreach (var obj in addedObjects) + { + eventArgsAddedObjects.Add(obj); + + //var notifyTimeChanged = obj as INotifyTimeChanged; + //if (notifyTimeChanged != null) + // notifyTimeChanged.TimeChanged += OnObjectTimeChanged; + } + + eventsArgs.Add(eventArgs); + if (!IsInBatchOperation()) + OnCollectionChanged(eventsArgs); + } + + private void HandleObjectsRemoved(IEnumerable removedObjects) + { + var eventsArgs = _collectionChangedEventsArgs ?? new List(); + var eventArgs = new ObservableTimedObjectsCollectionChangedEventArgs(); + + var eventArgsRemovedObjects = eventArgs.RemovedObjects; + if (eventArgsRemovedObjects == null) + eventArgs.RemovedObjects = eventArgsRemovedObjects = new List(); + + foreach (var obj in removedObjects) + { + eventArgsRemovedObjects.Add(obj); + + //var notifyTimeChanged = obj as INotifyTimeChanged; + //if (notifyTimeChanged != null) + // notifyTimeChanged.TimeChanged -= OnObjectTimeChanged; + } + + eventsArgs.Add(eventArgs); + if (!IsInBatchOperation()) + OnCollectionChanged(eventsArgs); + } + + private void HandleObjectChanged(ITimedObject timedObject, long oldTime) + { + var eventsArgs = _collectionChangedEventsArgs ?? new List(); + var eventArgs = new ObservableTimedObjectsCollectionChangedEventArgs(); + + var eventArgsChangedObjects = eventArgs.ChangedObjects; + if (eventArgsChangedObjects == null) + eventArgs.ChangedObjects = eventArgsChangedObjects = new List(); + + var changedTimedObject = new ChangedTimedObject(timedObject, oldTime); + + if (!eventArgsChangedObjects.Contains(changedTimedObject)) + { + eventArgsChangedObjects.Add(changedTimedObject); + } + + eventsArgs.Add(eventArgs); + if (!IsInBatchOperation()) + OnCollectionChanged(eventsArgs); + } + + private void OnCollectionChanged(ICollection eventsArgs) + { + var allAddedObjects = new List(); + var allRemovedObjects = new List(); + var allChangedObjects = new List(); + + foreach (var eventArgs in eventsArgs) + { + var addedObjects = GetNonNullList(eventArgs.AddedObjects); + var removedObjects = GetNonNullList(eventArgs.RemovedObjects); + var changedObjects = GetNonNullList(eventArgs.ChangedObjects); + + allAddedObjects.AddRange(addedObjects); + allRemovedObjects.AddRange(removedObjects); + allChangedObjects.AddRange(changedObjects); + } + + var changedObjectsHashSet = new HashSet(); + + for (var i = 0; i < allChangedObjects.Count; i++) + { + var changedObject = allChangedObjects[i]; + if (!changedObjectsHashSet.Add(changedObject.TimedObject)) + { + allChangedObjects.RemoveAt(i); + i--; + } + } + + var finalAddedObjects = new List(allAddedObjects); + var finalRemovedObjects = new List(allRemovedObjects); + var finalChangedObjects = new List(allChangedObjects); + + foreach (var obj in allRemovedObjects) + { + if (finalAddedObjects.Remove(obj)) + finalRemovedObjects.Remove(obj); + + var removedChangedObjects = finalChangedObjects.Where(c => object.ReferenceEquals(c.TimedObject, obj)).ToArray(); + foreach (var removedChangedObject in removedChangedObjects) + { + finalChangedObjects.Remove(removedChangedObject); + } + } + + foreach (var obj in allAddedObjects) + { + finalRemovedObjects.Remove(obj); + } + + var args = new ObservableTimedObjectsCollectionChangedEventArgs + { + AddedObjects = finalAddedObjects, + RemovedObjects = finalRemovedObjects, + ChangedObjects = finalChangedObjects, + }; + + if (!args.HasData) + return; + + CollectionChanged?.Invoke(this, args); + } + + private List GetNonNullList(IEnumerable source) + { + return (source ?? Enumerable.Empty()).ToList(); + } + + private bool IsInBatchOperation() + { + return _collectionChangedEventsArgs != null; + } + + #endregion + + #region IEnumerable + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return _objects.OrderBy(obj => obj.Time).GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + } +} diff --git a/DryWetMidi/Interaction/TimedObject/ObservableTimedObjectsCollectionChangedEventArgs.cs b/DryWetMidi/Interaction/TimedObject/ObservableTimedObjectsCollectionChangedEventArgs.cs new file mode 100644 index 000000000..fdf70834b --- /dev/null +++ b/DryWetMidi/Interaction/TimedObject/ObservableTimedObjectsCollectionChangedEventArgs.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Melanchall.DryWetMidi.Interaction +{ + public sealed class ObservableTimedObjectsCollectionChangedEventArgs : EventArgs + { + #region Properties + + public ICollection AddedObjects { get; set; } + + public ICollection RemovedObjects { get; set; } + + public ICollection ChangedObjects { get; set; } + + public bool HasData + { + get + { + return + AddedObjects?.Any() == true || + RemovedObjects?.Any() == true || + ChangedObjects?.Any() == true; + } + } + + #endregion + } +} diff --git a/DryWetMidi/Multimedia/Playback/Playback.DataManagement.cs b/DryWetMidi/Multimedia/Playback/Playback.DataManagement.cs new file mode 100644 index 000000000..477d60096 --- /dev/null +++ b/DryWetMidi/Multimedia/Playback/Playback.DataManagement.cs @@ -0,0 +1,274 @@ +using Melanchall.DryWetMidi.Common; +using Melanchall.DryWetMidi.Interaction; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Melanchall.DryWetMidi.Multimedia +{ + public partial class Playback + { + #region Fields + + private readonly HashSet _notesMetadataHashSet = new HashSet(); + + #endregion + + #region Methods + + private void SubscribeToObjectsCollectionChanged(IEnumerable timedObjects) + { + var observableTimedObjectsCollection = timedObjects as IObservableTimedObjectsCollection; + if (observableTimedObjectsCollection != null) + observableTimedObjectsCollection.CollectionChanged += OnObservableTimedObjectsCollectionChanged; + } + + private void OnObservableTimedObjectsCollectionChanged(object sender, ObservableTimedObjectsCollectionChangedEventArgs e) + { + // TODO: what about tempo map events changes + + lock (_playbackLockObject) + { + if (e.AddedObjects != null) + { + foreach (var obj in e.AddedObjects) + { + AddNewTimedObject(obj); + } + } + + if (e.RemovedObjects != null) + { + foreach (var obj in e.RemovedObjects) + { + RemoveTimedObject(obj, obj.Time); + } + } + + if (e.ChangedObjects != null) + { + foreach (var changedObject in e.ChangedObjects) + { + var obj = changedObject.TimedObject; + var oldTime = changedObject.OldTime; + + RemoveTimedObject(obj, oldTime); + AddNewTimedObject(obj); + } + } + + // TODO: optimize (see below for add, need to think about remove) + var lastPlaybackEvent = _playbackEvents.GetMaximumNode().Value; + _duration = lastPlaybackEvent?.Time ?? TimeSpan.Zero; + _durationInTicks = lastPlaybackEvent?.RawTime ?? 0; + + if (!_playbackEvents.Any()) + { + _playbackEventsPosition = null; + _beforeStart = true; + } + + if (IsRunning) + { + SendTrackedData(); + StopStartNotes(); + _clock?.Start(); + } + + var currentTime = _clock.CurrentTime; + if (!Loop && currentTime >= _duration) + { + _clock.StopInternally(); + OnFinished(); + return; + } + } + } + + private void AddNewTimedObject(ITimedObject timedObject) + { + AddTimedObject(timedObject, TempoMap, false); + } + + private void RemoveTimedObject(ITimedObject timedObject, long oldTime) + { + var time = TimeConverter.ConvertTo(oldTime, TempoMap); + var playbackEventsNodes = _playbackEvents + .SearchNodes(time) + .Where(n => object.ReferenceEquals(n.Value.ObjectReference, timedObject)) + .SelectMany(n => n.Value.EventsGroup ?? Enumerable.Empty>()) + .Distinct() + .ToArray(); + + foreach (var playbackEventNode in playbackEventsNodes) + { + var playbackEvent = playbackEventNode.Value; + + if (playbackEventNode == _playbackEventsPosition) + { + _playbackEventsPosition = _playbackEvents.GetNextNode(_playbackEventsPosition); + if (_playbackEventsPosition == null) + _beforeStart = false; + } + + _playbackEvents.Delete(playbackEventNode); + + var noteMetadata = playbackEvent.Metadata.Note; + if (noteMetadata != null) + { + _notesMetadataHashSet.Remove(noteMetadata); + + var metadataNodes = _notesMetadata.SearchNodes(noteMetadata.StartTime); + + foreach (var metadataNode in metadataNodes) + { + if (metadataNode.Value == noteMetadata) + _notesMetadata.Delete(metadataNode); + } + } + + _playbackDataTracker.RemoveData(playbackEvent.Event, oldTime); + } + } + + private void InitializePlaybackEvents(IEnumerable timedObjects, TempoMap tempoMap) + { + foreach (var timedObject in timedObjects) + { + AddTimedObject(timedObject, tempoMap, true); + } + } + + private void AddTimedObject(ITimedObject timedObject, TempoMap tempoMap, bool isInitialObject) + { + var playbackEvents = GetPlaybackEvents(timedObject, tempoMap); + var eventsGroup = new HashSet>(); + + var minTime = TimeSpan.MaxValue; + + foreach (var e in playbackEvents) + { + var node = _playbackEvents.Add(e.Time, e); + e.EventsGroup = eventsGroup; + + eventsGroup.Add(node); + + var noteMetadata = e.Metadata.Note; + if (noteMetadata != null && _notesMetadataHashSet.Add(noteMetadata)) + _notesMetadata.Add(noteMetadata.StartTime, noteMetadata); + + _playbackDataTracker.InitializeData( + e.Event, + e.RawTime, + e.Metadata.TimedEvent.Metadata); + + if (!isInitialObject && _hasBeenStarted && e.Time > _clock.CurrentTime && (_playbackEventsPosition == null || e.Time < _playbackEventsPosition.Key) && e.Time < minTime) + { + _playbackEventsPosition = node; + minTime = e.Time; + } + + // TODO: see above + //if (e.Time > _duration) + //{ + // _duration = e.Time; + // _durationInTicks = e.RawTime; + //} + } + } + + private IEnumerable GetPlaybackEvents(ITimedObject timedObject, TempoMap tempoMap) + { + var result = new List(); + + var customObjectProcessed = false; + foreach (var e in GetTimedEvents(timedObject)) + { + result.Add(GetPlaybackEvent(e, tempoMap)); + customObjectProcessed = true; + } + + if (customObjectProcessed) + return result; + + var chord = timedObject as Chord; + if (chord != null) + { + return GetPlaybackEvents(chord, tempoMap); + } + + var note = timedObject as Note; + if (note != null) + { + return GetPlaybackEvents(note, tempoMap, note); + } + + var timedEvent = timedObject as TimedEvent; + if (timedEvent != null) + { + result.Add(GetPlaybackEvent(timedEvent, tempoMap)); + return result; + } + + var registeredParameter = timedObject as RegisteredParameter; + if (registeredParameter != null) + { + result.AddRange(registeredParameter.GetTimedEvents().Select(e => GetPlaybackEvent(e, tempoMap))); + return result; + } + + return result; + } + + private PlaybackEvent GetPlaybackEvent(TimedEvent timedEvent, TempoMap tempoMap) + { + var playbackEvent = CreatePlaybackEvent(timedEvent, tempoMap, timedEvent); + playbackEvent.Metadata.TimedEvent = new TimedEventPlaybackEventMetadata((timedEvent as IMetadata)?.Metadata); + return playbackEvent; + } + + private IEnumerable GetPlaybackEvents(Chord chord, TempoMap tempoMap) + { + foreach (var note in chord.Notes) + { + foreach (var playbackEvent in GetPlaybackEvents(note, tempoMap, chord)) + { + yield return playbackEvent; + } + } + } + + private IEnumerable GetPlaybackEvents(Note note, TempoMap tempoMap, ITimedObject objectReference) + { + TimeSpan noteStartTime = note.TimeAs(tempoMap); + TimeSpan noteEndTime = TimeConverter.ConvertTo(note.EndTime, tempoMap); + var noteMetadata = new NotePlaybackEventMetadata(note, noteStartTime, noteEndTime); + + yield return GetPlaybackEventWithNoteMetadata(note.GetTimedNoteOnEvent(), tempoMap, noteMetadata, objectReference); + yield return GetPlaybackEventWithNoteMetadata(note.GetTimedNoteOffEvent(), tempoMap, noteMetadata, objectReference); + } + + private PlaybackEvent GetPlaybackEventWithNoteMetadata( + TimedEvent timedEvent, + TempoMap tempoMap, + NotePlaybackEventMetadata noteMetadata, + ITimedObject objectReference) + { + var playbackEvent = CreatePlaybackEvent(timedEvent, tempoMap, objectReference); + playbackEvent.Metadata.Note = noteMetadata; + playbackEvent.Metadata.TimedEvent = new TimedEventPlaybackEventMetadata((timedEvent as IMetadata)?.Metadata); + return playbackEvent; + } + + private PlaybackEvent CreatePlaybackEvent(TimedEvent timedEvent, TempoMap tempoMap, ITimedObject objectReference) + { + return new PlaybackEvent( + timedEvent.Event, + timedEvent.TimeAs(tempoMap), + timedEvent.Time, + objectReference); + } + + #endregion + } +} diff --git a/DryWetMidi/Multimedia/Playback/Playback.cs b/DryWetMidi/Multimedia/Playback/Playback.cs index 5b840e841..f4304a6b6 100644 --- a/DryWetMidi/Multimedia/Playback/Playback.cs +++ b/DryWetMidi/Multimedia/Playback/Playback.cs @@ -8,6 +8,9 @@ using Melanchall.DryWetMidi.Core; using Melanchall.DryWetMidi.Interaction; +using PlaybackEventsCollection = Melanchall.DryWetMidi.Common.RedBlackTree; +using NotePlaybackEventMetadataCollection = Melanchall.DryWetMidi.Common.RedBlackTree; + namespace Melanchall.DryWetMidi.Multimedia { /// @@ -18,7 +21,7 @@ namespace Melanchall.DryWetMidi.Multimedia /// You can derive from the class to create your own playback logic. /// Please see Custom playback article to learn more. /// - public class Playback : IDisposable, IClockDrivenObject + public partial class Playback : IDisposable, IClockDrivenObject { #region Constants @@ -78,11 +81,13 @@ public class Playback : IDisposable, IClockDrivenObject #region Fields - private readonly PlaybackEvent[] _playbackEvents; - private int _playbackEventsPosition = -1; + private readonly PlaybackEventsCollection _playbackEvents = new PlaybackEventsCollection(); + private readonly object _playbackLockObject = new object(); + private RedBlackTreeNode _playbackEventsPosition; + private bool _beforeStart = true; - private readonly TimeSpan _duration; - private readonly long _durationInTicks; + private TimeSpan _duration = TimeSpan.Zero; + private long _durationInTicks; private ITimeSpan _playbackStart; private ITimeSpan _playbackEnd; @@ -93,8 +98,7 @@ public class Playback : IDisposable, IClockDrivenObject private readonly MidiClock _clock; private readonly ConcurrentDictionary _activeNotesMetadata = new ConcurrentDictionary(); - private readonly HashSet _notesMetadataHashSet = new HashSet(); - private readonly NotePlaybackEventMetadata[] _notesMetadata; + private readonly NotePlaybackEventMetadataCollection _notesMetadata = new NotePlaybackEventMetadataCollection(); private readonly PlaybackDataTracker _playbackDataTracker; @@ -127,6 +131,8 @@ public Playback(IEnumerable timedObjects, TempoMap tempoMap, Playb ThrowIfArgument.IsNull(nameof(timedObjects), timedObjects); ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); + SubscribeToObjectsCollectionChanged(timedObjects); + playbackSettings = playbackSettings ?? new PlaybackSettings(); TempoMap = tempoMap; @@ -134,15 +140,13 @@ public Playback(IEnumerable timedObjects, TempoMap tempoMap, Playb var noteDetectionSettings = playbackSettings.NoteDetectionSettings ?? new NoteDetectionSettings(); _playbackDataTracker = new PlaybackDataTracker(TempoMap); - _playbackEvents = GetPlaybackEvents(timedObjects.GetNotesAndTimedEventsLazy(noteDetectionSettings, true), tempoMap); + InitializePlaybackEvents(timedObjects.GetNotesAndTimedEventsLazy(noteDetectionSettings, true), tempoMap); MoveToNextPlaybackEvent(); - var lastPlaybackEvent = _playbackEvents.LastOrDefault(); + var lastPlaybackEvent = _playbackEvents.GetMaximumNode().Value; _duration = lastPlaybackEvent?.Time ?? TimeSpan.Zero; _durationInTicks = lastPlaybackEvent?.RawTime ?? 0; - _notesMetadata = _notesMetadataHashSet.ToArray(); - var clockSettings = playbackSettings.ClockSettings ?? new MidiClockSettings(); _clock = new MidiClock(false, clockSettings.CreateTickGeneratorCallback(), ClockInterval); _clock.Ticked += OnClockTicked; @@ -747,7 +751,7 @@ public void MoveToStart() /// An error occurred on device. public void MoveToTime(ITimeSpan time) { - MoveToTime(time, 0, _playbackEvents.Length - 1); + MoveToTimeInternal(time); } /// @@ -763,10 +767,7 @@ public void MoveForward(ITimeSpan step) EnsureIsNotDisposed(); var currentTime = (MetricTimeSpan)_clock.CurrentTime; - MoveToTime( - currentTime.Add(step, TimeSpanMode.TimeLength), - _playbackEventsPosition, - _playbackEvents.Length - 1); + MoveToTimeInternal(currentTime.Add(step, TimeSpanMode.TimeLength)); } /// @@ -782,12 +783,10 @@ public void MoveBack(ITimeSpan step) EnsureIsNotDisposed(); var currentTime = (MetricTimeSpan)_clock.CurrentTime; - MoveToTime( + MoveToTimeInternal( TimeConverter.ConvertTo(step, TempoMap) > currentTime ? new MetricTimeSpan() - : currentTime.Subtract(step, TimeSpanMode.TimeLength), - 0, - _playbackEventsPosition); + : currentTime.Subtract(step, TimeSpanMode.TimeLength)); } /// @@ -844,10 +843,10 @@ private void StopStartNotes() var notesToPlay = GetNotesMetadataAtCurrentTime(); var onNotesMetadata = notesToPlay.Any() - ? notesToPlay.Where(n => !_activeNotesMetadata.Keys.Contains(n)).ToArray() + ? notesToPlay.Where(n => !_activeNotesMetadata.Keys.Any(nn => new NoteId(nn.RawNote.Channel, nn.RawNote.NoteNumber).Equals(new NoteId(n.RawNote.Channel, n.RawNote.NoteNumber)))).ToArray() : new NotePlaybackEventMetadata[0]; var offNotesMetadata = notesToPlay.Any() - ? _activeNotesMetadata.Keys.Where(n => !notesToPlay.Contains(n)).ToArray() + ? _activeNotesMetadata.Keys.Where(n => !notesToPlay.Any(nn => new NoteId(nn.RawNote.Channel, nn.RawNote.NoteNumber).Equals(new NoteId(n.RawNote.Channel, n.RawNote.NoteNumber)))).ToArray() : _activeNotesMetadata.Keys; OutputDevice?.PrepareForEventsSending(); @@ -878,36 +877,39 @@ private void StopStartNotes() private ICollection GetNotesMetadataAtCurrentTime() { var currentTime = _clock.CurrentTime; + var notesToPlay = new List(); + + var firstNode = _notesMetadata.GetLastNodeBelowThreshold( + currentTime, + node => node.Value.EndTime); + + if (firstNode == null) + firstNode = _notesMetadata.GetMinimumNode(); - int firstIndex; - MathUtilities.GetLastElementBelowThreshold( - _notesMetadata, - currentTime.Ticks, - m => m.EndTime.Ticks, - out firstIndex); + if (firstNode == RedBlackTreeNode.Void) + firstNode = null; - while (++firstIndex < _notesMetadata.Length && _notesMetadata[firstIndex].EndTime <= currentTime) + while (firstNode != null && firstNode.Value.EndTime <= currentTime) { + firstNode = _notesMetadata.GetNextNode(firstNode); } - var notesToPlay = new List(); - - if (firstIndex < _notesMetadata.Length) + if (firstNode != null) { - int lastIndex; - MathUtilities.GetLastElementBelowThreshold( - _notesMetadata, - firstIndex, - _notesMetadata.Length - 1, - currentTime.Ticks, - m => m.StartTime.Ticks, - out lastIndex); - - for (var i = firstIndex; i <= lastIndex; i++) + var lastNode = _notesMetadata.GetLastNodeBelowThreshold( + currentTime, + node => node.Value.StartTime); + + while (firstNode != null) { - var metadata = _notesMetadata[i]; + var metadata = firstNode.Value; if (metadata.StartTime < currentTime && metadata.EndTime > currentTime) notesToPlay.Add(metadata); + + if (firstNode == lastNode) + break; + + firstNode = _notesMetadata.GetNextNode(firstNode); } } @@ -958,66 +960,69 @@ private void OnDeviceErrorOccurred(Exception exception) private void OnClockTicked(object sender, EventArgs e) { - do + lock (_playbackLockObject) { - var time = _clock.CurrentTime; - if (time >= _playbackEndMetric) - break; + do + { + var time = _clock.CurrentTime; + if (time >= _playbackEndMetric) + break; - var playbackEvent = GetCurrentPlaybackEvent(); - if (playbackEvent == null) - continue; + var playbackEvent = GetCurrentPlaybackEvent(); + if (playbackEvent == null) + continue; - if (playbackEvent.Time > time) - return; + if (playbackEvent.Time > time) + return; - var midiEvent = playbackEvent.Event; - if (midiEvent == null) - continue; + var midiEvent = playbackEvent.Event; + if (midiEvent == null) + continue; - if (!IsRunning) - return; + if (!IsRunning) + return; - Note note; - if (TryPlayNoteEvent(playbackEvent, out note)) - { - if (note != null) + Note note; + if (TryPlayNoteEvent(playbackEvent, out note)) { - if (playbackEvent.Event is NoteOnEvent) - OnNotesPlaybackStarted(note); - else - OnNotesPlaybackFinished(note); + if (note != null) + { + if (playbackEvent.Event is NoteOnEvent) + OnNotesPlaybackStarted(note); + else + OnNotesPlaybackFinished(note); + } + + continue; } - continue; - } - - var eventCallback = EventCallback; - if (eventCallback != null) - midiEvent = eventCallback(midiEvent.Clone(), playbackEvent.RawTime, time); + var eventCallback = EventCallback; + if (eventCallback != null) + midiEvent = eventCallback(midiEvent.Clone(), playbackEvent.RawTime, time); - if (midiEvent == null) - continue; + if (midiEvent == null) + continue; - PlayEvent(midiEvent, playbackEvent.Metadata.TimedEvent.Metadata); - } - while (MoveToNextPlaybackEvent()); + PlayEvent(midiEvent, playbackEvent.Metadata.TimedEvent.Metadata); + } + while (MoveToNextPlaybackEvent()); - if (!Loop) - { - _clock.StopInternally(); - OnFinished(); - return; - } + if (!Loop) + { + _clock.StopInternally(); + OnFinished(); + return; + } - _clock.StopShortly(); - _clock.ResetCurrentTime(); - ResetPlaybackEventsPosition(); - MoveToNextPlaybackEvent(); + _clock.StopShortly(); + _clock.ResetCurrentTime(); + ResetPlaybackEventsPosition(); + MoveToNextPlaybackEvent(); - MoveToStart(); - _clock.Start(); - OnRepeatStarted(); + MoveToStart(); + _clock.Start(); + OnRepeatStarted(); + } } private void EnsureIsNotDisposed() @@ -1026,55 +1031,52 @@ private void EnsureIsNotDisposed() throw new ObjectDisposedException("Playback is disposed."); } - private void MoveToTime(ITimeSpan time, int firstIndex, int lastIndex) + private void MoveToTimeInternal(ITimeSpan time) { ThrowIfArgument.IsNull(nameof(time), time); EnsureIsNotDisposed(); - if (TimeConverter.ConvertFrom(time, TempoMap) > _durationInTicks) - time = (MetricTimeSpan)_duration; + lock (_playbackLockObject) + { + if (TimeConverter.ConvertFrom(time, TempoMap) > _durationInTicks) + time = (MetricTimeSpan)_duration; - var isRunning = IsRunning; + var isRunning = IsRunning; - SetStartTime(time, firstIndex, lastIndex); + SetStartTime(time); - if (isRunning) - { - SendTrackedData(); - StopStartNotes(); - _clock.Start(); + if (isRunning) + { + SendTrackedData(); + StopStartNotes(); + _clock?.Start(); + } } } - private void SetStartTime(ITimeSpan time, int firstIndex, int lastIndex) + private void SetStartTime(ITimeSpan time) { var convertedTime = (TimeSpan)TimeConverter.ConvertTo(time, TempoMap); _clock.SetCurrentTime(convertedTime); if (convertedTime.Ticks == 0) { - _playbackEventsPosition = 0; + _playbackEventsPosition = _playbackEvents.GetMinimumNode(); return; } if (convertedTime > _duration) { - _playbackEventsPosition = _playbackEvents.Length; + _playbackEventsPosition = null; + _beforeStart = false; return; } - if (firstIndex >= lastIndex) - return; - - MathUtilities.GetLastElementBelowThreshold( - _playbackEvents, - Math.Max(firstIndex, 0), - Math.Min(lastIndex, _playbackEvents.Length - 1), - convertedTime.Ticks, - e => e.Time.Ticks, - out _playbackEventsPosition); + _playbackEventsPosition = _playbackEvents.GetLastNodeBelowThreshold(convertedTime); + if (_playbackEventsPosition == null) + _beforeStart = true; - _playbackEventsPosition++; + MoveToNextPlaybackEvent(); } private void PlayEvent(MidiEvent midiEvent, object metadata) @@ -1156,130 +1158,31 @@ private bool TryPlayNoteEvent(NotePlaybackEventMetadata noteMetadata, MidiEvent return true; } - private PlaybackEvent[] GetPlaybackEvents(IEnumerable timedObjects, TempoMap tempoMap) - { - var playbackEvents = new List(); - - foreach (var timedObject in timedObjects) - { - var customObjectProcessed = false; - foreach (var e in GetTimedEvents(timedObject)) - { - playbackEvents.Add(GetPlaybackEvent(e, tempoMap)); - customObjectProcessed = true; - } - - if (customObjectProcessed) - continue; - - var chord = timedObject as Chord; - if (chord != null) - { - playbackEvents.AddRange(GetPlaybackEvents(chord, tempoMap)); - continue; - } - - var note = timedObject as Note; - if (note != null) - { - playbackEvents.AddRange(GetPlaybackEvents(note, tempoMap)); - continue; - } - - var timedEvent = timedObject as TimedEvent; - if (timedEvent != null) - { - playbackEvents.Add(GetPlaybackEvent(timedEvent, tempoMap)); - continue; - } - - var registeredParameter = timedObject as RegisteredParameter; - if (registeredParameter != null) - { - playbackEvents.AddRange(registeredParameter.GetTimedEvents().Select(e => GetPlaybackEvent(e, tempoMap))); - continue; - } - } - - return playbackEvents.OrderBy(e => e, new PlaybackEventsComparer()).ToArray(); - } - - private PlaybackEvent GetPlaybackEvent(TimedEvent timedEvent, TempoMap tempoMap) - { - var playbackEvent = CreatePlaybackEvent(timedEvent, tempoMap); - playbackEvent.Metadata.TimedEvent = new TimedEventPlaybackEventMetadata((timedEvent as IMetadata)?.Metadata); - InitializeTrackedData(playbackEvent); - return playbackEvent; - } - - private IEnumerable GetPlaybackEvents(Chord chord, TempoMap tempoMap) - { - foreach (var note in chord.Notes) - { - foreach (var playbackEvent in GetPlaybackEvents(note, tempoMap)) - { - yield return playbackEvent; - } - } - } - - private IEnumerable GetPlaybackEvents(Note note, TempoMap tempoMap) - { - TimeSpan noteStartTime = note.TimeAs(tempoMap); - TimeSpan noteEndTime = TimeConverter.ConvertTo(note.EndTime, tempoMap); - var noteMetadata = new NotePlaybackEventMetadata(note, noteStartTime, noteEndTime); - - yield return GetPlaybackEventWithNoteMetadata(note.TimedNoteOnEvent, tempoMap, noteMetadata); - yield return GetPlaybackEventWithNoteMetadata(note.TimedNoteOffEvent, tempoMap, noteMetadata); - } - - private PlaybackEvent GetPlaybackEventWithNoteMetadata(TimedEvent timedEvent, TempoMap tempoMap, NotePlaybackEventMetadata noteMetadata) - { - var playbackEvent = CreatePlaybackEvent(timedEvent, tempoMap); - playbackEvent.Metadata.Note = noteMetadata; - playbackEvent.Metadata.TimedEvent = new TimedEventPlaybackEventMetadata((timedEvent as IMetadata)?.Metadata); - InitializeTrackedData(playbackEvent); - return playbackEvent; - } - - private PlaybackEvent CreatePlaybackEvent(TimedEvent timedEvent, TempoMap tempoMap) - { - return new PlaybackEvent(timedEvent.Event, timedEvent.TimeAs(tempoMap), timedEvent.Time); - } - - private void InitializeTrackedData(PlaybackEvent playbackEvent) - { - _playbackDataTracker.InitializeData( - playbackEvent.Event, - playbackEvent.RawTime, - playbackEvent.Metadata.TimedEvent.Metadata); - - var noteMetadata = playbackEvent.Metadata.Note; - if (noteMetadata != null) - _notesMetadataHashSet.Add(noteMetadata); - } - private void ResetPlaybackEventsPosition() { - _playbackEventsPosition = -1; + _playbackEventsPosition = null; + _beforeStart = true; } private bool MoveToNextPlaybackEvent() { - _playbackEventsPosition++; + _playbackEventsPosition = _playbackEventsPosition != null + ? _playbackEvents.GetNextNode(_playbackEventsPosition) + : (_beforeStart ? _playbackEvents.GetMinimumNode() : null); + _beforeStart = _playbackEventsPosition != null; return IsPlaybackEventsPositionValid(); } private PlaybackEvent GetCurrentPlaybackEvent() { return IsPlaybackEventsPositionValid() - ? _playbackEvents[_playbackEventsPosition] + ? _playbackEventsPosition.Value : null; } private bool IsPlaybackEventsPositionValid() { - return _playbackEventsPosition >= 0 && _playbackEventsPosition < _playbackEvents.Length; + return _playbackEventsPosition != null; } #endregion diff --git a/DryWetMidi/Multimedia/Playback/PlaybackDataTracker.cs b/DryWetMidi/Multimedia/Playback/PlaybackDataTracker.cs index 3344baaed..a382abc73 100644 --- a/DryWetMidi/Multimedia/Playback/PlaybackDataTracker.cs +++ b/DryWetMidi/Multimedia/Playback/PlaybackDataTracker.cs @@ -59,7 +59,7 @@ protected DataChange(TData data, object metadata, bool isDefault) public bool IsDefault { get; } } - private sealed class ProgramChange : DataChange + private sealed class ProgramChange : DataChange, IEquatable { public ProgramChange(SevenBitNumber programNumber, object metadata) : base(programNumber, metadata) @@ -70,9 +70,16 @@ public ProgramChange(SevenBitNumber programNumber, object metadata, bool isDefau : base(programNumber, metadata, isDefault) { } + + public bool Equals(ProgramChange other) + { + return other != null && + Data == other.Data && + IsDefault == other.IsDefault; + } } - private sealed class PitchValueChange : DataChange + private sealed class PitchValueChange : DataChange, IEquatable { public PitchValueChange(ushort pitchValue, object metadata) : base(pitchValue, metadata) @@ -83,9 +90,16 @@ public PitchValueChange(ushort pitchValue, object metadata, bool isDefault) : base(pitchValue, metadata, isDefault) { } + + public bool Equals(PitchValueChange other) + { + return other != null && + Data == other.Data && + IsDefault == other.IsDefault; + } } - private sealed class ControlValueChange : DataChange + private sealed class ControlValueChange : DataChange, IEquatable { public ControlValueChange(SevenBitNumber controlValue, object metadata) : base(controlValue, metadata) @@ -96,6 +110,13 @@ public ControlValueChange(SevenBitNumber controlValue, object metadata, bool isD : base(controlValue, metadata, isDefault) { } + + public bool Equals(ControlValueChange other) + { + return other != null && + Data == other.Data && + IsDefault == other.IsDefault; + } } #endregion @@ -103,7 +124,7 @@ public ControlValueChange(SevenBitNumber controlValue, object metadata, bool isD #region Constants private static readonly ProgramChange DefaultProgramChange = new ProgramChange(SevenBitNumber.MinValue, null, true); - private static readonly PitchValueChange DefaultPitchValueChange = new PitchValueChange(ushort.MinValue, null, true); + private static readonly PitchValueChange DefaultPitchValueChange = new PitchValueChange(PitchBendEvent.DefaultPitchValue, null, true); private static readonly ControlValueChange DefaultControlValueChange = new ControlValueChange(SevenBitNumber.MinValue, null, true); #endregion @@ -111,18 +132,18 @@ public ControlValueChange(SevenBitNumber controlValue, object metadata, bool isD #region Fields private readonly ProgramChange[] _currentProgramChanges = new ProgramChange[FourBitNumber.MaxValue + 1]; - private readonly ValueLine[] _programsChangesLinesByChannels = FourBitNumber.Values - .Select(n => new ValueLine(DefaultProgramChange)) + private readonly RedBlackTree[] _programChangesTreesByChannel = FourBitNumber.Values + .Select(n => new RedBlackTree()) .ToArray(); private readonly PitchValueChange[] _currentPitchValues = new PitchValueChange[FourBitNumber.MaxValue + 1]; - private readonly ValueLine[] _pitchValuesLinesByChannel = FourBitNumber.Values - .Select(n => new ValueLine(DefaultPitchValueChange)) + private readonly RedBlackTree[] _pitchValuesTreesByChannel = FourBitNumber.Values + .Select(n => new RedBlackTree()) .ToArray(); private readonly Dictionary[] _currentControlsValuesChangesByChannel = new Dictionary[FourBitNumber.MaxValue + 1]; - private readonly Dictionary>[] _controlsValuesChangesLinesByChannel = FourBitNumber.Values - .Select(n => new Dictionary>()) + private readonly Dictionary>[] _controlsValuesChangesTreesByChannel = FourBitNumber.Values + .Select(n => new Dictionary>()) .ToArray(); private readonly TempoMap _tempoMap; @@ -172,6 +193,13 @@ public void UpdateCurrentData(MidiEvent midiEvent, object metadata) UpdateCurrentControlData(midiEvent as ControlChangeEvent, metadata); } + public void RemoveData(MidiEvent midiEvent, long time) + { + RemoveProgramChangeData(midiEvent as ProgramChangeEvent, time); + RemovePitchBendData(midiEvent as PitchBendEvent, time); + RemoveControlData(midiEvent as ControlChangeEvent, time); + } + public IEnumerable GetEventsAtTime(TimeSpan time, TrackedParameterType trackedParameterType) { var convertedTime = TimeConverter.ConvertFrom((MetricTimeSpan)time, _tempoMap); @@ -201,7 +229,25 @@ private void InitializeProgramChangeData(ProgramChangeEvent programChangeEvent, if (programChangeEvent == null) return; - _programsChangesLinesByChannels[programChangeEvent.Channel].SetValue(time, new ProgramChange(programChangeEvent.ProgramNumber, metadata)); + var tree = _programChangesTreesByChannel[programChangeEvent.Channel]; + tree.Add(time, new ProgramChange(programChangeEvent.ProgramNumber, metadata)); + } + + private void RemoveProgramChangeData(ProgramChangeEvent programChangeEvent, long time) + { + if (programChangeEvent == null) + return; + + var tree = _programChangesTreesByChannel[programChangeEvent.Channel]; + var nodes = tree.SearchNodes(time); + + var programChange = new ProgramChange(programChangeEvent.ProgramNumber, null); + + foreach (var node in nodes) + { + if (node.Value.Equals(programChange)) + tree.Delete(node); + } } private IEnumerable GetProgramChangeEventsAtTime(long time) @@ -211,13 +257,12 @@ private IEnumerable GetProgramChangeEventsAtTime(long time) foreach (var channel in FourBitNumber.Values) { - var valueChange = _programsChangesLinesByChannels[channel].GetValueChangeAtTime(time); - if (valueChange?.Time == time) + var tree = _programChangesTreesByChannel[channel]; + var node = tree.GetLastNodeBelowThreshold(time + 1); + if (node?.Key == time) continue; - var programChangeAtTime = valueChange != null - ? valueChange.Value - : DefaultProgramChange; + var programChangeAtTime = node?.Value ?? DefaultProgramChange; var currentProgramChange = _currentProgramChanges[channel]; if (programChangeAtTime.Data != currentProgramChange?.Data && (currentProgramChange != null || !programChangeAtTime.IsDefault)) @@ -240,7 +285,25 @@ private void InitializePitchBendData(PitchBendEvent pitchBendEvent, long time, o if (pitchBendEvent == null) return; - _pitchValuesLinesByChannel[pitchBendEvent.Channel].SetValue(time, new PitchValueChange(pitchBendEvent.PitchValue, metadata)); + var tree = _pitchValuesTreesByChannel[pitchBendEvent.Channel]; + tree.Add(time, new PitchValueChange(pitchBendEvent.PitchValue, metadata)); + } + + private void RemovePitchBendData(PitchBendEvent pitchBendEvent, long time) + { + if (pitchBendEvent == null) + return; + + var tree = _pitchValuesTreesByChannel[pitchBendEvent.Channel]; + var nodes = tree.SearchNodes(time); + + var pitchBend = new PitchValueChange(pitchBendEvent.PitchValue, null); + + foreach (var node in nodes) + { + if (node.Value.Equals(pitchBend)) + tree.Delete(node); + } } private IEnumerable GetPitchBendEventsAtTime(long time) @@ -250,13 +313,12 @@ private IEnumerable GetPitchBendEventsAtTime(long time) foreach (var channel in FourBitNumber.Values) { - var valueChange = _pitchValuesLinesByChannel[channel].GetValueChangeAtTime(time); - if (valueChange?.Time == time) + var tree = _pitchValuesTreesByChannel[channel]; + var node = tree.GetLastNodeBelowThreshold(time + 1); + if (node?.Key == time) continue; - var pitchValueChangeAtTime = valueChange != null - ? valueChange.Value - : DefaultPitchValueChange; + var pitchValueChangeAtTime = node?.Value ?? DefaultPitchValueChange; var currentPitchValueChange = _currentPitchValues[channel]; if (pitchValueChangeAtTime.Data != currentPitchValueChange?.Data && (currentPitchValueChange != null || !pitchValueChangeAtTime.IsDefault)) @@ -283,13 +345,35 @@ private void InitializeControlData(ControlChangeEvent controlChangeEvent, long t if (controlChangeEvent == null) return; - var controlsLines = _controlsValuesChangesLinesByChannel[controlChangeEvent.Channel]; + var trees = _controlsValuesChangesTreesByChannel[controlChangeEvent.Channel]; + + RedBlackTree tree; + if (!trees.TryGetValue(controlChangeEvent.ControlNumber, out tree)) + trees.Add(controlChangeEvent.ControlNumber, tree = new RedBlackTree()); + + tree.Add(time, new ControlValueChange(controlChangeEvent.ControlValue, metadata)); + } + + private void RemoveControlData(ControlChangeEvent controlChangeEvent, long time) + { + if (controlChangeEvent == null) + return; + + var trees = _controlsValuesChangesTreesByChannel[controlChangeEvent.Channel]; + + RedBlackTree tree; + if (!trees.TryGetValue(controlChangeEvent.ControlNumber, out tree)) + trees.Add(controlChangeEvent.ControlNumber, tree = new RedBlackTree()); + + var nodes = tree.SearchNodes(time); - ValueLine controlValueLine; - if (!controlsLines.TryGetValue(controlChangeEvent.ControlNumber, out controlValueLine)) - controlsLines.Add(controlChangeEvent.ControlNumber, controlValueLine = new ValueLine(DefaultControlValueChange)); + var controlValueChange = new ControlValueChange(controlChangeEvent.ControlValue, null); - controlValueLine.SetValue(time, new ControlValueChange(controlChangeEvent.ControlValue, metadata)); + foreach (var node in nodes) + { + if (node.Value.Equals(controlValueChange)) + tree.Delete(node); + } } private IEnumerable GetControlChangeEventsAtTime(long time) @@ -299,22 +383,20 @@ private IEnumerable GetControlChangeEventsAtTime(long time) foreach (var channel in FourBitNumber.Values) { - var controlsValuesChangesLinesByControlNumber = _controlsValuesChangesLinesByChannel[channel]; + var controlsValuesChangesTreesByControlNumber = _controlsValuesChangesTreesByChannel[channel]; var currentControlsValuesChangesByControlNumber = _currentControlsValuesChangesByChannel[channel]; foreach (var controlNumber in SevenBitNumber.Values) { - ValueLine controlValueLine; - if (!controlsValuesChangesLinesByControlNumber.TryGetValue(controlNumber, out controlValueLine)) + RedBlackTree tree; + if (!controlsValuesChangesTreesByControlNumber.TryGetValue(controlNumber, out tree)) continue; - var valueChange = controlValueLine.GetValueChangeAtTime(time); - if (valueChange?.Time == time) + var node = tree.GetLastNodeBelowThreshold(time + 1); + if (node?.Key == time) continue; - var controlValueChangeAtTime = valueChange != null - ? valueChange.Value - : DefaultControlValueChange; + var controlValueChangeAtTime = node?.Value ?? DefaultControlValueChange; ControlValueChange currentControlValueChange = null; if (currentControlsValuesChangesByControlNumber != null) diff --git a/DryWetMidi/Multimedia/Playback/PlaybackEvent.cs b/DryWetMidi/Multimedia/Playback/PlaybackEvent.cs index 5910aef14..5e387ab59 100644 --- a/DryWetMidi/Multimedia/Playback/PlaybackEvent.cs +++ b/DryWetMidi/Multimedia/Playback/PlaybackEvent.cs @@ -1,17 +1,25 @@ using System; +using System.Collections.Generic; +using Melanchall.DryWetMidi.Common; using Melanchall.DryWetMidi.Core; +using Melanchall.DryWetMidi.Interaction; namespace Melanchall.DryWetMidi.Multimedia { - internal sealed class PlaybackEvent + internal sealed class PlaybackEvent : IEquatable { #region Constructor - public PlaybackEvent(MidiEvent midiEvent, TimeSpan time, long rawTime) + public PlaybackEvent( + MidiEvent midiEvent, + TimeSpan time, + long rawTime, + ITimedObject objectReference) { Event = midiEvent; Time = time; RawTime = rawTime; + ObjectReference = objectReference; } #endregion @@ -24,8 +32,30 @@ public PlaybackEvent(MidiEvent midiEvent, TimeSpan time, long rawTime) public long RawTime { get; } + public ITimedObject ObjectReference { get; } + + public ICollection> EventsGroup { get; set; } + public PlaybackEventMetadata Metadata { get; } = new PlaybackEventMetadata(); #endregion + + #region IEquatable + + public bool Equals(PlaybackEvent other) + { + return object.ReferenceEquals(this, other); + } + + #endregion + + #region Overrides + + public override string ToString() + { + return $"{Time}: {Event}"; + } + + #endregion } } diff --git a/DryWetMidi/Multimedia/Playback/PlaybackEventMetadata/NotePlaybackEventMetadata.cs b/DryWetMidi/Multimedia/Playback/PlaybackEventMetadata/NotePlaybackEventMetadata.cs index ba1c2f92e..519f66a17 100644 --- a/DryWetMidi/Multimedia/Playback/PlaybackEventMetadata/NotePlaybackEventMetadata.cs +++ b/DryWetMidi/Multimedia/Playback/PlaybackEventMetadata/NotePlaybackEventMetadata.cs @@ -3,7 +3,7 @@ namespace Melanchall.DryWetMidi.Multimedia { - internal sealed class NotePlaybackEventMetadata + internal sealed class NotePlaybackEventMetadata : IEquatable { #region Constructor @@ -60,5 +60,14 @@ public void SetCustomNotePlaybackData(NotePlaybackData notePlaybackData) } #endregion + + #region IEquatable + + public bool Equals(NotePlaybackEventMetadata other) + { + return object.ReferenceEquals(this, other); + } + + #endregion } }