Skip to content

Commit

Permalink
Added RemoveDuplicatedNotes property to SanitizingSettings
Browse files Browse the repository at this point in the history
  • Loading branch information
melanchall committed Aug 18, 2024
1 parent 9fbf2dc commit 598cb1e
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Core;
using Melanchall.DryWetMidi.Tools;
using NUnit.Framework;

namespace Melanchall.DryWetMidi.Tests.Tools
{
[TestFixture]
public sealed partial class SanitizerTests
{
#region Test methods

[Test]
public void Sanitize_RemoveDuplicatedNotes_EmptyFile() => Sanitize(
midiFile: new MidiFile(),
settings: null,
expectedMidiFile: new MidiFile());

[Test]
public void Sanitize_RemoveDuplicatedNotes_NoNotes_1() => Sanitize(
midiFile: new MidiFile(
new TrackChunk(
new TextEvent("A"))),
settings: null,
expectedMidiFile: new MidiFile(
new TrackChunk(
new TextEvent("A"))));

[Test]
public void Sanitize_RemoveDuplicatedNotes_NoNotes_2() => Sanitize(
midiFile: new MidiFile(
new TrackChunk(
new TextEvent("A") { DeltaTime = 10 },
new TextEvent("B") { DeltaTime = 15 })),
settings: new SanitizingSettings
{
Trim = false
},
expectedMidiFile: new MidiFile(
new TrackChunk(
new TextEvent("A") { DeltaTime = 10 },
new TextEvent("B") { DeltaTime = 15 })));

[Test]
public void Sanitize_RemoveDuplicatedNotes_SingleNote_1() => Sanitize(
midiFile: new MidiFile(
new TrackChunk(
new TextEvent("A") { DeltaTime = 10 },
new NoteOnEvent(),
new TextEvent("B") { DeltaTime = 15 },
new NoteOffEvent())),
settings: new SanitizingSettings
{
Trim = false
},
expectedMidiFile: new MidiFile(
new TrackChunk(
new TextEvent("A") { DeltaTime = 10 },
new NoteOnEvent(),
new TextEvent("B") { DeltaTime = 15 },
new NoteOffEvent())));

[Test]
public void Sanitize_RemoveDuplicatedNotes_SingleNote_2() => Sanitize(
midiFile: new MidiFile(
new TrackChunk(
new NoteOnEvent(),
new NoteOffEvent())),
settings: null,
expectedMidiFile: new MidiFile(
new TrackChunk(
new NoteOnEvent(),
new NoteOffEvent())));

[Test]
public void Sanitize_RemoveDuplicatedNotes_1() => Sanitize(
midiFile: new MidiFile(
new TrackChunk(
new NoteOnEvent(),
new NoteOffEvent(),
new NoteOnEvent(),
new NoteOffEvent())),
settings: null,
expectedMidiFile: new MidiFile(
new TrackChunk(
new NoteOnEvent(),
new NoteOffEvent())));

[Test]
public void Sanitize_RemoveDuplicatedNotes_2() => Sanitize(
midiFile: new MidiFile(
new TrackChunk(
new NoteOnEvent(),
new NoteOnEvent(),
new NoteOffEvent(),
new NoteOffEvent())),
settings: null,
expectedMidiFile: new MidiFile(
new TrackChunk(
new NoteOnEvent(),
new NoteOffEvent())));

[Test]
public void Sanitize_RemoveDuplicatedNotes_3() => Sanitize(
midiFile: new MidiFile(
new TrackChunk(
new NoteOnEvent(),
new NoteOnEvent() { Channel = (FourBitNumber)5 },
new NoteOnEvent(),
new NoteOnEvent() { Channel = (FourBitNumber)5 },
new TextEvent("A") { DeltaTime = 20 },
new NoteOffEvent() { Channel = (FourBitNumber)5 },
new NoteOffEvent(),
new NoteOffEvent() { Channel = (FourBitNumber)5 },
new NoteOffEvent())),
settings: null,
expectedMidiFile: new MidiFile(
new TrackChunk(
new NoteOnEvent(),
new NoteOnEvent() { Channel = (FourBitNumber)5 },
new TextEvent("A") { DeltaTime = 20 },
new NoteOffEvent() { Channel = (FourBitNumber)5 },
new NoteOffEvent())));

#endregion
}
}
63 changes: 34 additions & 29 deletions DryWetMidi/Tools/Sanitizer/Sanitizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,46 +203,51 @@ private static void RemoveNoteData(

if (!removeShortNotes &&
!removeSilentNotes &&
!settings.RemoveDuplicatedNotes &&
!settings.RemoveOrphanedNoteOnEvents &&
!settings.RemoveOrphanedNoteOffEvents)
return;

var tempoMap = midiFile.GetTempoMap();
var timeSpanType = noteMinLength?.GetType();

foreach (var trackChunk in midiFile.GetTrackChunks())
{
using (var objectsManager = new TimedObjectsManager(
trackChunk.Events,
ObjectType.TimedEvent | ObjectType.Note,
new ObjectDetectionSettings { NoteDetectionSettings = settings.NoteDetectionSettings }))
var lastNotes = new Note[FourBitNumber.MaxValue + 1, SevenBitNumber.MaxValue + 1];

midiFile.RemoveObjects(
ObjectType.TimedEvent | ObjectType.Note,
obj =>
{
objectsManager.Objects.RemoveAll(obj =>
var note = obj as Note;
if (note != null)
{
var note = obj as Note;
if (note != null)
{
if (removeShortNotes &&
LengthConverter.ConvertTo((MidiTimeSpan)note.Length, timeSpanType, note.Time, tempoMap).CompareTo(noteMinLength) < 0)
return true;
var lastNote = lastNotes[note.Channel, note.NoteNumber];
lastNotes[note.Channel, note.NoteNumber] = note;

if (settings.RemoveDuplicatedNotes &&
lastNote?.Time == note.Time &&
lastNote?.Length == note.Length)
return true;

if (removeShortNotes &&
LengthConverter.ConvertTo((MidiTimeSpan)note.Length, timeSpanType, note.Time, tempoMap).CompareTo(noteMinLength) < 0)
return true;

if (removeSilentNotes &&
note.Velocity < noteMinVelocity)
return true;
}
else
{
var timedEvent = (TimedEvent)obj;

if (removeSilentNotes &&
note.Velocity < noteMinVelocity)
return true;
}
else
{
var timedEvent = (TimedEvent)obj;
if ((settings.RemoveOrphanedNoteOnEvents && timedEvent.Event.EventType == MidiEventType.NoteOn) ||
(settings.RemoveOrphanedNoteOffEvents && timedEvent.Event.EventType == MidiEventType.NoteOff))
return true;
}

if ((settings.RemoveOrphanedNoteOnEvents && timedEvent.Event.EventType == MidiEventType.NoteOn) ||
(settings.RemoveOrphanedNoteOffEvents && timedEvent.Event.EventType == MidiEventType.NoteOff))
return true;
}

return false;
});
}
}
return false;
},
new ObjectDetectionSettings { NoteDetectionSettings = settings.NoteDetectionSettings });
}

#endregion
Expand Down
2 changes: 2 additions & 0 deletions DryWetMidi/Tools/Sanitizer/SanitizingSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public sealed class SanitizingSettings

public SevenBitNumber NoteMinVelocity { get; set; }

public bool RemoveDuplicatedNotes { get; set; } = true;

/// <summary>
/// Gets or sets settings which define how notes should be detected and built. More info in the
/// <see href="xref:a_getting_objects#settings">Getting objects: GetNotes: Settings</see> article.
Expand Down

0 comments on commit 598cb1e

Please sign in to comment.