Skip to content

Commit

Permalink
Added PatternBuilder.PianoRoll method
Browse files Browse the repository at this point in the history
  • Loading branch information
melanchall committed Jul 20, 2024
1 parent ffd962e commit 77fa52f
Show file tree
Hide file tree
Showing 5 changed files with 487 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
using Melanchall.DryWetMidi.Common;
using Melanchall.DryWetMidi.Composing;
using Melanchall.DryWetMidi.Interaction;
using Melanchall.DryWetMidi.MusicTheory;
using NUnit.Framework;
using System;
using System.Collections.Generic;

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

[Test]
public void PianoRoll_1()
{
var step = MusicalTimeSpan.Sixteenth;
var velocity = (SevenBitNumber)70;

var pattern = new PatternBuilder()
.SetNoteLength(step)
.SetVelocity(velocity)
.PianoRoll(@"A#5 --|--|--[==]--[....]")
.Build();

PatternTestUtilities.TestNotes(pattern, new[]
{
new NoteInfo(NoteName.ASharp, 5, step * 2, step, velocity),
new NoteInfo(NoteName.ASharp, 5, step * 5, step, velocity),
new NoteInfo(NoteName.ASharp, 5, step * 8, step * 4, velocity),
new NoteInfo(NoteName.ASharp, 5, step * 14, step * 6, velocity),
});
}

[Test]
public void PianoRoll_2()
{
var step = MusicalTimeSpan.Sixteenth;
var velocity = (SevenBitNumber)90;

var pattern = new PatternBuilder()
.SetNoteLength(step)
.SetVelocity(velocity)
.PianoRoll(@"
A3 ---- ---|
B2 --|- --|-
G#2 |--- |--|
")
.Build();

PatternTestUtilities.TestNotes(pattern, new[]
{
new NoteInfo(NoteName.GSharp, 2, step * 0, step, velocity),
new NoteInfo(NoteName.B, 2, step * 2, step, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 4, step, velocity),
new NoteInfo(NoteName.B, 2, step * 6, step, velocity),
new NoteInfo(NoteName.A, 3, step * 7, step, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 7, step, velocity),
});
}

[Test]
public void PianoRoll_CustomSymbols()
{
var step = MusicalTimeSpan.Sixteenth;
var velocity = (SevenBitNumber)90;

var pattern = new PatternBuilder()
.SetNoteLength(step)
.SetVelocity(velocity)
.PianoRoll(@"
A3 ---- ---+ <--->---
B2 --+- --+- --<--->-
G#2 +--- +--+ ---<--->
", new PianoRollSettings
{
SingleCellNoteSymbol = '+',
MultiCellNoteStartSymbol = '<',
MultiCellNoteEndSymbol = '>',
})
.Build();

PatternTestUtilities.TestNotes(pattern, new[]
{
new NoteInfo(NoteName.GSharp, 2, step * 0, step, velocity),
new NoteInfo(NoteName.B, 2, step * 2, step, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 4, step, velocity),
new NoteInfo(NoteName.B, 2, step * 6, step, velocity),
new NoteInfo(NoteName.A, 3, step * 7, step, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 7, step, velocity),

new NoteInfo(NoteName.A, 3, step * 8, step * 5, velocity),
new NoteInfo(NoteName.B, 2, step * 10, step * 5, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 11, step * 5, velocity),
});
}

[Test]
public void PianoRoll_CustomSymbols_SingleCellNoteSymbolIsSpace() => Assert.Throws<ArgumentException>(
() => new PatternBuilder().PianoRoll(@"AH3 ----", new PianoRollSettings
{
SingleCellNoteSymbol = ' ',
}));

[Test]
public void PianoRoll_CustomSymbols_MultiCellNoteStartSymbolIsSpace() => Assert.Throws<ArgumentException>(
() => new PatternBuilder().PianoRoll(@"AH3 ----", new PianoRollSettings
{
MultiCellNoteStartSymbol = ' ',
}));

[Test]
public void PianoRoll_CustomSymbols_MultiCellNoteEndSymbolIsSpace() => Assert.Throws<ArgumentException>(
() => new PatternBuilder().PianoRoll(@"AH3 ----", new PianoRollSettings
{
MultiCellNoteEndSymbol = ' ',
}));

[Test]
public void PianoRoll_CustomActions()
{
var step = MusicalTimeSpan.Sixteenth;
var velocity = (SevenBitNumber)90;

var pattern = new PatternBuilder()
.SetNoteLength(step)
.SetVelocity(velocity)
.PianoRoll(@"
B2 --/- --#-
G#2 +--- +---
", new PianoRollSettings
{
SingleCellNoteSymbol = '+',
CustomActions = new Dictionary<char, Action<DryWetMidi.MusicTheory.Note, PatternBuilder>>
{
['/'] = (note, builder) => builder
.StepBack(MusicalTimeSpan.ThirtySecond)
.Note(note, MusicalTimeSpan.ThirtySecond, (SevenBitNumber)(builder.Velocity * 0.5))
.Note(note),
['#'] = (note, builder) => builder
.StepBack(MusicalTimeSpan.ThirtySecond)
.Note(note, new MusicalTimeSpan(1, 64), (SevenBitNumber)70)
.Note(note, new MusicalTimeSpan(1, 64), (SevenBitNumber)50)
.Note(note),
},
})
.Build();

PatternTestUtilities.TestNotes(pattern, new[]
{
new NoteInfo(NoteName.GSharp, 2, step * 0, step, velocity),

new NoteInfo(NoteName.B, 2, step * 2 - MusicalTimeSpan.ThirtySecond, MusicalTimeSpan.ThirtySecond, (SevenBitNumber)45),
new NoteInfo(NoteName.B, 2, step * 2, step, velocity),

new NoteInfo(NoteName.GSharp, 2, step * 4, step, velocity),

new NoteInfo(NoteName.B, 2, step * 6 - MusicalTimeSpan.ThirtySecond, new MusicalTimeSpan(1, 64), (SevenBitNumber)70),
new NoteInfo(NoteName.B, 2, step * 6 - new MusicalTimeSpan(1, 64), new MusicalTimeSpan(1, 64), (SevenBitNumber)50),
new NoteInfo(NoteName.B, 2, step * 6, step, velocity),
});
}

[Test]
public void PianoRoll_CustomActions_ContainsSingleCellNoteSymbol() => Assert.Throws<ArgumentOutOfRangeException>(
() => new PatternBuilder().PianoRoll(@"AH3 ----", new PianoRollSettings
{
CustomActions = new Dictionary<char, Action<DryWetMidi.MusicTheory.Note, PatternBuilder>>
{
['|'] = (note, builder) => { },
}
}));

[Test]
public void PianoRoll_CustomActions_ContainsMultiCellNoteStartSymbol() => Assert.Throws<ArgumentOutOfRangeException>(
() => new PatternBuilder().PianoRoll(@"AH3 ----", new PianoRollSettings
{
CustomActions = new Dictionary<char, Action<DryWetMidi.MusicTheory.Note, PatternBuilder>>
{
['['] = (note, builder) => { },
}
}));

[Test]
public void PianoRoll_CustomActions_ContainsMultiCellNoteEndSymbol() => Assert.Throws<ArgumentOutOfRangeException>(
() => new PatternBuilder().PianoRoll(@"AH3 ----", new PianoRollSettings
{
CustomActions = new Dictionary<char, Action<DryWetMidi.MusicTheory.Note, PatternBuilder>>
{
[']'] = (note, builder) => { },
}
}));

[Test]
public void PianoRoll_Repeat()
{
var step = MusicalTimeSpan.Sixteenth;
var velocity = (SevenBitNumber)90;

var pattern = new PatternBuilder()
.SetNoteLength(step)
.SetVelocity(velocity)
.PianoRoll(@"
A3 ---- ---|
B2 --|- --|-
G#2 |--- |--|
")
.Repeat(1)
.Build();

PatternTestUtilities.TestNotes(pattern, new[]
{
new NoteInfo(NoteName.GSharp, 2, step * 0, step, velocity),
new NoteInfo(NoteName.B, 2, step * 2, step, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 4, step, velocity),
new NoteInfo(NoteName.B, 2, step * 6, step, velocity),
new NoteInfo(NoteName.A, 3, step * 7, step, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 7, step, velocity),

new NoteInfo(NoteName.GSharp, 2, step * 8, step, velocity),
new NoteInfo(NoteName.B, 2, step * 10, step, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 12, step, velocity),
new NoteInfo(NoteName.B, 2, step * 14, step, velocity),
new NoteInfo(NoteName.A, 3, step * 15, step, velocity),
new NoteInfo(NoteName.GSharp, 2, step * 15, step, velocity),
});
}

[Test]
public void PianoRoll_FailedToParseNote() => Assert.Throws<InvalidOperationException>(
() => new PatternBuilder().PianoRoll(@"AH3 ----"));

[Test]
public void PianoRoll_SingleCellNoteInMultiCellOne() => Assert.Throws<InvalidOperationException>(
() => new PatternBuilder().PianoRoll(@"A3 -[-|-]"));

[Test]
public void PianoRoll_NoteStartedWithPreviousNotEnded() => Assert.Throws<InvalidOperationException>(
() => new PatternBuilder().PianoRoll(@"A3 -[-[-]]"));

[Test]
public void PianoRoll_NoteNotStarted() => Assert.Throws<InvalidOperationException>(
() => new PatternBuilder().PianoRoll(@"A3 -[]--]--"));

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static MidiFile TestNotes(Pattern pattern, ICollection<NoteInfo> expected
var expectedTime = TimeConverter.ConvertFrom(i.Time ?? new MetricTimeSpan(), tempoMap);
var expectedLength = LengthConverter.ConvertFrom(i.Length, expectedTime, tempoMap);

return new DryWetMidi.Interaction.Note(i.NoteNumber, expectedLength, expectedTime)
return new Note(i.NoteNumber, expectedLength, expectedTime)
{
Velocity = i.Velocity,
Channel = Channel
Expand Down
6 changes: 6 additions & 0 deletions DryWetMidi/Common/ThrowIfArgument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ internal static class ThrowIfArgument

#region Methods

internal static void IsProhibitedValue(string parameterName, char argument, char invalidValue)
{
if (argument == invalidValue)
throw new ArgumentException($"'{invalidValue}' is the prohibted value for this parameter.", parameterName);
}

internal static void IsNull(string parameterName, object argument)
{
if (argument == null)
Expand Down
Loading

0 comments on commit 77fa52f

Please sign in to comment.