Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
melanchall committed Apr 22, 2019
2 parents 95c39f0 + 9bb8a44 commit 2d602ae
Show file tree
Hide file tree
Showing 111 changed files with 5,575 additions and 819 deletions.
4 changes: 4 additions & 0 deletions DryWetMidi.Benchmarks/BenchmarkTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public abstract class BenchmarkTest
[SetUp]
public void SetupTest()
{
#if DEBUG
Assert.Inconclusive("Unable to run benchmarks on Debug configuration. Use Release.");
#endif

Environment.CurrentDirectory = TestContext.CurrentContext.TestDirectory;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,16 @@ public void CheckEventsReceivingOnConnectedDevices()
[Test]
public void CheckFileEventsReceivingOnConnectedDevices()
{
var filesToTestCount = 5;
var maxFileDuration = TimeSpan.FromSeconds(10);

var filesToTest = TestFilesProvider.GetValidFiles(
f => f.GetTrackChunks().Count() == 1,
f =>
{
var tempoMap = f.GetTempoMap();
return (TimeSpan)f.GetTimedEvents().Last().TimeAs<MetricTimeSpan>(tempoMap) < TimeSpan.FromSeconds(30);
})
.Take(5)
f => f.GetTrackChunks().Count() == 1,
f => (TimeSpan)f.GetDuration<MetricTimeSpan>() < maxFileDuration)
.OrderByDescending(f => f.GetDuration<MetricTimeSpan>())
.Take(filesToTestCount)
.ToArray();
Debug.Assert(filesToTest.Length == filesToTestCount, "Not enough files for test.");

for (var i = 0; i < filesToTest.Length; i++)
{
Expand Down
1,519 changes: 1,296 additions & 223 deletions DryWetMidi.Tests/Devices/Playback/PlaybackTests.cs

Large diffs are not rendered by default.

189 changes: 189 additions & 0 deletions DryWetMidi.Tests/Devices/Playback/PlaybackUtilitiesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Devices;
using Melanchall.DryWetMidi.Smf;
using Melanchall.DryWetMidi.Smf.Interaction;
using Melanchall.DryWetMidi.Standards;
using Melanchall.DryWetMidi.Tests.Utilities;
using NUnit.Framework;

namespace Melanchall.DryWetMidi.Tests.Devices
{
[TestFixture]
public sealed class PlaybackUtilitiesTests
{
#region Constants

private const int RetriesNumber = 3;

#endregion

#region Nested classes

private sealed class ReceivedNote
{
#region Constructor

public ReceivedNote(Note note, TimeSpan time)
{
Note = note;
Time = time;
}

#endregion

#region Properties

public Note Note { get; }

public TimeSpan Time { get; }

#endregion
}

#endregion

#region Test methods

[Retry(RetriesNumber)]
[Test]
public void CheckNotesPlayback_ProgramNumber()
{
var programNumber = (SevenBitNumber)100;
CheckNotesPlayback(
(notes, tempoMap, outputDevice) => notes.GetPlayback(tempoMap, outputDevice, programNumber),
channel => new[] { new ProgramChangeEvent(programNumber) { Channel = channel } });
}

[Retry(RetriesNumber)]
[Test]
public void CheckNotesPlayback_GeneralMidiProgram()
{
var generalMidiProgram = GeneralMidiProgram.Agogo;
CheckNotesPlayback(
(notes, tempoMap, outputDevice) => notes.GetPlayback(tempoMap, outputDevice, generalMidiProgram),
channel => new[] { generalMidiProgram.GetProgramEvent(channel) });
}

[Retry(RetriesNumber)]
[Test]
public void CheckNotesPlayback_GeneralMidi2Program()
{
var generalMidi2Program = GeneralMidi2Program.AnalogSynthBrass2;
CheckNotesPlayback(
(notes, tempoMap, outputDevice) => notes.GetPlayback(tempoMap, outputDevice, generalMidi2Program),
channel => generalMidi2Program.GetProgramEvents(channel));
}

#endregion

#region Methods

private static void CheckNotesPlayback(Func<IEnumerable<Note>, TempoMap, OutputDevice, Playback> playbackGetter,
Func<FourBitNumber, IEnumerable<MidiEvent>> programEventsGetter)
{
var tempoMap = TempoMap.Default;
var channel1 = (FourBitNumber)5;
var channel2 = (FourBitNumber)7;

var notes1 = new PatternBuilder()
.Note(DryWetMidi.MusicTheory.NoteName.A, new MetricTimeSpan(0, 0, 5))
.MoveToTime(new MetricTimeSpan(0, 0, 1))
.Note(DryWetMidi.MusicTheory.NoteName.B, new MetricTimeSpan(0, 0, 1))
.Note(DryWetMidi.MusicTheory.NoteName.C, new MetricTimeSpan(0, 0, 1))
.Build()
.ToTrackChunk(tempoMap, channel1)
.GetNotes();
var notes2 = new PatternBuilder()
.StepForward(new MetricTimeSpan(0, 0, 2))
.Note(DryWetMidi.MusicTheory.NoteName.D, new MetricTimeSpan(0, 0, 5))
.Build()
.ToTrackChunk(tempoMap, channel2)
.GetNotes();
var notes = notes1.Concat(notes2).ToList();

CheckNotesPlayback(notes, (m, d) => playbackGetter(notes, m, d), programEventsGetter);
}

private static void CheckNotesPlayback(IEnumerable<Note> notes,
Func<TempoMap, OutputDevice, Playback> playbackGetter,
Func<FourBitNumber, IEnumerable<MidiEvent>> programEventsGetter)
{
var tempoMap = TempoMap.Default;
var stopwatch = new Stopwatch();

var receivedNotesStarted = new List<ReceivedNote>();
var receivedNotesFinished = new List<ReceivedNote>();

var expectedReceivedNotesStarted = notes.OrderBy(n => n.Time)
.Select(n => new ReceivedNote(n, n.TimeAs<MetricTimeSpan>(tempoMap)))
.ToList();
var expectedReceivedNotesFinished = notes.OrderBy(n => n.Time + n.Length)
.Select(n => new ReceivedNote(n, n.TimeAs<MetricTimeSpan>(tempoMap) + n.LengthAs<MetricTimeSpan>(tempoMap)))
.ToList();

var sentEvents = new List<SentEvent>();

using (var outputDevice = OutputDevice.GetByName(SendReceiveUtilities.DeviceToTestOnName))
{
outputDevice.EventSent += (_, e) => sentEvents.Add(new SentEvent(e.Event, stopwatch.Elapsed));

using (var playback = playbackGetter(tempoMap, outputDevice))
{
playback.NotesPlaybackStarted += (_, e) => receivedNotesStarted.AddRange(e.Notes.Select(n => new ReceivedNote(n, stopwatch.Elapsed)));
playback.NotesPlaybackFinished += (_, e) => receivedNotesFinished.AddRange(e.Notes.Select(n => new ReceivedNote(n, stopwatch.Elapsed)));

stopwatch.Start();
playback.Play();

Assert.IsFalse(playback.IsRunning, "Playback is running after completed.");
}
}

CompareReceivedNotes(receivedNotesStarted, expectedReceivedNotesStarted);
CompareReceivedNotes(receivedNotesFinished, expectedReceivedNotesFinished);

var expectedProgramEvents = notes.Select(n => n.Channel).Distinct().SelectMany(c => programEventsGetter(c)).ToList();

CheckProgramEvents(sentEvents, expectedProgramEvents);
}

private static void CompareReceivedNotes(IReadOnlyList<ReceivedNote> receivedNotes, IReadOnlyList<ReceivedNote> expectedReceivedNotes)
{
Assert.AreEqual(expectedReceivedNotes.Count, receivedNotes.Count, "Count of received notes is invalid.");

for (var i = 0; i < receivedNotes.Count; i++)
{
var receivedNote = receivedNotes[i];
var expectedReceivedNote = expectedReceivedNotes[i];

Assert.AreSame(expectedReceivedNote.Note, receivedNote.Note, $"Received note {receivedNote.Note} is not {expectedReceivedNote.Note}.");

var offsetFromExpectedTime = (receivedNote.Time - expectedReceivedNote.Time).Duration();
Assert.LessOrEqual(
offsetFromExpectedTime,
SendReceiveUtilities.MaximumEventSendReceiveDelay,
$"Note was received at wrong time (at {receivedNote.Time} instead of {expectedReceivedNote.Time}).");
}
}

private static void CheckProgramEvents(IReadOnlyList<SentEvent> sentEvents, IReadOnlyList<MidiEvent> expectedProgramEvents)
{
foreach (var programEvent in expectedProgramEvents)
{
var sentEvent = sentEvents.FirstOrDefault(e => MidiEventEquality.AreEqual(e.Event, programEvent, false));
Assert.IsNotNull(sentEvent, $"Program event {programEvent} was not sent.");

Assert.LessOrEqual(
sentEvent.Time,
SendReceiveUtilities.MaximumEventSendReceiveDelay,
$"Program event was sent at wrong time (at {sentEvent.Time} instead of zero).");
}
}

#endregion
}
}
2 changes: 1 addition & 1 deletion DryWetMidi.Tests/Devices/Recording/RecordingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public sealed class RecordingTests

private const int RetriesNumber = 3;

private static readonly object[] ParametersForDurationCheck = new[]
private static readonly object[] ParametersForDurationCheck =
{
new object[] { TimeSpan.Zero, TimeSpan.FromSeconds(2) },
new object[] { TimeSpan.FromMilliseconds(500), TimeSpan.FromSeconds(3) },
Expand Down
9 changes: 3 additions & 6 deletions DryWetMidi.Tests/Devices/SendReceiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,14 @@ public void CheckEventsReceiving()
});
}

// TODO: Use files collecting from devices connector tests (hangs on midiInStop)
[Retry(RetriesNumber)]
[Test]
public void CheckFileEventsReceiving()
{
var filesToTest = TestFilesProvider.GetValidFiles(
f => f.GetTrackChunks().Count() == 1,
f =>
{
var tempoMap = f.GetTempoMap();
return (TimeSpan)f.GetTimedEvents().Last().TimeAs<MetricTimeSpan>(tempoMap) < TimeSpan.FromSeconds(30);
})
f => f.GetTrackChunks().Count() == 1,
f => (TimeSpan)f.GetDuration<MetricTimeSpan>() < TimeSpan.FromSeconds(30))
.Take(5)
.ToArray();

Expand Down
30 changes: 17 additions & 13 deletions DryWetMidi.Tests/Melanchall.DryWetMidi.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<Compile Include="Devices\DevicesConnector\DevicesConnectorTests.cs" />
<Compile Include="Devices\InpurDevice\InputDeviceTests.cs" />
<Compile Include="Devices\OutputDevice\OutputDeviceTests.cs" />
<Compile Include="Devices\Playback\PlaybackUtilitiesTests.cs" />
<Compile Include="Devices\Playback\PlaybackTests.cs" />
<Compile Include="Devices\Recording\RecordingTests.cs" />
<Compile Include="Devices\SendReceiveTests.cs" />
Expand All @@ -59,6 +60,9 @@
<Compile Include="Devices\Utilities\ReceivedEvent.cs" />
<Compile Include="Devices\Utilities\SendReceiveUtilities.cs" />
<Compile Include="Devices\Utilities\SentEvent.cs" />
<Compile Include="Smf.Interaction\Chords\ChordsManagingUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\Notes\NotesManagingUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\TimedEvents\TimedEventsManagingUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\Utilities\MidiFileUtilitiesTests.cs" />
<Compile Include="Utilities\ChordMethods.cs" />
<Compile Include="Utilities\LengthedObjectMethods.cs" />
Expand All @@ -67,22 +71,22 @@
<Compile Include="MusicTheory\Scale\ScaleTests.cs" />
<Compile Include="MusicTheory\Scale\ScaleUtilitiesTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Smf.Interaction\ChordsManager\ChordsManagerTests.cs" />
<Compile Include="Smf.Interaction\ChordsManager\ChordTests.cs" />
<Compile Include="Smf.Interaction\ChordsManager\ChordTestUtilities.cs" />
<Compile Include="Smf.Interaction\NotesManager\NotesManagerTests.cs" />
<Compile Include="Smf.Interaction\NotesManager\NoteTests.cs" />
<Compile Include="Smf.Interaction\NotesManager\NoteTestUtilities.cs" />
<Compile Include="Smf.Interaction\Chords\ChordsManagerTests.cs" />
<Compile Include="Smf.Interaction\Chords\ChordTests.cs" />
<Compile Include="Smf.Interaction\Chords\ChordTestUtilities.cs" />
<Compile Include="Smf.Interaction\Notes\NotesManagerTests.cs" />
<Compile Include="Smf.Interaction\Notes\NoteTests.cs" />
<Compile Include="Smf.Interaction\Notes\NoteTestUtilities.cs" />
<Compile Include="MusicTheory\Interval\IntervalTests.cs" />
<Compile Include="MusicTheory\Note\NoteTests.cs" />
<Compile Include="Smf.Interaction\NotesManager\Utilities\GetNotesAndRestsUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\NotesManager\Utilities\GetTimedEventsAndNotesUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\NotesManager\Utilities\ResizeNotesUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\Notes\Utilities\GetNotesAndRestsUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\Notes\Utilities\GetTimedEventsAndNotesUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\Notes\Utilities\ResizeNotesUtilitiesTests.cs" />
<Compile Include="Smf.Interaction\Pattern\PatternTests.cs" />
<Compile Include="Smf.Interaction\TempoMapManager\TempoMapManagerTests.cs" />
<Compile Include="Smf.Interaction\TempoMapManager\TempoMapTests.cs" />
<Compile Include="Smf.Interaction\TimedEventsManager\TimedEventsManagerTests.cs" />
<Compile Include="Smf.Interaction\TimedEventsManager\TimedEventTests.cs" />
<Compile Include="Smf.Interaction\TempoMap\TempoMapManagerTests.cs" />
<Compile Include="Smf.Interaction\TempoMap\TempoMapTests.cs" />
<Compile Include="Smf.Interaction\TimedEvents\TimedEventsManagerTests.cs" />
<Compile Include="Smf.Interaction\TimedEvents\TimedEventTests.cs" />
<Compile Include="Smf.Interaction\TimedObject\TimedObjectsCollectionTestUtilities.cs" />
<Compile Include="Smf.Interaction\TimeSpan\BarBeatTimeSpanTests.cs" />
<Compile Include="Smf.Interaction\TimeSpan\MetricTimeSpanTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,10 +387,40 @@ public void Split_TimeInsideChord_IntersectingNotAllNotes()

#endregion

#region EndTimeAs

[Test]
public void EndTimeAs_ZeroTime_ZeroLength()
{
CheckEndTime(new MetricTimeSpan(), new MetricTimeSpan(), new MetricTimeSpan());
}

[Test]
public void EndTimeAs_ZeroLength()
{
CheckEndTime(MusicalTimeSpan.Eighth, new MetricTimeSpan(), MusicalTimeSpan.Eighth);
}

[Test]
public void EndTimeAs()
{
CheckEndTime(MusicalTimeSpan.Eighth, MusicalTimeSpan.Eighth, MusicalTimeSpan.Quarter);
}

#endregion

#endregion

#region Private methods

private static void CheckEndTime<TTimeSpan>(ITimeSpan time, ITimeSpan length, TTimeSpan expectedEndTime)
where TTimeSpan : ITimeSpan
{
var tempoMap = TempoMap.Default;
var chord = new ChordMethods().Create(time, length, tempoMap);
TimeSpanTestUtilities.AreEqual(expectedEndTime, chord.EndTimeAs<TTimeSpan>(tempoMap), "End time is invalid.");
}

private static Chord GetChord_NoNotes()
{
return new Chord();
Expand Down
Loading

0 comments on commit 2d602ae

Please sign in to comment.