From a19e3bd0d621e5236bf850731cb9f20a3c78fffb Mon Sep 17 00:00:00 2001 From: Maxim Dobroselsky Date: Sat, 24 Aug 2024 22:39:42 +0300 Subject: [PATCH] [Docs] Commented public API --- Docs/articles/composing/Pattern.md | 181 ++++++++- .../composing/files/pianoroll-custom.mid | Bin 0 -> 2026 bytes .../files/pianoroll-moonlight-sonata.mid | Bin 0 -> 525 bytes .../composing/files/pianoroll-simple.mid | Bin 0 -> 986 bytes .../custom-data-structures/Custom-chunks.md | 18 +- .../Custom-meta-events.md | 29 +- Docs/articles/dev/Manual-build.md | 2 +- Docs/articles/dev/Nativeless-package.md | 4 +- Docs/articles/dev/Project-health.md | 4 +- Docs/articles/dev/Support.md | 8 +- Docs/articles/dev/Using-in-Unity.md | 4 +- Docs/articles/devices/Common-problems.md | 4 +- Docs/articles/devices/Devices-connector.md | 8 +- Docs/articles/devices/Devices-watcher.md | 10 +- Docs/articles/devices/Input-device.md | 36 +- Docs/articles/devices/Output-device.md | 46 ++- Docs/articles/devices/Overview.md | 10 +- Docs/articles/devices/Virtual-device.md | 12 +- .../Lazy-reading-writing.md | 6 +- .../file-reading-writing/MIDI-file-reading.md | 4 +- .../file-reading-writing/MIDI-file-writing.md | 6 +- .../high-level-managing/Getting-objects.md | 42 +- .../high-level-managing/Objects-managers.md | 6 +- .../high-level-managing/Processing-objects.md | 234 +++++++++++ .../high-level-managing/Removing-objects.md | 101 +++++ .../articles/high-level-managing/Tempo-map.md | 10 +- .../high-level-managing/Time-and-length.md | 16 +- .../music-theory/Chord-progression.md | 4 +- Docs/articles/music-theory/Chord.md | 28 +- Docs/articles/music-theory/Interval.md | 4 +- Docs/articles/music-theory/Note.md | 4 +- Docs/articles/music-theory/Octave.md | 4 +- Docs/articles/music-theory/Overview.md | 4 +- Docs/articles/music-theory/Scale.md | 2 +- Docs/articles/playback/Common-problems.md | 6 +- .../playback/Current-time-watching.md | 8 +- Docs/articles/playback/Custom-playback.md | 6 +- Docs/articles/playback/Data-tracking.md | 8 +- Docs/articles/playback/Overview.md | 12 +- Docs/articles/playback/Tick-generator.md | 14 +- Docs/articles/recording/Overview.md | 6 +- Docs/articles/toc.md | 2 + Docs/articles/tools/CSV-serializer.md | 2 +- Docs/articles/tools/MIDI-file-splitting.md | 10 +- Docs/articles/tools/MIDI-files-merging.md | 12 +- Docs/articles/tools/Merger.md | 2 +- Docs/articles/tools/Objects-merging.md | 10 +- Docs/articles/tools/Objects-splitting.md | 8 +- Docs/articles/tools/Overview.md | 2 +- Docs/articles/tools/Quantizer.md | 16 +- Docs/articles/tools/Repeater.md | 6 +- Docs/articles/tools/Resizer.md | 6 +- Docs/articles/tools/Sanitizer.md | 64 ++- Docs/articles/tools/Splitter.md | 2 +- Docs/docfx.json | 3 +- Docs/templates/dwm/styles/main.css | 12 +- .../PatternBuilderTests.PianoRoll.cs | 27 ++ .../Composing/PatternBuilder.ControlChange.cs | 7 + .../Composing/PatternBuilder.PianoRoll.cs | 45 ++- .../Composing/PatternBuilder.PitchBend.cs | 20 +- DryWetMidi/Composing/PianoRollSettings.cs | 145 +++++++ .../Interaction/Chords/ChordProcessingHint.cs | 2 +- .../Chords/ChordsManagingUtilities.cs | 132 +++++- .../Notes/NotesManagingUtilities.cs | 82 +++- .../TimedEventsManagingUtilities.cs | 36 +- .../TimedObject/ObjectProcessingHint.cs | 42 ++ .../TimedObjectUtilities.ProcessObjects.cs | 376 +++++++++++++++++- .../TimedObjectUtilities.RemoveObjects.cs | 212 +++++++++- .../TimedObject/TimedObjectsManager.cs | 3 +- .../Tools/Sanitizer/SanitizingSettings.cs | 16 + 70 files changed, 1940 insertions(+), 273 deletions(-) create mode 100644 Docs/articles/composing/files/pianoroll-custom.mid create mode 100644 Docs/articles/composing/files/pianoroll-moonlight-sonata.mid create mode 100644 Docs/articles/composing/files/pianoroll-simple.mid create mode 100644 Docs/articles/high-level-managing/Processing-objects.md create mode 100644 Docs/articles/high-level-managing/Removing-objects.md diff --git a/Docs/articles/composing/Pattern.md b/Docs/articles/composing/Pattern.md index fa252b3ac..af9f4b15f 100644 --- a/Docs/articles/composing/Pattern.md +++ b/Docs/articles/composing/Pattern.md @@ -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; @@ -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': @@ -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> + { + ['*'] = (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. \ No newline at end of file diff --git a/Docs/articles/composing/files/pianoroll-custom.mid b/Docs/articles/composing/files/pianoroll-custom.mid new file mode 100644 index 0000000000000000000000000000000000000000..7ed62ff6487b6ece6bd8643a5ed86259e8b6e248 GIT binary patch literal 2026 zcmeYb$w*;fU|?fl1i}R0kfLk`2KFlqGgVRyW~v!6bgD2IbgD5h%v4K(&?+esKrx`a z5tIgst1zg{R8!=Eshg>l!T@9&bZPZ&70_I<6IvC9#qvaK_Tt<||qvbB7ejCpDRhji9RkapO`ehmiK}y3|z)9GRoBkwt#XSwT7}CWPd9k zHe;S&~%UwQS$v&EWQff#I=6>bh{mNAzHYFF652!xt8vYC4Qyb2wr!V}HFLO;; x7qdoDeR365<|}+P^Xv14O~^`y`egj>+0v8uY3b+eFS$8$#g|f(QTr literal 0 HcmV?d00001 diff --git a/Docs/articles/composing/files/pianoroll-simple.mid b/Docs/articles/composing/files/pianoroll-simple.mid new file mode 100644 index 0000000000000000000000000000000000000000..c75a24e507cf5cf4a54ed6cb1a2374c915020f27 GIT binary patch literal 986 zcmeYb$w*;fU|?fl1i}R0kfLk`2IeCSGqq9}W~!tZbZRkx*_|p3aP~|!Bylw)@lo}V Ra2pMu(eyLG`Q^Vp0|0K|@aX^m literal 0 HcmV?d00001 diff --git a/Docs/articles/custom-data-structures/Custom-chunks.md b/Docs/articles/custom-data-structures/Custom-chunks.md index b0abe63cd..7fffcadb5 100644 --- a/Docs/articles/custom-data-structures/Custom-chunks.md +++ b/Docs/articles/custom-data-structures/Custom-chunks.md @@ -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 @@ -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: @@ -73,9 +73,9 @@ 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: @@ -83,9 +83,9 @@ Each change is: * 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: @@ -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: @@ -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) @@ -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 diff --git a/Docs/articles/custom-data-structures/Custom-meta-events.md b/Docs/articles/custom-data-structures/Custom-meta-events.md index b7c6a9ab0..9455262f4 100644 --- a/Docs/articles/custom-data-structures/Custom-meta-events.md +++ b/Docs/articles/custom-data-structures/Custom-meta-events.md @@ -4,7 +4,7 @@ uid: a_custom_meta_event # Custom meta events -Meta events specify non-MIDI information useful to specific application. As with [custom chunks](xref:a_custom_chunk), future or custom meta events may be designed. Format of meta events allows to programs which don't know about these new events to skip them without reading process failure. DryWetMIDI allows you to implement custom meta events which can be written to a MIDI file [track chunk](xref:Melanchall.DryWetMidi.Core.TrackChunk) and be read from it. +Meta events specify non-MIDI information useful to specific applications. As with [custom chunks](xref:a_custom_chunk), future or custom meta events may be designed. Format of meta events allows programs which don't know about these new events to skip them without reading process failure. DryWetMIDI allows you to implement custom meta events which can be written to a MIDI file [track chunk](xref:Melanchall.DryWetMidi.Core.TrackChunk) and be read from it. For example, let's create an event which will hold an image. Custom meta event must be derived from the [MetaEvent](xref:Melanchall.DryWetMidi.Core.MetaEvent) and must implement four abstract methods: @@ -13,7 +13,7 @@ For example, let's create an event which will hold an image. Custom meta event m * [GetContentSize](xref:Melanchall.DryWetMidi.Core.MetaEvent.GetContentSize(Melanchall.DryWetMidi.Core.WritingSettings)); * [CloneEvent](xref:Melanchall.DryWetMidi.Core.MidiEvent.CloneEvent). -Also a class must have parameterless constructor. +Also a class must have a parameterless constructor. ```csharp public sealed class ImageEvent : MetaEvent @@ -68,7 +68,7 @@ protected override void ReadContent(MidiReader reader, ReadingSettings settings, } ``` -Every meta event contains size of the event's content. Size is passed to `ReadContent` through `size` parameter so we know how much bytes we need to read in order to restore an image. +Every meta event contains the size of the event's content. Size is passed to `ReadContent` through the `size` parameter so we know how many bytes we need to read in order to restore an image. Now let's implement `WriteContent`: @@ -111,16 +111,16 @@ public override MidiEvent CloneEvent() } ``` -Custom meta event is completely implemented. In order to read and write it we must assign status byte to the event. You have to pick value from the **[0x5F; 0x7E]** range which will be the status byte of your event type. You can get status bytes of standard meta events via [MetaEvent.GetStandardMetaEventStatusBytes](xref:Melanchall.DryWetMidi.Core.MetaEvent.GetStandardMetaEventStatusBytes). See code sample below to know how to read and write custom meta event: +The custom meta event is completely implemented. In order to read and write it we must assign a status byte to the event. You have to pick a value from the **[0x5F; 0x7E]** range which will be the status byte of your event type. You can get status bytes of standard meta events via [MetaEvent.GetStandardMetaEventStatusBytes](xref:Melanchall.DryWetMidi.Core.MetaEvent.GetStandardMetaEventStatusBytes). See code sample below to know how to read and write custom meta event: ```csharp // Define collection of custom meta event types along with // corresponding status bytes. var customMetaEventTypes = new EventTypesCollection - { - { typeof(ImageEvent), 0x5F } - }; +{ + { typeof(ImageEvent), 0x5F } +}; // Write an image event to an existing file. @@ -132,13 +132,14 @@ var image = Image.FromFile("My image.jpg"); var imageEvent = new ImageEvent(image); trackChunk.Events.Add(imageEvent); -file.Write("My Great Song.mid", - true, - MidiFileFormat.MultiTrack, - new WritingSettings - { - CustomMetaEventTypes = customMetaEventTypes - }); +file.Write( + "My Great Song.mid", + true, + MidiFileFormat.MultiTrack, + new WritingSettings + { + CustomMetaEventTypes = customMetaEventTypes + }); // Read a MIDI file with ImageEvent inside. // diff --git a/Docs/articles/dev/Manual-build.md b/Docs/articles/dev/Manual-build.md index 73552c520..6b23d1664 100644 --- a/Docs/articles/dev/Manual-build.md +++ b/Docs/articles/dev/Manual-build.md @@ -6,7 +6,7 @@ uid: a_develop_manual_build This article describes how you can manually build the library from sources. Just follow the steps below: -1. Select branch you want to build sources from (_master_ or _develop_). +1. Select the branch you want to build sources from (_master_ or _develop_). 2. Download sources with any method you want: * via `Code` → `Download ZIP` button on GitHub, then extract archive; or * `git clone https://github.com/melanchall/drywetmidi.git`; or diff --git a/Docs/articles/dev/Nativeless-package.md b/Docs/articles/dev/Nativeless-package.md index 2574d1adf..7d0b8b10d 100644 --- a/Docs/articles/dev/Nativeless-package.md +++ b/Docs/articles/dev/Nativeless-package.md @@ -9,7 +9,7 @@ DryWetMIDI is shipped in two versions: * [Melanchall.DryWetMidi](https://www.nuget.org/packages/Melanchall.DryWetMidi); * [Melanchall.DryWetMidi.Nativeless](https://www.nuget.org/packages/Melanchall.DryWetMidi.Nativeless). -First one is the version containing all the features of the library and you should use it in most cases. But some things require platform-specific code which placed in native binaries packed along with the main library. If you've encountered problems with such code and you don't need API that depends on native binaries, you can use [Melanchall.DryWetMidi.Nativeless](https://www.nuget.org/packages/Melanchall.DryWetMidi.Nativeless) package where such things are cut out. Following types are unavailable in the nativeless package: +First one is the version containing all the features of the library and you should use it in most cases. But some things require platform-specific code which is placed in native binaries packed along with the main library. If you've encountered problems with such code and you don't need an API that depends on native binaries, you can use [Melanchall.DryWetMidi.Nativeless](https://www.nuget.org/packages/Melanchall.DryWetMidi.Nativeless) package where such things are cut out. Following types are unavailable in the nativeless package: * [VirtualDevice](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice); * [DevicesWatcher](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher); @@ -27,4 +27,4 @@ First one is the version containing all the features of the library and you shou Also default tick generator for [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) there is [RegularPrecisionTickGenerator](xref:Melanchall.DryWetMidi.Multimedia.RegularPrecisionTickGenerator) instead of [HighPrecisionTickGenerator](xref:Melanchall.DryWetMidi.Multimedia.HighPrecisionTickGenerator). -Although built-in implementations of [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) and [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice) are unavailable in the nativeless package, you are still able to create your own implementations and use across the library API (in [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) for example). \ No newline at end of file +Although built-in implementations of [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) and [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice) are unavailable in the nativeless package, you are still able to create your own implementations and use them across the library API (in [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) for example). \ No newline at end of file diff --git a/Docs/articles/dev/Project-health.md b/Docs/articles/dev/Project-health.md index 8ae250301..24437c434 100644 --- a/Docs/articles/dev/Project-health.md +++ b/Docs/articles/dev/Project-health.md @@ -4,9 +4,9 @@ uid: a_develop_project_health # Project health -Here you can see "health" of the project in terms of test pipelines are passed or not. First of all, we need to define two subsets of the library API: +Here you can see the "health" of the project in terms of whether test pipelines are passed or not. First of all, we need to define two subsets of the library API: -* **Core API** – it's all API except Multimedia one; in other words it's the API that is supported by .NET itself, so it can be run on any platform .NET Core / .NET supported. +* **Core API** – it's all API except Multimedia one; in other words, it's the API that is supported by .NET itself, so it can be run on any platform .NET Core / .NET supported. * **Multimedia API** – it's platform-specific API. The list of the key classes can be found in the [Supported OS](xref:a_develop_supported_os) article. ## Windows diff --git a/Docs/articles/dev/Support.md b/Docs/articles/dev/Support.md index f6b778f16..d6282d90b 100644 --- a/Docs/articles/dev/Support.md +++ b/Docs/articles/dev/Support.md @@ -11,15 +11,15 @@ Here you'll find information about how to create good issues on the DryWetMIDI s Things to pay attention for are: -1. If possible provide MIDI files you have problem with or create sample ones to reproduce the error or show what you want to do. +1. If possible, provide MIDI files you have a problem with or create sample ones to reproduce the error or show what you want to do. 2. Provide the error message or/and screenshot of it in case of exception or build error. -3. Provide your code (as text, not as a screenshot!) and point me to the place there where the error occurred or where you need to do something. +3. Provide your code (as text, not as a screenshot!) and point me to the place where the error occurred or where you need to do something. 4. What is your operating system? 5. What version of the library do you use? 6. Use proper MIDI terminology. Official MIDI specification can be found on the [midi.org](https://midi.org/midi-1-0-detailed-specification). 7. Use formatting for your messages (especially for code blocks): * on GitHub use [Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax); * on emails use formatting provided by your mail client. -8. Use English language. +8. Use the English language. -Creating an issue or a discussion on the project's GitHub page, be sure to provide short title and put all required info as the description. \ No newline at end of file +Creating an issue or a discussion on the project's GitHub page, be sure to provide a short title and put all required info as the description. \ No newline at end of file diff --git a/Docs/articles/dev/Using-in-Unity.md b/Docs/articles/dev/Using-in-Unity.md index 8a1357f0f..59e387d8a 100644 --- a/Docs/articles/dev/Using-in-Unity.md +++ b/Docs/articles/dev/Using-in-Unity.md @@ -17,7 +17,7 @@ It's the simplest way. Just use built-in ways to import the official [DryWetMIDI ## Manual installation -Instruction below shows how to integrate full version of the DryWetMIDI into your Unity project manually. If you want to use the [nativeless version](xref:a_develop_nativeless), just take an archive with _-nativeless_ suffix on the second step and skip third one. +Instruction below shows how to integrate the full version of the DryWetMIDI into your Unity project manually. If you want to use the [nativeless version](xref:a_develop_nativeless), just take an archive with _-nativeless_ suffix on the second step and skip the third one. 1. Create _Melanchall_ folder in project's _Assets_ folder and _DryWetMIDI_ subfolder within the _Melanchall_ one. 2. Download the library main binary: @@ -156,4 +156,4 @@ public class DemoScript : MonoBehaviour ``` > [!IMPORTANT] -> Pay attention to `OnApplicationQuit` method. You should always take care about disposing MIDI devices. Without it all resources taken by the device will live until GC collect them. In case of Unity it means Unity may need be reopened to be able to use the same devices again (for example, on Windows). \ No newline at end of file +> Pay attention to `OnApplicationQuit` method. You should always take care about disposing of MIDI devices. Without it all resources taken by the device will live until GC collects them. In the case of Unity, it means Unity may need to be reopened to be able to use the same devices again (for example, on Windows). \ No newline at end of file diff --git a/Docs/articles/devices/Common-problems.md b/Docs/articles/devices/Common-problems.md index 3b1e8e7b3..63977a288 100644 --- a/Docs/articles/devices/Common-problems.md +++ b/Docs/articles/devices/Common-problems.md @@ -6,7 +6,7 @@ uid: a_devices_commonproblems ## `StartCoroutine` can only be called from the main thread in Unity -Sometimes you want to start Unity coroutine in a handler of [EventReceived](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.EventReceived) event of [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.InputDevice). Your code can be executed on separate thread in these case. It can happen because of events are received by device on separate (system) thread. +Sometimes you want to start Unity coroutine in a handler of the [EventReceived](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.EventReceived) event of [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.InputDevice). Your code can be executed on a separate thread in this case. It can happen because events are received by device on a separate (system) thread. But UI related things like call of `StartCoroutine` can be executed on UI thread only. You can use the solution from here: https://stackoverflow.com/a/56715254. @@ -48,4 +48,4 @@ private void StartListening() } ``` -And don't forget to dispose the device when you're done with it. Please read the [Input device](xref:a_dev_input) article to learn more. \ No newline at end of file +And don't forget to dispose of the device when you're done with it. Please read the [Input device](xref:a_dev_input) article to learn more. \ No newline at end of file diff --git a/Docs/articles/devices/Devices-connector.md b/Docs/articles/devices/Devices-connector.md index 2668aab8b..5b50c5905 100644 --- a/Docs/articles/devices/Devices-connector.md +++ b/Docs/articles/devices/Devices-connector.md @@ -4,9 +4,9 @@ uid: a_dev_connector # Devices connector -You can redirect MIDI events from [input device](Input-device.md) to [output device(s)](Output-device.md) using [DevicesConnector](xref:Melanchall.DryWetMidi.Multimedia.DevicesConnector) class. To understand what input and output MIDI device is in DryWetMIDI, please read [Overview](Overview.md) article. +You can redirect MIDI events from [input device](Input-device.md) to [output device(s)](Output-device.md) using [DevicesConnector](xref:Melanchall.DryWetMidi.Multimedia.DevicesConnector) class. To understand what input and output MIDI device is in DryWetMIDI, please read the [Overview](Overview.md) article. -Device connector connects an instance of the [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) to one or multiple instances of the [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice). To get an instance of `DevicesConnector` class you can use either its constructor or `Connect` extension method on `IInputDevice`. In first case you need to call [Connect](xref:Melanchall.DryWetMidi.Multimedia.DevicesConnector.Connect) method after you get `DevicesConnector` instance. In second case the method will be called automatically. +Device connector connects an instance of the [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) to one or multiple instances of the [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice). To get an instance of `DevicesConnector` class you can use either its constructor or `Connect` extension method on `IInputDevice`. In the first case you need to call the [Connect](xref:Melanchall.DryWetMidi.Multimedia.DevicesConnector.Connect) method after you get an instance of the `DevicesConnector`. In the second case the method will be called automatically. Also you can call [Disconnect](xref:Melanchall.DryWetMidi.Multimedia.DevicesConnector.Disconnect) at any time to disable connection between devices. @@ -30,6 +30,6 @@ using (var outputDevice2 = OutputDevice.GetByName("MIDI Out 2")) } ``` -So if a MIDI event will be received by _MIDI In_ device, the event will be sent to both _MIDI Out 1_ and _MIDI Out 2_. +So if a MIDI event is received by _MIDI In_ device, the event will be sent to both _MIDI Out 1_ and _MIDI Out 2_. -Don't forget to call [StartEventsListening](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.StartEventsListening) on input device to make sure [EventReceived](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.EventReceived) will be fired and MIDI event redirected to output devices. Read more in [Input device](Input-device.md) article. \ No newline at end of file +Don't forget to call [StartEventsListening](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.StartEventsListening) on input device to make sure [EventReceived](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.EventReceived) will be fired and MIDI event redirected to output devices. Read more in the [Input device](Input-device.md) article. \ No newline at end of file diff --git a/Docs/articles/devices/Devices-watcher.md b/Docs/articles/devices/Devices-watcher.md index a840b247e..98228c0ea 100644 --- a/Docs/articles/devices/Devices-watcher.md +++ b/Docs/articles/devices/Devices-watcher.md @@ -7,9 +7,9 @@ uid: a_dev_watcher > [!IMPORTANT] > Devices watching API is a platform-specific one so please refer to the [Supported OS](xref:a_develop_supported_os) article to learn more. -DryWetMIDI allows to track whether a MIDI device added to or removed from the system. There is [DevicesWatcher](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher) class for that purpose. The class is singleton and you can get the instance with [Instance](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher.Instance) property. +DryWetMIDI allows to track whether a MIDI device is added to or removed from the system. There is the [DevicesWatcher](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher) class for that purpose. The class is singleton and you can get the instance with [Instance](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher.Instance) property. -`DevicesWatcher` provides two events: [DeviceAdded](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher.DeviceAdded) and [DeviceRemoved](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher.DeviceRemoved). First one will be fired when a MIDI device is added to the system, and second one – when a device removed from it. You can then cast device instance from the events arguments to [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.InputDevice) or [OutputDevice](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice). See following sample program: +`DevicesWatcher` provides two events: [DeviceAdded](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher.DeviceAdded) and [DeviceRemoved](xref:Melanchall.DryWetMidi.Multimedia.DevicesWatcher.DeviceRemoved). First one will be fired when a MIDI device is added to the system, and second one – when a device is removed from it. You can then cast a device instance from the event's arguments to [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.InputDevice) or [OutputDevice](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice). See following sample program: ```csharp using System; @@ -60,7 +60,7 @@ Device removed: Melanchall.DryWetMidi.Multimedia.InputDevice Device removed: Melanchall.DryWetMidi.Multimedia.OutputDevice ``` -When device is added you can immediately interact with it using an instance from the `DeviceAdded` event's arguments. But an instance from the `DeviceRemoved` event's arguments is non-interactable, because device is removed and doesn't exist in the system anymore. Any attempt to call methods or properties on that instance will throw an exception: +When a device is added you can immediately interact with it using an instance from the `DeviceAdded` event's arguments. But an instance from the `DeviceRemoved` event's arguments is non-interactable, because the device is removed and doesn't exist in the system anymore. Any attempt to call methods or properties on that instance will throw an exception: ```csharp using System; @@ -154,7 +154,7 @@ Device removed. Getting its name... Name is MyDevice ``` -Device instances comparing can be useful in programs with GUI where you need update the list of available devices. So when a device is added, you just add it to the list. When some device is removed, you find corresponding item in the current list via `Equals` on device instances and remove that item. +Device instances comparison can be useful in programs with GUI where you need to update the list of available devices. So when a device is added, you just add it to the list. When some device is removed, you find the corresponding item in the current list via `Equals` on device instances and remove that item. > [!IMPORTANT] -> Checking for devices equality supported for **macOS** only. On Windows any call of `Equals` will just compare references. +> Checking for devices equality supported for **macOS** only. On Windows call of `Equals` will just compare references. diff --git a/Docs/articles/devices/Input-device.md b/Docs/articles/devices/Input-device.md index 3ff9b226a..460ad3033 100644 --- a/Docs/articles/devices/Input-device.md +++ b/Docs/articles/devices/Input-device.md @@ -4,16 +4,34 @@ uid: a_dev_input # Input device -In DryWetMIDI an input MIDI device is represented by [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) interface. It allows to receive events from a MIDI device. To understand what an input MIDI device is in DryWetMIDI, please read [Overview](Overview.md) article. +In DryWetMIDI an input MIDI device is represented by the [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) interface. It allows to receive events from a MIDI device. To understand what an input MIDI device is in DryWetMIDI, please read the [Overview](Overview.md) article. -The library provides built-in implementation of `IInputDevice`: [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.InputDevice) class. To get an instance of `InputDevice` you can use either [GetByName](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetByName(System.String)) or [GetByIndex](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetByIndex(System.Int32)) static methods. ID of a MIDI device is a number from `0` to _devices count minus one_. To get count of input MIDI devices presented in the system there is the [GetDevicesCount](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetDevicesCount) method. You can get all input MIDI devices with [GetAll](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetAll) method. +The library provides built-in implementation of `IInputDevice`: [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.InputDevice) class. To get an instance of `InputDevice` you can use either [GetByName](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetByName(System.String)) or [GetByIndex](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetByIndex(System.Int32)) static methods. ID of a MIDI device is a number from `0` to _devices count minus one_. To get count of input MIDI devices presented in the system there is the [GetDevicesCount](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetDevicesCount) method. You can get all input MIDI devices with the [GetAll](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetAll) method. > [!IMPORTANT] -> You can use `InputDevice` built-in implementation of `IInputDevice` only on the systems listed in the [Supported OS](xref:a_develop_supported_os) article. Of course you can create your own implementation of `IInputDevice` as described in [Custom input device](#custom-input-device) section below. +> You can use `InputDevice` built-in implementation of `IInputDevice` only on the systems listed in the [Supported OS](xref:a_develop_supported_os) article. Of course you can create your own implementation of `IInputDevice` as described in the [Custom input device](#custom-input-device) section below. -After an instance of `InputDevice` is obtained, call [StartEventsListening](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.StartEventsListening) to start listening incoming MIDI events going from an input MIDI device. If you don't need to listen for events anymore, call [StopEventsListening](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.StopEventsListening). Also this method will be called automatically on [Dispose](xref:Melanchall.DryWetMidi.Multimedia.MidiDevice.Dispose). To check whether `InputDevice` is currently listening for events or not use [IsListeningForEvents](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.IsListeningForEvents) property. +After an instance of `InputDevice` is obtained, call [StartEventsListening](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.StartEventsListening) to start listening to incoming MIDI events going from an input MIDI device. If you don't need to listen for events anymore, call [StopEventsListening](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.StopEventsListening). Also this method will be called automatically on [Dispose](xref:Melanchall.DryWetMidi.Multimedia.MidiDevice.Dispose). To check whether `InputDevice` is currently listening for events or not use [IsListeningForEvents](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.IsListeningForEvents) property. -If an input device is listening for events, it will fire [EventReceived](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.EventReceived) event for each incoming MIDI event. Received MIDI event will be passed to event's handler. +If an input device is listening for events, it will fire the [EventReceived](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.EventReceived) event for each incoming MIDI event. Received MIDI event will be passed to an event's handler. + +> [!IMPORTANT] +> If you use an instance of the `InputDevice` within a `using` block, you need to be very careful. In general it's not a good practice and can cause problems. For example, with this code +> ```csharp +> using (var inputDevice = InputDevice.GetByName("Some MIDI device")) +> { +> inputDevice.EventReceived += OnEventReceived; +> inputDevice.StartEventsListening(); +> } +> +> // ... +> +> private static void OnEventReceived(object? sender, MidiEventReceivedEventArgs e) +> { +> // ... +> } +> ``` +> the `OnEventReceived` method will not be probably called at all since the program leaves the `using` block before any event is received, and thus the device instance will be destroyed and not functioning of course. Small example (console app) that shows receiving MIDI data: @@ -49,15 +67,15 @@ namespace InputDeviceExample ``` > [!IMPORTANT] -> You must always take care about disposing an `InputDevice`, so use it inside `using` block or call `Dispose` manually. Without it all resources taken by the device will live until GC collect them via finalizer of the `InputDevice`. It means that sometimes you will not be able to use different instances of the same device across multiple applications or different pieces of a program. +> You must always take care about disposing an `InputDevice`, so use it inside `using` block or call `Dispose` manually. Without it all resources taken by the device will live until GC collects them via the finalizer of the `InputDevice`. It means that sometimes you will not be able to use different instances of the same device across multiple applications or different pieces of a program. -`InputDevice` has [MidiTimeCodeReceived](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.MidiTimeCodeReceived) event which, by default, will be fired only when **all** MIDI Time Code components (separate [MidiTimeCodeEvent](xref:Melanchall.DryWetMidi.Core.MidiTimeCodeEvent) events) are received forming _hours:minutes:seconds:frames_ timestamp. You can turn this behavior off by setting [RaiseMidiTimeCodeReceived](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.RaiseMidiTimeCodeReceived) to `false`. +`InputDevice` has the [MidiTimeCodeReceived](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.MidiTimeCodeReceived) event which, by default, will be fired only when **all** MIDI Time Code components (separate [MidiTimeCodeEvent](xref:Melanchall.DryWetMidi.Core.MidiTimeCodeEvent) events) are received forming _hours:minutes:seconds:frames_ timestamp. You can turn this behavior off by setting [RaiseMidiTimeCodeReceived](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.RaiseMidiTimeCodeReceived) to `false`. -If an invalid [channel](xref:Melanchall.DryWetMidi.Core.ChannelEvent), [system common](xref:Melanchall.DryWetMidi.Core.SystemCommonEvent) or [system real-time](xref:Melanchall.DryWetMidi.Core.SystemRealTimeEvent) or system exclusive event received, [ErrorOccurred](xref:Melanchall.DryWetMidi.Multimedia.MidiDevice.ErrorOccurred) event will be fired with `Data` property of the exception filled with an information about the error. +If an invalid [channel](xref:Melanchall.DryWetMidi.Core.ChannelEvent), [system common](xref:Melanchall.DryWetMidi.Core.SystemCommonEvent) or [system real-time](xref:Melanchall.DryWetMidi.Core.SystemRealTimeEvent) or system exclusive event received, [ErrorOccurred](xref:Melanchall.DryWetMidi.Multimedia.MidiDevice.ErrorOccurred) event will be fired with the `Data` property of the exception filled with information about the error. ## Custom input device -You can create your own input device implementation and use it in your app. For example, let's create device that will listen for specific keyboard keys and report corresponding note via [EventReceived](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.EventReceived) event. Also we will control current octave with _up arrow_ and _down arrow_ increasing or decreasing octave number correspondingly. Following image shows the scheme of our device: +You can create your own input device implementation and use it in your app. For example, let's create a device that will listen for specific keyboard keys and report corresponding notes via the [EventReceived](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.EventReceived) event. Also we will control the current octave with _up arrow_ and _down arrow_ keys increasing or decreasing octave number correspondingly. Following image shows the scheme of our device: ![Custom input device](images/CustomInputDevice.png) diff --git a/Docs/articles/devices/Output-device.md b/Docs/articles/devices/Output-device.md index f2aa1f40e..5bd1a870c 100644 --- a/Docs/articles/devices/Output-device.md +++ b/Docs/articles/devices/Output-device.md @@ -4,9 +4,9 @@ uid: a_dev_output # Output device -In DryWetMIDI an output MIDI device is represented by [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice) interface. It allows to send events to a MIDI device. To understand what an output MIDI device is in DryWetMIDI, please read [Overview](Overview.md) article. +In DryWetMIDI an output MIDI device is represented by the [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice) interface. It allows to send events to a MIDI device. To understand what an output MIDI device is in DryWetMIDI, please read the [Overview](Overview.md) article. -The library provides built-in implementation of `IOutputDevice`: [OutputDevice](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice). To get an instance of `OutputDevice` you can use either [GetByName](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.GetByName(System.String)) or [GetByIndex](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.GetByIndex(System.Int32)) static methods. To retrieve count of output MIDI devices presented in the system there is the [GetDevicesCount](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.GetDevicesCount) method. You can get all output MIDI devices with [GetAll](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.GetAll) method: +The library provides built-in implementation of `IOutputDevice`: [OutputDevice](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice). To get an instance of `OutputDevice` you can use either [GetByName](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.GetByName(System.String)) or [GetByIndex](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.GetByIndex(System.Int32)) static methods. To retrieve the count of output MIDI devices presented in the system there is the [GetDevicesCount](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.GetDevicesCount) method. You can get all output MIDI devices with [GetAll](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.GetAll) method: ```csharp using System; @@ -21,11 +21,11 @@ foreach (var outputDevice in OutputDevice.GetAll()) ``` > [!IMPORTANT] -> You can use `OutputDevice` built-in implementation of `IOutputDevice` only on the systems listed in the [Supported OS](xref:a_develop_supported_os) article. Of course you can create your own implementation of `IOutputDevice` as described in [Custom output device](#custom-output-device) section below. +> You can use `OutputDevice` built-in implementation of `IOutputDevice` only on the systems listed in the [Supported OS](xref:a_develop_supported_os) article. Of course you can create your own implementation of `IOutputDevice` as described in the [Custom output device](#custom-output-device) section below. -After an instance of `OutputDevice` is obtained, you can send MIDI events to device via [SendEvent](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.SendEvent(Melanchall.DryWetMidi.Core.MidiEvent)) method. You cannot send [meta events](xref:Melanchall.DryWetMidi.Core.MetaEvent) since such events can be inside a MIDI file only. If you pass an instance of meta event class, `SendEvent` will do nothing. [EventSent](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice.EventSent) event will be fired for each event sent with `SendEvent` (except meta events) holding the MIDI event sent. The value of [DeltaTime](xref:Melanchall.DryWetMidi.Core.MidiEvent.DeltaTime) property of MIDI events will be ignored, events will be sent to device immediately. To take delta-times into account, use [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) class. +After an instance of `OutputDevice` is obtained, you can send MIDI events to the device via [SendEvent](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.SendEvent(Melanchall.DryWetMidi.Core.MidiEvent)) method. You cannot send [meta events](xref:Melanchall.DryWetMidi.Core.MetaEvent) since such events can be inside a MIDI file only. If you pass an instance of meta event class, `SendEvent` will do nothing. [EventSent](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice.EventSent) event will be fired for each event sent with `SendEvent` (except meta events) holding the MIDI event sent. The value of [DeltaTime](xref:Melanchall.DryWetMidi.Core.MidiEvent.DeltaTime) property of MIDI events will be ignored, events will be sent to the device immediately. To take delta-times into account, use [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) class. -If you need to interrupt all currently sounding notes, call the [TurnAllNotesOff](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.TurnAllNotesOff) method which will send _Note Off_ events on all channels for all note numbers (kind of "panic" button on MIDI devices). +If you need to interrupt all currently sounding notes, call the [TurnAllNotesOff](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice.TurnAllNotesOff) method which will send _Note Off_ events on all channels for all note numbers (a kind of "panic" button on MIDI devices). Small example that shows sending MIDI data: @@ -36,13 +36,15 @@ using Melanchall.DryWetMidi.Core; // ... -using (var outputDevice = OutputDevice.GetByName("Some MIDI device")) -{ - outputDevice.EventSent += OnEventSent; +private OutputDevice _outputDevice; - outputDevice.SendEvent(new NoteOnEvent()); - outputDevice.SendEvent(new NoteOffEvent()); -} +// ... + +_outputDevice = OutputDevice.GetByName("Some MIDI device"); +_outputDevice.EventSent += OnEventSent; + +_outputDevice.SendEvent(new NoteOnEvent()); +_outputDevice.SendEvent(new NoteOffEvent()); // ... @@ -51,12 +53,26 @@ private void OnEventSent(object sender, MidiEventSentEventArgs e) var midiDevice = (MidiDevice)sender; Console.WriteLine($"Event sent to '{midiDevice.Name}' at {DateTime.Now}: {e.Event}"); } + +// ... + +_outputDevice?.Dispose(); ``` > [!IMPORTANT] -> You must always take care about disposing an `OutputDevice`, so use it inside `using` block or call `Dispose` manually. Without it all resources taken by the device will live until GC collect them via finalizer of the `OutputDevice`. It means that sometimes you will not be able to use different instances of the same device across multiple applications or different pieces of a program. +> You must always take care about disposing an `OutputDevice`, so use it inside `using` block or call `Dispose` manually. Without it all resources taken by the device will live until GC collects them via the finalizer of the `OutputDevice`. It means that sometimes you will not be able to use different instances of the same device across multiple applications or different pieces of a program. + +> [!IMPORTANT] +> If you use an instance of the `OutputDevice` within a `using` block, you need to be very careful. In general it's not a good practice and can cause problems. For example, with this code +> ```csharp +> using (var outputDevice = OutputDevice.GetByName("Some MIDI device")) +> { +> outputDevice.SendEvent(new NoteOnEvent()); +> } +> ``` +> the `NoteOnEvent` can be not sent (it's a matter of race conditions) since the program leaves the `using` block before that, and thus the device instance will be destroyed. -First call of `SendEvent` method can take some time for allocating resources for device, so if you want to eliminate this operation on sending a MIDI event, you can call [PrepareForEventsSending](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice.PrepareForEventsSending) method before any MIDI event will be sent. +First call of the `SendEvent` method can take some time for allocating resources for a device, so if you want to eliminate this operation on sending a MIDI event, you can call [PrepareForEventsSending](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice.PrepareForEventsSending) method before any MIDI event will be sent. ## Custom output device @@ -82,6 +98,6 @@ private sealed class ConsoleOutputDevice : IOutputDevice } ``` -You can then use this device, for example, for debug in [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback). +You can then use this device, for example, for debugging in [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback). -Another one use case for custom output device is plugging some synth. So you create output device where [SendEvent](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice.SendEvent(Melanchall.DryWetMidi.Core.MidiEvent)) will redirect MIDI events to synth. \ No newline at end of file +Another use case for custom output device is plugging some synth. So you create an output device where [SendEvent](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice.SendEvent(Melanchall.DryWetMidi.Core.MidiEvent)) will redirect MIDI events to the synth. \ No newline at end of file diff --git a/Docs/articles/devices/Overview.md b/Docs/articles/devices/Overview.md index fd36a781b..d6c991e56 100644 --- a/Docs/articles/devices/Overview.md +++ b/Docs/articles/devices/Overview.md @@ -4,18 +4,18 @@ uid: a_dev_overview # Overview -DryWetMIDI provides ability to send MIDI data to or receive it from MIDI devices. For that purpose there are following types: +DryWetMIDI provides the ability to send MIDI data to or receive it from MIDI devices. For that purpose there are following types: * [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) (see [Input device](Input-device.md) article); * [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice) (see [Output device](Output-device.md) article); * [DevicesConnector](xref:Melanchall.DryWetMidi.Multimedia.DevicesConnector) (see [Devices connector](Devices-connector.md) article). -The library provides implementations for both `IInputDevice` and `IOutputDevice`: [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.InputDevice) and [OutputDevice](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice) correspondingly which represent MIDI devices visible by the operating system. Both classes implement [IDisposable](xref:System.IDisposable) interface so you should always dispose them to free devices for using by another applications. +The library provides implementations for both `IInputDevice` and `IOutputDevice`: [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.InputDevice) and [OutputDevice](xref:Melanchall.DryWetMidi.Multimedia.OutputDevice) correspondingly which represent MIDI devices visible by the operating system. Both classes implement [IDisposable](xref:System.IDisposable) interface so you should always dispose of them to free devices for use by other applications. > [!IMPORTANT] > You can use `InputDevice` and `OutputDevice` built-in implementations of `IInputDevice` and `IOutputDevice` only on the systems listed in the [Supported OS](xref:a_develop_supported_os) article. Of course you can create your own implementations of `IInputDevice` and `IOutputDevice`. -All classes that interact with devices work with interfaces mentioned above, so you can create custom implementation of your devices (see examples in [Input device](Input-device.md) and [Output device](Output-device.md) articles) and use it for playback or recording, for example. +All classes that interact with devices work with interfaces mentioned above, so you can create custom implementations of your devices (see examples in [Input device](Input-device.md) and [Output device](Output-device.md) articles) and use it for playback or recording, for example. MIDI devices API classes are placed in the [Melanchall.DryWetMidi.Multimedia](xref:Melanchall.DryWetMidi.Multimedia) namespace. @@ -23,6 +23,6 @@ To understand what is an input and an output device in DryWetMIDI take a look at ![Devices](images/Devices.png) -So, as you can see, although a MIDI port is _MIDI IN_ for MIDI device, it will be an **output device** in DryWetMIDI because your application will **send MIDI data to** this port. _MIDI OUT_ of MIDI device will be an **input device** in DryWetMIDI because a program will **receive MIDI data from** the port. +So, as you can see, although a MIDI port is _MIDI IN_ for a MIDI device, it will be an **output device** in DryWetMIDI because your application will **send MIDI data to** this port. _MIDI OUT_ of MIDI device will be an **input device** in DryWetMIDI because a program will **receive MIDI data from** the port. -If some error occurred during sending or receiving a MIDI event, the [ErrorOccurred](xref:Melanchall.DryWetMidi.Multimedia.MidiDevice.ErrorOccurred) event will be fired holding an exception caused the error. +If some error occurred during sending or receiving a MIDI event, the [ErrorOccurred](xref:Melanchall.DryWetMidi.Multimedia.MidiDevice.ErrorOccurred) event will be fired holding an exception caused by the error. diff --git a/Docs/articles/devices/Virtual-device.md b/Docs/articles/devices/Virtual-device.md index aa65da046..86e2c7034 100644 --- a/Docs/articles/devices/Virtual-device.md +++ b/Docs/articles/devices/Virtual-device.md @@ -5,17 +5,17 @@ uid: a_dev_virtual # Virtual device > [!IMPORTANT] -> Virtual devices API is a platform-specific one so please refer to the [Supported OS](xref:a_develop_supported_os) article to learn more. For Windows you can use products like [virtualMIDI SDK](https://www.tobias-erichsen.de/software/virtualmidi/virtualmidi-sdk.html) or similar to work with virtual MIDI ports programmatically. Be careful with license of these products. +> Virtual devices API is a platform-specific one so please refer to the [Supported OS](xref:a_develop_supported_os) article to learn more. For Windows you can use products like [virtualMIDI SDK](https://www.tobias-erichsen.de/software/virtualmidi/virtualmidi-sdk.html) or similar to work with virtual MIDI ports programmatically. Be careful with the license of these products. -With DryWetMIDI you can programmatically create virtual MIDI devices with the specified name using [VirtualDevice.Create](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice.Create(System.String)) method. In fact virtual device is an [input](xref:a_dev_input) and an [output](xref:a_dev_output) devices paired together in a way that any MIDI event sent to the output device will be immediately transferred back from the virtual device and can be received by an application from its input subdevice. +With DryWetMIDI you can programmatically create virtual MIDI devices with the specified name using [VirtualDevice.Create](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice.Create(System.String)) method. In fact, virtual device is an [input](xref:a_dev_input) and an [output](xref:a_dev_output) devices paired together in a way that any MIDI event sent to the output device will be immediately transferred back from the virtual device and can be received by an application from its input subdevice. -Thus we have [loopback](https://en.wikipedia.org/wiki/Loopback) device here. Loopback device is useful, for example, as intermediate layer between an application and some software synthesizer. In this case: +Thus we have a [loopback](https://en.wikipedia.org/wiki/Loopback) device here. Loopback device is useful, for example, as an intermediate layer between an application and some software synthesizer. In this case: 1. you create virtual device, for example, named as _MyDevice_; 2. in the application you set _MyDevice_ as an output MIDI port, so the application will send MIDI data to the output subdevice of the virtual device; -3. in software synthesizer you set _MyDevice_ as an input MIDI port. +3. in the software synthesizer you set _MyDevice_ as an input MIDI port. -So when you create virtual device an input device and an output one are created with the same name as the one specified on virtual device creation. Subdevices of a virtual device are available via [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice.InputDevice) and [OutputDevice](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice.OutputDevice) properties of the [VirtualDevice](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice) class. Of course you can use those device separately as regular input and output devices: +So when you create a virtual device an input device and an output one are created with the same name as the one specified on virtual device creation. Subdevices of a virtual device are available via [InputDevice](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice.InputDevice) and [OutputDevice](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice.OutputDevice) properties of the [VirtualDevice](xref:Melanchall.DryWetMidi.Multimedia.VirtualDevice) class. Of course you can use those device separately as regular input and output devices: ```csharp using System; @@ -118,4 +118,4 @@ Event Note On [5] (70, 60) received on device Leaf2. > virtualDevice.Dispose(); > ``` -You must not explicitly dispose subdevices of a virtual device. More than that calling `Dispose` on `virtualDevice.InputDevice` and `virtualDevice.OutputDevice` will throw an exception. But if you got references to the subdevices by regular methods (for example, by [InputDevice.GetByName](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetByName(System.String))), you can call `Dispose` on that references of course. +You must not explicitly dispose of subdevices of a virtual device. More than that, calling `Dispose` on `virtualDevice.InputDevice` and `virtualDevice.OutputDevice` will throw an exception. But if you got references to the subdevices by regular methods (for example, by [InputDevice.GetByName](xref:Melanchall.DryWetMidi.Multimedia.InputDevice.GetByName(System.String))), you can call `Dispose` on those references of course. diff --git a/Docs/articles/file-reading-writing/Lazy-reading-writing.md b/Docs/articles/file-reading-writing/Lazy-reading-writing.md index ff98d6fc1..93695ef6d 100644 --- a/Docs/articles/file-reading-writing/Lazy-reading-writing.md +++ b/Docs/articles/file-reading-writing/Lazy-reading-writing.md @@ -6,7 +6,7 @@ uid: a_file_lazy_reading_writing ## Reading -When you call [MidiFile.Read](xref:Melanchall.DryWetMidi.Core.MidiFile.Read*) method, the entire structure of a MIDI file will be put to memory as an object of the [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile) type. It means that more or less big MIDI file will span significant amount of bytes in the memory. Although it's much easy to work with such fully initialized object, memory consumption can be a critical parameter for your application. +When you call the [MidiFile.Read](xref:Melanchall.DryWetMidi.Core.MidiFile.Read*) method, the entire structure of a MIDI file will be put to memory as an object of the [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile) type. It means that a more or less big MIDI file will span a significant number of bytes in the memory. Although it's much easier to work with such fully initialized objects, memory consumption can be a critical parameter for your application. To solve the problem DryWetMIDI provides [MidiFile.ReadLazy](xref:Melanchall.DryWetMidi.Core.MidiFile.ReadLazy*) methods which return an instance of the [MidiTokensReader](xref:Melanchall.DryWetMidi.Core.MidiTokensReader) class that can read a MIDI file sequentially token by token: @@ -34,7 +34,7 @@ using (var tokensReader = MidiFile.ReadLazy("test.mid")) } ``` -With this approach each call of [ReadToken](xref:Melanchall.DryWetMidi.Core.MidiTokensReader.ReadToken) method will read next portion of data from a MIDI file (in fact, the internal reader in DryWetMIDI uses the buffer of `4096` bytes by default, the size of this buffer can be adjusted via [BufferSize](xref:Melanchall.DryWetMidi.Core.ReaderSettings.BufferSize) property of [ReadingSettings.ReaderSettings](xref:Melanchall.DryWetMidi.Core.ReadingSettings.ReaderSettings)). So the memory consumption will be almost always constant (despite a MIDI file size) and low. But of course there's the price – slower reading and much more difficult implementation of some logic and algorithms needed for your application. Also many high level tools provided by DryWetMIDI will be unavailable. +With this approach each call of [ReadToken](xref:Melanchall.DryWetMidi.Core.MidiTokensReader.ReadToken) method will read next portion of data from a MIDI file (in fact, the internal reader in DryWetMIDI uses the buffer of `4096` bytes by default, the size of this buffer can be adjusted via [BufferSize](xref:Melanchall.DryWetMidi.Core.ReaderSettings.BufferSize) property of [ReadingSettings.ReaderSettings](xref:Melanchall.DryWetMidi.Core.ReadingSettings.ReaderSettings)). So, the memory consumption will be almost always constant (despite a MIDI file size) and low. But of course, there's the price – slower reading and much more difficult implementation of some logic and algorithms needed for your application. Also, many high level tools provided by DryWetMIDI will be unavailable. Some useful methods can be found in the [MidiTokensReaderUtilities](xref:Melanchall.DryWetMidi.Core.MidiTokensReaderUtilities) class. Following example shows how you can calculate count of all _A#_ notes for each track chunk ([EnumerateObjects](xref:Melanchall.DryWetMidi.Interaction.GetObjectsUtilities.EnumerateObjects*) method also used): @@ -131,4 +131,4 @@ using (var objectsWriter = new TimedObjectsWriter(tokensWriter)) } ``` -The code above writes one million notes to a MIDI file. Of course you can combine [MidiTokensReader](xref:Melanchall.DryWetMidi.Core.MidiTokensReader), [EnumerateObjects](xref:Melanchall.DryWetMidi.Interaction.GetObjectsUtilities.EnumerateObjects*), [MidiTokensWriter](xref:Melanchall.DryWetMidi.Core.MidiTokensWriter) and [TimedObjectsWriter](xref:Melanchall.DryWetMidi.Interaction.TimedObjectsWriter) to process MIDI objects transforming a MIDI file into another one. \ No newline at end of file +The code above writes one million notes to a MIDI file. Of course, you can combine [MidiTokensReader](xref:Melanchall.DryWetMidi.Core.MidiTokensReader), [EnumerateObjects](xref:Melanchall.DryWetMidi.Interaction.GetObjectsUtilities.EnumerateObjects*), [MidiTokensWriter](xref:Melanchall.DryWetMidi.Core.MidiTokensWriter) and [TimedObjectsWriter](xref:Melanchall.DryWetMidi.Interaction.TimedObjectsWriter) to process MIDI objects transforming a MIDI file into another one. \ No newline at end of file diff --git a/Docs/articles/file-reading-writing/MIDI-file-reading.md b/Docs/articles/file-reading-writing/MIDI-file-reading.md index 4005103ab..d4a1c6151 100644 --- a/Docs/articles/file-reading-writing/MIDI-file-reading.md +++ b/Docs/articles/file-reading-writing/MIDI-file-reading.md @@ -10,7 +10,7 @@ The simplest code for MIDI file reading is: var file = MidiFile.Read("Some great song.mid"); ``` -After that you have instance of the [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile). Its [Chunks](xref:Melanchall.DryWetMidi.Core.MidiFile.Chunks) property returns collection of chunks within the MIDI file read. Using this collection you can manage chunks (add new, delete existing one and so on). +After that you have an instance of the [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile). Its [Chunks](xref:Melanchall.DryWetMidi.Core.MidiFile.Chunks) property returns a collection of chunks within the MIDI file read. Using this collection, you can manage chunks (add new, delete existing one and so on). MIDI file reading process can be finely adjusted via [ReadingSettings](xref:Melanchall.DryWetMidi.Core.ReadingSettings). For example, if we want to abort reading on unknown chunk (about reading custom chunks see [Custom chunks](xref:a_custom_chunk) article), you can set corresponding policy: @@ -43,4 +43,4 @@ var file = MidiFile.Read("Some great song.mid", new ReadingSettings }); ``` -Please read more about these properties in the API section to learn about what options are available to handle each error. If some policies set to `Abort`, an instance of corresponding exception will be thrown. All such exceptions types are derived from [MidiException](xref:Melanchall.DryWetMidi.Common.MidiException) and listed in _Exceptions_ section of `Read` methods ([by file path](xref:Melanchall.DryWetMidi.Core.MidiFile.Read(System.String,Melanchall.DryWetMidi.Core.ReadingSettings)) or [by stream](xref:Melanchall.DryWetMidi.Core.MidiFile.Read(System.IO.Stream,Melanchall.DryWetMidi.Core.ReadingSettings))) on API documentation. \ No newline at end of file +Please read more about these properties in the API section to learn about what options are available to handle each error. If some policies are set to `Abort`, an instance of corresponding exception will be thrown. Types of all such exceptions are derived from [MidiException](xref:Melanchall.DryWetMidi.Common.MidiException) and listed in the _Exceptions_ section of `Read` methods ([by file path](xref:Melanchall.DryWetMidi.Core.MidiFile.Read(System.String,Melanchall.DryWetMidi.Core.ReadingSettings)) or [by stream](xref:Melanchall.DryWetMidi.Core.MidiFile.Read(System.IO.Stream,Melanchall.DryWetMidi.Core.ReadingSettings))) on API documentation. \ No newline at end of file diff --git a/Docs/articles/file-reading-writing/MIDI-file-writing.md b/Docs/articles/file-reading-writing/MIDI-file-writing.md index 131381579..77518d522 100644 --- a/Docs/articles/file-reading-writing/MIDI-file-writing.md +++ b/Docs/articles/file-reading-writing/MIDI-file-writing.md @@ -10,7 +10,7 @@ The simplest code for MIDI file writing is: file.Write("Some great song.mid"); ``` -If file with this name already exist, you'll get an exception. To overwrite existing file pass `true` to `overwriteFile` parameter: +If a file with this name already exists, you'll get an exception. To overwrite existing file pass `true` to `overwriteFile` parameter: ```csharp file.Write("Some great song.mid", overwriteFile: true); @@ -18,7 +18,7 @@ file.Write("Some great song.mid", overwriteFile: true); ## Compression -You can set specific policies via [WritingSettings](xref:Melanchall.DryWetMidi.Core.WritingSettings) to reduce size of an output file. For example, to use running status and thus don't write status bytes of channel events of the same type, set properties shown in the following code: +You can set specific policies via [WritingSettings](xref:Melanchall.DryWetMidi.Core.WritingSettings) to reduce the size of an output file. For example, to use running status and thus don't write status bytes of channel events of the same type, set properties shown in the following code: ```csharp file.Write("Some great song.mid", settings: new WritingSettings @@ -28,4 +28,4 @@ file.Write("Some great song.mid", settings: new WritingSettings }); ``` -Complete list of available properties is placed in documentation of [WritingSettings](xref:Melanchall.DryWetMidi.Core.WritingSettings). +Complete list of available properties is placed in the documentation of [WritingSettings](xref:Melanchall.DryWetMidi.Core.WritingSettings). diff --git a/Docs/articles/high-level-managing/Getting-objects.md b/Docs/articles/high-level-managing/Getting-objects.md index 5e0ac70d6..62ef39a1f 100644 --- a/Docs/articles/high-level-managing/Getting-objects.md +++ b/Docs/articles/high-level-managing/Getting-objects.md @@ -30,7 +30,7 @@ namespace DwmExamples } ``` -Please examine [TimedEventsManagingUtilities](xref:Melanchall.DryWetMidi.Interaction.TimedEventsManagingUtilities) class to see other `GetTimedEvents` overloads. +Please examine the [TimedEventsManagingUtilities](xref:Melanchall.DryWetMidi.Interaction.TimedEventsManagingUtilities) class to see other `GetTimedEvents` overloads. ## GetNotes @@ -106,11 +106,11 @@ All `GetNotes` overloads can accept [NoteDetectionSettings](xref:Melanchall.DryW More than that, notes are built on top of timed events. So you can pass [TimedEventDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.TimedEventDetectionSettings) as a separate parameter to control how underlying MIDI events will be constructed. -Let's see each setting of the [NoteDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings) in details. +Let's see each setting of the [NoteDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings) in detail. #### `NoteStartDetectionPolicy` -The [NoteStartDetectionPolicy](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings.NoteStartDetectionPolicy) property defines how start event of a note should be found in case of overlapping notes with the same note number and channel. The default value is [NoteStartDetectionPolicy.FirstNoteOn](xref:Melanchall.DryWetMidi.Interaction.NoteStartDetectionPolicy.FirstNoteOn). +The [NoteStartDetectionPolicy](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings.NoteStartDetectionPolicy) property defines how the start event of a note should be found in case of overlapping notes with the same note number and channel. The default value is [NoteStartDetectionPolicy.FirstNoteOn](xref:Melanchall.DryWetMidi.Interaction.NoteStartDetectionPolicy.FirstNoteOn). To understand how this policy works let's take a look at the following events sequence: @@ -122,11 +122,11 @@ If we set `NoteStartDetectionPolicy` to [NoteStartDetectionPolicy.FirstNoteOn](x ![NoteStartDetectionPolicy-FirstNoteOn](images/Getting-objects-NoteStartDetectionPolicy-FirstNoteOn.png) -So every _Note Off_ event will be combined with **first** free _Note On_ event into a note (events are processed one by one consecutively). But if set `NoteStartDetectionPolicy` to [NoteStartDetectionPolicy.LastNoteOn](xref:Melanchall.DryWetMidi.Interaction.NoteStartDetectionPolicy.LastNoteOn), we'll get another picture: +So every _Note Off_ event will be combined with the **first** free _Note On_ event into a note (events are processed one by one consecutively). But if set `NoteStartDetectionPolicy` to [NoteStartDetectionPolicy.LastNoteOn](xref:Melanchall.DryWetMidi.Interaction.NoteStartDetectionPolicy.LastNoteOn), we'll get another picture: ![NoteStartDetectionPolicy-LastNoteOn](images/Getting-objects-NoteStartDetectionPolicy-LastNoteOn.png) -So _Note Off_ events will be combined with **last** free _Note On_ event into a note. +So _Note Off_ events will be combined with the **last** free _Note On_ event into a note. ## GetChords @@ -231,17 +231,17 @@ chord off velocity = 30 ``` -Please examine [ChordsManagingUtilities](xref:Melanchall.DryWetMidi.Interaction.ChordsManagingUtilities) class to see other `GetChords` overloads. +Please examine the [ChordsManagingUtilities](xref:Melanchall.DryWetMidi.Interaction.ChordsManagingUtilities) class to see other `GetChords` overloads. ### Settings -All `GetChords` overloads can accept [ChordDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.ChordDetectionSettings) as a parameter. Via this parameter you can adjust the process of chords building. +All `GetChords` overloads can accept [ChordDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.ChordDetectionSettings) as a parameter. Via this parameter you can adjust the process of chord building. Also note that chords are built on top of notes. So to build chords we need to build notes. The process of notes building is adjustable via [NoteDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings) which you can pass to the methods too. Properties of the [NoteDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings) are described in detail [above](#settings). More than that, notes are built on top of timed events as described [above](#settings). So you can pass [TimedEventDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.TimedEventDetectionSettings) too. -Let's see each setting of the [ChordDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.ChordDetectionSettings) in details. +Let's see each setting of the [ChordDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.ChordDetectionSettings) in detail. #### `NotesTolerance` @@ -255,7 +255,7 @@ If we set notes tolerance to `0` (which is default value), we'll get three diffe ![NotesTolerance-0](images/Getting-objects-NotesTolerance-0.png) -Different colors denotes different chords. If we set notes tolerance to `1`, we'll get two chords: +Different colors denote different chords. If we set notes tolerance to `1`, we'll get two chords: ![NotesTolerance-1](images/Getting-objects-NotesTolerance-1.png) @@ -265,7 +265,7 @@ With tolerance of `2` we'll finally get a single chord: #### `NotesMinCount` -The [NotesMinCount](xref:Melanchall.DryWetMidi.Interaction.ChordDetectionSettings.NotesMinCount) property defines the minimum count of notes a chord can contain. So if count of simultaneously sounding notes is less than this value, they won't make up a chord. The default value is `1` which means a single note can be turned to a chord. +The [NotesMinCount](xref:Melanchall.DryWetMidi.Interaction.ChordDetectionSettings.NotesMinCount) property defines the minimum count of notes a chord can contain. So if the count of simultaneously sounding notes is less than this value, they won't make up a chord. The default value is `1` which means a single note can be turned to a chord. To understand how this property works let's take a look at the following notes (cross means any non-note event): @@ -279,15 +279,15 @@ If we set notes min count to `2`, we'll get only one chord: ![NotesMinCount-2](images/Getting-objects-NotesMinCount-2.png) -Last note will not be turned into a chord because count of notes for a chord will be `1` which is less than the specified minimum count. With minimum count of notes of `3` we'll get no chords: +Last note will not be turned into a chord because the count of notes for a chord will be `1` which is less than the specified minimum count. With minimum count of notes of `3` we'll get no chords: ![NotesMinCount-3](images/Getting-objects-NotesMinCount-3.png) -First possible chord will contain two notes and second chord will contain one note. In both cases count of notes is less than the specified minimum count. +First possible chord will contain two notes and the second chord will contain one note. In both cases the count of notes is less than the specified minimum count. ## GetObjects -All methods we saw before return collection of objects of the **same type**. So you can get only either notes or chords or timed events. To highlight the problem, let's take a look at the following events sequence: +All methods we saw before return a collection of objects of the **same type**. So you can get only either notes or chords or timed events. To highlight the problem, let's take a look at the following events sequence: ![GetObjects-Initial](images/Getting-objects-GetObjects-Initial.png) @@ -400,7 +400,7 @@ Getting chords... [Chord] C-1 C-1 (time = 5) ``` -As you can see there is "free" _Note On_ event without corresponding _Note Off_ one so we can't build a note for it. What if we want to get all possible notes and all remaining timed events? DryWetMIDI provides [GetObjectsUtilities](xref:Melanchall.DryWetMidi.Interaction.GetObjectsUtilities) class which contains `GetObjects` methods (for the same MIDI structures as previous methods). We can change printing part of the program above to: +As you can see there is a "free" _Note On_ event without corresponding _Note Off_ one so we can't build a note for it. What if we want to get all possible notes and all remaining timed events? DryWetMIDI provides the [GetObjectsUtilities](xref:Melanchall.DryWetMidi.Interaction.GetObjectsUtilities) class which contains `GetObjects` methods (for the same MIDI structures as previous methods). We can change printing part of the program above to: ```csharp @@ -491,7 +491,7 @@ To build rests you need to use extension methods from the [RestsUtilities](xref: If you take a look into the class, you'll discover two methods – [WithRests](xref:Melanchall.DryWetMidi.Interaction.RestsUtilities.WithRests*) and [GetRests](xref:Melanchall.DryWetMidi.Interaction.RestsUtilities.GetRests*). The first one adds rests to a collection of objects you've passed to the method. The second method returns rests only. -It will be much easier to understand how rests building works with examples. So let's look on [WithRests](xref:Melanchall.DryWetMidi.Interaction.RestsUtilities.WithRests*) (there is no great value to discuss [GetRests](xref:Melanchall.DryWetMidi.Interaction.RestsUtilities.GetRests*) since it works in the same way but just returns rests only). +It will be much easier to understand how rests building works with examples. So let's look at the [WithRests](xref:Melanchall.DryWetMidi.Interaction.RestsUtilities.WithRests*) (there is no great value to discuss [GetRests](xref:Melanchall.DryWetMidi.Interaction.RestsUtilities.GetRests*) since it works in the same way but just returns rests only). Supposing we have following notes (with two different note numbers on two different channels): @@ -511,9 +511,9 @@ we'll get only one rest: ![GetObjects-Rests-NoSeparation](images/Getting-objects-GetObjects-Rests-NoSeparation.png) -An important concept we need to discuss is a key selection. Key is used to calculate rests. Rests are always calculated only between objects with the same key. If an object with different key is encountered, rests will be calculated for that key. +An important concept we need to discuss is **key selection**. Key is used to calculate rests. Rests are always calculated only between objects with the same key. If an object with different key is encountered, rests will be calculated for that key. -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). +In the code above we're saying: _The key of each object is 0_. So for the rests building algorithm all objects are the 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: @@ -538,9 +538,9 @@ var notesAndRests = notes So rests are separated by channels. Channel is the key of an object. Note number of a note doesn't matter, all numbers are treated as the same one. So rests will be constructed separately for each channel at spaces where there are no notes (with any note numbers). -The key for which a rest has been built will be store in the [Key](xref:Melanchall.DryWetMidi.Interaction.Rest.Key) property of [Rest](xref:Melanchall.DryWetMidi.Interaction.Rest) class. `notesAndRests` is a collection containing both `notes` and calculated rests, and elements of this collection are sorted by their times. +The key for which a rest has been built will be stored in the [Key](xref:Melanchall.DryWetMidi.Interaction.Rest.Key) property of [Rest](xref:Melanchall.DryWetMidi.Interaction.Rest) class. `notesAndRests` is a collection containing both `notes` and calculated rests, and elements of this collection are sorted by their times. -Note that you can build rests for objects of different types. Why not to get chords from a MIDI file and add rests between them? +Note that you can build rests for objects of different types. Why not get chords from a MIDI file and add rests between them? ```csharp var chordsAndRests = midiFile @@ -551,7 +551,7 @@ var chordsAndRests = midiFile }); ``` -And a couple of words about return value of key selector. If `null` is returned, an object won't participate in rests building process. It allows you to have rests for desired objects only. For example: +And a couple of words about return value of the key selector. If `null` is returned, an object won't participate in rests building process. It allows you to have rests for desired objects only. For example: ```csharp var notesAndChordsAndRests = midiFile @@ -580,7 +580,7 @@ Rests: ![GetObjects-Rests-SeparateByNoteNumber](images/Getting-objects-GetObjects-Rests-SeparateByNoteNumber.png) -As you can see rests now are separated by note number (channel doesn't matter). So rests will be constructed for each note number at spaces where there are no notes (with any channel). +As you can see, rests now are separated by note number (channel doesn't matter). So rests will be constructed for each note number at spaces where there are no notes (with any channel). Code: diff --git a/Docs/articles/high-level-managing/Objects-managers.md b/Docs/articles/high-level-managing/Objects-managers.md index 6d4caa963..4de62e396 100644 --- a/Docs/articles/high-level-managing/Objects-managers.md +++ b/Docs/articles/high-level-managing/Objects-managers.md @@ -4,7 +4,7 @@ uid: a_managers # Objects managers -Working with low-level objects (like MIDI event) sometimes is not convenient. In this cases it's much more handy to manage MIDI data via concepts of [timed events](xref:Melanchall.DryWetMidi.Interaction.TimedEvent), [notes](xref:Melanchall.DryWetMidi.Interaction.Note) or [chords](xref:Melanchall.DryWetMidi.Interaction.Chord). +Working with low-level objects (like a MIDI event) sometimes is not convenient. In this case it's much more handy to manage MIDI data via concepts of [timed events](xref:Melanchall.DryWetMidi.Interaction.TimedEvent), [notes](xref:Melanchall.DryWetMidi.Interaction.Note) or [chords](xref:Melanchall.DryWetMidi.Interaction.Chord). DryWetMIDI provides a way to work with such high-level objects – [TimedObjectsManager](xref:Melanchall.DryWetMidi.Interaction.TimedObjectsManager). This class allows to work with objects of different types within an events collection (see [TrackChunk.Events](xref:Melanchall.DryWetMidi.Core.TrackChunk.Events)): @@ -42,7 +42,7 @@ var notesManager = new TimedObjectsManager(trackChunk.Events); notesManager.SaveChanges(); ``` -Objects will be placed in the underlying events collection in chronological order of course. Also as you can see there is the generic constructor that allows to manage objects of the single type. `Objects` property will return in this case objects of this type, no need to cast them to the type. +Objects will be placed in the underlying events collection in chronological order of course. Also as you can see there is the generic constructor that allows managing objects of the single type. `Objects` property will return in this case objects of this type, no need to cast them to the type. ## Simultaneous editing of events collection @@ -70,4 +70,4 @@ var notesManager = new TimedObjectsManager(trackChunk.Events); timedEventsManager.SaveChanges(); ``` -will cause changes made with the `notesManager` will be lost because `SaveChanges` (or `Dispose` in first code snippet) of `timedEventsManager` executed after `SaveChanges` of `notesManager`, and thus rewrites underlying events collection. You need to save changes made with a previous manager before managing objects with next one. \ No newline at end of file +will cause changes made with the `notesManager` will be lost because `SaveChanges` (or `Dispose` in first code snippet) of `timedEventsManager` executed after `SaveChanges` of `notesManager`, and thus rewrites underlying events collection. You need to save changes made with a previous manager before managing objects with the next one. \ No newline at end of file diff --git a/Docs/articles/high-level-managing/Processing-objects.md b/Docs/articles/high-level-managing/Processing-objects.md new file mode 100644 index 000000000..40717b7f2 --- /dev/null +++ b/Docs/articles/high-level-managing/Processing-objects.md @@ -0,0 +1,234 @@ +--- +uid: a_processing_objects +--- + +# Processing objects + +> [!IMPORTANT] +> Please read the [Getting objects](xref:a_getting_objects) article before reading this one. + +This article describes ways to process different objects (like [timed events](xref:Melanchall.DryWetMidi.Interaction.TimedEvent) or [notes](xref:Melanchall.DryWetMidi.Interaction.Note)) within MIDI files and track chunks. Processing means changing properties of the objects, including time or length. + +## ProcessTimedEvents + +Let's dive into the [ProcessTimedEvents](xref:Melanchall.DryWetMidi.Interaction.TimedEventsManagingUtilities.ProcessTimedEvents*) methods immediately. Well, we have such a file: + +```csharp +var midiFile = new MidiFile( + new TrackChunk( + new TextEvent("A"), + new NoteOnEvent(), + new NoteOffEvent()), + new TrackChunk( + new TextEvent("B"))); +``` + +And now we want to change the text of all _Text_ events to `"C"`: + +```csharp +midiFile.ProcessTimedEvents( + e => ((TextEvent)e.Event).Text = "C", + e => e.Event.EventType == MidiEventType.Text); +``` + +After this instruction executed, the file will be equal to this one: + +```csharp +var midiFile = new MidiFile( + new TrackChunk( + new TextEvent("C"), + new NoteOnEvent(), + new NoteOffEvent()), + new TrackChunk( + new TextEvent("C"))); +``` + +So the first argument (`e => ((TextEvent)e.Event).Text = "C"`) is what we want to do and the second one (`e => e.Event.EventType == MidiEventType.Text`) defines what objects to process. We can also use the overload without predicate to process all timed events: + +```csharp +midiFile.ProcessTimedEvents(e => e.Time += 10); +``` + +So the first event in each track chunk will have [DeltaTime](xref:Melanchall.DryWetMidi.Core.MidiEvent.DeltaTime) of `10` now so the absolute time of each event increases by `10`: + +```csharp +var midiFile = new MidiFile( + new TrackChunk( + new TextEvent("C") { DeltaTime = 10 }, + new NoteOnEvent(), + new NoteOffEvent()), + new TrackChunk( + new TextEvent("C") { DeltaTime = 10 })); +``` + +Please examine the [TimedEventsManagingUtilities](xref:Melanchall.DryWetMidi.Interaction.TimedEventsManagingUtilities) class to see other `ProcessTimedEvents` overloads. + +By the way, what if we want to process only events with even indices? First of all we need to define subclass of the [TimedEvent](xref:Melanchall.DryWetMidi.Interaction.TimedEvent): + +```csharp +private sealed class CustomTimedEvent : TimedEvent +{ + public CustomTimedEvent(MidiEvent midiEvent, long time, int eventIndex) + : base(midiEvent, time) + { + EventIndex = eventIndex; + } + + public int EventIndex { get; } +} +``` + +And now we are ready to increase the time of even-indexed events by `10` ticks: + +```csharp +midiFile.ProcessTimedEvents( + e => e.Time += 10, + e => ((CustomTimedEvent)e).EventIndex % 2 == 0, + new TimedEventDetectionSettings + { + Constructor = data => new CustomTimedEvent(data.Event, data.Time, data.EventIndex) + }); +``` + +New file will be: + +```csharp +var midiFile = new MidiFile( + new TrackChunk( + new NoteOnEvent(), + new TextEvent("C") { DeltaTime = 10 }, + new NoteOffEvent()), + new TrackChunk( + new TextEvent("C") { DeltaTime = 10 })); +``` + +Looks right. In the first track chunk _Text_ and _Note Off_ events are even-indexed ones (`0` and `2`), so the time of each one will be `10`. _Note On_ event will be left untouched so its time remains `0`. As for the second track chunk, the only event is even-indexed of course and thus is processed. + +Optionally you can pass [TimedEventProcessingHint](xref:Melanchall.DryWetMidi.Interaction.TimedEventProcessingHint) to set a hint for the processing engine which can improve performance. Please read about available options by the specified link. For example, if you know that time of events won't be changed, you can set [TimedEventProcessingHint.None](xref:Melanchall.DryWetMidi.Interaction.TimedEventProcessingHint.None) option to eliminate events resorting (which is required if times can be changed) and to reduce the time needed to perform the processing: + +```csharp +midiFile.ProcessTimedEvents( + e => ((TextEvent)e.Event).Text = "C", + e => e.Event.EventType == MidiEventType.Text, + hint: TimedEventProcessingHint.None); +``` + +All [ProcessTimedEvents](xref:Melanchall.DryWetMidi.Interaction.TimedEventsManagingUtilities.ProcessTimedEvents*) methods return count of processed objects. + +## ProcessNotes + +Use [ProcessNotes](xref:Melanchall.DryWetMidi.Interaction.NotesManagingUtilities.ProcessNotes*) methods to process notes. There is nothing tricky here and the approach is the same as for timed events processing (see above): you just work with notes instead of timed events. + +Please read [GetNotes: Settings](xref:a_getting_objects#settings) article to learn more about how to customize notes detection and building. Be aware that note is in fact a pair of _Note On_ / _Note Off_ events. So to have full control on the notes construction process, [ProcessNotes](xref:Melanchall.DryWetMidi.Interaction.NotesManagingUtilities.ProcessNotes*) methods accept [TimedEventDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.TimedEventDetectionSettings) along with [NoteDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings). + +As with timed events you can create subclass of the [Note](xref:Melanchall.DryWetMidi.Interaction.Note) class and use it to customize the matching logic: + +```csharp +private sealed class CustomNote : Note +{ + public CustomNote(TimedEvent timedNoteOnEvent, TimedEvent timedNoteOffEvent, bool isNoteOnEventUnevenIndexed) + : base(timedNoteOnEvent, timedNoteOffEvent) + { + IsNoteOnEventUnevenIndexed = isNoteOnEventUnevenIndexed; + } + + public bool IsNoteOnEventUnevenIndexed { get; } +} + +// ... + +midiFile.ProcessNotes( + n => n.Velocity += (SevenBitNumber)10, + n => ((CustomNote)n).IsNoteOnEventUnevenIndexed, + new NoteDetectionSettings + { + Constructor = data => new CustomNote( + data.TimedNoteOnEvent, + data.TimedNoteOffEvent, + ((CustomTimedEvent)data.TimedNoteOnEvent).EventIndex % 2 != 0) + }, + new TimedEventDetectionSettings + { + Constructor = data => new CustomTimedEvent( + data.Event, + data.Time, + data.EventIndex) + }); +``` + +So the velocity of a note will be increased by `10` if the note's _Note On_ event is uneven-indexed. + +And of course you can manage performance with notes processing too via [NoteProcessingHint](xref:Melanchall.DryWetMidi.Interaction.NoteProcessingHint). By default it's set to the value allowing you to change time and length. But you can reduce execution time via the [NoteProcessingHint.None](xref:Melanchall.DryWetMidi.Interaction.NoteProcessingHint.None) flag. With this option any changes of time or length of a note won't be saved. + +## ProcessChords + +Obviously you can process chords too – [ProcessChords](xref:Melanchall.DryWetMidi.Interaction.ChordsManagingUtilities.ProcessChords*) is what you need. It's redundant to describe how to use these methods since usage is the same as for [timed events](#processtimedevents) and [notes](#processnotes). + +A chord is in fact a collection of notes, each one is a pair of _Note On_ / _Note Off_ events (please read about [notes processing](#processnotes) above). So to have full control on the chord construction process, [ProcessChords](xref:Melanchall.DryWetMidi.Interaction.ChordsManagingUtilities.ProcessChords*) methods accept [TimedEventDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.TimedEventDetectionSettings) and [NoteDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings) along with [ChordDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.ChordDetectionSettings). + +As with timed events and notes you can create a subclass of the [Chord](xref:Melanchall.DryWetMidi.Interaction.Chord) class and use it to customize the matching logic. + +But for chords processing there are two additional options to control performance of the methods. These flags from [ChordProcessingHint](xref:Melanchall.DryWetMidi.Interaction.ChordProcessingHint) are: + +* [NoteTimeOrLengthCanBeChanged](xref:Melanchall.DryWetMidi.Interaction.ChordProcessingHint.NoteTimeOrLengthCanBeChanged): time or length of a note within a chord's notes can be changed; +* [NotesCollectionCanBeChanged](xref:Melanchall.DryWetMidi.Interaction.ChordProcessingHint.NotesCollectionCanBeChanged): chord's notes collection can be changed, for example, a note can be added or removed. + +By default those two options are not enabled. So if you want to say the engine that all chord's properties can be changed: + +```csharp +midiFile.ProcessChords( + c => c.Velocity += (SevenBitNumber)10, + hint: ChordProcessingHint.AllPropertiesCanBeChanged); +``` + +or if you want more precise control: + +```csharp +midiFile.ProcessChords( + c => c.Velocity += (SevenBitNumber)10, + hint: ChordProcessingHint.TimeOrLengthCanBeChanged | ChordProcessingHint.NoteTimeOrLengthCanBeChanged); +``` + +## ProcessObjects + +All methods we saw before process objects of the **same type**. So you can process only either notes or chords or timed events. But there is a way to process objects of different types at the same time – [ProcessObjects](xref:Melanchall.DryWetMidi.Interaction.TimedObjectUtilities.ProcessObjects*). For example, if you want to increase velocity of notes that are not a part of some chord: + +```csharp +midiFile.ProcessObjects( + ObjectType.Note | ObjectType.Chord, + obj => ((Note)obj).Velocity += (SevenBitNumber)10, + obj => obj is Note); +``` + +Or we can increase velocity based on the type of an object: + +```csharp +midiFile.ProcessObjects( + ObjectType.Note | ObjectType.Chord, + obj => + { + if (obj is Note note) + note.Velocity += (SevenBitNumber)10; + else + ((Chord)obj).Velocity += (SevenBitNumber)20; + }); +``` + +Performance of the [ProcessObjects](xref:Melanchall.DryWetMidi.Interaction.TimedObjectUtilities.ProcessObjects*) methods can be managed too – via passing [ObjectProcessingHint](xref:Melanchall.DryWetMidi.Interaction.ObjectProcessingHint). Possible values and how they affect the processing is the subject already described in previous sections. + +If you need to process objects of several types simultaneously, [ProcessObjects](xref:Melanchall.DryWetMidi.Interaction.TimedObjectUtilities.ProcessObjects*) will be much faster than consecutive calls of methods for specific object types. I.e. this instruction + +```csharp +midiFile.ProcessObjects( + ObjectType.Note | ObjectType.Chord, + /*...*/); +``` + +will execute faster than + +```csharp +midiFile.ProcessChords(/*...*/); +midiFile.ProcessNotes(/*...*/); +``` + +More than that, some cases can be covered with [ProcessObjects](xref:Melanchall.DryWetMidi.Interaction.TimedObjectUtilities.ProcessObjects*) only like the example described above – _increase velocity of notes that are not a part of some chord_. Of course you can always use [objects managers](xref:a_managers) to perform any transformations you want, but processing objects in that way is much slower. \ No newline at end of file diff --git a/Docs/articles/high-level-managing/Removing-objects.md b/Docs/articles/high-level-managing/Removing-objects.md new file mode 100644 index 000000000..7cb0f97f7 --- /dev/null +++ b/Docs/articles/high-level-managing/Removing-objects.md @@ -0,0 +1,101 @@ +--- +uid: a_removing_objects +--- + +# Removing objects + +> [!IMPORTANT] +> Please read [Getting objects](xref:a_getting_objects) and [Processing objects](xref:a_processing_objects) articles before reading this one. + +This article describes ways to remove different objects (like [timed events](xref:Melanchall.DryWetMidi.Interaction.TimedEvent) or [notes](xref:Melanchall.DryWetMidi.Interaction.Note)) from MIDI files and track chunks. + +## RemoveTimedEvents + +[RemoveTimedEvents](xref:Melanchall.DryWetMidi.Interaction.TimedEventsManagingUtilities.RemoveTimedEvents*) methods is what you need to remove MIDI events by the specified conditions. For example, we have such a file: + +```csharp +var midiFile = new MidiFile( + new TrackChunk( + new TextEvent("A") { DeltaTime = 10 }, + new NoteOnEvent(), + new NoteOffEvent()), + new TrackChunk( + new TextEvent("B"))); +``` + +And we want to remove all _Text_ events: + +```csharp +midiFile.RemoveTimedEvents( + e => e.Event.EventType == MidiEventType.Text); +``` + +After this instruction executed, the file will be equal to this one: + +```csharp +var midiFile = new MidiFile( + new TrackChunk( + new NoteOnEvent() { DeltaTime = 10 }, + new NoteOffEvent()), + new TrackChunk()); +``` + +So `e => e.Event.EventType == MidiEventType.Text` defines what objects to remove. Of course, absolute times of all events will be preserved as you can see. We can also use the overload without predicate to remove all timed events: + +```csharp +midiFile.RemoveTimedEvents(); +``` + +Please examine the [TimedEventsManagingUtilities](xref:Melanchall.DryWetMidi.Interaction.TimedEventsManagingUtilities) class to see other `RemoveTimedEvents` overloads. + +As you've learned from the [Processing objects: ProcessTimedEvents](xref:a_processing_objects#processtimedevents) article, you can instantiate timed events as custom classes derived from [TimedEvent](xref:Melanchall.DryWetMidi.Interaction.TimedEvent) and use those instances within a predicate to select objects. This article won't repeat information from there so please read it. + +All [RemoveTimedEvents](xref:Melanchall.DryWetMidi.Interaction.TimedEventsManagingUtilities.RemoveTimedEvents*) methods return count of removed objects. + +## RemoveNotes + +Use [RemoveNotes](xref:Melanchall.DryWetMidi.Interaction.NotesManagingUtilities.RemoveNotes*) methods to remove notes. The approach is the same as for timed events removing (see above): you just work with notes instead of timed events. + +Please read [Processing objects: ProcessNotes](xref:a_processing_objects#processnotes) article to learn about how to customize objects selection. + +## RemoveChords + +Obviously you can remove chords too – [RemoveChords](xref:Melanchall.DryWetMidi.Interaction.ChordsManagingUtilities.RemoveChords*) is what you need. It's redundant to describe how to use these methods since usage is the same as for [timed events](#removetimedevents) and [notes](#removenotes). Also please read [Processing objects: ProcessChords](xref:a_processing_objects#processchords) article. + +## RemoveObjects + +All methods we saw before remove objects of the **same type**. So you can remove only either notes or chords or timed events. But there is a way to remove objects of different types at the same time – [RemoveObjects](xref:Melanchall.DryWetMidi.Interaction.TimedObjectUtilities.RemoveObjects*). For example, if you want to remove notes that are not a part of some chord: + +```csharp +midiFile.RemoveObjects( + ObjectType.Note | ObjectType.Chord, + obj => obj is Note); +``` + +Or we can remove objects based on the type of an object: + +```csharp +midiFile.RemoveObjects( + ObjectType.Note | ObjectType.Chord, + obj => obj is Note note + ? note.Velocity > 10 + : ((Chord)obj).Velocity > 20); +``` + + +If you need to remove objects of several types simultaneously, [RemoveObjects](xref:Melanchall.DryWetMidi.Interaction.TimedObjectUtilities.RemoveObjects*) will be much faster than consecutive calls of methods for specific object types. I.e. this instruction + +```csharp +midiFile.RemoveObjects( + ObjectType.Note | ObjectType.Chord, + /*...*/); +``` + +will execute faster than + +```csharp +midiFile.RemoveChords(/*...*/); +midiFile.RemoveNotes(/*...*/); +``` + +More than that, some cases can be covered with [RemoveObjects](xref:Melanchall.DryWetMidi.Interaction.TimedObjectUtilities.RemoveObjects*) only like the example described above – _remove notes that are not a part of some chord_. Of course you can always use [objects managers](xref:a_managers) to remove objects with any desired logic, but that way is much slower. \ No newline at end of file diff --git a/Docs/articles/high-level-managing/Tempo-map.md b/Docs/articles/high-level-managing/Tempo-map.md index fa453c282..6ffb636cb 100644 --- a/Docs/articles/high-level-managing/Tempo-map.md +++ b/Docs/articles/high-level-managing/Tempo-map.md @@ -4,11 +4,11 @@ uid: a_tempo_map # Tempo map -Tempo map is a set of changes of time signature and tempo. Tempo map is one of the key objects in high-level managing of MIDI data. You need to have a tempo map to convert [time and length](Time-and-length.md) between different representations, either explicitly or internally at some parts of the library (for example, in [tools](xref:a_tools_overview)). Following image shows how tempo map constructed for a given set of events or MIDI file: +Tempo map is a set of changes of time signature and tempo. Tempo map is one of the key objects in high-level management of MIDI data. You need to have a tempo map to convert [time and length](Time-and-length.md) between different representations, either explicitly or internally at some parts of the library (for example, in [tools](xref:a_tools_overview)). Following image shows how tempo map constructed for a given set of events or MIDI file: ![Tempo map](images/TempoMap.png) -So as you can see tempo map doesn't reflect original MIDI events. Tempo map holds real tempo and time signature changes. Default tempo is 120 BPM by MIDI specifications so there is no need to hold first two tempo "changes" because tempo is not changed in fact. The same for time signature (which is `4/4` by default). Also any repeated values are ignored since they don't change tempo or time signature. +So as you can see a tempo map doesn't reflect original MIDI events. Tempo map holds real tempo and time signature changes. Default tempo is 120 BPM by MIDI specifications so there is no need to hold first two tempo "changes" because tempo is not changed in fact. The same for time signature (which is `4/4` by default). Also any repeated values are ignored since they don't change tempo or time signature. Instead of messing with [Time Signature](xref:Melanchall.DryWetMidi.Core.TimeSignatureEvent) and [Set Tempo](xref:Melanchall.DryWetMidi.Core.SetTempoEvent) events DryWetMIDI provides [TempoMapManager](xref:Melanchall.DryWetMidi.Interaction.TempoMapManager) which helps to manage tempo map of a MIDI file: @@ -29,9 +29,9 @@ using (var tempoMapManager = new TempoMapManager( } ``` -To get tempo map being managed by the current `TempoMapManager` you need to use [TempoMap](xref:Melanchall.DryWetMidi.Interaction.TempoMapManager.TempoMap) property which returns an instance of the [TempoMap](xref:Melanchall.DryWetMidi.Interaction.TempoMap) class. +To get the tempo map being managed by the current `TempoMapManager` you need to use [TempoMap](xref:Melanchall.DryWetMidi.Interaction.TempoMapManager.TempoMap) property which returns an instance of the [TempoMap](xref:Melanchall.DryWetMidi.Interaction.TempoMap) class. -Once you've got an instance of [TempoMap](xref:Melanchall.DryWetMidi.Interaction.TempoMap) you can use [GetTempoChanges](xref:Melanchall.DryWetMidi.Interaction.TempoMap.GetTempoChanges) method to get all tempo changes. Use [GetTimeSignatureChanges](xref:Melanchall.DryWetMidi.Interaction.TempoMap.GetTimeSignatureChanges) method to get time signature changes. [GetTempoAtTime](xref:Melanchall.DryWetMidi.Interaction.TempoMap.GetTempoAtTime(Melanchall.DryWetMidi.Interaction.ITimeSpan)) and [GetTimeSignatureAtTime](xref:Melanchall.DryWetMidi.Interaction.TempoMap.GetTimeSignatureAtTime(Melanchall.DryWetMidi.Interaction.ITimeSpan)) methods allow to get tempo and time signature at the specified time. +Once you've got an instance of [TempoMap](xref:Melanchall.DryWetMidi.Interaction.TempoMap) you can use the [GetTempoChanges](xref:Melanchall.DryWetMidi.Interaction.TempoMap.GetTempoChanges) method to get all tempo changes. Use [GetTimeSignatureChanges](xref:Melanchall.DryWetMidi.Interaction.TempoMap.GetTimeSignatureChanges) method to get time signature changes. [GetTempoAtTime](xref:Melanchall.DryWetMidi.Interaction.TempoMap.GetTempoAtTime(Melanchall.DryWetMidi.Interaction.ITimeSpan)) and [GetTimeSignatureAtTime](xref:Melanchall.DryWetMidi.Interaction.TempoMap.GetTimeSignatureAtTime(Melanchall.DryWetMidi.Interaction.ITimeSpan)) methods allow to get tempo and time signature at the specified time. You can also create new tempo map with `TempoMapManager`: @@ -53,7 +53,7 @@ using (var tempoMapManager = midiFile.ManageTempoMap()) } ``` -This method and another useful ones are placed in [TempoMapManagingUtilities](xref:Melanchall.DryWetMidi.Interaction.TempoMapManagingUtilities). For example, to get tempo map of a MIDI file you can write: +This method and other useful ones are placed in [TempoMapManagingUtilities](xref:Melanchall.DryWetMidi.Interaction.TempoMapManagingUtilities). For example, to get tempo map of a MIDI file you can write: ```csharp TempoMap tempoMap = midiFile.GetTempoMap(); diff --git a/Docs/articles/high-level-managing/Time-and-length.md b/Docs/articles/high-level-managing/Time-and-length.md index 0f724d1dd..34cb6a6ad 100644 --- a/Docs/articles/high-level-managing/Time-and-length.md +++ b/Docs/articles/high-level-managing/Time-and-length.md @@ -10,11 +10,11 @@ All times and lengths in a MIDI file are presented as some long values in units * [BarBeatTicksTimeSpan](#bars-beats-and-ticks) for time span in terms of number of bars, beats and ticks; * [BarBeatFractionTimeSpan](#bars-beats-and-fraction) for time span in terms of number of bars and fractional beats (for example, `0.5` beats); * [MusicalTimeSpan](#musical) for time span in terms of a fraction of the whole note length; -* [MidiTimeSpan](#midi) exists for unification purposes and simply holds long value in units defined by the time division of a file. +* [MidiTimeSpan](#midi) exists for unification purposes and simply holds a `long` value in units defined by the time division of a file. -All time span classes implement [ITimeSpan](xref:Melanchall.DryWetMidi.Interaction.ITimeSpan) interface. +All time span classes implement the [ITimeSpan](xref:Melanchall.DryWetMidi.Interaction.ITimeSpan) interface. -To convert time span between different representations you should use [TimeConverter](xref:Melanchall.DryWetMidi.Interaction.TimeConverter) or [LengthConverter](xref:Melanchall.DryWetMidi.Interaction.LengthConverter) classes (these conversions require [tempo map](xref:Melanchall.DryWetMidi.Interaction.TempoMap) of a MIDI file). (You can use `LengthConverter` for time conversions too but with the `TimeConverter` you don't need to specify time where time span starts since it is always zero.) +To convert time span between different representations you should use [TimeConverter](xref:Melanchall.DryWetMidi.Interaction.TimeConverter) or [LengthConverter](xref:Melanchall.DryWetMidi.Interaction.LengthConverter) classes (these conversions require the [tempo map](xref:Melanchall.DryWetMidi.Interaction.TempoMap) of a MIDI file). (You can use `LengthConverter` for time conversions too but with the `TimeConverter` you don't need to specify the time where time span starts since it is always zero.) Examples of time conversions: @@ -66,7 +66,7 @@ MusicalTimeSpan musicalLengthFromMetric = LengthConverter.ConvertTo`m7`|`0 3 7 10`| -|`min9`
`min7(9)`
`m9`
`m7(9)`|`0 3 7 10 14`
`0 3 10 14`
`3 10 14` (not supported)
`3 7 10 14` (not supported)| -|`min11`
`min7(9,11)`
`m11`
`m7(9,11)`|`0 3 7 10 14 17`
`0 3 10 14 17`
`3 10 14 17` (not supported)
`3 7 10 14 17` (not supported)| +|`min9`
`min7(9)`
`m9`
`m7(9)`|`0 3 7 10 14`
`0 3 10 14`| +|`min11`
`min7(9,11)`
`m11`
`m7(9,11)`|`0 3 7 10 14 17`
`0 3 10 14 17`| |`maj7`|`0 4 7 11`| -|`maj7(9)`
`M7(9)`|`0 4 7 11 14`
`0 4 11 14`
`4 11 14` (not supported)
`4 7 11 14` (not supported)| -|`maj7(#11)`
`M7(#11)`|`0 4 7 11 14 18`
`0 4 11 14 18`
`4 11 14 18` (not supported)
`4 7 11 14 18` (not supported)| -|`maj7(13)`
`M7(13)`|`0 4 7 11 21`
`0 4 11 21`
`4 11 21` (not supported)
`4 7 11 21` (not supported)| -|`maj7(9,13)`
`M7(9,13)`|`0 4 7 11 14 21`
`0 4 11 14 21`
`4 11 14 21` (not supported)
`4 7 11 14 21` (not supported)| +|`maj7(9)`
`M7(9)`|`0 4 7 11 14`
`0 4 11 14`| +|`maj7(#11)`
`M7(#11)`|`0 4 7 11 14 18`
`0 4 11 14 18`| +|`maj7(13)`
`M7(13)`|`0 4 7 11 21`
`0 4 11 21`| +|`maj7(9,13)`
`M7(9,13)`|`0 4 7 11 14 21`
`0 4 11 14 21`| |`maj7#5`
`M7#5`|`0 4 8 11`| -|`maj7#5(9)`
`M7#5(9)`|`0 4 8 11 14`
`4 8 11 14` (not supported)| +|`maj7#5(9)`
`M7#5(9)`|`0 4 8 11 14`| |`minMaj7`
`mM7`|`0 3 7 11`| -|`minMaj7(9)`
`mM7(9)`|`0 3 7 11 14`
`0 3 11 14`
`3 11 14` (not supported)
`3 7 11 14` (not supported)| +|`minMaj7(9)`
`mM7(9)`|`0 3 7 11 14`
`0 3 11 14`| |`5`|`0 7`| |`7b5`
`dom7dim5`
`7dim5`|`0 4 6 10`| |`ø`
`ø7`
`m7b5`
`min7dim5`
`m7dim5`
`min7b5`
`m7b5`|`0 3 6 10`| @@ -92,8 +90,8 @@ Please note that some interval sets start from number greater than zero. Current |`dim7`|`0 3 6 9`| |`add9`|`0 4 7 14`| |`minAdd9`
`mAdd9`|`0 3 7 14`| -|`maj6(9)`
`6(9)`
`6/9`
`M6/9`
`M6(9)`|`0 4 7 9 14`
`4 7 9 14` (not supported)
`0 4 9 14`
`4 9 14` (not supported)| -|`min6(9)`
`m6(9)`
`m6/9`
`min6/9`|`0 3 7 9 14`
`3 7 9 14` (not supported)
`0 3 9 14`
`3 9 14` (not supported)| +|`maj6(9)`
`6(9)`
`6/9`
`M6/9`
`M6(9)`|`0 4 7 9 14`
`0 4 9 14`| +|`min6(9)`
`m6(9)`
`m6/9`
`min6/9`|`0 3 7 9 14`
`0 3 9 14`| |`9`|`0 4 7 10 14`| |`9sus2`|`0 2 7 10 14`| |`9sus4`|`0 5 7 10 14`| diff --git a/Docs/articles/music-theory/Interval.md b/Docs/articles/music-theory/Interval.md index 3a6fbcec5..bb14da442 100644 --- a/Docs/articles/music-theory/Interval.md +++ b/Docs/articles/music-theory/Interval.md @@ -4,7 +4,7 @@ uid: a_mt_interval # Interval -[Interval](xref:Melanchall.DryWetMidi.MusicTheory.Interval) holds number of half steps and used, for example, to transpose notes or describing chords when working with [PatternBuilder](xref:Melanchall.DryWetMidi.Composing.PatternBuilder). Some examples of usage: +[Interval](xref:Melanchall.DryWetMidi.MusicTheory.Interval) holds a number of half steps and is used, for example, to transpose notes or describing chords when working with [PatternBuilder](xref:Melanchall.DryWetMidi.Composing.PatternBuilder). Some examples of usage: ```csharp // Get A4 note @@ -41,7 +41,7 @@ where * `A` for augmented interval; * `m` for minor interval; * `M` for major interval. -* **IntervalNumber** is a number of interval, for example, `1`. A number must be greater than zero. +* **IntervalNumber** is the number of an interval, for example, `1`. A number must be greater than zero. Examples of valid interval strings: diff --git a/Docs/articles/music-theory/Note.md b/Docs/articles/music-theory/Note.md index 31c3c2045..c3279b6f0 100644 --- a/Docs/articles/music-theory/Note.md +++ b/Docs/articles/music-theory/Note.md @@ -23,7 +23,7 @@ var note4 = Note.Parse("a3"); var note5Parsed = Note.TryParse("c#2", out var note5); ``` -Also see [Interval](Interval.md) article for additional examples. +Also see the [Interval](Interval.md) article for additional examples. ## Parsing @@ -37,7 +37,7 @@ where * **Accidental** is one of the following strings: * `#` or `sharp` for sharp; * `b` or `flat` for flat. -* **OctaveNumber** is a number of octave. A number must be between `-1` and `9`. +* **OctaveNumber** is the number of an octave. A number must be between `-1` and `9`. Examples of valid note strings: diff --git a/Docs/articles/music-theory/Octave.md b/Docs/articles/music-theory/Octave.md index 90eeecf06..1e7a8cbc2 100644 --- a/Docs/articles/music-theory/Octave.md +++ b/Docs/articles/music-theory/Octave.md @@ -4,7 +4,7 @@ uid: a_mt_octave # Octave -The main purpose of the [Octave](xref:Melanchall.DryWetMidi.MusicTheory.Octave) class is to provide alternative way to get an instance of the [Note](xref:Melanchall.DryWetMidi.MusicTheory.Note) class. Some examples of usage: +The main purpose of the [Octave](xref:Melanchall.DryWetMidi.MusicTheory.Octave) class is to provide an alternative way to get an instance of the [Note](xref:Melanchall.DryWetMidi.MusicTheory.Note) class. Some examples of usage: ```csharp // Get first octave @@ -26,7 +26,7 @@ Following strings can be parsed to `Octave`: where -* **OctaveNumber** is a number of octave. A number must be between `-1` and `9`. +* **OctaveNumber** is the number of an octave. A number must be between `-1` and `9`. Examples of valid interval strings: diff --git a/Docs/articles/music-theory/Overview.md b/Docs/articles/music-theory/Overview.md index 430bfa2e5..de8292255 100644 --- a/Docs/articles/music-theory/Overview.md +++ b/Docs/articles/music-theory/Overview.md @@ -13,6 +13,6 @@ DryWetMIDI provides types and methods to work with music theory objects like sca * [Chord progression](Chord-progression.md); * [Scale](Scale.md). -Note that DryWetMIDI uses [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation) so **middle C** note is C4 one. [Octave.Middle](xref:Melanchall.DryWetMidi.MusicTheory.Octave.Middle) returns that 4th octave. You can read interesting discussion about different notations here: [MIDI Octave and Note Numbering Standard](https://midi.org/community/midi-specifications/midi-octave-and-note-numbering-standard). +Note that DryWetMIDI uses [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation) so the **middle C** note is the _C4_ one. [Octave.Middle](xref:Melanchall.DryWetMidi.MusicTheory.Octave.Middle) returns that 4th octave. You can read an interesting discussion about different notations here: [MIDI Octave and Note Numbering Standard](https://midi.org/community/midi-specifications/midi-octave-and-note-numbering-standard). -All corresponding classes have `Parse` and `TryParse` method so you can get an instance of a class from string. For example, you can parse `"C#"` string to C# note. \ No newline at end of file +All corresponding classes have `Parse` and `TryParse` methods so you can get an instance of a class from string. For example, you can parse `"C#"` string to C# note. \ No newline at end of file diff --git a/Docs/articles/music-theory/Scale.md b/Docs/articles/music-theory/Scale.md index ec25d3118..5c5597e8f 100644 --- a/Docs/articles/music-theory/Scale.md +++ b/Docs/articles/music-theory/Scale.md @@ -48,7 +48,7 @@ where * **RootNote** is a note name (with or without accidental), for example, `C` or `A#`. * **ScaleName** is the name of a known scale, for example, `major` or `bebop minor`. You can take a look at [ScaleIntervals](xref:Melanchall.DryWetMidi.MusicTheory.ScaleIntervals) to see all known scales supported by the library. -* **Interval** is a string a musical interval can be parsed from. See [Parsing](Interval.md#parsing) section of the [Interval](Interval.md) article to see how an interval can be represented as a string. +* **Interval** is a string a musical interval can be parsed from. See the [Parsing](Interval.md#parsing) section of the [Interval](Interval.md) article to see how an interval can be represented as a string. Examples of valid scale strings: diff --git a/Docs/articles/playback/Common-problems.md b/Docs/articles/playback/Common-problems.md index 5e015be0e..af7a99381 100644 --- a/Docs/articles/playback/Common-problems.md +++ b/Docs/articles/playback/Common-problems.md @@ -6,7 +6,7 @@ uid: a_playback_commonproblems ## Playback doesn't produce sound or events logs -Make sure an instance of [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) class is held by class field, global variable or something else that can live longer that method where you instantiate `Playback`. In this case +Make sure an instance of [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) class is held by class field, global variable or something else that can live longer than the method where you instantiate `Playback`. In this case ```csharp private void StartPlayback() @@ -16,11 +16,11 @@ private void StartPlayback() } ``` -`playback` variable will "die" (will be ready to be collected by GC) after program exits `StartPlayback` method so playback won't work. +`playback` variable will "die" (will be ready to be collected by GC) after the program exits the `StartPlayback` method so playback won't work. ## SetActive can only be called from the main thread in Unity -Sometimes you want to handle [playback events](xref:Melanchall.DryWetMidi.Multimedia.Playback#events) or use [event](xref:Melanchall.DryWetMidi.Multimedia.Playback.EventCallback) or [note](xref:Melanchall.DryWetMidi.Multimedia.Playback.NoteCallback) callbacks. Your code can be executed on separate thread in these cases. It can happen because of `Playback`'s internals (like [tick generator](Tick-generator.md)'s tick handling) work on separate thread. +Sometimes you want to handle [playback events](xref:Melanchall.DryWetMidi.Multimedia.Playback#events) or use [event](xref:Melanchall.DryWetMidi.Multimedia.Playback.EventCallback) or [note](xref:Melanchall.DryWetMidi.Multimedia.Playback.NoteCallback) callbacks. Your code can be executed on a separate thread in these cases. It can happen because `Playback`'s internals (like [tick generator](Tick-generator.md)'s tick handling) work on separate thread. But UI related things like call of `SetActive` can be executed on UI thread only. You can use the solution from here: https://stackoverflow.com/a/56715254. diff --git a/Docs/articles/playback/Current-time-watching.md b/Docs/articles/playback/Current-time-watching.md index 47f63fd06..8cc1fd123 100644 --- a/Docs/articles/playback/Current-time-watching.md +++ b/Docs/articles/playback/Current-time-watching.md @@ -4,7 +4,7 @@ uid: a_playback_curtime # Current time watching -To watch current time of a playback you can create a timer and call [GetCurrentTime](xref:Melanchall.DryWetMidi.Multimedia.Playback.GetCurrentTime(Melanchall.DryWetMidi.Interaction.TimeSpanType)) method on each timer's tick. To simplify this task (especially if you're running multiple playbacks simultaneously) DryWetMIDI provides [PlaybackCurrentTimeWatcher](xref:Melanchall.DryWetMidi.Multimedia.PlaybackCurrentTimeWatcher) class. This class is singleton in order to prevent too many high resolution tick generators are created (which is not good since it can affect whole system performance). Please read [Tick generator](Tick-generator.md) article to learn how you can ajust internals of the `PlaybackCurrentTimeWatcher`. +To watch the current time of a playback you can create a timer and call [GetCurrentTime](xref:Melanchall.DryWetMidi.Multimedia.Playback.GetCurrentTime(Melanchall.DryWetMidi.Interaction.TimeSpanType)) method on each timer's tick. To simplify this task (especially if you're running multiple playbacks simultaneously) DryWetMIDI provides the [PlaybackCurrentTimeWatcher](xref:Melanchall.DryWetMidi.Multimedia.PlaybackCurrentTimeWatcher) class. This class is singleton in order to prevent creation of too many high resolution tick generators (which is not good since it can affect whole system performance). Please read [Tick generator](Tick-generator.md) article to learn how you can adjust internals of the `PlaybackCurrentTimeWatcher`. Small example: @@ -27,7 +27,7 @@ private static void OnCurrentTimeChanged(object sender, PlaybackCurrentTimeChang } ``` -In this example we watch current time of playback and request to report time in MIDI format which is ticks (and thus we cast received time to [MidiTimeSpan](xref:Melanchall.DryWetMidi.Interaction.MidiTimeSpan)). You can set any desired time format and cast to corresponding implementation of the [ITimeSpan](xref:Melanchall.DryWetMidi.Interaction.ITimeSpan). +In this example we watch the current time of playback and request to report time in MIDI format which is _ticks_ (and thus we cast received time to [MidiTimeSpan](xref:Melanchall.DryWetMidi.Interaction.MidiTimeSpan)). You can set any desired time format and cast to corresponding implementation of the [ITimeSpan](xref:Melanchall.DryWetMidi.Interaction.ITimeSpan). You can add multiple different playbacks to watch their current times. When you don't want to watch playback anymore remove it from the watcher: @@ -41,9 +41,9 @@ By default polling interval of watcher is `100` ms, but you can alter it: PlaybackCurrentTimeWatcher.Instance.PollingInterval = TimeSpan.FromMilliseconds(50); ``` -Please don't set too small intervals. Polling interval defines how often [CurrentTimeChanged](xref:Melanchall.DryWetMidi.Multimedia.PlaybackCurrentTimeWatcher.CurrentTimeChanged) event will be fired. If you want to pause firing the event, call [Stop](xref:Melanchall.DryWetMidi.Multimedia.PlaybackCurrentTimeWatcher.Stop) method. +Please don't set too small intervals. Polling interval defines how often the [CurrentTimeChanged](xref:Melanchall.DryWetMidi.Multimedia.PlaybackCurrentTimeWatcher.CurrentTimeChanged) event will be fired. If you want to pause firing the event, call the [Stop](xref:Melanchall.DryWetMidi.Multimedia.PlaybackCurrentTimeWatcher.Stop) method. -When your application is about to close, dispose watcher to kill the internal timer: +When your application is about to close, dispose of watcher to kill the internal timer: ```csharp PlaybackCurrentTimeWatcher.Instance.Dispose(); diff --git a/Docs/articles/playback/Custom-playback.md b/Docs/articles/playback/Custom-playback.md index 993ed2b3c..2bdd0b467 100644 --- a/Docs/articles/playback/Custom-playback.md +++ b/Docs/articles/playback/Custom-playback.md @@ -9,11 +9,11 @@ You can subclass from [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) * [bool TryPlayEvent(MidiEvent midiEvent, object metadata)](xref:Melanchall.DryWetMidi.Multimedia.Playback.TryPlayEvent(Melanchall.DryWetMidi.Core.MidiEvent,System.Object)) * [IEnumerable GetTimedEvents(ITimedObject timedObject)](xref:Melanchall.DryWetMidi.Multimedia.Playback.GetTimedEvents(Melanchall.DryWetMidi.Interaction.ITimedObject)) -Let's see what each method needed for. +Let's see what each method is needed for. ## TryPlayEvent -`TryPlayEvent` method called by playback each time an event should be played. Result value of the method tells playback whether the event was played or not. Default implementation of the method just sends a MIDI event to [output device](xref:Melanchall.DryWetMidi.Multimedia.Playback.OutputDevice) and returns `true`. +`TryPlayEvent` method called by playback each time an event should be played. Result value of the method tells playback whether the event was played or not. Default implementation of the method just sends a MIDI event to an [output device](xref:Melanchall.DryWetMidi.Multimedia.Playback.OutputDevice) and returns `true`. So you can implement your own logic of playing a MIDI event. Please pay attention to the second parameter of the method – `metadata`. If input objects of playback implement [IMetadata](xref:Melanchall.DryWetMidi.Common.IMetadata) interface, metadata will be passed via that parameter. For example, you can subclass from `TimedEvent` and implement `IMetadata` on a new class, and then create your own playback on instances of that class. @@ -64,4 +64,4 @@ var playback = new MyPlayback(timedEvents, tempoMap); ## GetTimedEvents -`Playback` internally transforms all input objects to instances of `TimedEvent`. So if some input objects implement [ITimedObject](xref:Melanchall.DryWetMidi.Interaction.ITimedObject) but their type is unknown for DryWetMIDI, we need to override `GetTimedEvents` method to provide transformation of our custom timed object to collection of timed events. Of course those timed events can be subclasses of `TimedEvent` and implement `IMetadata` (see previous section) so metadata will correctly go between a playback's internals. By default the method returns empty collection. \ No newline at end of file +`Playback` internally transforms all input objects to instances of `TimedEvent`. So if some input objects implement [ITimedObject](xref:Melanchall.DryWetMidi.Interaction.ITimedObject) but their type is unknown for DryWetMIDI, we need to override `GetTimedEvents` method to provide transformation of our custom timed object to collection of timed events. Of course those timed events can be subclasses of `TimedEvent` and implement `IMetadata` (see previous section) so metadata will correctly go between a playback's internals. By default the method returns an empty collection. \ No newline at end of file diff --git a/Docs/articles/playback/Data-tracking.md b/Docs/articles/playback/Data-tracking.md index dfd0417ae..87364c2e0 100644 --- a/Docs/articles/playback/Data-tracking.md +++ b/Docs/articles/playback/Data-tracking.md @@ -31,7 +31,7 @@ The same situation with opposite case: ![Move from note with TrackNotes set to true](images/NotesTrack-FromNote-True.png) -So we want here to jump from the middle of a note to the time after the note. As in previous example if `TrackNotes` is `false`, just the current time of the playback will be changed. But if in case of `true`, new _Note Off_ event will be generated and played when we jump to the new time. +So we want to jump from the middle of a note to the time after the note. As in the previous example, if `TrackNotes` is `false`, just the current time of the playback will be changed. But if in case of `true`, a new _Note Off_ event will be generated and played when we jump to the new time. So `TrackNotes = true` tells playback to track time jumps when the current time pointer of the playback either leaves a note or enters one to finish or start the note correspondingly. @@ -51,11 +51,11 @@ And now we want to jump from the current time of a playback to a new time (with ![Move after B program](images/ProgTrack-AfterB.png) -So by the current time `A` event is played and the current program corresponds to `A`. If the playback just change the current time, the note will be played using program `A` which may be wrong since the note is actually is under `B` program influence. +So by the current time `A` event is played and the current program corresponds to `A`. If the playback just changes the current time, the note will be played using program `A` which may be wrong since the note is actually under `B` program influence. -To track a program `Playback` class has [TrackProgram](xref:Melanchall.DryWetMidi.Multimedia.Playback.TrackProgram) property. If it's set to `false`, nothing will happen except changing the current time. All following notes can sound incorrectly due to possibly skipped program changes. +To track a program, `Playback` class has the [TrackProgram](xref:Melanchall.DryWetMidi.Multimedia.Playback.TrackProgram) property. If it's set to `false`, nothing will happen except changing the current time. All following notes can sound incorrectly due to possibly skipped program changes. -But if we set `TrackProgram` to `true`, playback will play required _Program Change_ event immediately after time changed. So in our example `B` will be played and then playback continues from new time: +But if we set `TrackProgram` to `true`, playback will play the required _Program Change_ event immediately after time changed. So in our example `B` will be played and then playback continues from new time: ![Move after B program with TrackProgram set to true](images/ProgTrack-AfterB-2.png) diff --git a/Docs/articles/playback/Overview.md b/Docs/articles/playback/Overview.md index 67ebe9842..2cf101074 100644 --- a/Docs/articles/playback/Overview.md +++ b/Docs/articles/playback/Overview.md @@ -6,7 +6,7 @@ uid: a_playback_overview [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) class allows to play MIDI events via an [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice) (see [Output device](xref:a_dev_output) article) or without a device at all (see [Playback without device](#playback-without-device)). To get an instance of the `Playback` you can use its [constructors](xref:Melanchall.DryWetMidi.Multimedia.Playback#constructors) or `GetPlayback` extension methods in [PlaybackUtilities](xref:Melanchall.DryWetMidi.Multimedia.PlaybackUtilities). -Following example shows simple console app where specified MIDI file is played until end of the file reached or `B` note is about to be played. So in our example `B` note means to stop playback. +Following example shows a simple console app where the specified MIDI file is played until the end of the file reached or `B` note is about to be played. So in our example `B` note means to stop playback. ```csharp using System; @@ -52,13 +52,13 @@ Please read [Tick generator](Tick-generator.md) article and [PlaybackSettings](x ## Playback without device -There are constructors of [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) that don't accept [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice) as an argument. It can be useful, for example, for notes visualization without sound. [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) provides events that will be fired with or without output device (see [Events](xref:Melanchall.DryWetMidi.Multimedia.Playback#events) section of the [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) API page). Also all `GetPlayback` extensions methods have overloads without the `outputDevice` parameter. +There are constructors of [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) that don't accept [IOutputDevice](xref:Melanchall.DryWetMidi.Multimedia.IOutputDevice) as an argument. It can be useful, for example, for notes visualization without sound. [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) provides events that will be fired with or without an output device (see [Events](xref:Melanchall.DryWetMidi.Multimedia.Playback#events) section of the [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) API page). Also all `GetPlayback` extensions methods have overloads without the `outputDevice` parameter. -Also if you don't specify output device and use [tick generator](Tick-generator.md) other than [HighPrecisionTickGenerator](xref:Melanchall.DryWetMidi.Multimedia.HighPrecisionTickGenerator), you can use `Playback` in cross-platform app like Unity game that is supposed to be built for different platforms. +Also if you don't specify an output device and use a [tick generator](Tick-generator.md) other than [HighPrecisionTickGenerator](xref:Melanchall.DryWetMidi.Multimedia.HighPrecisionTickGenerator), you can use `Playback` in a cross-platform app like Unity game that is supposed to be built for different platforms. ## Blocking playback -If you call [Play](xref:Melanchall.DryWetMidi.Multimedia.Playback.Play) method of the `Playback`, the calling thread will be blocked until entire collection of MIDI events will be played. Note that execution of this method will be infinite if the [Loop](xref:Melanchall.DryWetMidi.Multimedia.Playback.Loop) property set to `true`. +If you call the [Play](xref:Melanchall.DryWetMidi.Multimedia.Playback.Play) method of the `Playback`, the calling thread will be blocked until the entire collection of MIDI events is played. Note that execution of this method will be infinite if the [Loop](xref:Melanchall.DryWetMidi.Multimedia.Playback.Loop) property is set to `true`. There are also extension methods `Play` in [PlaybackUtilities](xref:Melanchall.DryWetMidi.Multimedia.PlaybackUtilities): @@ -73,7 +73,7 @@ using (var outputDevice = OutputDevice.GetByName("Output MIDI device")) ## Non-blocking playback -Is you call [Start](xref:Melanchall.DryWetMidi.Multimedia.Playback.Start) method of the [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback), execution of the calling thread will continue immediately after the method is called. To stop playback use [Stop](xref:Melanchall.DryWetMidi.Multimedia.Playback.Stop) method. Note that there is no any pausing method since it's useless. `Stop` leaves playback at the point where the method was called. To move to the start of the playback use [MoveToStart](xref:Melanchall.DryWetMidi.Multimedia.Playback.MoveToStart) method. +If you call the [Start](xref:Melanchall.DryWetMidi.Multimedia.Playback.Start) method of the [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback), execution of the calling thread will continue immediately after the method is called. To stop playback use the [Stop](xref:Melanchall.DryWetMidi.Multimedia.Playback.Stop) method. Note that there is no any pausing method since it's useless. `Stop` leaves playback at the point where the method was called. To move to the start of the playback use the [MoveToStart](xref:Melanchall.DryWetMidi.Multimedia.Playback.MoveToStart) method. > [!IMPORTANT] > You should be very careful with this approach and `using` block. Example below shows the case where part of MIDI data **will not be played** because of playback is disposed before the last MIDI event will be played: @@ -88,4 +88,4 @@ Is you call [Start](xref:Melanchall.DryWetMidi.Multimedia.Playback.Start) method > } > ``` > -> With non-blocking approach you must call `Dispose` manually after you've finished work with playback object. \ No newline at end of file +> With the non-blocking approach you must call `Dispose` manually after you've finished work with the playback object. \ No newline at end of file diff --git a/Docs/articles/playback/Tick-generator.md b/Docs/articles/playback/Tick-generator.md index be214183a..381ec189d 100644 --- a/Docs/articles/playback/Tick-generator.md +++ b/Docs/articles/playback/Tick-generator.md @@ -4,12 +4,12 @@ uid: a_playback_tickgen # Tick generator -Playback uses timer under the hood. In DryWetMIDI this timer called tick generator. On every tick of timer playback looks what objects should be played by the current time, plays them and advances position within objects list waiting for next tick. +Playback uses a timer under the hood. In DryWetMIDI this timer is called **tick generator**. On every tick of the timer playback looks at what objects should be played by the current time, plays them and advances position within the objects list waiting for the next tick. -To make playback smooth and correct, precision of timer should be ~1ms. So tick will be generated every one millisecond. By default, DryWetMIDI uses [HighPrecisionTickGenerator](xref:Melanchall.DryWetMidi.Multimedia.HighPrecisionTickGenerator) which is the best option in terms of CPU usage, memory usage and precision. +To make playback smooth and correct, the precision of the timer should be ~1ms. So tick will be generated every one millisecond. By default, DryWetMIDI uses [HighPrecisionTickGenerator](xref:Melanchall.DryWetMidi.Multimedia.HighPrecisionTickGenerator) which is the best option in terms of CPU usage, memory usage and precision. > [!IMPORTANT] -> `HighPrecisionTickGenerator` is supported for Windows and macOS only at now. +> `HighPrecisionTickGenerator` is supported for Windows and macOS only at the moment. You can also use [RegularPrecisionTickGenerator](xref:Melanchall.DryWetMidi.Multimedia.RegularPrecisionTickGenerator) which uses standard [Timer](xref:System.Timers.Timer) and thus provides precision about 16ms on Windows. But this tick generator is cross-platform. @@ -27,9 +27,9 @@ var playback = midiFile.GetPlayback(new PlaybackSettings ## Custom tick generator -All built-in tick generators extend abstract [TickGenerator](xref:Melanchall.DryWetMidi.Multimedia.TickGenerator) class so you can create your own and use it for [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) and [PlaybackCurrentTimeWatcher](xref:Melanchall.DryWetMidi.Multimedia.PlaybackCurrentTimeWatcher). +All built-in tick generators extend the abstract [TickGenerator](xref:Melanchall.DryWetMidi.Multimedia.TickGenerator) class so you can create your own and use it for [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback) and [PlaybackCurrentTimeWatcher](xref:Melanchall.DryWetMidi.Multimedia.PlaybackCurrentTimeWatcher). -As an example we create simple loop tick generator working in separate thread. The code is: +As an example we create a simple loop tick generator working in a separate thread. The code is: ```csharp private sealed class ThreadTickGenerator : TickGenerator @@ -96,7 +96,7 @@ Of course this tick generator will use a lot of CPU due to infinite loop but it' ## Manual ticking -Also you can tick playback's internal clock manually without tick generator via [TickClock](xref:Melanchall.DryWetMidi.Multimedia.Playback.TickClock) method of [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback). For example, you can use manual ticking within every frame update in Unity. +Also you can tick playback's internal clock manually without a tick generator via the [TickClock](xref:Melanchall.DryWetMidi.Multimedia.Playback.TickClock) method of [Playback](xref:Melanchall.DryWetMidi.Multimedia.Playback). For example, you can use manual ticking within every frame update in Unity. To use only manual ticking you should return `null` in [CreateTickGeneratorCallback](xref:Melanchall.DryWetMidi.Multimedia.MidiClockSettings.CreateTickGeneratorCallback): @@ -118,4 +118,4 @@ playback.TickClock(); when needed. -You also can use manual ticking in conjunction with tick generator. \ No newline at end of file +You also can use manual ticking in conjunction with a tick generator. \ No newline at end of file diff --git a/Docs/articles/recording/Overview.md b/Docs/articles/recording/Overview.md index f52cdcb36..c3e85479f 100644 --- a/Docs/articles/recording/Overview.md +++ b/Docs/articles/recording/Overview.md @@ -20,11 +20,11 @@ using (var inputDevice = InputDevice.GetByName("Input MIDI device")) } ``` -Don't forget to call [StartEventsListening](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.StartEventsListening) on [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) before you start recording since `Recording` do nothing with the device you've specified. +Don't forget to call [StartEventsListening](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice.StartEventsListening) on [IInputDevice](xref:Melanchall.DryWetMidi.Multimedia.IInputDevice) before you start recording since `Recording` does nothing with the device you've specified. -To start recording call [Start](xref:Melanchall.DryWetMidi.Multimedia.Recording.Start) method. To stop it call [Stop](xref:Melanchall.DryWetMidi.Multimedia.Recording.Stop) method. You can resume recording after it has been stopped by calling `Start` again. To check whether recording is currently running or not, get a value of the [IsRunning](xref:Melanchall.DryWetMidi.Multimedia.Recording.IsRunning) property. `Start` and `Stop` methods fire [Started](xref:Melanchall.DryWetMidi.Multimedia.Recording.Started) and [Stopped](xref:Melanchall.DryWetMidi.Multimedia.Recording.Stopped) events respectively. +To start recording, call the [Start](xref:Melanchall.DryWetMidi.Multimedia.Recording.Start) method. To stop it, call the [Stop](xref:Melanchall.DryWetMidi.Multimedia.Recording.Stop) method. You can resume recording after it has been stopped by calling `Start` again. To check whether recording is currently running or not, get a value of the [IsRunning](xref:Melanchall.DryWetMidi.Multimedia.Recording.IsRunning) property. `Start` and `Stop` methods fire [Started](xref:Melanchall.DryWetMidi.Multimedia.Recording.Started) and [Stopped](xref:Melanchall.DryWetMidi.Multimedia.Recording.Stopped) events respectively. -You can get recorded events as with [GetEvents](xref:Melanchall.DryWetMidi.Multimedia.Recording.GetEvents) method. +You can get recorded events as with the [GetEvents](xref:Melanchall.DryWetMidi.Multimedia.Recording.GetEvents) method. Take a look at small example of MIDI data recording: diff --git a/Docs/articles/toc.md b/Docs/articles/toc.md index 0fc14c8a0..aeecfea4d 100644 --- a/Docs/articles/toc.md +++ b/Docs/articles/toc.md @@ -20,6 +20,8 @@ ## [Tempo map](high-level-managing/Tempo-map.md) ## [Time and length](high-level-managing/Time-and-length.md) ## [Getting objects](high-level-managing/Getting-objects.md) +## [Processing objects](high-level-managing/Processing-objects.md) +## [Removing objects](high-level-managing/Removing-objects.md) # Tools ## [Overview](tools/Overview.md) diff --git a/Docs/articles/tools/CSV-serializer.md b/Docs/articles/tools/CSV-serializer.md index 5ec6e415e..de537d1b7 100644 --- a/Docs/articles/tools/CSV-serializer.md +++ b/Docs/articles/tools/CSV-serializer.md @@ -69,7 +69,7 @@ In other cases of `ObjectName` it's assumed that this component represents the [ Time,EventData ``` -As for the `Time`, it holds the time of a [TimedEvent](xref:Melanchall.DryWetMidi.Interaction.TimedEvent) object. You can change `Time` representation via [TimeType](xref:Melanchall.DryWetMidi.Tools.CsvSerializationSettings.TimeType) property of the [CsvSerializationSettings](xref:Melanchall.DryWetMidi.Tools.CsvSerializationSettings). `EventData` depends on the type of an event: +As for the `Time`, it holds the time of a [TimedEvent](xref:Melanchall.DryWetMidi.Interaction.TimedEvent) object. You can change `Time` representation via the [TimeType](xref:Melanchall.DryWetMidi.Tools.CsvSerializationSettings.TimeType) property of the [CsvSerializationSettings](xref:Melanchall.DryWetMidi.Tools.CsvSerializationSettings). `EventData` depends on the type of an event: |ObjectName|EventData|Modifiers| |---|---|---| diff --git a/Docs/articles/tools/MIDI-file-splitting.md b/Docs/articles/tools/MIDI-file-splitting.md index 5056407da..ea02f72ba 100644 --- a/Docs/articles/tools/MIDI-file-splitting.md +++ b/Docs/articles/tools/MIDI-file-splitting.md @@ -39,7 +39,7 @@ To split a file by objects the tool needs to determine the key of each object. O |[Note](xref:Melanchall.DryWetMidi.Interaction.ObjectType.Note)|Pair of the [channel](xref:Melanchall.DryWetMidi.Interaction.Note.Channel) and [note number](xref:Melanchall.DryWetMidi.Interaction.Note.NoteNumber) of a note.| |[Chord](xref:Melanchall.DryWetMidi.Interaction.ObjectType.Chord)|Collection of keys of the underlying [notes](xref:Melanchall.DryWetMidi.Interaction.Chord.Notes).| -You can alter key calculation logic providing custom key selector. For example, to separate notes by only note number ignoring a note's channel: +You can alter key calculation logic by providing custom key selector. For example, to separate notes by only note number ignoring a note's channel: ```csharp var newFiles = midiFile.SplitByObjects( @@ -53,7 +53,7 @@ var newFiles = midiFile.SplitByObjects( Here a key will be resolved to `null` if an object is not an instance of the `Note`. `null` key means to use the default key calculation logic shown above. -If key selection is complex, you may decide to implement a class for such key. Just for example, let's create the key class that identifies a chord by its shortest name: +If key selection is complex, you may decide to implement a class for such a key. Just for example, let's create the key class that identifies a chord by its shortest name: ```csharp private sealed class ChordNameId @@ -89,11 +89,11 @@ Please see documentation on [SplitByObjectsSettings](xref:Melanchall.DryWetMidi. ## SplitByGrid -[SplitByGrid](xref:Melanchall.DryWetMidi.Tools.Splitter.SplitByGrid*) method splits MIDI file by the specified grid. Each file will preserve original tempo map and all parameters changes (like a control value or program changes). The image below shows general case of splitting a MIDI file by grid: +[SplitByGrid](xref:Melanchall.DryWetMidi.Tools.Splitter.SplitByGrid*) method splits a MIDI file by the specified grid. Each file will preserve the original tempo map and all parameters changes (like a control value or program changes). The image below shows general case of splitting a MIDI file by grid: ![Split MIDI file by grid](images/Splitter/SplitFileByGrid.png) -Splitting can be adjusted via `settings` parameter of the [SliceMidiFileSettings](xref:Melanchall.DryWetMidi.Tools.SliceMidiFileSettings) type. [SplitNotes](xref:Melanchall.DryWetMidi.Tools.SliceMidiFileSettings.SplitNotes) and [PreserveTimes](xref:Melanchall.DryWetMidi.Tools.SliceMidiFileSettings.PreserveTimes) properties described below. Please see all available properties in documentation for [SliceMidiFileSettings](xref:Melanchall.DryWetMidi.Tools.SliceMidiFileSettings). +Splitting can be adjusted via the `settings` parameter of the [SliceMidiFileSettings](xref:Melanchall.DryWetMidi.Tools.SliceMidiFileSettings) type. [SplitNotes](xref:Melanchall.DryWetMidi.Tools.SliceMidiFileSettings.SplitNotes) and [PreserveTimes](xref:Melanchall.DryWetMidi.Tools.SliceMidiFileSettings.PreserveTimes) properties described below. Please see all available properties in documentation for [SliceMidiFileSettings](xref:Melanchall.DryWetMidi.Tools.SliceMidiFileSettings). ### SplitNotes @@ -109,7 +109,7 @@ Splitting can be adjusted via `settings` parameter of the [SliceMidiFileSettings ## SkipPart -[SkipPart](xref:Melanchall.DryWetMidi.Tools.Splitter.SkipPart*) method skips part of the specified length of a MIDI file and returns remaining part as an instance of [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile). The image below shows general case of skipping a part of a MIDI file: +[SkipPart](xref:Melanchall.DryWetMidi.Tools.Splitter.SkipPart*) method skips part of the specified length of a MIDI file and returns the remaining part as an instance of [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile). The image below shows general case of skipping a part of a MIDI file: ![Skip part of a MIDI file](images/Splitter/SkipPart.png) diff --git a/Docs/articles/tools/MIDI-files-merging.md b/Docs/articles/tools/MIDI-files-merging.md index 2c35a36a4..b9793ed41 100644 --- a/Docs/articles/tools/MIDI-files-merging.md +++ b/Docs/articles/tools/MIDI-files-merging.md @@ -17,13 +17,13 @@ Image below shows a quick overview of what you'll get with the [MergeSequentiall ![MergeSequentially](images/Merger/MergeFiles/MergeSequentially.png) -Here we're merging three files each of two track chunks. So as you can see events of the second file are placed right after last event of the first file, and events of the third file are placed directly after last event of the second one. In the code it looks like that: +Here we're merging three files each of two track chunks. So as you can see, events of the second file are placed right after the last event of the first file, and events of the third file are placed directly after the last event of the second one. In the code it looks like that: ```csharp midiFiles.MergeSequentially(); ``` -There is one important note about how merging works. Obviously files can have different time divisions. If shortly, a MIDI tick (single unit of time) can have different length in milliseconds. Just placing files one after other and using time division of the first file, for example, we'll ruine timings of MIDI events of the files are merged. +There is one important note about how merging works. Obviously files can have different time divisions. In short, a MIDI tick (single unit of time) can have different length in milliseconds. Just placing files one after another and using time division of the first file, for example, we'll ruine timings of MIDI events of the files being merged. To preserve correct timings of all MIDI events of all input files in the result one [MergeSequentially](xref:Melanchall.DryWetMidi.Tools.Merger.MergeSequentially*) does following steps: @@ -42,7 +42,7 @@ midiFiles.MergeSequentially(new SequentialMergingSettings ![MergeSequentially with DelayBetweenFiles](images/Merger/MergeFiles/MergeSequentially-DelayBetweenFiles.png) -We may want also to align starts of the input files to a grid. It would be better to describe the task with an example: +We may also want to align the starts of the input files to a grid. It would be better to describe the task with an example: ```csharp var midiFile = new PatternBuilder() @@ -70,13 +70,13 @@ midiFiles.MergeSequentially(new SequentialMergingSettings }); ``` -Here we state that duartion of each file should be [rounded up](xref:Melanchall.DryWetMidi.Interaction.TimeSpanRoundingPolicy.RoundUp) to 1 bar and then a next file can be placed: +Here we state that duration of each file should be [rounded up](xref:Melanchall.DryWetMidi.Interaction.TimeSpanRoundingPolicy.RoundUp) to 1 bar and then a next file can be placed: ![MergeSequentially with FileDurationRoundingStep](images/Merger/MergeFiles/MergeSequentially-FileDurationRoundingStep.png) If both [DelayBetweenFiles](xref:Melanchall.DryWetMidi.Tools.SequentialMergingSettings.DelayBetweenFiles) and [FileDurationRoundingStep](xref:Melanchall.DryWetMidi.Tools.SequentialMergingSettings.FileDurationRoundingStep) are set, the method will first round duration and then add a delay. -In all previous examples you can see that a result file has number of track chunks that is the sum of all input files' track chunks numbers. So a track chunk of an input MIDI file will be written to a separate track chunk in a result MIDI file. We can change this default behavior setting [ResultTrackChunksCreationPolicy](xref:Melanchall.DryWetMidi.Tools.SequentialMergingSettings.ResultTrackChunksCreationPolicy) to the [ResultTrackChunksCreationPolicy](xref:Melanchall.DryWetMidi.Tools.ResultTrackChunksCreationPolicy.MinimizeCount) value: +In all previous examples you can see that a result file has the number of track chunks that is the sum of all input files' track chunks numbers. So a track chunk of an input MIDI file will be written to a separate track chunk in a resulting MIDI file. We can change this default behavior setting [ResultTrackChunksCreationPolicy](xref:Melanchall.DryWetMidi.Tools.SequentialMergingSettings.ResultTrackChunksCreationPolicy) to the [ResultTrackChunksCreationPolicy](xref:Melanchall.DryWetMidi.Tools.ResultTrackChunksCreationPolicy.MinimizeCount) value: ```csharp midiFiles.MergeSequentially(new SequentialMergingSettings @@ -99,7 +99,7 @@ So with this code: midiFiles.MergeSimultaneously(); ``` -track chunks of all input files will be "stacked" in the result MIDI file. +track chunks of all input files will be "stacked" in the resulting MIDI file. This method uses the same logic to preserve timings of the input files as the [MergeSequentially](#mergesequentially) one. But [MergeSimultaneously](xref:Melanchall.DryWetMidi.Tools.Merger.MergeSimultaneously*) has a limitation by default: tempo maps of all input files after this logic applied must be equal. It means that all files must have the same tempo changes and time signature changes and at the same times. You can turn off this check with the [IgnoreDifferentTempoMaps](xref:Melanchall.DryWetMidi.Tools.SimultaneousMergingSettings.IgnoreDifferentTempoMaps) property set to `true`: diff --git a/Docs/articles/tools/Merger.md b/Docs/articles/tools/Merger.md index 01822fae1..9bb730f14 100644 --- a/Docs/articles/tools/Merger.md +++ b/Docs/articles/tools/Merger.md @@ -9,4 +9,4 @@ You can merge MIDI objects in different ways using extension methods from the [M * [objects merging](Objects-merging.md); * [MIDI files merging](MIDI-files-merging.md). -Please note that these articles doesn't cover all possible methods and their settings. Please read API documentation on [Merger](xref:Melanchall.DryWetMidi.Tools.Merger) to get complete information. \ No newline at end of file +Please note that these articles don't cover all possible methods and their settings. Please read API documentation on [Merger](xref:Melanchall.DryWetMidi.Tools.Merger) to get complete information. \ No newline at end of file diff --git a/Docs/articles/tools/Objects-merging.md b/Docs/articles/tools/Objects-merging.md index 23648327f..2cd7a55c2 100644 --- a/Docs/articles/tools/Objects-merging.md +++ b/Docs/articles/tools/Objects-merging.md @@ -4,7 +4,7 @@ uid: a_obj_merging # Objects merging -To merge nearby objects into one DryWetMIDI provides [Merger](xref:Melanchall.DryWetMidi.Tools.Merger) class. Quick example of merging in action: +To merge nearby objects into one DryWetMIDI provides the [Merger](xref:Melanchall.DryWetMidi.Tools.Merger) class. Quick example of merging in action: ![Objects merging](images/Merger/MergeObjects/MergeObjects.png) @@ -19,11 +19,11 @@ var newObjects = objects.MergeObjects( }); ``` -Now objects will be merged if the distance between them from `0` to `1` second. So tolerance is maximum distance between two objects to consider them as nearby. Please take a look at how tolerance (`T`) affects process of merging: +Now objects will be merged if the distance between them is from `0` to `1` second. So tolerance is the maximum distance between two objects to consider them as nearby. Please take a look at how tolerance (`T`) affects process of merging: ![Objects merging tolerance](images/Merger/MergeObjects/MergeObjects-Tolerance.png) -Of course merging available not for objects collections only. You can use also [MergeObjects](xref:Melanchall.DryWetMidi.Tools.Merger.MergeObjects*) methods on [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile) and [TrackChunk](xref:Melanchall.DryWetMidi.Core.TrackChunk): +Of course merging is available not for objects collections only. You can use also [MergeObjects](xref:Melanchall.DryWetMidi.Tools.Merger.MergeObjects*) methods on [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile) and [TrackChunk](xref:Melanchall.DryWetMidi.Core.TrackChunk): ```csharp midiFile.MergeObjects( @@ -41,13 +41,13 @@ midiFile.MergeObjects( }); ``` -The tool need to determine somehow whether two objects have the same "key" or not to take decision about merging them. For example, if we have a `C` note and `D` one, by default such notes are different in terms of their keys and thus won't be merged. To understand what the key is, please read [MIDI file splitting: SplitByObjects](xref:a_file_splitting#splitbyobjects) article. +The tool needs to determine somehow whether two objects have the same "key" or not to make a decision about merging them. For example, if we have a `C` note and `D` one, by default such notes are different in terms of their keys and thus won't be merged. To understand what the key is, please read [MIDI file splitting: SplitByObjects](xref:a_file_splitting#splitbyobjects) article. Of course you can customize how objects are merged. For example, following picture shows how chords are merged using the default merging logic: ![Chords merging using default logic](images/Merger/MergeObjects/MergeObjects-Chords.png) -Now let's change the logic: chords can be merged only if there are notes in them without gap. Also notes in result chord need to start at the same time and have the same length. Following image shows how chords will be merged: +Now let's change the logic: chords can be merged only if there are notes in them without gaps. Also notes in the result chord need to start at the same time and have the same length. Following image shows how chords will be merged: ![Chords merging using custom logic](images/Merger/MergeObjects/MergeObjects-Chords-Custom.png) diff --git a/Docs/articles/tools/Objects-splitting.md b/Docs/articles/tools/Objects-splitting.md index d9ec3b1d8..1f9c32caf 100644 --- a/Docs/articles/tools/Objects-splitting.md +++ b/Docs/articles/tools/Objects-splitting.md @@ -11,13 +11,13 @@ With DryWetMIDI you can use methods of the [Splitter](xref:Melanchall.DryWetMidi * [SplitObjectsByGrid](xref:Melanchall.DryWetMidi.Tools.Splitter.SplitObjectsByGrid*) * [SplitObjectsAtDistance](xref:Melanchall.DryWetMidi.Tools.Splitter.SplitObjectsAtDistance*) -Each method takes collection of [timed objects](xref:Melanchall.DryWetMidi.Interaction.ITimedObject) or MIDI containers (like [file](xref:Melanchall.DryWetMidi.Core.MidiFile) or [track chunk](xref:Melanchall.DryWetMidi.Core.TrackChunk)) and splits objects within by the specified strategy. These methods are discussed in details below. +Each method takes a collection of [timed objects](xref:Melanchall.DryWetMidi.Interaction.ITimedObject) or MIDI containers (like [file](xref:Melanchall.DryWetMidi.Core.MidiFile) or [track chunk](xref:Melanchall.DryWetMidi.Core.TrackChunk)) and splits objects within by the specified strategy. These methods are discussed in detail below. Also please note that every class that implements [ILengthedObject](xref:Melanchall.DryWetMidi.Interaction.ILengthedObject) has [Split](xref:Melanchall.DryWetMidi.Interaction.ILengthedObject.Split*) method allowing to split an object at the specified time. ## SplitObjectsByStep -[SplitObjectsByStep](xref:Melanchall.DryWetMidi.Tools.Splitter.SplitObjectsByStep*) methods split each object by the specified step starting at the start of an object. For example, if step is `1` second, an object will be split at `1` second from its start, at `1` second from previous point of splitting (`2` seconds from the object's start), at `1` second from previous point of splitting (`3` seconds from the object's start) and so on. If an object's length is less than the specified step, the object will not be split and copy of it will be returned. +[SplitObjectsByStep](xref:Melanchall.DryWetMidi.Tools.Splitter.SplitObjectsByStep*) methods split each object by the specified step starting at the start of an object. For example, if step is `1` second, an object will be split at `1` second from its start, at `1` second from previous point of splitting (`2` seconds from the object's start), at `1` second from previous point of splitting (`3` seconds from the object's start) and so on. If an object's length is less than the specified step, the object will not be split and a copy of it will be returned. The image below illustrates splitting notes and chord by the same step: @@ -25,7 +25,7 @@ The image below illustrates splitting notes and chord by the same step: ## SplitObjectsByPartsNumber -[SplitObjectsByPartsNumber](xref:Melanchall.DryWetMidi.Tools.Splitter.SplitObjectsByPartsNumber*) methods split each object into the specified number of parts of equal length. It is necessary to specify the `lengthType` argument to meet your expectations. For example, with metric type each part of an input object will last the same number of microseconds, while with musical type each part's length will represent the same fraction of the whole note's length. But the length of parts can be different in terms of MIDI ticks using different length type depending on tempo map passed to the method. +[SplitObjectsByPartsNumber](xref:Melanchall.DryWetMidi.Tools.Splitter.SplitObjectsByPartsNumber*) methods split each object into the specified number of parts of equal length. It is necessary to specify the `lengthType` argument to meet your expectations. For example, with metric type each part of an input object will last the same number of microseconds, while with musical type each part's length will represent the same fraction of the whole note's length. But the length of parts can be different in terms of MIDI ticks using different length types depending on the tempo map passed to the method. The image below illustrates splitting notes and chord into `4` parts: @@ -47,6 +47,6 @@ The image below illustrates splitting notes and chord at the same distance from ![Split at distance by step from start](images/Splitter/SplitAtDistanceByStepFromStart.png) -Next image illustrates splitting notes and chord by the ration of `0.25` from the end of an object: +Next image illustrates splitting notes and chord by the ratio of `0.25` from the end of an object: ![Split at distance by ratio from end](images/Splitter/SplitAtDistanceByRatioFromEnd.png) \ No newline at end of file diff --git a/Docs/articles/tools/Overview.md b/Docs/articles/tools/Overview.md index a18679dd3..dbaa8d93a 100644 --- a/Docs/articles/tools/Overview.md +++ b/Docs/articles/tools/Overview.md @@ -11,5 +11,5 @@ DryWetMIDI provides some useful tools to solve complex tasks: * [Merger](xref:a_merger) to merge nearby objects and MIDI files. * [Resizer](xref:a_resizer) to resize groups of objects or MIDI files. * [Repeater](xref:a_repeater) to repeat groups of objects or MIDI files. -* [Sanitizer](xref:a_sanitizer) to clean a MIDI file up removing redundant events and so on. +* [Sanitizer](xref:a_sanitizer) to clean a MIDI file, removing redundant events and so on. * [CSV serializer](xref:a_csv_serializer) to convert MIDI objects to CSV representation and read them back. \ No newline at end of file diff --git a/Docs/articles/tools/Quantizer.md b/Docs/articles/tools/Quantizer.md index c82a79314..5bc8e4b75 100644 --- a/Docs/articles/tools/Quantizer.md +++ b/Docs/articles/tools/Quantizer.md @@ -4,7 +4,7 @@ uid: a_quantizer # Quantizer -DryWetMIDI provides a tool to perform quantizing of objects of different types by the specified grid. The class aimed to solve this task is [Quantizer](xref:Melanchall.DryWetMidi.Tools.Quantizer). Sections below describe usage of the tool in details. +DryWetMIDI provides a tool to perform quantization of objects of different types by the specified grid. The class aimed to solve this task is [Quantizer](xref:Melanchall.DryWetMidi.Tools.Quantizer). Sections below describe usage of the tool in detail. Note that quantizing routine modifies passed objects instead of returning new ones with quantized times. So be sure you've cloned input objects if you want to save them. All classes implementing [ITimedObject](xref:Melanchall.DryWetMidi.Interaction.ITimedObject) as well as [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile) and [TrackChunk](xref:Melanchall.DryWetMidi.Core.TrackChunk) have `Clone` method you can use for that purpose. @@ -32,17 +32,17 @@ An arbitrary object implements [ITimedObject](xref:Melanchall.DryWetMidi.Interac You choose the desired option specifying [QuantizingSettings.Target](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.Target) property. -By default if an object quantized, it will be entirely moved to a grid position. So if you quantize start time, end time can be changed since the object will be moved. You can see the process in action on the first image of the article. Of course this behavior can be altered. Just set [FixOppositeEnd](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.FixOppositeEnd) to `true` to prevent changing of time that is not the target of quantizing. The following image illustrates quantizing of start time with the property set to `true`: +By default if an object is quantized, it will be entirely moved to a grid position. So if you quantize start time, end time can be changed since the object will be moved. You can see the process in action on the first image of the article. Of course this behavior can be altered. Just set [FixOppositeEnd](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.FixOppositeEnd) to `true` to prevent changing of time that is not the target of quantizing. The following image illustrates quantizing of start time with the property set to `true`: ![Quantizing with FixOppositeEnd set to true](images/Quantizer/FixOppositeEnd.png) Of course this property works in case of end time quantizing too. -When the start time of an object is not fixed, there is a chance that the object's end time will be quantized in a such way that the start time will be negative due to the object is moved to the left. Negative time is invalid so you can set [QuantizingSettings.QuantizingBeyondZeroPolicy](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.QuantizingBeyondZeroPolicy) property to desired value to handle this situation. The image below shows how quantizing works if the property set to [FixAtZero](xref:Melanchall.DryWetMidi.Tools.QuantizingBeyondZeroPolicy.FixAtZero): +When the start time of an object is not fixed, there is a chance that the object's end time will be quantized in such a way that the start time will be negative due to the object is moved to the left. Negative time is invalid so you can set [QuantizingSettings.QuantizingBeyondZeroPolicy](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.QuantizingBeyondZeroPolicy) property to desired value to handle this situation. The image below shows how quantizing works if the property set to [FixAtZero](xref:Melanchall.DryWetMidi.Tools.QuantizingBeyondZeroPolicy.FixAtZero): ![Quantizing with QuantizeEndBeyondZero set to FixAtZero](images/Quantizer/QuantizeEndBeyondZero.png) -Also if one side (start or end) of an object is fixed, there is a chance that the object's opposite time will be quantized in a such way that the object will be reversed resulting to negative length. You can handle this situation with [QuantizingSettings.QuantizingBeyondFixedEndPolicy](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.QuantizingBeyondFixedEndPolicy) property. The image below shows some options in action when start time is being quantized beyond the end one: +Also if one side (start or end) of an object is fixed, there is a chance that the object's opposite time will be quantized in such a way that the object will be reversed resulting to negative length. You can handle this situation with the [QuantizingSettings.QuantizingBeyondFixedEndPolicy](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.QuantizingBeyondFixedEndPolicy) property. The image below shows some options in action when start time is being quantized beyond the end one: ![Quantize start time beyond fixed end](images/Quantizer/QuantizeBeyondFixedEnd.png) @@ -50,7 +50,7 @@ Also if one side (start or end) of an object is fixed, there is a chance that th You can derive from the [Quantizer](xref:Melanchall.DryWetMidi.Tools.Quantizer) class and override its [OnObjectQuantizing](xref:Melanchall.DryWetMidi.Tools.Quantizer.OnObjectQuantizing*) method. Inside this method you can decide whether quantizing for an object should be performed or not and if yes, what new time should be set. -Information about what quantizer is going to do with an object is passed via `quantizedTime` parameter of [QuantizedTime](xref:Melanchall.DryWetMidi.Tools.QuantizedTime) type. Image below shows what information is held within this class: +Information about what the quantizer is going to do with an object is passed via the `quantizedTime` parameter of [QuantizedTime](xref:Melanchall.DryWetMidi.Tools.QuantizedTime) type. Image below shows what information is held within this class: ![QuantizedTime](images/Quantizer/QuantizedTime.png) @@ -64,7 +64,7 @@ The new time of an object that was calculated during quantizing. **C**: [DistanceToGridTime](xref:Melanchall.DryWetMidi.Tools.QuantizedTime.DistanceToGridTime) -The distance between an object's current time and the nearest grid time. There is also [ConvertedDistanceToGridTime](xref:Melanchall.DryWetMidi.Tools.QuantizedTime.ConvertedDistanceToGridTime) which holds the distance as [time span](xref:Melanchall.DryWetMidi.Interaction.ITimeSpan) of the type specified by [DistanceCalculationType](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.DistanceCalculationType) property of quantizing settings. +The distance between an object's current time and the nearest grid time. There is also [ConvertedDistanceToGridTime](xref:Melanchall.DryWetMidi.Tools.QuantizedTime.ConvertedDistanceToGridTime) which holds the distance as [time span](xref:Melanchall.DryWetMidi.Interaction.ITimeSpan) of the type specified by [DistanceCalculationType](xref:Melanchall.DryWetMidi.Tools.QuantizingSettings.DistanceCalculationType) property of quantization settings. **D**: [Shift](xref:Melanchall.DryWetMidi.Tools.QuantizedTime.Shift) @@ -90,7 +90,7 @@ public sealed class SoftQuantizer : Quantizer } ``` -What it does? If distance between an object and the nearest grid time is greater than `1/8`, just don't quantize the object. Otherwise do base quantizing. +What does it do? If the distance between an object and the nearest grid time is greater than `1/8`, just don't quantize the object. Otherwise do base quantizing. Our small program to test the tool: @@ -163,4 +163,4 @@ Note [C#3]: time = [1/1], length = [1/8] Press any key to exit... ``` -So all works as expected, middle note is not quantized since it's too far from grid times. +So all works as expected, the middle note is not quantized since it's too far from grid times. diff --git a/Docs/articles/tools/Repeater.md b/Docs/articles/tools/Repeater.md index 24e94d051..c17103200 100644 --- a/Docs/articles/tools/Repeater.md +++ b/Docs/articles/tools/Repeater.md @@ -8,7 +8,7 @@ With DryWetMIDI you can easily repeat groups of objects or entire MIDI files usi ![Objects repeating](images/Repeater/Repeat.png) -It's a simple case. To calculate the distance to move each new part by, the tool looks at the value of the [ShiftPolicy](xref:Melanchall.DryWetMidi.Tools.RepeatingSettings.ShiftPolicy) property of the settings passed to [Repeat](xref:Melanchall.DryWetMidi.Tools.Repeater.Repeat*) methods. The default value is [ShiftPolicy.ShiftByMaxTime](xref:Melanchall.DryWetMidi.Tools.ShiftPolicy.ShiftByMaxTime) and you can see how this options works on the image above – the tool takes maximum time among all objects and shifts objects within each new part by this value. +It's a simple case. To calculate the distance to move each new part by, the tool looks at the value of the [ShiftPolicy](xref:Melanchall.DryWetMidi.Tools.RepeatingSettings.ShiftPolicy) property of the settings passed to [Repeat](xref:Melanchall.DryWetMidi.Tools.Repeater.Repeat*) methods. The default value is [ShiftPolicy.ShiftByMaxTime](xref:Melanchall.DryWetMidi.Tools.ShiftPolicy.ShiftByMaxTime) and you can see how this option works on the image above – the tool takes maximum time among all objects and shifts objects within each new part by this value. But you can use fixed value to shift objects by. You need to specify [ShiftPolicy.ShiftByFixedValue](xref:Melanchall.DryWetMidi.Tools.ShiftPolicy.ShiftByFixedValue) for shift policy and set [Shift](xref:Melanchall.DryWetMidi.Tools.RepeatingSettings.Shift) property of the settings. So times of objects won't affect distance calculation now and data will be shifted by the value of the `Shift` property: @@ -22,11 +22,11 @@ var newFile = midiFile.Repeat(2, new RepeatingSettings ![Shift objects by fixed value](images/Repeater/ShiftByFixedValue.png) -Also [RepeatingSettings](xref:Melanchall.DryWetMidi.Tools.RepeatingSettings) class provides options to round shift value (calculated by max time or constant one). It can be useful, for example, when objects are not aligned with the grid. Please take a look at the following image: +Also the [RepeatingSettings](xref:Melanchall.DryWetMidi.Tools.RepeatingSettings) class provides options to round a shift value (calculated by max time or constant one). It can be useful, for example, when objects are not aligned with the grid. Please take a look at the following image: ![Round shift up](images/Repeater/RoundingUp.png) -Here the data doesn't reach bar line time, but we want to repeat the objects with aligning to bars lines. Obviously we can't use [ShiftPolicy.ShiftByMaxTime](xref:Melanchall.DryWetMidi.Tools.ShiftPolicy.ShiftByMaxTime) option here. But we can't use [ShiftPolicy.ShiftByFixedValue](xref:Melanchall.DryWetMidi.Tools.ShiftPolicy.ShiftByFixedValue) too because we don't know the length of data in general case. We just want to repeat the objects and be sure the start of the objects group is always on a bar line. So we can write this code: +Here the data doesn't reach bar line time, but we want to repeat the objects by aligning to bars lines. Obviously we can't use the [ShiftPolicy.ShiftByMaxTime](xref:Melanchall.DryWetMidi.Tools.ShiftPolicy.ShiftByMaxTime) option here. But we can't use [ShiftPolicy.ShiftByFixedValue](xref:Melanchall.DryWetMidi.Tools.ShiftPolicy.ShiftByFixedValue) too because we don't know the length of data in the general case. We just want to repeat the objects and be sure the start of the objects group is always on a bar line. So we can write this code: ```csharp var newFile = midiFile.Repeat(2, new RepeatingSettings diff --git a/Docs/articles/tools/Resizer.md b/Docs/articles/tools/Resizer.md index 0c7976966..19e965c6a 100644 --- a/Docs/articles/tools/Resizer.md +++ b/Docs/articles/tools/Resizer.md @@ -8,12 +8,12 @@ DryWetMIDI provides the tool to resize a group of objects or an entire MIDI file ![Objects resizing](images/Resizer/ResizeObjectsGroup.png) -So as you can see the tool treats all passed objects as a single group and then sizes this group to the new length saving all time-to-length ratios. For example, if an event was at the middle of objects group, it will be at the middle of the group too after resizing. Following image explains this visually for the group of three notes: +So as you can see the tool treats all passed objects as a single group and then sizes this group to the new length saving all time-to-length ratios. For example, if an event was at the middle of an objects group, it will be at the middle of the group too after resizing. Following image explains this visually for the group of three notes: ![Objects resizing in detail](images/Resizer/ResizeObjectsGroup-Details.png) You use [ResizeObjectsGroup](xref:Melanchall.DryWetMidi.Tools.Resizer.ResizeObjectsGroup*) methods to stretch or shrink a group of [timed objects](xref:Melanchall.DryWetMidi.Interaction.ITimedObject). You can size a group either to the specified new length or by ratio. Ratio means that all distances from the start of a group will be multiplied by this value. So if ratio of `2` is specified, an objects group will be enlarged by 2 times. And `0.5` means the group will be shrunk by 2 times (new length will be half (`0.5`) of the original one). -There also [Resize](xref:Melanchall.DryWetMidi.Tools.Resizer.Resize*) methods to resize [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile) or [TrackChunk](xref:Melanchall.DryWetMidi.Core.TrackChunk). +There are also [Resize](xref:Melanchall.DryWetMidi.Tools.Resizer.Resize*) methods to resize [MidiFile](xref:Melanchall.DryWetMidi.Core.MidiFile) or [TrackChunk](xref:Melanchall.DryWetMidi.Core.TrackChunk). -If you're resizing a group or file to the specified length, please take care of the distance calculation type which can be set via [ObjectsGroupResizingSettings.DistanceCalculationType](xref:Melanchall.DryWetMidi.Tools.ObjectsGroupResizingSettings.DistanceCalculationType) property of the settings that you can pass to the tool's methods. For example, if you specify new length as a [metric](xref:a_time_length#metric) one – `10` seconds – it worth to set `DistanceCalculationType` to the [TimeSpanType.Metric](xref:Melanchall.DryWetMidi.Interaction.TimeSpanType.Metric) value. +If you're resizing a group or file to the specified length, please take care of the distance calculation type which can be set via [ObjectsGroupResizingSettings.DistanceCalculationType](xref:Melanchall.DryWetMidi.Tools.ObjectsGroupResizingSettings.DistanceCalculationType) property of the settings that you can pass to the tool's methods. For example, if you specify new length as a [metric](xref:a_time_length#metric) one – `10` seconds – it's worth to set the `DistanceCalculationType` to the [TimeSpanType.Metric](xref:Melanchall.DryWetMidi.Interaction.TimeSpanType.Metric) value. diff --git a/Docs/articles/tools/Sanitizer.md b/Docs/articles/tools/Sanitizer.md index 546272e3f..97d3eeb5e 100644 --- a/Docs/articles/tools/Sanitizer.md +++ b/Docs/articles/tools/Sanitizer.md @@ -4,7 +4,7 @@ uid: a_sanitizer # Sanitizer -DryWetMIDI provides a tool to clean a MIDI file up removing redundant events and so on – [Sanitizer](xref:Melanchall.DryWetMidi.Tools.Sanitizer). The process can be run via the [Sanitize](xref:Melanchall.DryWetMidi.Tools.Sanitizer.Sanitize*) extension method: +DryWetMIDI provides a tool to clean a MIDI file by removing redundant events and so on – [Sanitizer](xref:Melanchall.DryWetMidi.Tools.Sanitizer). The process can be run via the [Sanitize](xref:Melanchall.DryWetMidi.Tools.Sanitizer.Sanitize*) extension method: ```csharp midiFile.Sanitize(); @@ -38,6 +38,58 @@ Image below shows how you can get rid of short notes with this option: Please notice that how notes will be detected is handled by the [NoteDetectionSettings](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.NoteDetectionSettings) property. You can read more about the [NoteDetectionSettings](xref:Melanchall.DryWetMidi.Interaction.NoteDetectionSettings) class in the [Getting objects: GetNotes: Settings](xref:a_getting_objects#settings) article. +### NoteMinVelocity + +[NoteMinVelocity](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.NoteMinVelocity) property of the [SanitizingSettings](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings) sets a minimum velocity for notes within an input file. All notes with velocity below this value will be removed. The default value is zero which means notes can have any velocity (zero or above). + +For example, if we have such a file with two notes (with numbers of `70` and `50`): + +```csharp +var midiFile = new MidiFile( + new TrackChunk( + new NoteOnEvent((SevenBitNumber)70, (SevenBitNumber)50), + new NoteOffEvent((SevenBitNumber)70, (SevenBitNumber)0), + new NoteOnEvent((SevenBitNumber)50, (SevenBitNumber)40), + new NoteOffEvent((SevenBitNumber)50, (SevenBitNumber)0))); +``` + +then this instruction will remove the second one: + +```csharp +midiFile.Sanitize(new SanitizingSettings +{ + NoteMinVelocity = (SevenBitNumber)45 +}); +``` + +### RemoveDuplicatedNotes + +[RemoveDuplicatedNotes](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.RemoveDuplicatedNotes) property of the [SanitizingSettings](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings) determines whether duplicated notes should be removed or not. The default value is `true`. + +Notes are considered duplicated when they are meet all the following conditions: + +1. the same note number; +2. the same channel; +3. the same time; +4. the same length. + +For example, if we have such a file with two notes (with number of `70` and length of `20`): + +```csharp +var midiFile = new MidiFile( + new TrackChunk( + new NoteOnEvent((SevenBitNumber)70, (SevenBitNumber)50), + new NoteOnEvent((SevenBitNumber)70, (SevenBitNumber)40), + new NoteOffEvent((SevenBitNumber)70, (SevenBitNumber)0) { DeltaTime = 20 }, + new NoteOffEvent((SevenBitNumber)70, (SevenBitNumber)0))); +``` + +then this instruction will remove the second one: + +```csharp +midiFile.Sanitize(); +``` + ### RemoveEmptyTrackChunks [RemoveEmptyTrackChunks](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.RemoveEmptyTrackChunks) property determines whether track chunks without MIDI events should be removed or not. @@ -105,6 +157,10 @@ var midiFile = new MidiFile( [RemoveDuplicatedPitchBendEvents](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.RemoveDuplicatedPitchBendEvents) does the same actions as the [RemoveDuplicatedSetTempoEvents](#removeduplicatedsettempoevents) or [RemoveDuplicatedTimeSignatureEvents](#removeduplicatedtimesignatureevents) one but for duplicated [Pitch Bend](xref:Melanchall.DryWetMidi.Core.PitchBendEvent) events. +### RemoveDuplicatedSequenceTrackNameEvents + +[RemoveDuplicatedSequenceTrackNameEvents](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.RemoveDuplicatedSequenceTrackNameEvents) does the same actions as the [RemoveDuplicatedSetTempoEvents](#removeduplicatedsettempoevents) or [RemoveDuplicatedTimeSignatureEvents](#removeduplicatedtimesignatureevents) one but for duplicated [Sequence/Track Name](xref:Melanchall.DryWetMidi.Core.SequenceTrackNameEvent) events. + ### RemoveEventsOnUnusedChannels If a MIDI file doesn't contain notes on some channel, we can safely remove all events on that channel. For example, the [Control Change](xref:Melanchall.DryWetMidi.Core.ControlChangeEvent) event on channel `2` in the following example is redundant: @@ -118,7 +174,7 @@ var midiFile = new MidiFile( new NoteOffEvent { Channel = (FourBitNumber)5 })); ``` -[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: +[RemoveEventsOnUnusedChannels](xref:Melanchall.DryWetMidi.Tools.SanitizingSettings.RemoveEventsOnUnusedChannels) property turns on or off removing such channel events that have no effect since there are no notes on those channels: ![SanitizingSettings.RemoveEventsOnUnusedChannels](images/Sanitizer/RemoveEventsOnUnusedChannels.png) @@ -144,4 +200,6 @@ var midiFile = new MidiFile( new TrackChunk( new TextEvent("B") { DeltaTime = 10 }, new TextEvent("C") { DeltaTime = 15 })); -``` \ No newline at end of file +``` + +The default value is `false` since this behavior can be undesired. \ No newline at end of file diff --git a/Docs/articles/tools/Splitter.md b/Docs/articles/tools/Splitter.md index 6235ffeb0..e38eb1232 100644 --- a/Docs/articles/tools/Splitter.md +++ b/Docs/articles/tools/Splitter.md @@ -9,4 +9,4 @@ You can split MIDI objects in different ways using extension methods from the [S * [objects splitting](Objects-splitting.md); * [MIDI file splitting](MIDI-file-splitting.md). -Please note that these articles doesn't cover all possible methods and their settings. Please read API documentation on [Splitter](xref:Melanchall.DryWetMidi.Tools.Splitter) to get complete information. \ No newline at end of file +Please note that these articles don't cover all possible methods and their settings. Please read API documentation on [Splitter](xref:Melanchall.DryWetMidi.Tools.Splitter) to get complete information. \ No newline at end of file diff --git a/Docs/docfx.json b/Docs/docfx.json index 169b4d2d0..aeb2b8f43 100644 --- a/Docs/docfx.json +++ b/Docs/docfx.json @@ -30,7 +30,8 @@ { "files": [ "images/**", - "articles/**/images/**" ] + "articles/**/images/**", + "articles/**/*.mid" ] } ], "globalMetadata": { diff --git a/Docs/templates/dwm/styles/main.css b/Docs/templates/dwm/styles/main.css index 58eb1d62a..929e7e4da 100644 --- a/Docs/templates/dwm/styles/main.css +++ b/Docs/templates/dwm/styles/main.css @@ -190,7 +190,7 @@ hr { color: rgb(227, 227, 227); } -.hljs-keyword, .hljs-selector-tag, .hljs-type { +.hljs-keyword, .hljs-selector-tag, .hljs-type, .hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number, .hljs-operator, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id, .hljs-variable, .hljs-built_in { color: #569cd6; } @@ -210,6 +210,10 @@ hr { color: #ce9178; } +.hljs-subst { + color: rgb(227, 227, 227); +} + pre code.hljs { padding: 0; } @@ -500,7 +504,7 @@ img[src*="logo.png"], img[src*="build/status"], img[src*="img.shields.io"] { .alert-danger { color: #F8F8F8; border-radius: 0px; - background-color: transparent; + background-color: #EC550811; border-color: #EC5508; } @@ -536,4 +540,8 @@ img.text-image { background-color: transparent; padding: 0px; border: none; +} + +.alert > pre { + margin-top: 10px; } \ No newline at end of file diff --git a/DryWetMidi.Tests/Composing/PatternBuilder/PatternBuilderTests.PianoRoll.cs b/DryWetMidi.Tests/Composing/PatternBuilder/PatternBuilderTests.PianoRoll.cs index ed1c1d604..5ea72d917 100644 --- a/DryWetMidi.Tests/Composing/PatternBuilder/PatternBuilderTests.PianoRoll.cs +++ b/DryWetMidi.Tests/Composing/PatternBuilder/PatternBuilderTests.PianoRoll.cs @@ -61,6 +61,33 @@ public void PianoRoll_2() }); } + [Test] + public void PianoRoll_3() + { + var step = MusicalTimeSpan.Sixteenth; + var velocity = (SevenBitNumber)90; + + var pattern = new PatternBuilder() + .SetNoteLength(step) + .SetVelocity(velocity) + .PianoRoll(@" + 57 ---- ---| + 47 --|- --|- + 44 |--- |--| + ") + .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() { diff --git a/DryWetMidi/Composing/PatternBuilder.ControlChange.cs b/DryWetMidi/Composing/PatternBuilder.ControlChange.cs index 19b255c6e..4af31128f 100644 --- a/DryWetMidi/Composing/PatternBuilder.ControlChange.cs +++ b/DryWetMidi/Composing/PatternBuilder.ControlChange.cs @@ -1,4 +1,5 @@ using Melanchall.DryWetMidi.Common; +using Melanchall.DryWetMidi.Core; namespace Melanchall.DryWetMidi.Composing { @@ -6,6 +7,12 @@ public sealed partial class PatternBuilder { #region Methods + /// + /// Inserts to specify a change of a controller that will be used by following notes. + /// + /// Controller number. + /// Controller value. + /// The current . public PatternBuilder ControlChange(SevenBitNumber controlNumber, SevenBitNumber controlValue) { return AddAction(new AddControlChangeEventAction(controlNumber, controlValue)); diff --git a/DryWetMidi/Composing/PatternBuilder.PianoRoll.cs b/DryWetMidi/Composing/PatternBuilder.PianoRoll.cs index f20dfc169..d3d4ee167 100644 --- a/DryWetMidi/Composing/PatternBuilder.PianoRoll.cs +++ b/DryWetMidi/Composing/PatternBuilder.PianoRoll.cs @@ -1,6 +1,7 @@ using Melanchall.DryWetMidi.Common; using Melanchall.DryWetMidi.Interaction; using System; +using System.IO; using System.Linq; namespace Melanchall.DryWetMidi.Composing @@ -15,6 +16,33 @@ public sealed partial class PatternBuilder #region Methods + /// + /// Inserts notes data by the specified piano roll string. More info in the + /// Pattern: Piano roll article. + /// + /// String that represents notes data as a piano roll. + /// Settings according to which a piano roll should be handled. + /// The current . + /// is null, a zero-length string, + /// contains only white space, or contains one or more invalid characters as defined by + /// . + /// + /// One of the following errors occurred: + /// + /// + /// Failed to parse a note. + /// + /// + /// Single-cell note can't be placed inside a multi-cell one. + /// + /// + /// Note can't be started while a previous one is not ended. + /// + /// + /// Note is not started. + /// + /// + /// public PatternBuilder PianoRoll( string pianoRoll, PianoRollSettings settings = null) @@ -60,14 +88,23 @@ private static MusicTheory.Note IdentifyLineNote( string line, out int dataStartIndex) { - var digitIndex = line.IndexOfAny(Digits); - var notePart = line.Substring(0, digitIndex + 1); + var notePartEndIndex = line.IndexOfAny(Digits); + var notePart = line.Substring(0, notePartEndIndex + 1); MusicTheory.Note note; if (!MusicTheory.Note.TryParse(notePart, out note)) - throw new InvalidOperationException($"Failed to parse note from '{notePart}'."); + { + notePartEndIndex = Enumerable.Range(0, line.Length).FirstOrDefault(i => !char.IsDigit(line[i])) - 1; + notePart = line.Substring(0, notePartEndIndex + 1).Trim(); + + SevenBitNumber noteNumber; + if (!SevenBitNumber.TryParse(notePart, out noteNumber)) + throw new InvalidOperationException($"Failed to parse a note from '{notePart}'."); + else + note = MusicTheory.Note.Get(noteNumber); + } - dataStartIndex = digitIndex + 1; + dataStartIndex = notePartEndIndex + 1; return note; } diff --git a/DryWetMidi/Composing/PatternBuilder.PitchBend.cs b/DryWetMidi/Composing/PatternBuilder.PitchBend.cs index 089dab9ad..38f2c3109 100644 --- a/DryWetMidi/Composing/PatternBuilder.PitchBend.cs +++ b/DryWetMidi/Composing/PatternBuilder.PitchBend.cs @@ -1,11 +1,29 @@ -namespace Melanchall.DryWetMidi.Composing +using Melanchall.DryWetMidi.Common; +using Melanchall.DryWetMidi.Core; +using System; + +namespace Melanchall.DryWetMidi.Composing { public sealed partial class PatternBuilder { #region Methods + /// + /// Inserts to specify a pitch bend that will be used by following notes. + /// + /// Pitch value. + /// The current . + /// is out of + /// [; ] range. public PatternBuilder PitchBend(ushort pitchValue) { + ThrowIfArgument.IsOutOfRange( + nameof(pitchValue), + pitchValue, + PitchBendEvent.MinPitchValue, + PitchBendEvent.MaxPitchValue, + $"Pitch value is out of [{PitchBendEvent.MinPitchValue}; {PitchBendEvent.MaxPitchValue}] range."); + return AddAction(new AddPitchBendEventAction(pitchValue)); } diff --git a/DryWetMidi/Composing/PianoRollSettings.cs b/DryWetMidi/Composing/PianoRollSettings.cs index 45e74fc49..0d2088651 100644 --- a/DryWetMidi/Composing/PianoRollSettings.cs +++ b/DryWetMidi/Composing/PianoRollSettings.cs @@ -5,6 +5,11 @@ namespace Melanchall.DryWetMidi.Composing { + /// + /// Settings to control handling of a piano roll be the + /// method. More info in the + /// Pattern: Piano roll: Customization article. + /// public sealed class PianoRollSettings { #region Constants @@ -29,6 +34,25 @@ public sealed class PianoRollSettings #region Properties + /// + /// Gets or sets a symbol which means a single-cell note. The default value + /// is '|'. + /// + /// Space (' ') is the prohibted character. + /// + /// One of the following errors occurred: + /// + /// + /// The same symbol defined by the property. + /// + /// + /// The same symbol defined by the property. + /// + /// + /// The symbol used for a custom action wuthin the . + /// + /// + /// public char SingleCellNoteSymbol { get { return _singleCellNoteSymbol; } @@ -36,10 +60,47 @@ public char SingleCellNoteSymbol { ThrowIfArgument.IsProhibitedValue(nameof(value), value, ProhibitedSymbol); + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => v != MultiCellNoteStartSymbol, + $"The same symbol defined by the {nameof(MultiCellNoteStartSymbol)} property."); + + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => v != MultiCellNoteEndSymbol, + $"The same symbol defined by the {nameof(MultiCellNoteEndSymbol)} property."); + + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => CustomActions?.Keys.Contains(v) != true, + $"The symbol used for a custom action wuthin the {nameof(CustomActions)}."); + _singleCellNoteSymbol = value; } } + /// + /// Gets or sets a symbol which means the start of a multi-cell note. The default value + /// is '['. + /// + /// Space (' ') is the prohibted character. + /// + /// One of the following errors occurred: + /// + /// + /// The same symbol defined by the property. + /// + /// + /// The same symbol defined by the property. + /// + /// + /// The symbol used for a custom action wuthin the . + /// + /// + /// public char MultiCellNoteStartSymbol { get { return _multiCellNoteStartSymbol; } @@ -47,10 +108,47 @@ public char MultiCellNoteStartSymbol { ThrowIfArgument.IsProhibitedValue(nameof(value), value, ProhibitedSymbol); + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => v != SingleCellNoteSymbol, + $"The same symbol defined by the {nameof(SingleCellNoteSymbol)} property."); + + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => v != MultiCellNoteEndSymbol, + $"The same symbol defined by the {nameof(MultiCellNoteEndSymbol)} property."); + + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => CustomActions?.Keys.Contains(v) != true, + $"The symbol used for a custom action wuthin the {nameof(CustomActions)}."); + _multiCellNoteStartSymbol = value; } } + /// + /// Gets or sets a symbol which means the end of a multi-cell note. The default value + /// is ']'. + /// + /// Space (' ') is the prohibted character. + /// + /// One of the following errors occurred: + /// + /// + /// The same symbol defined by the property. + /// + /// + /// The same symbol defined by the property. + /// + /// + /// The symbol used for a custom action wuthin the . + /// + /// + /// public char MultiCellNoteEndSymbol { get { return _multiCellNoteEndSymbol; } @@ -58,10 +156,51 @@ public char MultiCellNoteEndSymbol { ThrowIfArgument.IsProhibitedValue(nameof(value), value, ProhibitedSymbol); + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => v != SingleCellNoteSymbol, + $"The same symbol defined by the {nameof(SingleCellNoteSymbol)} property."); + + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => v != MultiCellNoteStartSymbol, + $"The same symbol defined by the {nameof(MultiCellNoteStartSymbol)} property."); + + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => CustomActions?.Keys.Contains(v) != true, + $"The symbol used for a custom action wuthin the {nameof(CustomActions)}."); + _multiCellNoteEndSymbol = value; } } + /// + /// Gets or sets a dictionary which maps specified symbols to custom actions. + /// + /// + /// One of the following errors occurred: + /// + /// + /// Actions keys contain the space (' ') symbol which is prohibited. + /// + /// + /// Actions keys contain the symbol defined by the + /// property which is prohibited. + /// + /// + /// Actions keys contain the symbol defined by the + /// property which is prohibited. + /// + /// + /// Actions keys contain the symbol defined by the + /// property which is prohibited. + /// + /// + /// public Dictionary> CustomActions { get { return _customActions; } @@ -69,6 +208,12 @@ public char MultiCellNoteEndSymbol { if (value != null) { + ThrowIfArgument.DoesntSatisfyCondition( + nameof(value), + value, + v => !v.Keys.Contains(ProhibitedSymbol), + $"Actions keys contain the space (' ') symbol which is prohibited."); + ThrowIfArgument.DoesntSatisfyCondition( nameof(value), value, diff --git a/DryWetMidi/Interaction/Chords/ChordProcessingHint.cs b/DryWetMidi/Interaction/Chords/ChordProcessingHint.cs index e0f7cf3b9..2cf819217 100644 --- a/DryWetMidi/Interaction/Chords/ChordProcessingHint.cs +++ b/DryWetMidi/Interaction/Chords/ChordProcessingHint.cs @@ -46,7 +46,7 @@ public enum ChordProcessingHint /// /// The processing algorithm will consider that everything related to a chord can be changed. - /// this hint means minimum performance, i.e. the processing will take more time and consume more memory. + /// This hint means minimum performance, i.e. the processing will take more time and consume more memory. /// For maximum performance see option. /// AllPropertiesCanBeChanged = diff --git a/DryWetMidi/Interaction/Chords/ChordsManagingUtilities.cs b/DryWetMidi/Interaction/Chords/ChordsManagingUtilities.cs index 69dd74f27..4fa827e9d 100644 --- a/DryWetMidi/Interaction/Chords/ChordsManagingUtilities.cs +++ b/DryWetMidi/Interaction/Chords/ChordsManagingUtilities.cs @@ -157,6 +157,10 @@ public ChordDescriptorIndexed(long time, LinkedListNode /// that holds chords to manage. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Comparer that will be used to order objects on enumerating and saving objects /// back to the via /// or . @@ -190,6 +194,10 @@ public static TimedObjectsManager ManageChords( /// /// that holds chords to manage. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Comparer that will be used to order objects on enumerating and saving objects /// back to the via /// or . @@ -214,6 +222,10 @@ public static TimedObjectsManager ManageChords( /// /// Collection of to search for chords. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Collection of chords contained in ordered by time. /// is null. public static ICollection GetChords( @@ -240,6 +252,10 @@ public static ICollection GetChords( /// /// to search for chords. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Collection of chords contained in ordered by time. /// is null. /// @@ -270,6 +286,10 @@ public static ICollection GetChords( /// /// to search for chords. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Collection of chords contained in ordered by time. /// is null. /// @@ -294,6 +314,10 @@ public static ICollection GetChords( /// /// Track chunks to search for chords. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Collection of chords contained in ordered by time. /// is null. /// @@ -333,6 +357,10 @@ public static ICollection GetChords( /// /// to search for chords. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Collection of chords contained in ordered by time. /// is null. /// @@ -356,6 +384,10 @@ public static ICollection GetChords( /// /// Notes to create chords from. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Collection of chords made up from . /// is null. public static IEnumerable GetChords( @@ -371,11 +403,16 @@ public static IEnumerable GetChords( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessChords article. /// /// to search for chords to process. /// The action to perform on each contained in the /// . /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -412,12 +449,17 @@ public static int ProcessChords( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessChords article. /// /// to search for chords to process. /// The action to perform on each contained in the /// . /// The predicate that defines the conditions of the to process. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -465,11 +507,16 @@ public static int ProcessChords( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessChords article. /// /// to search for chords to process. /// The action to perform on each contained in the /// . /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -506,12 +553,17 @@ public static int ProcessChords( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessChords article. /// /// to search for chords to process. /// The action to perform on each contained in the /// . /// The predicate that defines the conditions of the to process. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -553,12 +605,17 @@ public static int ProcessChords( /// /// Performs the specified action on each contained in the collection of - /// . + /// . More info in the + /// Processing objects: ProcessChords article. /// /// Collection of to search for chords to process. /// The action to perform on each contained in the /// . /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -595,13 +652,18 @@ public static int ProcessChords( /// /// Performs the specified action on each contained in the collection of - /// . + /// . More info in the + /// Processing objects: ProcessChords article. /// /// Collection of to search for chords to process. /// The action to perform on each contained in the /// . /// The predicate that defines the conditions of the to process. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -652,11 +714,16 @@ public static int ProcessChords( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessChords article. /// /// to search for chords to process. /// The action to perform on each contained in the /// . /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -693,12 +760,17 @@ public static int ProcessChords( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessChords article. /// /// to search for chords to process. /// The action to perform on each contained in the /// . /// The predicate that defines the conditions of the to process. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -739,10 +811,15 @@ public static int ProcessChords( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all chords from the . More info in the + /// Removing objects: RemoveChords article. /// /// to search for chords to remove. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Count of removed chords. /// is null. /// @@ -758,11 +835,16 @@ public static int RemoveChords( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes chords that match the specified conditions from the . + /// More info in the Removing objects: RemoveChords article. /// /// to search for chords to remove. /// The predicate that defines the conditions of the to remove. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Count of removed chords. /// /// One of the following errors occurred: @@ -808,10 +890,15 @@ public static int RemoveChords( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all chords from the . More info in the + /// Removing objects: RemoveChords article. /// /// to search for chords to remove. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Count of removed chords. /// is null. /// @@ -827,11 +914,16 @@ public static int RemoveChords( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes chords that match the specified conditions from the . + /// More info in the Removing objects: RemoveChords article. /// /// to search for chords to remove. /// The predicate that defines the conditions of the to remove. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Count of removed chords. /// /// One of the following errors occurred: @@ -859,10 +951,15 @@ public static int RemoveChords( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all chords from the collection of . More info in the + /// Removing objects: RemoveChords article. /// /// Collection of to search for chords to remove. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Count of removed chords. /// is null. /// @@ -878,11 +975,16 @@ public static int RemoveChords( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes chords that match the specified conditions from the collection of . + /// More info in the Removing objects: RemoveChords article. /// /// Collection of to search for chords to remove. /// The predicate that defines the conditions of the to remove. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Count of removed chords. /// /// One of the following errors occurred: @@ -928,10 +1030,15 @@ public static int RemoveChords( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all chords from the . More info in the + /// Removing objects: RemoveChords article. /// /// to search for chords to remove. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Count of removed chords. /// is null. /// @@ -947,11 +1054,16 @@ public static int RemoveChords( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes chords that match the specified conditions from the . + /// More info in the Removing objects: RemoveChords article. /// /// to search for chords to remove. /// The predicate that defines the conditions of the to remove. /// Settings according to which chords should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. + /// Settings according to which timed events should be detected + /// and built to construct notes for chords. /// Count of removed chords. /// /// One of the following errors occurred: diff --git a/DryWetMidi/Interaction/Notes/NotesManagingUtilities.cs b/DryWetMidi/Interaction/Notes/NotesManagingUtilities.cs index 2c156130b..3eb1cde40 100644 --- a/DryWetMidi/Interaction/Notes/NotesManagingUtilities.cs +++ b/DryWetMidi/Interaction/Notes/NotesManagingUtilities.cs @@ -207,6 +207,8 @@ public TimedObjectAt GetIndexedObject(Func constru /// /// that holds notes to manage. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Comparer that will be used to order objects on enumerating and saving objects /// back to the via /// or . @@ -238,6 +240,8 @@ public static TimedObjectsManager ManageNotes( /// /// that holds notes to manage. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Comparer that will be used to order objects on enumerating and saving objects /// back to the via /// or . @@ -261,6 +265,8 @@ public static TimedObjectsManager ManageNotes( /// /// Collection of to search for notes. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Collection of notes contained in ordered by time. /// is null. /// @@ -286,6 +292,8 @@ public static ICollection GetNotes( /// /// to search for notes. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Collection of notes contained in ordered by time. /// is null. /// @@ -315,6 +323,8 @@ public static ICollection GetNotes( /// /// to search for notes. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Collection of notes contained in ordered by time. /// is null. /// @@ -338,6 +348,8 @@ public static ICollection GetNotes( /// /// Track chunks to search for notes. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Collection of notes contained in ordered by time. /// is null. /// @@ -376,6 +388,8 @@ public static ICollection GetNotes( /// /// to search for notes. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Collection of notes contained in ordered by time. /// is null. /// @@ -395,11 +409,14 @@ public static ICollection GetNotes( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessNotes article. /// /// to search for notes to process. /// The action to perform on each contained in the /// . /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -435,12 +452,15 @@ public static int ProcessNotes( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessNotes article. /// /// to search for notes to process. /// The action to perform on each contained in the /// . /// The predicate that defines the conditions of the to process. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -481,11 +501,14 @@ public static int ProcessNotes( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessNotes article. /// /// to search for notes to process. /// The action to perform on each contained in the /// . /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -521,12 +544,15 @@ public static int ProcessNotes( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessNotes article. /// /// to search for notes to process. /// The action to perform on each contained in the /// . /// The predicate that defines the conditions of the to process. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -566,12 +592,15 @@ public static int ProcessNotes( /// /// Performs the specified action on each contained in the collection of - /// . + /// . More info in the + /// Processing objects: ProcessNotes article. /// /// Collection of to search for notes to process. /// The action to perform on each contained in the /// . /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -607,13 +636,16 @@ public static int ProcessNotes( /// /// Performs the specified action on each contained in the collection of - /// . + /// . More info in the + /// Processing objects: ProcessNotes article. /// /// Collection of to search for notes to process. /// The action to perform on each contained in the /// . /// The predicate that defines the conditions of the to process. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -657,11 +689,14 @@ public static int ProcessNotes( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessNotes article. /// /// to search for notes to process. /// The action to perform on each contained in the /// . /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -697,12 +732,15 @@ public static int ProcessNotes( /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessNotes article. /// /// to search for notes to process. /// The action to perform on each contained in the /// . /// The predicate that defines the conditions of the to process. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Hint which tells the processing algorithm how it can optimize its performance. /// The default value is . /// @@ -742,10 +780,13 @@ public static int ProcessNotes( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all notes from the . + /// More info in the Removing objects: RemoveNotes article. /// /// to search for notes to remove. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Count of removed notes. /// is null. /// @@ -760,11 +801,14 @@ public static int RemoveNotes( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes notes that match the specified conditions from the . + /// More info in the Removing objects: RemoveNotes article. /// /// to search for notes to remove. /// The predicate that defines the conditions of the to remove. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Count of removed notes. /// /// One of the following errors occurred: @@ -802,10 +846,13 @@ public static int RemoveNotes( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all notes from the . + /// More info in the Removing objects: RemoveNotes article. /// /// to search for notes to remove. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Count of removed notes. /// is null. /// @@ -820,11 +867,14 @@ public static int RemoveNotes( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes notes that match the specified conditions from the . + /// More info in the Removing objects: RemoveNotes article. /// /// to search for notes to remove. /// The predicate that defines the conditions of the to remove. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Count of removed notes. /// /// One of the following errors occurred: @@ -851,10 +901,13 @@ public static int RemoveNotes( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all notes from the collection of . + /// More info in the Removing objects: RemoveNotes article. /// /// Collection of to search for notes to remove. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Count of removed notes. /// is null. /// @@ -869,11 +922,14 @@ public static int RemoveNotes( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes notes that match the specified conditions from the collection of . + /// More info in the Removing objects: RemoveNotes article. /// /// Collection of to search for notes to remove. /// The predicate that defines the conditions of the to remove. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Count of removed notes. /// /// One of the following errors occurred: @@ -911,10 +967,13 @@ public static int RemoveNotes( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all notes from the . + /// More info in the Removing objects: RemoveNotes article. /// /// to search for notes to remove. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Count of removed notes. /// is null. /// @@ -929,11 +988,14 @@ public static int RemoveNotes( } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes notes that match the specified conditions from the . + /// More info in the Removing objects: RemoveNotes article. /// /// to search for notes to remove. /// The predicate that defines the conditions of the to remove. /// Settings according to which notes should be detected and built. + /// Settings according to which timed events should be detected + /// and built to construct notes. /// Count of removed notes. /// /// One of the following errors occurred: diff --git a/DryWetMidi/Interaction/TimedEvents/TimedEventsManagingUtilities.cs b/DryWetMidi/Interaction/TimedEvents/TimedEventsManagingUtilities.cs index 5344014dc..6590862db 100644 --- a/DryWetMidi/Interaction/TimedEvents/TimedEventsManagingUtilities.cs +++ b/DryWetMidi/Interaction/TimedEvents/TimedEventsManagingUtilities.cs @@ -156,6 +156,7 @@ public static ICollection GetTimedEvents(this MidiFile file, TimedEv /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessTimedEvents article. /// /// to search for events to process. /// The action to perform on each contained in the @@ -191,6 +192,7 @@ public static int ProcessTimedEvents(this EventsCollection eventsCollection, Act /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessTimedEvents article. /// /// to search for events to process. /// The action to perform on each contained in the @@ -231,6 +233,7 @@ public static int ProcessTimedEvents(this EventsCollection eventsCollection, Act /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessTimedEvents article. /// /// to search for events to process. /// The action to perform on each contained in the @@ -266,6 +269,7 @@ public static int ProcessTimedEvents(this TrackChunk trackChunk, Action /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessTimedEvents article. /// /// to search for events to process. /// The action to perform on each contained in the @@ -306,7 +310,8 @@ public static int ProcessTimedEvents(this TrackChunk trackChunk, Action /// Performs the specified action on each contained in the collection of - /// . + /// . More info in the + /// Processing objects: ProcessTimedEvents article. /// /// Collection of to search for events to process. /// The action to perform on each contained in the @@ -342,7 +347,8 @@ public static int ProcessTimedEvents(this IEnumerable trackChunks, A /// /// Performs the specified action on each contained in the collection of - /// . + /// . More info in the + /// Processing objects: ProcessTimedEvents article. /// /// Collection of to search for events to process. /// The action to perform on each contained in the @@ -386,6 +392,7 @@ public static int ProcessTimedEvents(this IEnumerable trackChunks, A /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessTimedEvents article. /// /// to search for events to process. /// The action to perform on each contained in the @@ -421,6 +428,7 @@ public static int ProcessTimedEvents(this MidiFile file, Action acti /// /// Performs the specified action on each contained in the . + /// More info in the Processing objects: ProcessTimedEvents article. /// /// to search for events to process. /// The action to perform on each contained in the @@ -460,7 +468,8 @@ public static int ProcessTimedEvents(this MidiFile file, Action acti } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all timed events from the . + /// More info in the Removing objects: RemoveTimedEvents article. /// /// to search for events to remove. /// Count of removed timed events. @@ -476,7 +485,8 @@ public static int RemoveTimedEvents(this EventsCollection eventsCollection) } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes timed events that match the specified conditions from the . + /// More info in the Removing objects: RemoveTimedEvents article. /// /// to search for events to remove. /// The predicate that defines the conditions of the to remove. @@ -534,7 +544,8 @@ public static int RemoveTimedEvents(this EventsCollection eventsCollection, Pred } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all timed events from the . + /// More info in the Removing objects: RemoveTimedEvents article. /// /// to search for events to remove. /// Count of removed timed events. @@ -550,7 +561,8 @@ public static int RemoveTimedEvents(this TrackChunk trackChunk) } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes timed events that match the specified conditions from the . + /// More info in the Removing objects: RemoveTimedEvents article. /// /// to search for events to remove. /// The predicate that defines the conditions of the to remove. @@ -577,7 +589,8 @@ public static int RemoveTimedEvents(this TrackChunk trackChunk, Predicate - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all timed events from the collection of . + /// More info in the Removing objects: RemoveTimedEvents article. /// /// Collection of to search for events to remove. /// Count of removed timed events. @@ -598,7 +611,8 @@ public static int RemoveTimedEvents(this IEnumerable trackChunks) } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes timed events that match the specified conditions from the collection of . + /// More info in the Removing objects: RemoveTimedEvents article. /// /// Collection of to search for events to remove. /// The predicate that defines the conditions of the to remove. @@ -689,7 +703,8 @@ public static int RemoveTimedEvents(this IEnumerable trackChunks, Pr } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes all timed events from the . + /// More info in the Removing objects: RemoveTimedEvents article. /// /// to search for events to remove. /// Count of removed timed events. @@ -703,7 +718,8 @@ public static int RemoveTimedEvents(this MidiFile file) } /// - /// Removes all the that match the conditions defined by the specified predicate. + /// Removes timed events that match the specified conditions from the . + /// More info in the Removing objects: RemoveTimedEvents article. /// /// to search for events to remove. /// The predicate that defines the conditions of the to remove. diff --git a/DryWetMidi/Interaction/TimedObject/ObjectProcessingHint.cs b/DryWetMidi/Interaction/TimedObject/ObjectProcessingHint.cs index 61225447e..edef1eb3b 100644 --- a/DryWetMidi/Interaction/TimedObject/ObjectProcessingHint.cs +++ b/DryWetMidi/Interaction/TimedObject/ObjectProcessingHint.cs @@ -1,16 +1,58 @@ using System; +using Melanchall.DryWetMidi.Core; namespace Melanchall.DryWetMidi.Interaction { + /// + /// Defines a hint which tells the objects processing algorithm how it can optimize its performance. + /// + /// + /// If you want to get the maximum performance of an object processing (for example, + /// ), + /// choose a hint carefully. Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// [Flags] public enum ObjectProcessingHint { + /// + /// The processing algorithm will consider that properties that don't affect underlying MIDI + /// events positions will be changed only. This hint means maximum performance, i.e. the processing will + /// take less time and consume less memory. If you're going to change, for example, objects times, or + /// notes collections of chords, your changes won't be saved with this option. + /// None = 0, + /// + /// or of an object can be changed. + /// TimeOrLengthCanBeChanged = 1, + + /// + /// can be changed, i.e. a note can be added to or removed from a chord. + /// NotesCollectionCanBeChanged = 2, + + /// + /// or can be changed on a note within + /// a chord's notes (). + /// NoteTimeOrLengthCanBeChanged = 4, + /// + /// Default hint. Equals to . + /// Default = TimeOrLengthCanBeChanged, + + /// + /// The processing algorithm will consider that everything on an object can be changed. + /// This hint means minimum performance, i.e. the processing will take more time and consume more memory. + /// For maximum performance see option. + /// + AllPropertiesCanBeChanged = + TimeOrLengthCanBeChanged | + NotesCollectionCanBeChanged | + NoteTimeOrLengthCanBeChanged, } } diff --git a/DryWetMidi/Interaction/TimedObject/TimedObjectUtilities.ProcessObjects.cs b/DryWetMidi/Interaction/TimedObject/TimedObjectUtilities.ProcessObjects.cs index 71cc54b71..ec1263020 100644 --- a/DryWetMidi/Interaction/TimedObject/TimedObjectUtilities.ProcessObjects.cs +++ b/DryWetMidi/Interaction/TimedObject/TimedObjectUtilities.ProcessObjects.cs @@ -3,6 +3,8 @@ using System; using System.Linq; using Melanchall.DryWetMidi.Common; +using System.Text.RegularExpressions; +using System.IO; namespace Melanchall.DryWetMidi.Interaction { @@ -34,6 +36,35 @@ private sealed class ProcessingContext #region Methods + /// + /// Performs the specified action on each object contained in the . Objects + /// for processing will be selected by the specified object type. More info in the + /// Processing objects: ProcessObjects article. + /// + /// to search for objects to process. + /// Types of objects to process (for example, ObjectType.Chord | ObjectType.TimedEvent). + /// The action to perform on each object contained in the + /// . + /// Settings according to which objects should be detected and built. + /// Hint which tells the processing algorithm how it can optimize its performance. + /// The default value is . + /// + /// Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// + /// Count of processed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int ProcessObjects( this EventsCollection eventsCollection, ObjectType objectType, @@ -44,9 +75,53 @@ public static int ProcessObjects( ThrowIfArgument.IsNull(nameof(eventsCollection), eventsCollection); ThrowIfArgument.IsNull(nameof(action), action); - return eventsCollection.ProcessObjects(objectType, action, note => true, settings, hint); + switch (objectType) + { + case ObjectType.TimedEvent: + return eventsCollection.ProcessTimedEvents(action, settings?.TimedEventDetectionSettings, hint.ToTimedEventProcessingHint()); + case ObjectType.Note: + return eventsCollection.ProcessNotes(action, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToNoteProcessingHint()); + case ObjectType.Chord: + return eventsCollection.ProcessChords(action, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToChordProcessingHint()); + } + + return eventsCollection.ProcessObjects(objectType, action, obj => true, settings, hint); } + /// + /// Performs the specified action on objects contained in the . Objects + /// for processing will be selected by the specified object type and matching predicate. More info in the + /// Processing objects: ProcessObjects article. + /// + /// to search for objects to process. + /// Types of objects to process (for example, ObjectType.Chord | ObjectType.TimedEvent). + /// The action to perform on each object contained in the + /// . + /// The predicate that defines the conditions of an object to process. Predicate + /// should return true for an object to process. + /// Settings according to which objects should be detected and built. + /// Hint which tells the processing algorithm how it can optimize its performance. + /// The default value is . + /// + /// Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// + /// Count of processed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int ProcessObjects( this EventsCollection eventsCollection, ObjectType objectType, @@ -59,9 +134,48 @@ public static int ProcessObjects( ThrowIfArgument.IsNull(nameof(action), action); ThrowIfArgument.IsNull(nameof(match), match); + switch (objectType) + { + case ObjectType.TimedEvent: + return eventsCollection.ProcessTimedEvents(action, match, settings?.TimedEventDetectionSettings, hint.ToTimedEventProcessingHint()); + case ObjectType.Note: + return eventsCollection.ProcessNotes(action, match, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToNoteProcessingHint()); + case ObjectType.Chord: + return eventsCollection.ProcessChords(action, match, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToChordProcessingHint()); + } + return new[] { eventsCollection }.ProcessObjectsInternal(objectType, action, match, settings, hint); } + /// + /// Performs the specified action on each object contained in the . Objects + /// for processing will be selected by the specified object type. More info in the + /// Processing objects: ProcessObjects article. + /// + /// to search for objects to process. + /// Types of objects to process (for example, ObjectType.Chord | ObjectType.TimedEvent). + /// The action to perform on each object contained in the + /// . + /// Settings according to which objects should be detected and built. + /// Hint which tells the processing algorithm how it can optimize its performance. + /// The default value is . + /// + /// Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// + /// Count of processed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int ProcessObjects( this TrackChunk trackChunk, ObjectType objectType, @@ -72,9 +186,53 @@ public static int ProcessObjects( ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); ThrowIfArgument.IsNull(nameof(action), action); - return trackChunk.ProcessObjects(objectType, action, note => true, settings, hint); + switch (objectType) + { + case ObjectType.TimedEvent: + return trackChunk.ProcessTimedEvents(action, settings?.TimedEventDetectionSettings, hint.ToTimedEventProcessingHint()); + case ObjectType.Note: + return trackChunk.ProcessNotes(action, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToNoteProcessingHint()); + case ObjectType.Chord: + return trackChunk.ProcessChords(action, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToChordProcessingHint()); + } + + return trackChunk.ProcessObjects(objectType, action, obj => true, settings, hint); } + /// + /// Performs the specified action on objects contained in the . Objects + /// for processing will be selected by the specified object type and matching predicate. More info in the + /// Processing objects: ProcessObjects article. + /// + /// to search for objects to process. + /// Types of objects to process (for example, ObjectType.Chord | ObjectType.TimedEvent). + /// The action to perform on each object contained in the + /// . + /// The predicate that defines the conditions of an object to process. Predicate + /// should return true for an object to process. + /// Settings according to which objects should be detected and built. + /// Hint which tells the processing algorithm how it can optimize its performance. + /// The default value is . + /// + /// Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// + /// Count of processed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int ProcessObjects( this TrackChunk trackChunk, ObjectType objectType, @@ -86,9 +244,48 @@ public static int ProcessObjects( ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); ThrowIfArgument.IsNull(nameof(action), action); + switch (objectType) + { + case ObjectType.TimedEvent: + return trackChunk.ProcessTimedEvents(action, match, settings?.TimedEventDetectionSettings, hint.ToTimedEventProcessingHint()); + case ObjectType.Note: + return trackChunk.ProcessNotes(action, match, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToNoteProcessingHint()); + case ObjectType.Chord: + return trackChunk.ProcessChords(action, match, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToChordProcessingHint()); + } + return trackChunk.Events.ProcessObjects(objectType, action, match, settings, hint); } + /// + /// Performs the specified action on each object contained in the collection of . Objects + /// for processing will be selected by the specified object type. More info in the + /// Processing objects: ProcessObjects article. + /// + /// The collection of to search for objects to process. + /// Types of objects to process (for example, ObjectType.Chord | ObjectType.TimedEvent). + /// The action to perform on each object contained in the + /// . + /// Settings according to which objects should be detected and built. + /// Hint which tells the processing algorithm how it can optimize its performance. + /// The default value is . + /// + /// Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// + /// Count of processed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int ProcessObjects( this IEnumerable trackChunks, ObjectType objectType, @@ -99,9 +296,53 @@ public static int ProcessObjects( ThrowIfArgument.IsNull(nameof(trackChunks), trackChunks); ThrowIfArgument.IsNull(nameof(action), action); - return trackChunks.ProcessObjects(objectType, action, note => true, settings, hint); + switch (objectType) + { + case ObjectType.TimedEvent: + return trackChunks.ProcessTimedEvents(action, settings?.TimedEventDetectionSettings, hint.ToTimedEventProcessingHint()); + case ObjectType.Note: + return trackChunks.ProcessNotes(action, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToNoteProcessingHint()); + case ObjectType.Chord: + return trackChunks.ProcessChords(action, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToChordProcessingHint()); + } + + return trackChunks.ProcessObjects(objectType, action, obj => true, settings, hint); } + /// + /// Performs the specified action on objects contained in the collection of . Objects + /// for processing will be selected by the specified object type and matching predicate. More info in the + /// Processing objects: ProcessObjects article. + /// + /// The collection of to search for objects to process. + /// Types of objects to process (for example, ObjectType.Chord | ObjectType.TimedEvent). + /// The action to perform on each object contained in the + /// . + /// The predicate that defines the conditions of an object to process. Predicate + /// should return true for an object to process. + /// Settings according to which objects should be detected and built. + /// Hint which tells the processing algorithm how it can optimize its performance. + /// The default value is . + /// + /// Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// + /// Count of processed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int ProcessObjects( this IEnumerable trackChunks, ObjectType objectType, @@ -114,12 +355,51 @@ public static int ProcessObjects( ThrowIfArgument.IsNull(nameof(action), action); ThrowIfArgument.IsNull(nameof(match), match); + switch (objectType) + { + case ObjectType.TimedEvent: + return trackChunks.ProcessTimedEvents(action, match, settings?.TimedEventDetectionSettings, hint.ToTimedEventProcessingHint()); + case ObjectType.Note: + return trackChunks.ProcessNotes(action, match, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToNoteProcessingHint()); + case ObjectType.Chord: + return trackChunks.ProcessChords(action, match, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToChordProcessingHint()); + } + return trackChunks .Where(c => c != null) .Select(c => c.Events) .ProcessObjectsInternal(objectType, action, match, settings, hint); } + /// + /// Performs the specified action on each object contained in . Objects + /// for processing will be selected by the specified object type. More info in the + /// Processing objects: ProcessObjects article. + /// + /// to search for objects to process. + /// Types of objects to process (for example, ObjectType.Chord | ObjectType.TimedEvent). + /// The action to perform on each object contained in the + /// . + /// Settings according to which objects should be detected and built. + /// Hint which tells the processing algorithm how it can optimize its performance. + /// The default value is . + /// + /// Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// + /// Count of processed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int ProcessObjects( this MidiFile file, ObjectType objectType, @@ -130,9 +410,53 @@ public static int ProcessObjects( ThrowIfArgument.IsNull(nameof(file), file); ThrowIfArgument.IsNull(nameof(action), action); - return file.ProcessObjects(objectType, action, note => true, settings, hint); + switch (objectType) + { + case ObjectType.TimedEvent: + return file.ProcessTimedEvents(action, settings?.TimedEventDetectionSettings, hint.ToTimedEventProcessingHint()); + case ObjectType.Note: + return file.ProcessNotes(action, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToNoteProcessingHint()); + case ObjectType.Chord: + return file.ProcessChords(action, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToChordProcessingHint()); + } + + return file.ProcessObjects(objectType, action, obj => true, settings, hint); } + /// + /// Performs the specified action on objects contained in . Objects + /// for processing will be selected by the specified object type and matching predicate. More info in the + /// Processing objects: ProcessObjects article. + /// + /// to search for objects to process. + /// Types of objects to process (for example, ObjectType.Chord | ObjectType.TimedEvent). + /// The action to perform on each object contained in the + /// . + /// The predicate that defines the conditions of an object to process. Predicate + /// should return true for an object to process. + /// Settings according to which objects should be detected and built. + /// Hint which tells the processing algorithm how it can optimize its performance. + /// The default value is . + /// + /// Note that you can always use an object manager to + /// perform any manipulations with objects but dedicated methods of the will + /// always be faster and will consume less memory. + /// + /// Count of processed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int ProcessObjects( this MidiFile file, ObjectType objectType, @@ -145,6 +469,16 @@ public static int ProcessObjects( ThrowIfArgument.IsNull(nameof(action), action); ThrowIfArgument.IsNull(nameof(match), match); + switch (objectType) + { + case ObjectType.TimedEvent: + return file.ProcessTimedEvents(action, match, settings?.TimedEventDetectionSettings, hint.ToTimedEventProcessingHint()); + case ObjectType.Note: + return file.ProcessNotes(action, match, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToNoteProcessingHint()); + case ObjectType.Chord: + return file.ProcessChords(action, match, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings, hint.ToChordProcessingHint()); + } + return file.GetTrackChunks().ProcessObjects(objectType, action, match, settings, hint); } @@ -344,6 +678,40 @@ private static bool TryProcessChord( return true; } + private static TimedEventProcessingHint ToTimedEventProcessingHint(this ObjectProcessingHint hint) + { + var result = TimedEventProcessingHint.None; + + if (hint.HasFlag(ObjectProcessingHint.TimeOrLengthCanBeChanged)) + result |= TimedEventProcessingHint.TimeCanBeChanged; + + return result; + } + + private static NoteProcessingHint ToNoteProcessingHint(this ObjectProcessingHint hint) + { + var result = NoteProcessingHint.None; + + if (hint.HasFlag(ObjectProcessingHint.TimeOrLengthCanBeChanged)) + result |= NoteProcessingHint.TimeOrLengthCanBeChanged; + + return result; + } + + private static ChordProcessingHint ToChordProcessingHint(this ObjectProcessingHint hint) + { + var result = ChordProcessingHint.None; + + if (hint.HasFlag(ObjectProcessingHint.TimeOrLengthCanBeChanged)) + result |= ChordProcessingHint.TimeOrLengthCanBeChanged; + if (hint.HasFlag(ObjectProcessingHint.NoteTimeOrLengthCanBeChanged)) + result |= ChordProcessingHint.NoteTimeOrLengthCanBeChanged; + if (hint.HasFlag(ObjectProcessingHint.NotesCollectionCanBeChanged)) + result |= ChordProcessingHint.NotesCollectionCanBeChanged; + + return result; + } + #endregion } } diff --git a/DryWetMidi/Interaction/TimedObject/TimedObjectUtilities.RemoveObjects.cs b/DryWetMidi/Interaction/TimedObject/TimedObjectUtilities.RemoveObjects.cs index 7a1e9b1a6..271e57d65 100644 --- a/DryWetMidi/Interaction/TimedObject/TimedObjectUtilities.RemoveObjects.cs +++ b/DryWetMidi/Interaction/TimedObject/TimedObjectUtilities.RemoveObjects.cs @@ -9,6 +9,15 @@ public static partial class TimedObjectUtilities { #region Methods + /// + /// Removes all objects of the specified type from . More info in the + /// Removing objects: RemoveObjects article. + /// + /// to search for objects to remove. + /// Types of objects to remove (for example, ObjectType.Chord | ObjectType.Note). + /// Settings according to which objects should be detected and built. + /// Count of removed objects. + /// is null. public static int RemoveObjects( this EventsCollection eventsCollection, ObjectType objectType, @@ -16,9 +25,41 @@ public static int RemoveObjects( { ThrowIfArgument.IsNull(nameof(eventsCollection), eventsCollection); - return eventsCollection.RemoveObjects(objectType, note => true, settings); + switch (objectType) + { + case ObjectType.TimedEvent: + return eventsCollection.RemoveTimedEvents(); + case ObjectType.Note: + return eventsCollection.RemoveNotes(settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + case ObjectType.Chord: + return eventsCollection.RemoveChords(settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + } + + return eventsCollection.RemoveObjects(objectType, obj => true, settings); } + /// + /// Removes objects from . Objects for removing will be selected by the specified + /// object type and matching predicate. More info in the + /// Removing objects: RemoveObjects article. + /// + /// to search for objects to remove. + /// Types of objects to remove (for example, ObjectType.Chord | ObjectType.Note). + /// The predicate that defines the conditions of an object to remove. Predicate + /// should return true for an object that must be removed. + /// Settings according to which objects should be detected and built. + /// Count of removed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int RemoveObjects( this EventsCollection eventsCollection, ObjectType objectType, @@ -28,6 +69,16 @@ public static int RemoveObjects( ThrowIfArgument.IsNull(nameof(eventsCollection), eventsCollection); ThrowIfArgument.IsNull(nameof(match), match); + switch (objectType) + { + case ObjectType.TimedEvent: + return eventsCollection.RemoveTimedEvents(match, settings?.TimedEventDetectionSettings); + case ObjectType.Note: + return eventsCollection.RemoveNotes(match, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + case ObjectType.Chord: + return eventsCollection.RemoveChords(match, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + } + var objectsToRemoveCount = eventsCollection.ProcessObjects( objectType, SetObjectFlag, @@ -42,6 +93,15 @@ public static int RemoveObjects( return objectsToRemoveCount; } + /// + /// Removes all objects of the specified type from . More info in the + /// Removing objects: RemoveObjects article. + /// + /// to search for objects to remove. + /// Types of objects to remove (for example, ObjectType.Chord | ObjectType.Note). + /// Settings according to which objects should be detected and built. + /// Count of removed objects. + /// is null. public static int RemoveObjects( this TrackChunk trackChunk, ObjectType objectType, @@ -49,9 +109,41 @@ public static int RemoveObjects( { ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); - return trackChunk.RemoveObjects(objectType, note => true, settings); + switch (objectType) + { + case ObjectType.TimedEvent: + return trackChunk.RemoveTimedEvents(); + case ObjectType.Note: + return trackChunk.RemoveNotes(settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + case ObjectType.Chord: + return trackChunk.RemoveChords(settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + } + + return trackChunk.RemoveObjects(objectType, obj => true, settings); } + /// + /// Removes objects from . Objects for removing will be selected by the specified + /// object type and matching predicate. More info in the + /// Removing objects: RemoveObjects article. + /// + /// to search for objects to remove. + /// Types of objects to remove (for example, ObjectType.Chord | ObjectType.Note). + /// The predicate that defines the conditions of an object to remove. Predicate + /// should return true for an object that must be removed. + /// Settings according to which objects should be detected and built. + /// Count of removed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int RemoveObjects( this TrackChunk trackChunk, ObjectType objectType, @@ -61,9 +153,28 @@ public static int RemoveObjects( ThrowIfArgument.IsNull(nameof(trackChunk), trackChunk); ThrowIfArgument.IsNull(nameof(match), match); + switch (objectType) + { + case ObjectType.TimedEvent: + return trackChunk.RemoveTimedEvents(match, settings?.TimedEventDetectionSettings); + case ObjectType.Note: + return trackChunk.RemoveNotes(match, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + case ObjectType.Chord: + return trackChunk.RemoveChords(match, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + } + return trackChunk.Events.RemoveObjects(objectType, match, settings); } + /// + /// Removes all objects of the specified type from the collection of . More info in the + /// Removing objects: RemoveObjects article. + /// + /// The collection of to search for objects to remove. + /// Types of objects to remove (for example, ObjectType.Chord | ObjectType.Note). + /// Settings according to which objects should be detected and built. + /// Count of removed objects. + /// is null. public static int RemoveObjects( this IEnumerable trackChunks, ObjectType objectType, @@ -71,9 +182,41 @@ public static int RemoveObjects( { ThrowIfArgument.IsNull(nameof(trackChunks), trackChunks); - return trackChunks.RemoveObjects(objectType, note => true, settings); + switch (objectType) + { + case ObjectType.TimedEvent: + return trackChunks.RemoveTimedEvents(); + case ObjectType.Note: + return trackChunks.RemoveNotes(settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + case ObjectType.Chord: + return trackChunks.RemoveChords(settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + } + + return trackChunks.RemoveObjects(objectType, obj => true, settings); } + /// + /// Removes objects from the collection of . Objects for removing will be selected + /// by the specified object type and matching predicate. More info in the + /// Removing objects: RemoveObjects article. + /// + /// The collection of to search for objects to remove. + /// Types of objects to remove (for example, ObjectType.Chord | ObjectType.Note). + /// The predicate that defines the conditions of an object to remove. Predicate + /// should return true for an object that must be removed. + /// Settings according to which objects should be detected and built. + /// Count of removed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int RemoveObjects( this IEnumerable trackChunks, ObjectType objectType, @@ -83,6 +226,16 @@ public static int RemoveObjects( ThrowIfArgument.IsNull(nameof(trackChunks), trackChunks); ThrowIfArgument.IsNull(nameof(match), match); + switch (objectType) + { + case ObjectType.TimedEvent: + return trackChunks.RemoveTimedEvents(match, settings?.TimedEventDetectionSettings); + case ObjectType.Note: + return trackChunks.RemoveNotes(match, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + case ObjectType.Chord: + return trackChunks.RemoveChords(match, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + } + var objectsToRemoveCount = trackChunks.ProcessObjects( objectType, SetObjectFlag, @@ -97,6 +250,15 @@ public static int RemoveObjects( return objectsToRemoveCount; } + /// + /// Removes all objects of the specified type from the . More info in the + /// Removing objects: RemoveObjects article. + /// + /// to search for objects to remove. + /// Types of objects to remove (for example, ObjectType.Chord | ObjectType.Note). + /// Settings according to which objects should be detected and built. + /// Count of removed objects. + /// is null. public static int RemoveObjects( this MidiFile file, ObjectType objectType, @@ -104,9 +266,41 @@ public static int RemoveObjects( { ThrowIfArgument.IsNull(nameof(file), file); - return file.RemoveObjects(objectType, note => true, settings); + switch (objectType) + { + case ObjectType.TimedEvent: + return file.RemoveTimedEvents(); + case ObjectType.Note: + return file.RemoveNotes(settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + case ObjectType.Chord: + return file.RemoveChords(settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + } + + return file.RemoveObjects(objectType, obj => true, settings); } + /// + /// Removes objects from . Objects for removing will be selected + /// by the specified object type and matching predicate. More info in the + /// Removing objects: RemoveObjects article. + /// + /// to search for objects to remove. + /// Types of objects to remove (for example, ObjectType.Chord | ObjectType.Note). + /// The predicate that defines the conditions of an object to remove. Predicate + /// should return true for an object that must be removed. + /// Settings according to which objects should be detected and built. + /// Count of removed objects. + /// + /// One of the following errors occurred: + /// + /// + /// is null. + /// + /// + /// is null. + /// + /// + /// public static int RemoveObjects( this MidiFile file, ObjectType objectType, @@ -116,6 +310,16 @@ public static int RemoveObjects( ThrowIfArgument.IsNull(nameof(file), file); ThrowIfArgument.IsNull(nameof(match), match); + switch (objectType) + { + case ObjectType.TimedEvent: + return file.RemoveTimedEvents(match, settings?.TimedEventDetectionSettings); + case ObjectType.Note: + return file.RemoveNotes(match, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + case ObjectType.Chord: + return file.RemoveChords(match, settings?.ChordDetectionSettings, settings?.NoteDetectionSettings, settings?.TimedEventDetectionSettings); + } + return file.GetTrackChunks().RemoveObjects(objectType, match, settings); } diff --git a/DryWetMidi/Interaction/TimedObject/TimedObjectsManager.cs b/DryWetMidi/Interaction/TimedObject/TimedObjectsManager.cs index 7216b8fee..55d7356b4 100644 --- a/DryWetMidi/Interaction/TimedObject/TimedObjectsManager.cs +++ b/DryWetMidi/Interaction/TimedObject/TimedObjectsManager.cs @@ -60,7 +60,8 @@ public TimedObjectsManager( /// /// To start manage objects you need to get an instance of the . /// Also ManageX methods within specific object type utilities class (for example, - /// ) + /// + /// or ) /// can be used. /// /// diff --git a/DryWetMidi/Tools/Sanitizer/SanitizingSettings.cs b/DryWetMidi/Tools/Sanitizer/SanitizingSettings.cs index 470f1a96e..413a9f7eb 100644 --- a/DryWetMidi/Tools/Sanitizer/SanitizingSettings.cs +++ b/DryWetMidi/Tools/Sanitizer/SanitizingSettings.cs @@ -20,8 +20,19 @@ public sealed class SanitizingSettings /// public ITimeSpan NoteMinLength { get; set; } + /// + /// Gets or sets a minimum velocity for notes within an input file. All notes with velocity below this + /// value will be removed. The default value is zero which means notes can have any velocity. + /// property affects how notes are detected. + /// More info in the Sanitizer: NoteMinVelocity article. + /// public SevenBitNumber NoteMinVelocity { get; set; } + /// + /// Gets or sets a value indicating whether duplicated notes should be removed or not. + /// The default value is true. More info in the + /// Sanitizer: RemoveDuplicatedNotes article. + /// public bool RemoveDuplicatedNotes { get; set; } = true; /// @@ -76,6 +87,11 @@ public sealed class SanitizingSettings /// public bool RemoveDuplicatedPitchBendEvents { get; set; } = true; + /// + /// Gets or sets a value indicating whether duplicated Sequence/Track Name (see ) + /// events should be removed or not. The default value is true. More info in the + /// Sanitizer: RemoveDuplicatedSequenceTrackNameEvents article. + /// public bool RemoveDuplicatedSequenceTrackNameEvents { get; set; } = true; ///