From 20d37b05dbe0893c7b97a9e56d1363dce9dc66c9 Mon Sep 17 00:00:00 2001 From: "Maxim K. Dobroselsky" Date: Sat, 18 Jan 2025 21:18:50 +0300 Subject: [PATCH] Remove playback's blocking mode --- .../Playback/PlaybackBenchmarks.Misc.cs | 12 - .../Multimedia/Playback/PlaybackTests.Misc.cs | 164 ------------ .../Playback/PlaybackUtilitiesTests.cs | 5 +- DryWetMidi/Multimedia/Playback/Playback.cs | 17 -- .../Multimedia/Playback/PlaybackUtilities.cs | 234 ------------------ Utilities/SendMidiData/DataSender.cs | 11 +- 6 files changed, 13 insertions(+), 430 deletions(-) diff --git a/DryWetMidi.Benchmarks/Devices/Playback/PlaybackBenchmarks.Misc.cs b/DryWetMidi.Benchmarks/Devices/Playback/PlaybackBenchmarks.Misc.cs index c11da30a5..7afc18fe3 100644 --- a/DryWetMidi.Benchmarks/Devices/Playback/PlaybackBenchmarks.Misc.cs +++ b/DryWetMidi.Benchmarks/Devices/Playback/PlaybackBenchmarks.Misc.cs @@ -46,18 +46,6 @@ public void IterationSetup() _playback.MoveToStart(); _playbackWithNoteCallback.MoveToStart(); } - - [Benchmark] - public void Play() - { - _playback.Play(); - } - - [Benchmark] - public void PlayWithNoteCallback() - { - _playbackWithNoteCallback.Play(); - } } #endregion diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Misc.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Misc.cs index f165a3b0f..9a5a0141e 100644 --- a/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Misc.cs +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackTests.Misc.cs @@ -423,170 +423,6 @@ public void PlayRecordedData() createPlayback: (outputDevice, playbackSettings) => recordedFile.GetPlayback(outputDevice, playbackSettings)); } - [Retry(RetriesNumber)] - [TestCase(1.0)] - [TestCase(2.0)] - [TestCase(0.5)] - public void CheckPlayback_Blocking(double speed) - { - var eventsToSend = new[] - { - new EventToSend(new NoteOnEvent((SevenBitNumber)100, (SevenBitNumber)20) { Channel = (FourBitNumber)5 }, TimeSpan.Zero), - new EventToSend(new NoteOffEvent((SevenBitNumber)100, (SevenBitNumber)10) { Channel = (FourBitNumber)5 }, TimeSpan.FromSeconds(2)), - new EventToSend(new NoteOnEvent(), TimeSpan.FromSeconds(1)), - new EventToSend(new NoteOnEvent((SevenBitNumber)30, (SevenBitNumber)50), TimeSpan.Zero), - new EventToSend(new NoteOffEvent(), TimeSpan.FromSeconds(3)), - new EventToSend(new NoteOffEvent((SevenBitNumber)30, (SevenBitNumber)50), TimeSpan.Zero) - }; - - CheckPlayback( - eventsToSend, - speed, - beforePlaybackStarted: NoPlaybackAction, - startPlayback: (context, playback) => playback.Play(), - afterPlaybackStarted: (context, playback) => - { - Assert.GreaterOrEqual(context.Stopwatch.Elapsed, context.ExpectedTimes.Last(), "Playback doesn't block current thread."); - }, - waiting: (context, playback) => - { - var areEventsReceived = WaitOperations.Wait(() => context.ReceivedEvents.Count == eventsToSend.Length, SendReceiveUtilities.MaximumEventSendReceiveDelay); - Assert.IsTrue(areEventsReceived, $"Events are not received."); - }, - finalChecks: NoPlaybackAction); - } - - [Retry(RetriesNumber)] - [TestCase(1.0)] - [TestCase(2.0)] - [TestCase(0.5)] - public void CheckPlayback_Blocking_CustomPlaybackStart(double speed) - { - var eventsToSend = new[] - { - new EventToSend(new NoteOnEvent((SevenBitNumber)100, (SevenBitNumber)20) { Channel = (FourBitNumber)5 }, TimeSpan.Zero), - new EventToSend(new NoteOffEvent((SevenBitNumber)100, (SevenBitNumber)10) { Channel = (FourBitNumber)5 }, TimeSpan.FromSeconds(1)), - - new EventToSend(new NoteOnEvent(), TimeSpan.FromSeconds(1)), - new EventToSend(new NoteOnEvent((SevenBitNumber)30, (SevenBitNumber)50), TimeSpan.Zero), - new EventToSend(new NoteOffEvent(), TimeSpan.FromSeconds(1)), - new EventToSend(new NoteOffEvent((SevenBitNumber)30, (SevenBitNumber)50), TimeSpan.Zero) - }; - - var expectedEventsTimes = new List - { - TimeSpan.FromMilliseconds(500), - TimeSpan.FromMilliseconds(500), - TimeSpan.FromSeconds(1.5), - TimeSpan.FromSeconds(1.5) - }; - - CheckPlayback( - eventsToSend, - speed, - beforePlaybackStarted: (context, playback) => playback.PlaybackStart = new MetricTimeSpan(0, 0, 1, 500), - startPlayback: (context, playback) => playback.Play(), - afterPlaybackStarted: (context, playback) => - { - Assert.GreaterOrEqual(context.Stopwatch.Elapsed, expectedEventsTimes.Last(), "Playback doesn't block current thread."); - }, - waiting: (context, playback) => - { - var areEventsReceived = WaitOperations.Wait(() => context.ReceivedEvents.Count == expectedEventsTimes.Count, SendReceiveUtilities.MaximumEventSendReceiveDelay); - Assert.IsTrue(areEventsReceived, $"Events are not received."); - }, - finalChecks: NoPlaybackAction, - expectedEventsTimes: expectedEventsTimes); - } - - [Retry(RetriesNumber)] - [TestCase(1.0)] - [TestCase(2.0)] - [TestCase(0.5)] - public void CheckPlayback_Blocking_CustomPlaybackEnd(double speed) - { - var eventsToSend = new[] - { - new EventToSend(new NoteOnEvent((SevenBitNumber)100, (SevenBitNumber)20) { Channel = (FourBitNumber)5 }, TimeSpan.Zero), - new EventToSend(new NoteOffEvent((SevenBitNumber)100, (SevenBitNumber)10) { Channel = (FourBitNumber)5 }, TimeSpan.FromSeconds(1)), - - new EventToSend(new NoteOnEvent(), TimeSpan.FromSeconds(1)), - new EventToSend(new NoteOnEvent((SevenBitNumber)30, (SevenBitNumber)50), TimeSpan.Zero), - new EventToSend(new NoteOffEvent(), TimeSpan.FromSeconds(1)), - new EventToSend(new NoteOffEvent((SevenBitNumber)30, (SevenBitNumber)50), TimeSpan.Zero) - }; - - var expectedEventsTimes = new List - { - TimeSpan.Zero, - TimeSpan.FromSeconds(1), - }; - - CheckPlayback( - eventsToSend, - speed, - beforePlaybackStarted: (context, playback) => playback.PlaybackEnd = new MetricTimeSpan(0, 0, 1, 500), - startPlayback: (context, playback) => playback.Play(), - afterPlaybackStarted: (context, playback) => - { - Assert.GreaterOrEqual(context.Stopwatch.Elapsed, expectedEventsTimes.Last(), "Playback doesn't block current thread."); - }, - waiting: (context, playback) => - { - var areEventsReceived = WaitOperations.Wait(() => context.ReceivedEvents.Count == expectedEventsTimes.Count, SendReceiveUtilities.MaximumEventSendReceiveDelay); - Assert.IsTrue(areEventsReceived, $"Events are not received."); - }, - finalChecks: NoPlaybackAction, - expectedEventsTimes: expectedEventsTimes); - } - - [Retry(RetriesNumber)] - [TestCase(1.0)] - [TestCase(2.0)] - [TestCase(0.5)] - public void CheckPlayback_Blocking_CustomPlaybackStartAndEnd(double speed) - { - var eventsToSend = new[] - { - new EventToSend(new NoteOnEvent((SevenBitNumber)100, (SevenBitNumber)20) { Channel = (FourBitNumber)5 }, TimeSpan.Zero), - new EventToSend(new NoteOffEvent((SevenBitNumber)100, (SevenBitNumber)10) { Channel = (FourBitNumber)5 }, TimeSpan.FromSeconds(1)), - - new EventToSend(new NoteOnEvent(), TimeSpan.FromSeconds(1)), - new EventToSend(new NoteOnEvent((SevenBitNumber)30, (SevenBitNumber)50), TimeSpan.Zero), - new EventToSend(new NoteOffEvent(), TimeSpan.FromSeconds(1)), - - new EventToSend(new NoteOffEvent((SevenBitNumber)30, (SevenBitNumber)50), TimeSpan.FromSeconds(1)) - }; - - var expectedEventsTimes = new List - { - TimeSpan.FromMilliseconds(500), - TimeSpan.FromMilliseconds(500), - TimeSpan.FromSeconds(1.5) - }; - - CheckPlayback( - eventsToSend, - speed, - beforePlaybackStarted: (context, playback) => - { - playback.PlaybackStart = new MetricTimeSpan(0, 0, 1, 500); - playback.PlaybackEnd = new MetricTimeSpan(0, 0, 3, 500); - }, - startPlayback: (context, playback) => playback.Play(), - afterPlaybackStarted: (context, playback) => - { - Assert.GreaterOrEqual(context.Stopwatch.Elapsed, expectedEventsTimes.Last(), "Playback doesn't block current thread."); - }, - waiting: (context, playback) => - { - var areEventsReceived = WaitOperations.Wait(() => context.ReceivedEvents.Count == expectedEventsTimes.Count, SendReceiveUtilities.MaximumEventSendReceiveDelay); - Assert.IsTrue(areEventsReceived, $"Events are not received."); - }, - finalChecks: NoPlaybackAction, - expectedEventsTimes: expectedEventsTimes); - } - [Retry(RetriesNumber)] [TestCase(1)] [TestCase(2)] diff --git a/DryWetMidi.Tests/Multimedia/Playback/PlaybackUtilitiesTests.cs b/DryWetMidi.Tests/Multimedia/Playback/PlaybackUtilitiesTests.cs index 34e315cc4..860d664b7 100644 --- a/DryWetMidi.Tests/Multimedia/Playback/PlaybackUtilitiesTests.cs +++ b/DryWetMidi.Tests/Multimedia/Playback/PlaybackUtilitiesTests.cs @@ -9,6 +9,7 @@ using Melanchall.DryWetMidi.Interaction; using Melanchall.DryWetMidi.Standards; using NUnit.Framework; +using System.Threading; namespace Melanchall.DryWetMidi.Tests.Multimedia { @@ -137,7 +138,9 @@ private static void CheckNotesPlayback(IEnumerable notes, playback.NotesPlaybackFinished += (_, e) => receivedNotesFinished.AddRange(e.Notes.Select(n => new ReceivedNote(n, stopwatch.Elapsed))); stopwatch.Start(); - playback.Play(); + + playback.Start(); + SpinWait.SpinUntil(() => !playback.IsRunning); Assert.IsFalse(playback.IsRunning, "Playback is running after completed."); } diff --git a/DryWetMidi/Multimedia/Playback/Playback.cs b/DryWetMidi/Multimedia/Playback/Playback.cs index f4304a6b6..9b3afe4d0 100644 --- a/DryWetMidi/Multimedia/Playback/Playback.cs +++ b/DryWetMidi/Multimedia/Playback/Playback.cs @@ -557,23 +557,6 @@ public void Stop() OnStopped(); } - /// - /// Starts playing of the MIDI data. This method will block execution of a program until all - /// MIDI data is played. - /// - /// - /// If is set to true, this method will execute forever. - /// - /// The current is disposed. - /// An error occurred on device. - public void Play() - { - EnsureIsNotDisposed(); - - Start(); - SpinWait.SpinUntil(() => !_clock.IsRunning); - } - /// /// Sets playback position to the time of the specified snap point. /// diff --git a/DryWetMidi/Multimedia/Playback/PlaybackUtilities.cs b/DryWetMidi/Multimedia/Playback/PlaybackUtilities.cs index faa71387d..b13c1a565 100644 --- a/DryWetMidi/Multimedia/Playback/PlaybackUtilities.cs +++ b/DryWetMidi/Multimedia/Playback/PlaybackUtilities.cs @@ -456,240 +456,6 @@ public static Playback GetPlayback(this IEnumerable objects, T playbackSettings); } - /// - /// Plays MIDI events contained in the specified . - /// - /// containing events to play. - /// Tempo map used to calculate events times. - /// Output MIDI device to play events through. - /// Settings according to which a playback should be created. - /// - /// One of the following errors occurred: - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// - public static void Play(this TrackChunk trackChunk, TempoMap tempoMap, IOutputDevice outputDevice, PlaybackSettings playbackSettings = null) - { - ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); - ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); - ThrowIfArgument.IsNull(nameof(outputDevice), outputDevice); - - using (var playback = trackChunk.GetPlayback(tempoMap, outputDevice, playbackSettings)) - { - playback.Play(); - } - } - - /// - /// Plays MIDI events contained in the specified collection of . - /// - /// Collection of containing events to play. - /// Tempo map used to calculate events times. - /// Output MIDI device to play events through. - /// Settings according to which a playback should be created. - /// - /// One of the following errors occurred: - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// - public static void Play(this IEnumerable trackChunks, TempoMap tempoMap, IOutputDevice outputDevice, PlaybackSettings playbackSettings = null) - { - ThrowIfArgument.IsNull(nameof(trackChunks), trackChunks); - ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); - ThrowIfArgument.IsNull(nameof(outputDevice), outputDevice); - - using (var playback = trackChunks.GetPlayback(tempoMap, outputDevice, playbackSettings)) - { - playback.Play(); - } - } - - /// - /// Plays MIDI events contained in the specified . - /// - /// containing events to play. - /// Output MIDI device to play events through. - /// Settings according to which a playback should be created. - /// - /// One of the following errors occurred: - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// - public static void Play(this MidiFile midiFile, IOutputDevice outputDevice, PlaybackSettings playbackSettings = null) - { - ThrowIfArgument.IsNull(nameof(midiFile), midiFile); - ThrowIfArgument.IsNull(nameof(outputDevice), outputDevice); - - midiFile.GetTrackChunks().Play(midiFile.GetTempoMap(), outputDevice, playbackSettings); - } - - /// - /// Plays MIDI events that will be produced by specified . - /// - /// producing events to play. - /// Tempo map used to calculate events times. - /// MIDI channel to play channel events on. - /// Output MIDI device to play events through. - /// Settings according to which a playback should be created. - /// - /// One of the following errors occurred: - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// - public static void Play(this Pattern pattern, TempoMap tempoMap, FourBitNumber channel, IOutputDevice outputDevice, PlaybackSettings playbackSettings = null) - { - ThrowIfArgument.IsNull(nameof(pattern), pattern); - ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); - ThrowIfArgument.IsNull(nameof(outputDevice), outputDevice); - - pattern.ToTrackChunk(tempoMap, channel).Play(tempoMap, outputDevice, playbackSettings); - } - - /// - /// Plays musical objects using the specified program. - /// - /// The type of objects to play. - /// Objects to play. - /// Tempo map used to calculate events times. - /// Output MIDI device to play through. - /// Program that should be used to play . - /// Settings according to which a playback should be created. - /// - /// One of the following errors occurred: - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// - public static void Play(this IEnumerable objects, TempoMap tempoMap, IOutputDevice outputDevice, SevenBitNumber programNumber, PlaybackSettings playbackSettings = null) - where TObject : IMusicalObject, ITimedObject - { - ThrowIfArgument.IsNull(nameof(objects), objects); - ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); - ThrowIfArgument.IsNull(nameof(outputDevice), outputDevice); - - using (var playback = objects.GetPlayback(tempoMap, outputDevice, programNumber, playbackSettings)) - { - playback.Play(); - } - } - - /// - /// Plays musical objects using the specified General MIDI 1 program. - /// - /// The type of objects to play. - /// Objects to play. - /// Tempo map used to calculate events times. - /// Output MIDI device to play through. - /// Program that should be used to play . - /// Settings according to which a playback should be created. - /// - /// One of the following errors occurred: - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// - /// specified an invalid value. - public static void Play(this IEnumerable objects, TempoMap tempoMap, IOutputDevice outputDevice, GeneralMidiProgram generalMidiProgram, PlaybackSettings playbackSettings = null) - where TObject : IMusicalObject, ITimedObject - { - ThrowIfArgument.IsNull(nameof(objects), objects); - ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); - ThrowIfArgument.IsNull(nameof(outputDevice), outputDevice); - ThrowIfArgument.IsInvalidEnumValue(nameof(generalMidiProgram), generalMidiProgram); - - using (var playback = objects.GetPlayback(tempoMap, outputDevice, generalMidiProgram, playbackSettings)) - { - playback.Play(); - } - } - - /// - /// Plays musical objects using the specified General MIDI 2 program. - /// - /// The type of objects to play. - /// Objects to play. - /// Tempo map used to calculate events times. - /// Output MIDI device to play through. - /// Program that should be used to play . - /// Settings according to which a playback should be created. - /// - /// One of the following errors occurred: - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// is null. - /// - /// - /// - /// specified an invalid value. - public static void Play(this IEnumerable objects, TempoMap tempoMap, IOutputDevice outputDevice, GeneralMidi2Program generalMidi2Program, PlaybackSettings playbackSettings = null) - where TObject : IMusicalObject, ITimedObject - { - ThrowIfArgument.IsNull(nameof(objects), objects); - ThrowIfArgument.IsNull(nameof(tempoMap), tempoMap); - ThrowIfArgument.IsNull(nameof(outputDevice), outputDevice); - ThrowIfArgument.IsInvalidEnumValue(nameof(generalMidi2Program), generalMidi2Program); - - using (var playback = objects.GetPlayback(tempoMap, outputDevice, generalMidi2Program, playbackSettings)) - { - playback.Play(); - } - } - private static Playback GetMusicalObjectsPlayback(IEnumerable objects, TempoMap tempoMap, IOutputDevice outputDevice, diff --git a/Utilities/SendMidiData/DataSender.cs b/Utilities/SendMidiData/DataSender.cs index 87e95b9b5..5860237be 100644 --- a/Utilities/SendMidiData/DataSender.cs +++ b/Utilities/SendMidiData/DataSender.cs @@ -5,6 +5,7 @@ using Melanchall.DryWetMidi.Multimedia; using System; using System.Collections.Generic; +using System.Threading; namespace Melanchall.SendMidiData { @@ -47,7 +48,10 @@ private static SendResult SendData(string[] array, IOutputDevice outputDevice) { var midiEvents = converter.ConvertMultiple(bytes.ToArray()); var playback = midiEvents.GetPlayback(TempoMap.Default, outputDevice); - playback.Play(); + + playback.Start(); + SpinWait.SpinUntil(() => !playback.IsRunning); + return SendResult.Sent; } } @@ -82,7 +86,10 @@ private static SendResult SendNote(string[] array, DryWetMidi.MusicTheory.Note m var note = new Note(musicTheoryNote.NoteNumber).SetLength(length, TempoMap.Default); var playback = new[] { note }.GetPlayback(TempoMap.Default, outputDevice, DryWetMidi.Standards.GeneralMidiProgram.AcousticGrandPiano); - playback.Play(); + + playback.Start(); + SpinWait.SpinUntil(() => !playback.IsRunning); + return SendResult.Sent; }