Skip to content

Commit

Permalink
[Docs] Commented public API
Browse files Browse the repository at this point in the history
  • Loading branch information
melanchall committed Apr 30, 2024
1 parent 348ba05 commit 7e0ce72
Show file tree
Hide file tree
Showing 11 changed files with 316 additions and 29 deletions.
7 changes: 7 additions & 0 deletions Docs/articles/high-level-managing/Getting-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,13 @@ An important concept we need to discuss is a key selection. Key is used to calcu

In the code above we're saying: _The key of each object is 0_. So for the rests building algorithm all objects are same, there is no difference between channels and note numbers, for example. So rests will be constructed only at spaces where there are no notes at all (with any channels and any note numbers).

Also please take a look at the predefined key selectors available via constants of the [RestDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.RestDetectionSettings). We can rewrite code above:

```csharp
var notesAndRests = notes
.WithRests(RestDetectionSettings.NoNotes);
```

Using following code:

```csharp
Expand Down
26 changes: 25 additions & 1 deletion Docs/articles/tools/Sanitizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,28 @@ var midiFile = new MidiFile(

[RemoveEventsOnUnusedChannels](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.RemoveEventsOnUnusedChannels) property turns on or off removing such channel events that have no effect since ther are no notes on those channels:

![SanitizingSettings.RemoveEventsOnUnusedChannels](images/Sanitizer/RemoveEventsOnUnusedChannels.png)
![SanitizingSettings.RemoveEventsOnUnusedChannels](images/Sanitizer/RemoveEventsOnUnusedChannels.png)

### Trim

[Trim](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.Trim) property allows to automatically remove an empty space at the start of a MIDI file. For example:

```csharp
var midiFile = new MidiFile(
new TrackChunk(
new TextEvent("A") { DeltaTime = 20 }),
new TrackChunk(
new TextEvent("B") { DeltaTime = 30 },
new TextEvent("C") { DeltaTime = 15 }));
```

Here we have a silence of 20 ticks at the start of the file. So after sanitizing with `Trim` property set to `true` (the default value) we will have this file:

```csharp
var midiFile = new MidiFile(
new TrackChunk(
new TextEvent("A") { DeltaTime = 0 }),
new TrackChunk(
new TextEvent("B") { DeltaTime = 10 },
new TextEvent("C") { DeltaTime = 15 }));
```
134 changes: 134 additions & 0 deletions DryWetMidi.Tests/Interaction/Rests/RestsUtilitiesTests.GetRests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,42 @@ public sealed partial class RestsUtilitiesTests
});
}

[TestCase(10, 10, 50, 50)]
[TestCase(10, 2, 50, 50)]
[TestCase(10, 10, 50, 100)]
[TestCase(10, 2, 50, 100)]
public void GetRests_Notes_SameKey_PredefinedKeySelector(
byte channel1,
byte channel2,
byte noteNumber1,
byte noteNumber2)
{
GetRests(
restDetectionSettings: RestDetectionSettings.NoNotes,
inputObjects: new ITimedObject[]
{
new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = (FourBitNumber)channel2 },
new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = (FourBitNumber)channel1 },
new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = (FourBitNumber)channel2 },
new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = (FourBitNumber)channel1 },
new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = (FourBitNumber)channel2 },
new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = (FourBitNumber)channel1 },
new Note((SevenBitNumber)noteNumber2, 1000, 10000) { Channel = (FourBitNumber)channel2 },
new Note((SevenBitNumber)noteNumber1, 1000, 100000) { Channel = (FourBitNumber)channel1 },
new Note((SevenBitNumber)noteNumber2, 10, 100100) { Channel = (FourBitNumber)channel2 },
new Note((SevenBitNumber)noteNumber1, 10, 110000) { Channel = (FourBitNumber)channel1 },
},
expectedRests: new[]
{
new Rest(0, 10, "Note"),
new Rest(130, 170, "Note"),
new Rest(350, 650, "Note"),
new Rest(2300, 7700, "Note"),
new Rest(11000, 89000, "Note"),
new Rest(101000, 9000, "Note"),
});
}

[TestCase(10, 10)]
[TestCase(10, 50)]
public void GetRests_Notes_RestsByChannel_SameChannel(
Expand Down Expand Up @@ -115,6 +151,36 @@ public sealed partial class RestsUtilitiesTests
});
}

[TestCase(10, 10)]
[TestCase(10, 50)]
public void GetRests_Notes_RestsByChannel_PredefinedKeySelector(
byte noteNumber1,
byte noteNumber2)
{
var channel1 = (FourBitNumber)10;
var channel2 = (FourBitNumber)2;

GetRests(
restDetectionSettings: RestDetectionSettings.NoNotesByChannel,
inputObjects: new ITimedObject[]
{
new Note((SevenBitNumber)noteNumber1, 100, 10) { Channel = channel1 },
new Note((SevenBitNumber)noteNumber1, 100, 30) { Channel = channel2 },
new Note((SevenBitNumber)noteNumber2, 50, 300) { Channel = channel1 },
new Note((SevenBitNumber)noteNumber1, 500, 1000) { Channel = channel2 },
new Note((SevenBitNumber)noteNumber2, 150, 1200) { Channel = channel1 },
new Note((SevenBitNumber)noteNumber1, 1000, 1300) { Channel = channel2 },
},
expectedRests: new[]
{
new Rest(0, 10, channel1),
new Rest(0, 30, channel2),
new Rest(110, 190, channel1),
new Rest(130, 870, channel2),
new Rest(350, 850, channel1),
});
}

[TestCase(10, 10)]
[TestCase(10, 5)]
public void GetRests_Notes_RestsByNoteNumber_SameNoteNumber(
Expand Down Expand Up @@ -168,6 +234,32 @@ public sealed partial class RestsUtilitiesTests
});
}

[TestCase(10, 10)]
[TestCase(10, 5)]
public void GetRests_Notes_RestsByNoteNumber_PredefinedKeySelector(
byte channel1,
byte channel2)
{
var noteNumber1 = (SevenBitNumber)10;
var noteNumber2 = (SevenBitNumber)100;

GetRests(
restDetectionSettings: RestDetectionSettings.NoNotesByNoteNumber,
inputObjects: new ITimedObject[]
{
new Note(noteNumber1, 100, 0) { Channel = (FourBitNumber)channel2 },
new Note(noteNumber2, 100, 30) { Channel = (FourBitNumber)channel1 },
new Note(noteNumber1, 50, 300) { Channel = (FourBitNumber)channel2 },
new Note(noteNumber2, 500, 1000) { Channel = (FourBitNumber)channel1 },
},
expectedRests: new[]
{
new Rest(0, 30, noteNumber2),
new Rest(100, 200, noteNumber1),
new Rest(130, 870, noteNumber2),
});
}

[Test]
public void GetRests_Notes_RestsByChannelAndNoteNumber()
{
Expand Down Expand Up @@ -198,6 +290,36 @@ public void GetRests_Notes_RestsByChannelAndNoteNumber()
});
}

[Test]
public void GetRests_Notes_RestsByChannelAndNoteNumber_PredefinedKeySelector()
{
var noteNumber1 = (SevenBitNumber)10;
var noteNumber2 = (SevenBitNumber)100;
var channel1 = (FourBitNumber)10;
var channel2 = (FourBitNumber)2;

GetRests(
restDetectionSettings: RestDetectionSettings.NoNotesByChannelAndNoteNumber,
inputObjects: new ITimedObject[]
{
new Note(noteNumber1, 100, 10) { Channel = channel1 },
new Note(noteNumber2, 100, 30) { Channel = channel1 },
new Note(noteNumber1, 50, 300) { Channel = channel2 },
new Note(noteNumber2, 500, 1000) { Channel = channel2 },
new Note(noteNumber1, 150, 1200) { Channel = channel1 },
new Note(noteNumber2, 1000, 1300) { Channel = channel1 },
},
expectedRests: new[]
{
new Rest(0, 10, Tuple.Create(channel1, noteNumber1)),
new Rest(0, 30, Tuple.Create(channel1, noteNumber2)),
new Rest(0, 300, Tuple.Create(channel2, noteNumber1)),
new Rest(0, 1000, Tuple.Create(channel2, noteNumber2)),
new Rest(110, 1090, Tuple.Create(channel1, noteNumber1)),
new Rest(130, 1170, Tuple.Create(channel1, noteNumber2)),
});
}

[Test]
public void GetRests_Notes_RestsByChannelAndNoteNumber_WithTimedEvents()
{
Expand Down Expand Up @@ -447,6 +569,18 @@ public void GetRests_AfterGetObjects()
MidiAsserts.AreEqual(expectedRests, actualRests, true, 0, "Rests are invalid.");
}

private void GetRests(
RestDetectionSettings restDetectionSettings,
IEnumerable<ITimedObject> inputObjects,
IEnumerable<Rest> expectedRests)
{
var actualRests = inputObjects
.GetRests(restDetectionSettings)
.ToArray();

MidiAsserts.AreEqual(expectedRests, actualRests, true, 0, "Rests are invalid.");
}

#endregion
}
}
8 changes: 7 additions & 1 deletion DryWetMidi/Interaction/LengthedObject/ILengthedObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ public interface ILengthedObject : ITimedObject
#region Properties

/// <summary>
/// Gets or sets the length of an object.
/// Gets the length of an object.
/// </summary>
/// <remarks>
/// Note that the returned value will be in ticks (not seconds, not milliseconds and so on).
/// Please read <see href="xref:a_time_length">Time and length</see> article to learn how you can
/// get the length in different representations.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> is negative.</exception>
long Length { get; set; }

/// <summary>
Expand Down
34 changes: 13 additions & 21 deletions DryWetMidi/Interaction/Rests/Rest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace Melanchall.DryWetMidi.Interaction
{
/// <summary>
/// Represents a musical rest. More info in the <see href="xref:a_getting_objects#rests">Getting objects: Rests</see> article.
/// </summary>
/// <seealso cref="RestsUtilities"/>
/// <seealso cref="RestDetectionSettings"/>
public sealed class Rest : ILengthedObject, INotifyTimeChanged, INotifyLengthChanged
{
#region Events
Expand Down Expand Up @@ -39,15 +44,7 @@ internal Rest(long time, long length, object key)

#region Properties

/// <summary>
/// Gets start time of an object.
/// </summary>
/// <remarks>
/// Note that the returned value will be in ticks (not seconds, not milliseconds and so on).
/// Please read <see href="xref:a_time_length">Time and length</see> article to learn how you can
/// get the time in different representations.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> is negative.</exception>
/// <inheritdoc/>
public long Time
{
get { return _time; }
Expand All @@ -64,15 +61,7 @@ public long Time
}
}

/// <summary>
/// Gets length of an object.
/// </summary>
/// <remarks>
/// Note that the returned value will be in ticks (not seconds, not milliseconds and so on).
/// Please read <see href="xref:a_time_length">Time and length</see> article to learn how you can
/// get the length in different representations.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> is negative.</exception>
/// <inheritdoc/>
public long Length
{
get { return _length; }
Expand All @@ -89,11 +78,14 @@ public long Length
}
}

/// <summary>
/// Gets the end time of an object.
/// </summary>
/// <inheritdoc/>
public long EndTime => Time + Length;

/// <summary>
/// Gets the key of objects the current rest has been built for. Please read
/// <see href="xref:a_getting_objects#rests">Getting objects: Rests</see> article to
/// understand the key concept.
/// </summary>
public object Key { get; }

#endregion
Expand Down
62 changes: 62 additions & 0 deletions DryWetMidi/Interaction/Rests/RestDetectionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,72 @@

namespace Melanchall.DryWetMidi.Interaction
{
/// <summary>
/// Settings which define how rests should be detected and built. More info in the
/// <see href="xref:a_getting_objects#rests">Getting objects: Rests</see> article.
/// </summary>
/// <seealso cref="RestsUtilities"/>
public sealed class RestDetectionSettings
{
#region Constants

/// <summary>
/// Rests will be built only at spaces without notes at all.
/// </summary>
public static readonly RestDetectionSettings NoNotes = new RestDetectionSettings
{
KeySelector = obj => obj is Note ? "Note" : null
};

/// <summary>
/// Rests will be built between notes separately for each channel.
/// </summary>
public static readonly RestDetectionSettings NoNotesByChannel = new RestDetectionSettings
{
KeySelector = obj => (obj as Note)?.Channel
};

/// <summary>
/// Rests will be built between notes separately for each note number.
/// </summary>
public static readonly RestDetectionSettings NoNotesByNoteNumber = new RestDetectionSettings
{
KeySelector = obj => (obj as Note)?.NoteNumber
};

/// <summary>
/// Rests will be built between notes separately for each channel and note number.
/// </summary>
public static readonly RestDetectionSettings NoNotesByChannelAndNoteNumber = new RestDetectionSettings
{
KeySelector = obj => obj is Note ? Tuple.Create(((Note)obj).Channel, ((Note)obj).NoteNumber) : null
};

/// <summary>
/// Rests will be built only at spaces without chords at all.
/// </summary>
public static readonly RestDetectionSettings NoChords = new RestDetectionSettings
{
KeySelector = obj => obj is Chord ? "Chord" : null
};

/// <summary>
/// Rests will be built between chords separately for each channel.
/// </summary>
public static readonly RestDetectionSettings NoChordsByChannel = new RestDetectionSettings
{
KeySelector = obj => (obj as Chord)?.Channel
};

#endregion

#region Properties

/// <summary>
/// Gets or sets a function that returns the key of an object. Please read
/// <see href="xref:a_getting_objects#rests">Getting objects: Rests</see> article to
/// understand the key concept.
/// </summary>
public Func<ITimedObject, object> KeySelector { get; set; }

#endregion
Expand Down
Loading

0 comments on commit 7e0ce72

Please sign in to comment.