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 Sep 4, 2024
1 parent d50ccf7 commit a19e3bd
Show file tree
Hide file tree
Showing 70 changed files with 1,940 additions and 273 deletions.
181 changes: 178 additions & 3 deletions Docs/articles/composing/Pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ uid: a_composing_pattern

# Pattern

DryWetMIDI provides a way to create a MIDI file in more "musical" manner. The key class here is the [PatternBuilder](xref:Melanchall.DryWetMidi.Composing.PatternBuilder) which allows to build a musical composition. `PatternBuilder` provides a fluent interface to program the music. For example, you can insert a note like this:
DryWetMIDI provides a way to create a MIDI file in a more "musical" manner. The key class here is the [PatternBuilder](xref:Melanchall.DryWetMidi.Composing.PatternBuilder) which allows to build a musical composition. `PatternBuilder` provides a fluent interface to program the music. For example, you can insert a note like this:

```csharp
using Melanchall.DryWetMidi.Composing;
Expand Down Expand Up @@ -36,7 +36,7 @@ var patternBuilder = new PatternBuilder()
.Note(Octave.Get(2).A);
```

Please take a look at entire API provided by [PatternBuilder](xref:Melanchall.DryWetMidi.Composing.PatternBuilder).
Please take a look at the entire API provided by [PatternBuilder](xref:Melanchall.DryWetMidi.Composing.PatternBuilder).

Following example shows how to create first four bars of Beethoven's 'Moonlight Sonata':

Expand Down Expand Up @@ -135,4 +135,179 @@ var pattern = new PatternBuilder()

Pattern can be then saved to [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile) (via [ToFile](xref:Melanchall.DryWetMidi.Composing.Pattern.ToFile*) method) or [TrackChunk](xref:Melanchall.DryWetMidi.Core.TrackChunk) (via [ToTrackChunk](xref:Melanchall.DryWetMidi.Composing.Pattern.ToTrackChunk*) method). You need to provide a [tempo map](xref:Melanchall.DryWetMidi.Interaction.TempoMap). Also you can optionally specify the channel that should be set to events. The default channel is `0`.

Also please see [Extension methods](xref:Melanchall.DryWetMidi.Composing.Pattern#extensionmethods) section of the [Pattern](xref:Melanchall.DryWetMidi.Composing.Pattern) API.
Also please see the [Extension methods](xref:Melanchall.DryWetMidi.Composing.Pattern#extensionmethods) section of the [Pattern](xref:Melanchall.DryWetMidi.Composing.Pattern) API.

## Piano roll

There is a way to create simple patterns easily – via the [PianoRoll](xref:Melanchall.DryWetMidi.Composing.PatternBuilder.PianoRoll*) method. To quickly dive into the method, just take a look at this example:

```csharp
var midiFile = new PatternBuilder()
.SetNoteLength(MusicalTimeSpan.Eighth)
.PianoRoll(@"
F#2 ||||||||
D2 --|---|-
C2 |---|---")
.Repeat(9)
.Build()
.ToFile(TempoMap.Default, (FourBitNumber)9);
midiFile.Write("pianoroll-simple.mid", true);
```

Each line starts with a note. Think about the line as a piano roll lane in your favorite DAW. So notes on the first line will be _F#2_, on the second line – _D2_ and on the third one – _C2_. Each character then **except spaces** means one cell. The length of a cell is determined by the [SetNoteLength](xref:Melanchall.DryWetMidi.Composing.PatternBuilder.SetNoteLength*) method.

`'|'` symbol means a single-cell note, i.e. the note's length is equal to a cell's length. So each note in the example will be an 8th one. By the way, you can alter this symbol with the [SingleCellNoteSymbol](xref:Melanchall.DryWetMidi.Composing.PianoRollSettings.SingleCellNoteSymbol) property of the [PianoRollSettings](xref:Melanchall.DryWetMidi.Composing.PianoRollSettings) passed to the [PianoRoll](xref:Melanchall.DryWetMidi.Composing.PatternBuilder.PianoRoll*) method.

Hyphen (`'-'`) means nothing except a step of a cell's length. We will call it as **fill symbol**.

> [!IMPORTANT]
> Spaces will be cut from the piano roll string before processing. So it's required to use a fill symbol to specify an empty space (rest) to get correct results. For example, this pattern:
> ```text
> F2 ||||
> D2 |
> C2 |
> ```
> will be transformed by the piano roll processing engine to these strings:
> ```text
> F2||||
> D2|
> C2|
> ```
> which is probably not what you want.
Be aware that fill symbol must not be the same as those used for notes and must not be a part of a collection of custom actions symbols (see [Customization](#customization) section further).

The example above demonstrates how to create a simple drum rhythm – standard 8th note groove – using General MIDI drum map. You can listen to the file produced – [pianoroll-simple.mid](files/pianoroll-simple.mid). By the way, you can use notes numbers instead of letters and octaves (and don't forget about string interpolation along with meaningful variables names):

```csharp
var bassDrum = 36;
var snareDrum = 38;
var closedHiHat = 42;

var midiFile = new PatternBuilder()
.SetNoteLength(MusicalTimeSpan.Eighth)
.PianoRoll(@$"
{closedHiHat} ||||||||
{snareDrum} --|---|-
{bassDrum} |---|---")
.Repeat(9)
.Build()
.ToFile(TempoMap.Default, (FourBitNumber)9);
midiFile.Write("pianoroll-simple.mid", true);
```

But let's take a more interesting example which we looked at above – 'Moonlight Sonata'. The same first four bars of it can be constructed via piano roll like this:

```csharp
var midiFile = new PatternBuilder()
.SetNoteLength(MusicalTimeSpan.Eighth.Triplet())
.PianoRoll(@"
F#4 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙|∙∙| ∙∙|∙∙∙∙∙∙∙∙∙
E4 ∙∙|∙∙|∙∙|∙∙| ∙∙|∙∙|∙∙|∙∙| ∙∙|∙∙|∙∙∙∙∙∙ ∙∙∙∙∙|∙∙∙∙∙∙
D#4 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙|∙∙|
C#4 ∙|∙∙|∙∙|∙∙|∙ ∙|∙∙|∙∙|∙∙|∙ ∙|∙∙|∙∙∙∙∙∙∙ ∙∙∙∙|∙∙|∙∙∙∙
C4 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙|∙∙∙∙∙∙∙∙|∙
D4 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙|∙∙|∙ ∙∙∙∙∙∙∙∙∙∙∙∙
A3 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ |∙∙|∙∙|∙∙|∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙
G#3 |∙∙|∙∙|∙∙|∙∙ |∙∙|∙∙|∙∙|∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ |∙∙|∙∙|∙∙∙∙∙
F#3 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙|∙∙
C#3 [==========] ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙
B2 ∙∙∙∙∙∙∙∙∙∙∙∙ [==========] ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙
A2 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ [====]∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙
G#2 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ [====][====]
F#2 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙[====] ∙∙∙∙∙∙∙∙∙∙∙∙
C#2 [==========] ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙
B1 ∙∙∙∙∙∙∙∙∙∙∙∙ [==========] ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙
A1 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ [====]∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙
G#1 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ [====][====]
F#1 ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙∙∙∙∙∙∙ ∙∙∙∙∙∙[====] ∙∙∙∙∙∙∙∙∙∙∙∙")
.Build()
.ToFile(TempoMap.Create(Tempo.FromBeatsPerMinute(69)));
midiFile.Write("pianoroll-moonlight-sonata.mid", true);
```

And here is the file – [pianoroll-moonlight-sonata.mid](files/pianoroll-moonlight-sonata.mid).

Along with usage of spaces to separate bars visually for more readability you can see several new symbols in this example:

* `'.'` and `'='` are both just fill symbols.
* `'['` and `']'` mean the start and the end of a multi-cell note correspondingly. These symbols can be changed too, see properties [MultiCellNoteStartSymbol](xref:Melanchall.DryWetMidi.Composing.PianoRollSettings.MultiCellNoteStartSymbol) and [MultiCellNoteEndSymbol](xref:Melanchall.DryWetMidi.Composing.PianoRollSettings.MultiCellNoteEndSymbol) of the [PianoRollSettings](xref:Melanchall.DryWetMidi.Composing.PianoRollSettings) that you can pass to the [PianoRoll](xref:Melanchall.DryWetMidi.Composing.PatternBuilder.PianoRoll*) method.

Well, we can define long notes (multi-cell) along with single-cell ones. As for length of such notes, let's see at this note:

```text
[====]
```

The length will be of six cells:

```text
[====]
123456
```

So if one cell means 8th triplet time span in our example, the length of the note will be `1/2`.

## Customization

It's time to discuss how you can adjust piano roll processing. First of all, as we said before, you can set custom symbols for a single-cell note, start and end of a multi-cell note:

```csharp
var midiFile = new PatternBuilder()
.SetNoteLength(MusicalTimeSpan.Eighth.Triplet())
.PianoRoll(@"
F#4 ∙∙@∙∙@∙∙∙∙∙∙
E4 ∙∙∙(~~~~~~~)", new PianoRollSettings
{
SingleCellNoteSymbol = '@',
MultiCellNoteStartSymbol = '(',
MultiCellNoteEndSymbol = ')',
})
.Build()
.ToFile(TempoMap.Default);
```

So the way to customize the piano roll algorithm is to pass [PianoRollSettings](xref:Melanchall.DryWetMidi.Composing.PianoRollSettings). But you can also define your own actions triggered by specified symbols. Let's take a look at the following example (yes, drums again):

```csharp
var pianoRollSettings = new PianoRollSettings
{
CustomActions = new Dictionary<char, Action<Melanchall.DryWetMidi.MusicTheory.Note, PatternBuilder>>
{
['*'] = (note, pianoRollBuilder) => pianoRollBuilder
.Note(note, velocity: (SevenBitNumber)(pianoRollBuilder.Velocity / 2)),
['║'] = (note, pianoRollBuilder) => pianoRollBuilder
.Note(note, pianoRollBuilder.NoteLength.Divide(2))
.Note(note, pianoRollBuilder.NoteLength.Divide(2), (SevenBitNumber)(pianoRollBuilder.Velocity / 2)),
['!'] = (note, pianoRollBuilder) => pianoRollBuilder
.StepBack(MusicalTimeSpan.ThirtySecond)
.Note(note, MusicalTimeSpan.ThirtySecond, (SevenBitNumber)(pianoRollBuilder.Velocity / 3))
.Note(note),
}
};

var bassDrum = 36;
var snareDrum = 38;
var closedHiHat = 42;

var midiFile = new PatternBuilder()
.SetNoteLength(MusicalTimeSpan.Eighth)
.PianoRoll(@$"
{closedHiHat} -------| ║║|----|
{snareDrum} -*|---!- --|--*!|
{bassDrum} |--║|--- |-|║|---",
pianoRollSettings)
.Repeat(9)
.Build()
.ToFile(TempoMap.Default, (FourBitNumber)9);
midiFile.Write("pianoroll-custom.mid", true);
```

And here the file – [pianoroll-custom.mid](files/pianoroll-custom.mid). But what we have in the piano roll string:

* `'*'` – ghost note (played with half of the current velocity);
* `'║'` – double note (two notes, each with length of half of the single-cell note);
* `'!'` – flam (ghost thirthy-second note right before main beat).

Right now it's possible to specify single-cell actions only. A way to put custom multi-cell actions will be implemented in the next release.
Binary file not shown.
Binary file not shown.
Binary file added Docs/articles/composing/files/pianoroll-simple.mid
Binary file not shown.
18 changes: 9 additions & 9 deletions Docs/articles/custom-data-structures/Custom-chunks.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ uid: a_custom_chunk

MIDI files are made up of **chunks**. Each chunk has a 4-character ID and a 32-bit length, which is the number of bytes in the chunk. This structure allows future or custom chunk types to be designed which may be easily ignored if encountered by a program written before a chunk type is introduced or if the program doesn't know about the type. DryWetMIDI allows you to implement custom chunks which can be written to a MIDI file and be read from it.

For example, we want to design a chunk that will contain information about changes in whatever we want. A change is described by **date** (day, month, year) and **comment**. Let's create the class to store single change.
For example, we want to design a chunk that will contain information about changes in whatever we want. A change is described by **date** (day, month, year) and **comment**. Let's create a class to store a single change.

```csharp
public sealed class Change
Expand All @@ -30,7 +30,7 @@ Now we are going to implement a custom chunk. Custom chunk class must be derived
* [GetContentSize](xref:Melanchall.DryWetMidi.Core.MidiChunk.GetContentSize(Melanchall.DryWetMidi.Core.WritingSettings));
* [Clone](xref:Melanchall.DryWetMidi.Core.MidiChunk.Clone).

Also the class must have parameterless constructor which calls constructor of the base class ([MidiChunk](xref:Melanchall.DryWetMidi.Core.MidiChunk)) passing chunk's ID to it. ID is a 4-character string which will be **Hstr** for our chunk. ID of custom chunk should not be the same as one of standard chunks IDs. To get IDs of standard chunks you can call [MidiChunk.GetStandardChunkIds](xref:Melanchall.DryWetMidi.Core.MidiChunk.GetStandardChunkIds).
Also, the class must have a parameterless constructor which calls the constructor of the base class ([MidiChunk](xref:Melanchall.DryWetMidi.Core.MidiChunk)) passing chunk's ID to it. ID is a 4-character string which will be **Hstr** for our chunk. The ID of a custom chunk should not be the same as one of standard chunks IDs. To get IDs of standard chunks you can call [MidiChunk.GetStandardChunkIds](xref:Melanchall.DryWetMidi.Core.MidiChunk.GetStandardChunkIds).

The class will look like this:

Expand Down Expand Up @@ -73,19 +73,19 @@ public sealed class HistoryChunk : MidiChunk
}
```

Before we will start to implement four methods mentioned above we need to determine the structure of change records according to which it should be read and written.
Before we will start to implement four methods mentioned above, we need to determine the structure of change records according to which it should be read and written.

Chunk's content will be started with the count of changes. We will write this count as [variable-length quantity](https://en.wikipedia.org/wiki/Variable-length_quantity) (VLQ) number. The count followed by change records.
Chunk's content will be started with the count of changes. We will write this count as a [variable-length quantity](https://en.wikipedia.org/wiki/Variable-length_quantity) (VLQ) number. The count followed by change records.

Each change is:

* one byte for **day**;
* one byte for **month**;
* two bytes for **year**;
* VLQ number bytes representing size of bytes array which is encoded comment;
* bytes which represent encoded comment string.
* bytes which represent an encoded comment string.

To store comments we will use [](xref:System.Text.Encoding.Unicode?title=Encoding.Unicode) encoding.
To store comments, we will use [](xref:System.Text.Encoding.Unicode?title=Encoding.Unicode) encoding.

Let's implement the `ReadContent` method:

Expand Down Expand Up @@ -117,7 +117,7 @@ protected override void ReadContent(MidiReader reader, ReadingSettings settings,
}
```

It is highly recommended that count of the bytes were read by this method is equal to the value passed to `size` parameter.
It is highly recommended that the count of the bytes read by this method is equal to the value passed to `size` parameter.

To be able to write the chunk we need to implement `WriteContent` method:

Expand Down Expand Up @@ -153,7 +153,7 @@ protected override void WriteContent(MidiWriter writer, WritingSettings settings
}
```

Every chunk starts with ID and its size. DryWetMIDI calls `GetContentSize` method of the `MidiChunk` to write its return value as chunk's size. You must calculate real size of the chunk's content in order to programs which will be read a MIDI file with your custom chunk will be able to skip it by advancing position of the reader on this size. Let's implement `GetContentSize`:
Every chunk starts with an ID and its size. DryWetMIDI calls `GetContentSize` method of the `MidiChunk` to write its return value as chunk's size. You must calculate the real size of the chunk's content in order to programs which will read a MIDI file with your custom chunk will be able to skip it by advancing the position of the reader on this size. Let's implement `GetContentSize`:

```csharp
protected override uint GetContentSize(WritingSettings settings)
Expand Down Expand Up @@ -183,7 +183,7 @@ public override MidiChunk Clone()
}
```

That's all! Custom chunk is completely implemented. See code sample below to know how to read and write it:
That's all! The custom chunk is completely implemented. See code sample below to know how to read and write it:

```csharp
// Create a history chunk and populate it by some changes
Expand Down
Loading

0 comments on commit a19e3bd

Please sign in to comment.