Skip to content

Commit

Permalink
Support on-the-fly tempo changes in Playback (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
melanchall committed Feb 9, 2025
1 parent 2f95e0d commit b9217b7
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 74 deletions.
4 changes: 1 addition & 3 deletions DryWetMidi/Interaction/TimedObject/ChangedTimedObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ public override bool Equals(object obj)
return false;

return
object.ReferenceEquals(TimedObject, changedTimedObject.TimedObject) &&
OldTime == changedTimedObject.OldTime;
object.ReferenceEquals(TimedObject, changedTimedObject.TimedObject);
}

public override int GetHashCode()
Expand All @@ -41,7 +40,6 @@ public override int GetHashCode()
{
var result = 17;
result = result * 23 + TimedObject.GetHashCode();
result = result * 23 + OldTime.GetHashCode();
return result;
}
}
Expand Down
155 changes: 84 additions & 71 deletions DryWetMidi/Interaction/TimedObject/ObservableTimedObjectsCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ namespace Melanchall.DryWetMidi.Interaction
{
public sealed class ObservableTimedObjectsCollection : IObservableTimedObjectsCollection, IEnumerable<ITimedObject>
{
#region Constants

private const short AddAction = 0;
private const short RemoveAction = 1;
private const short ChangeAction = 2;

#endregion

#region Events

public event EventHandler<ObservableTimedObjectsCollectionChangedEventArgs> CollectionChanged;
Expand All @@ -16,8 +24,11 @@ public sealed class ObservableTimedObjectsCollection : IObservableTimedObjectsCo

#region Fields

// TODO: RBT? No, times can be changed, so it's better to use List
private readonly List<ITimedObject> _objects = new List<ITimedObject>();
private ICollection<ObservableTimedObjectsCollectionChangedEventArgs> _collectionChangedEventsArgs;

private bool _batchOperationInProgress = false;
private readonly List<Tuple<short, ITimedObject, long>> _batchActions = new List<Tuple<short, ITimedObject, long>>();

#endregion

Expand Down Expand Up @@ -48,20 +59,23 @@ public void ChangeCollection(Action change)
{
ThrowIfArgument.IsNull(nameof(change), change);

var deepChange = _collectionChangedEventsArgs != null;
_collectionChangedEventsArgs = _collectionChangedEventsArgs ?? new List<ObservableTimedObjectsCollectionChangedEventArgs>();
var deepChange = _batchOperationInProgress;
_batchOperationInProgress = true;

try
{
change();

if (!deepChange)
OnCollectionChanged(_collectionChangedEventsArgs);
OnCollectionChanged();
}
finally
{
if (!deepChange)
_collectionChangedEventsArgs = null;
{
_batchOperationInProgress = false;
_batchActions.Clear();
}
}
}

Expand Down Expand Up @@ -134,7 +148,16 @@ private ICollection<ITimedObject> AddWithoutHandling(IEnumerable<ITimedObject> t

private void HandleObjectsAdded(IEnumerable<ITimedObject> addedObjects)
{
var eventsArgs = _collectionChangedEventsArgs ?? new List<ObservableTimedObjectsCollectionChangedEventArgs>();
if (_batchOperationInProgress)
{
foreach (var obj in addedObjects)
{
_batchActions.Add(Tuple.Create(AddAction, obj, 0L));
}

return;
}

var eventArgs = new ObservableTimedObjectsCollectionChangedEventArgs();

var eventArgsAddedObjects = eventArgs.AddedObjects;
Expand All @@ -146,14 +169,24 @@ private void HandleObjectsAdded(IEnumerable<ITimedObject> addedObjects)
eventArgsAddedObjects.Add(obj);
}

eventsArgs.Add(eventArgs);
if (!IsInBatchOperation())
OnCollectionChanged(eventsArgs);
if (!eventArgs.HasData)
return;

CollectionChanged?.Invoke(this, eventArgs);
}

private void HandleObjectsRemoved(IEnumerable<ITimedObject> removedObjects)
{
var eventsArgs = _collectionChangedEventsArgs ?? new List<ObservableTimedObjectsCollectionChangedEventArgs>();
if (_batchOperationInProgress)
{
foreach (var obj in removedObjects)
{
_batchActions.Add(Tuple.Create(RemoveAction, obj, 0L));
}

return;
}

var eventArgs = new ObservableTimedObjectsCollectionChangedEventArgs();

var eventArgsRemovedObjects = eventArgs.RemovedObjects;
Expand All @@ -165,14 +198,20 @@ private void HandleObjectsRemoved(IEnumerable<ITimedObject> removedObjects)
eventArgsRemovedObjects.Add(obj);
}

eventsArgs.Add(eventArgs);
if (!IsInBatchOperation())
OnCollectionChanged(eventsArgs);
if (!eventArgs.HasData)
return;

CollectionChanged?.Invoke(this, eventArgs);
}

private void HandleObjectChanged(ITimedObject timedObject, long oldTime)
{
var eventsArgs = _collectionChangedEventsArgs ?? new List<ObservableTimedObjectsCollectionChangedEventArgs>();
if (_batchOperationInProgress)
{
_batchActions.Add(Tuple.Create(ChangeAction, timedObject, oldTime));
return;
}

var eventArgs = new ObservableTimedObjectsCollectionChangedEventArgs();

var eventArgsChangedObjects = eventArgs.ChangedObjects;
Expand All @@ -181,71 +220,50 @@ private void HandleObjectChanged(ITimedObject timedObject, long oldTime)

var changedTimedObject = new ChangedTimedObject(timedObject, oldTime);

if (!eventArgsChangedObjects.Contains(changedTimedObject))
{
eventArgsChangedObjects.Add(changedTimedObject);
}
eventArgsChangedObjects.Add(changedTimedObject);

eventsArgs.Add(eventArgs);
if (!IsInBatchOperation())
OnCollectionChanged(eventsArgs);
if (!eventArgs.HasData)
return;

CollectionChanged?.Invoke(this, eventArgs);
}

private void OnCollectionChanged(ICollection<ObservableTimedObjectsCollectionChangedEventArgs> eventsArgs)
private void OnCollectionChanged()
{
var allAddedObjects = new List<ITimedObject>();
var allRemovedObjects = new List<ITimedObject>();
var allChangedObjects = new List<ChangedTimedObject>();
var addedObjects = new HashSet<ITimedObject>();
var removedObjects = new HashSet<ITimedObject>();
var changedObjects = new HashSet<ChangedTimedObject>();

foreach (var eventArgs in eventsArgs)
foreach (var item in _batchActions)
{
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 action = item.Item1;
var timedObject = item.Item2;
var oldTime = item.Item3;

var changedObjectsHashSet = new HashSet<ITimedObject>();

for (var i = 0; i < allChangedObjects.Count; i++)
{
var changedObject = allChangedObjects[i];
if (!changedObjectsHashSet.Add(changedObject.TimedObject))
switch (action)
{
allChangedObjects.RemoveAt(i);
i--;
case AddAction:
addedObjects.Add(timedObject);
if (removedObjects.Remove(timedObject))
addedObjects.Remove(timedObject);
break;
case RemoveAction:
removedObjects.Add(timedObject);
changedObjects.RemoveWhere(obj => obj.TimedObject == timedObject);
if (addedObjects.Remove(timedObject))
removedObjects.Remove(timedObject);
break;
case ChangeAction:
changedObjects.Add(new ChangedTimedObject(timedObject, oldTime));
break;
}
}

var finalAddedObjects = new List<ITimedObject>(allAddedObjects);
var finalRemovedObjects = new List<ITimedObject>(allRemovedObjects);
var finalChangedObjects = new List<ChangedTimedObject>(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,
AddedObjects = addedObjects,
RemovedObjects = removedObjects,
ChangedObjects = changedObjects,
};

if (!args.HasData)
Expand All @@ -259,11 +277,6 @@ private List<T> GetNonNullList<T>(IEnumerable<T> source)
return (source ?? Enumerable.Empty<T>()).ToList();
}

private bool IsInBatchOperation()
{
return _collectionChangedEventsArgs != null;
}

#endregion

#region IEnumerable<TObject>
Expand Down

0 comments on commit b9217b7

Please sign in to comment.