Skip to content

Commit

Permalink
Added PlaybackHint
Browse files Browse the repository at this point in the history
  • Loading branch information
melanchall committed Aug 30, 2023
1 parent 9cb581a commit ac32c4a
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 19 deletions.
38 changes: 37 additions & 1 deletion DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Asserts.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Multimedia;
using Melanchall.DryWetMidi.Core;
Expand All @@ -15,6 +14,43 @@ namespace Melanchall.DryWetMidi.Tests.Multimedia
[TestFixture]
public sealed partial class PlaybackTests
{
private void CheckNoEventsFiredTrackingDisabled(
ICollection<ITimedObject> objects,
PlaybackHint playbackHint)
{
var tempoMap = TempoMap.Default;
var playedEvents = new List<MidiEvent>();

using (var playback = new Playback(objects, tempoMap, new PlaybackSettings { Hint = playbackHint }))
{
playback.EventPlayed += (_, e) => playedEvents.Add(e.Event);

playback.Start();
playback.MoveToTime(new MetricTimeSpan(0, 0, 0, 750));
playback.MoveToStart();
playback.Stop();
}

CollectionAssert.IsEmpty(playedEvents, "Events are played.");
}

private void CheckFailOnEnableTracking(
PlaybackHint playbackHint,
Action<Playback> enableTracking)
{
var objects = new[]
{
new TimedEvent(new NoteOnEvent()),
};

using (var playback = new Playback(objects, TempoMap.Default, new PlaybackSettings { Hint = playbackHint }))
{
Assert.Throws<InvalidOperationException>(
() => enableTracking(playback),
"No exception thrown.");
}
}

private IEnumerable<SnapPoint> GetActiveSnapPoints(Playback playback)
{
return playback.Snapping.GetActiveSnapPoints().ToList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Interaction;
using Melanchall.DryWetMidi.Multimedia;
using NUnit.Framework;

namespace Melanchall.DryWetMidi.Tests.Multimedia
Expand All @@ -11,6 +13,25 @@ public sealed partial class PlaybackTests
{
#region Test methods

[Retry(RetriesNumber)]
[TestCase(PlaybackHint.DisableControlValueTracking)]
[TestCase(PlaybackHint.DisableDataTracking)]
public void DisableControlValueTracking_NoEventsFired(PlaybackHint playbackHint) => CheckNoEventsFiredTrackingDisabled(
objects: new[]
{
new TimedEvent(new NoteOnEvent()).SetTime(new MetricTimeSpan(0, 0, 0, 500), TempoMap.Default),
new TimedEvent(new ControlChangeEvent((SevenBitNumber)70, (SevenBitNumber)10)).SetTime(new MetricTimeSpan(0, 0, 0, 600), TempoMap.Default),
new TimedEvent(new NoteOffEvent()).SetTime(new MetricTimeSpan(0, 0, 1), TempoMap.Default),
},
playbackHint: playbackHint);

[Retry(RetriesNumber)]
[TestCase(PlaybackHint.DisableControlValueTracking)]
[TestCase(PlaybackHint.DisableDataTracking)]
public void DisableControlValueTracking_FailOnEnableTracking(PlaybackHint playbackHint) => CheckFailOnEnableTracking(
playbackHint: playbackHint,
enableTracking: playback => playback.TrackControlValue = true);

[Retry(RetriesNumber)]
[TestCase(true, 0)]
[TestCase(true, 100)]
Expand Down
23 changes: 23 additions & 0 deletions DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.TrackNotes.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Interaction;
using Melanchall.DryWetMidi.Multimedia;
using Melanchall.DryWetMidi.Tests.Common;
using NUnit.Framework;

namespace Melanchall.DryWetMidi.Tests.Multimedia
Expand All @@ -10,6 +15,24 @@ public sealed partial class PlaybackTests
{
#region Test methods

[Retry(RetriesNumber)]
[TestCase(PlaybackHint.DisableNotesTracking)]
[TestCase(PlaybackHint.DisableDataTracking)]
public void DisableNotesTracking_NoEventsFired(PlaybackHint playbackHint) => CheckNoEventsFiredTrackingDisabled(
objects: new[]
{
new TimedEvent(new NoteOnEvent()).SetTime(new MetricTimeSpan(0, 0, 0, 500), TempoMap.Default),
new TimedEvent(new NoteOffEvent()).SetTime(new MetricTimeSpan(0, 0, 1), TempoMap.Default),
},
playbackHint: playbackHint);

[Retry(RetriesNumber)]
[TestCase(PlaybackHint.DisableNotesTracking)]
[TestCase(PlaybackHint.DisableDataTracking)]
public void DisableNotesTracking_FailOnEnableTracking(PlaybackHint playbackHint) => CheckFailOnEnableTracking(
playbackHint: playbackHint,
enableTracking: playback => playback.TrackNotes = true);

[Retry(RetriesNumber)]
[TestCase(true)]
[TestCase(false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Interaction;
using Melanchall.DryWetMidi.Multimedia;
using NUnit.Framework;

namespace Melanchall.DryWetMidi.Tests.Multimedia
Expand All @@ -11,6 +13,25 @@ public sealed partial class PlaybackTests
{
#region Test methods

[Retry(RetriesNumber)]
[TestCase(PlaybackHint.DisablePitchValueTracking)]
[TestCase(PlaybackHint.DisableDataTracking)]
public void DisablePitchValueTracking_NoEventsFired(PlaybackHint playbackHint) => CheckNoEventsFiredTrackingDisabled(
objects: new[]
{
new TimedEvent(new NoteOnEvent()).SetTime(new MetricTimeSpan(0, 0, 0, 500), TempoMap.Default),
new TimedEvent(new PitchBendEvent(1200)).SetTime(new MetricTimeSpan(0, 0, 0, 600), TempoMap.Default),
new TimedEvent(new NoteOffEvent()).SetTime(new MetricTimeSpan(0, 0, 1), TempoMap.Default),
},
playbackHint: playbackHint);

[Retry(RetriesNumber)]
[TestCase(PlaybackHint.DisablePitchValueTracking)]
[TestCase(PlaybackHint.DisableDataTracking)]
public void DisablePitchValueTracking_FailOnEnableTracking(PlaybackHint playbackHint) => CheckFailOnEnableTracking(
playbackHint: playbackHint,
enableTracking: playback => playback.TrackPitchValue = true);

[Retry(RetriesNumber)]
[TestCase(true, 0)]
[TestCase(true, 100)]
Expand Down
21 changes: 21 additions & 0 deletions DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.TrackProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Interaction;
using Melanchall.DryWetMidi.Multimedia;
using NUnit.Framework;

namespace Melanchall.DryWetMidi.Tests.Multimedia
Expand All @@ -11,6 +13,25 @@ public sealed partial class PlaybackTests
{
#region Test methods

[Retry(RetriesNumber)]
[TestCase(PlaybackHint.DisableProgramTracking)]
[TestCase(PlaybackHint.DisableDataTracking)]
public void DisableProgramTracking_NoEventsFired(PlaybackHint playbackHint) => CheckNoEventsFiredTrackingDisabled(
objects: new[]
{
new TimedEvent(new NoteOnEvent()).SetTime(new MetricTimeSpan(0, 0, 0, 500), TempoMap.Default),
new TimedEvent(new ProgramChangeEvent((SevenBitNumber)70)).SetTime(new MetricTimeSpan(0, 0, 0, 600), TempoMap.Default),
new TimedEvent(new NoteOffEvent()).SetTime(new MetricTimeSpan(0, 0, 1), TempoMap.Default),
},
playbackHint: playbackHint);

[Retry(RetriesNumber)]
[TestCase(PlaybackHint.DisableProgramTracking)]
[TestCase(PlaybackHint.DisableDataTracking)]
public void DisableProgramTracking_FailOnEnableTracking(PlaybackHint playbackHint) => CheckFailOnEnableTracking(
playbackHint: playbackHint,
enableTracking: playback => playback.TrackProgram = true);

[Retry(RetriesNumber)]
[TestCase(true, 0)]
[TestCase(true, 100)]
Expand Down
87 changes: 77 additions & 10 deletions DryWetMidi/Multimedia/Playback/Playback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public class Playback : IDisposable, IClockDrivenObject
private readonly NotePlaybackEventMetadata[] _notesMetadata;

private readonly PlaybackDataTracker _playbackDataTracker;
private readonly PlaybackHint _hint;

private bool _trackNotes;

private bool _disposed = false;

Expand Down Expand Up @@ -128,30 +131,26 @@ public Playback(IEnumerable<ITimedObject> timedObjects, TempoMap tempoMap, Playb
ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap);

playbackSettings = playbackSettings ?? new PlaybackSettings();
_hint = playbackSettings.Hint;

var noteDetectionSettings = playbackSettings.NoteDetectionSettings ?? new NoteDetectionSettings();
_playbackEvents = GetPlaybackEvents(timedObjects.GetNotesAndTimedEventsLazy(noteDetectionSettings, true), tempoMap);
MoveToNextPlaybackEvent();

TempoMap = tempoMap;

var lastPlaybackEvent = _playbackEvents.LastOrDefault();
_duration = lastPlaybackEvent?.Time ?? TimeSpan.Zero;
_durationInTicks = lastPlaybackEvent?.RawTime ?? 0;

_notesMetadata = BuildNotesMetadata();

TempoMap = tempoMap;
_playbackDataTracker = BuildDataTracker();

var clockSettings = playbackSettings.ClockSettings ?? new MidiClockSettings();
_clock = new MidiClock(false, clockSettings.CreateTickGeneratorCallback(), ClockInterval);
_clock.Ticked += OnClockTicked;

Snapping = new PlaybackSnapping(_playbackEvents, tempoMap);

_playbackDataTracker = new PlaybackDataTracker(TempoMap);
foreach (var playbackEvent in _playbackEvents)
{
_playbackDataTracker.InitializeData(playbackEvent.Event, playbackEvent.RawTime, playbackEvent.Metadata.TimedEvent.Metadata);
}
}

/// <summary>
Expand Down Expand Up @@ -232,7 +231,19 @@ public Playback(IEnumerable<ITimedObject> timedObjects, TempoMap tempoMap, IOutp
/// will be treated as just Note On/Note Off events. The default value is <c>false</c>. More info in the
/// <see href="xref:a_playback_datatrack#notes-tracking">Data tracking: Notes tracking</see> article.
/// </summary>
public bool TrackNotes { get; set; }
/// <exception cref="InvalidOperationException">Can't enable notes tracking due to the
/// <see cref="PlaybackHint.DisableNotesTracking"/> hint has been specified on the playback creation.</exception>
public bool TrackNotes
{
get { return _trackNotes; }
set
{
if (value && _hint.HasFlag(PlaybackHint.DisableNotesTracking))
throw new InvalidOperationException($"Can't enable notes tracking due to the '{PlaybackHint.DisableNotesTracking}' hint has been specified on the playback creation.");

_trackNotes = value;
}
}

/// <summary>
/// Gets or sets a value indicating whether program must be tracked or not. If <c>true</c>, any jump
Expand All @@ -241,11 +252,16 @@ public Playback(IEnumerable<ITimedObject> timedObjects, TempoMap tempoMap, IOutp
/// <see href="xref:a_playback_datatrack#midi-parameters-values-tracking">Data tracking: MIDI parameters values tracking</see>
/// article.
/// </summary>
/// <exception cref="InvalidOperationException">Can't enable program tracking due to the
/// <see cref="PlaybackHint.DisableProgramTracking"/> hint has been specified on the playback creation.</exception>
public bool TrackProgram
{
get { return _playbackDataTracker.TrackProgram; }
set
{
if (value && _hint.HasFlag(PlaybackHint.DisableProgramTracking))
throw new InvalidOperationException($"Can't enable program tracking due to the '{PlaybackHint.DisableProgramTracking}' hint has been specified on the playback creation.");

if (_playbackDataTracker.TrackProgram == value)
return;

Expand All @@ -263,11 +279,16 @@ public bool TrackProgram
/// <see href="xref:a_playback_datatrack#midi-parameters-values-tracking">Data tracking: MIDI parameters values tracking</see>
/// article.
/// </summary>
/// <exception cref="InvalidOperationException">Can't enable pitch value tracking due to the
/// <see cref="PlaybackHint.DisablePitchValueTracking"/> hint has been specified on the playback creation.</exception>
public bool TrackPitchValue
{
get { return _playbackDataTracker.TrackPitchValue; }
set
{
if (value && _hint.HasFlag(PlaybackHint.DisablePitchValueTracking))
throw new InvalidOperationException($"Can't enable pitch value tracking due to the '{PlaybackHint.DisablePitchValueTracking}' hint has been specified on the playback creation.");

if (_playbackDataTracker.TrackPitchValue == value)
return;

Expand All @@ -285,11 +306,16 @@ public bool TrackPitchValue
/// <see href="xref:a_playback_datatrack#midi-parameters-values-tracking">Data tracking: MIDI parameters values tracking</see>
/// article.
/// </summary>
/// <exception cref="InvalidOperationException">Can't enable control value tracking due to the
/// <see cref="PlaybackHint.DisableControlValueTracking"/> hint has been specified on the playback creation.</exception>
public bool TrackControlValue
{
get { return _playbackDataTracker.TrackControlValue; }
set
{
if (value && _hint.HasFlag(PlaybackHint.DisableControlValueTracking))
throw new InvalidOperationException($"Can't enable control value tracking due to the '{PlaybackHint.DisableControlValueTracking}' hint has been specified on the playback creation.");

if (_playbackDataTracker.TrackControlValue == value)
return;

Expand Down Expand Up @@ -841,6 +867,25 @@ private bool TryToMoveToSnapPoint(SnapPoint snapPoint)

private void SendTrackedData(PlaybackDataTracker.TrackedParameterType trackedParameterType = PlaybackDataTracker.TrackedParameterType.All)
{
if (_hint.HasFlag(PlaybackHint.DisableDataTracking))
return;

switch (trackedParameterType)
{
case PlaybackDataTracker.TrackedParameterType.Program:
if (_hint.HasFlag(PlaybackHint.DisableProgramTracking))
return;
break;
case PlaybackDataTracker.TrackedParameterType.PitchValue:
if (_hint.HasFlag(PlaybackHint.DisablePitchValueTracking))
return;
break;
case PlaybackDataTracker.TrackedParameterType.ControlValue:
if (_hint.HasFlag(PlaybackHint.DisableControlValueTracking))
return;
break;
}

foreach (var eventWithMetadata in _playbackDataTracker.GetEventsAtTime(_clock.CurrentTime, trackedParameterType))
{
PlayEvent(eventWithMetadata.Event, eventWithMetadata.Metadata);
Expand Down Expand Up @@ -926,6 +971,9 @@ private ICollection<NotePlaybackEventMetadata> GetNotesMetadataAtCurrentTime()

private NotePlaybackEventMetadata[] BuildNotesMetadata()
{
if (_hint.HasFlag(PlaybackHint.DisableNotesTracking))
return new NotePlaybackEventMetadata[0];

var notesMetadata = new HashSet<NotePlaybackEventMetadata>();

foreach (var e in _playbackEvents)
Expand All @@ -938,6 +986,25 @@ private NotePlaybackEventMetadata[] BuildNotesMetadata()
return notesMetadata.ToArray();
}

private PlaybackDataTracker BuildDataTracker()
{
var playbackDataTracker = new PlaybackDataTracker(TempoMap);

if (_hint.HasFlag(PlaybackHint.DisableDataTracking))
return playbackDataTracker;

foreach (var playbackEvent in _playbackEvents)
{
playbackDataTracker.InitializeData(
playbackEvent.Event,
playbackEvent.RawTime,
playbackEvent.Metadata.TimedEvent.Metadata,
_hint);
}

return playbackDataTracker;
}

private void OnStarted()
{
Started?.Invoke(this, EventArgs.Empty);
Expand Down Expand Up @@ -1103,7 +1170,7 @@ private void SetStartTime(ITimeSpan time, int firstIndex, int lastIndex)

private void PlayEvent(MidiEvent midiEvent, object metadata)
{
_playbackDataTracker.UpdateCurrentData(midiEvent, metadata);
_playbackDataTracker.UpdateCurrentData(midiEvent, metadata, _hint);

try
{
Expand Down
Loading

0 comments on commit ac32c4a

Please sign in to comment.