# DryWetMIDI: reading, writing and playing MIDI data in .NET

> DryWetMIDI is a .NET library for MIDI data and MIDI devices, split into a full package and a nativeless one, with a high-level note model on top of raw events, exception classes for every file error, and tools for quantizing, splitting and CSV export. The current line is 8.x, with v8.0.3 published in December 2025.

**melanchall/drywetmidi** — Swiss knife for everything MIDI related

- Repository: https://github.com/melanchall/drywetmidi
- Website: https://melanchall.github.io/drywetmidi
- Stars: 704 · Forks: 83
- Language: C#
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/melanchall-drywetmidi

## Two NuGet packages decide whether you take a native dependency

DryWetMIDI ships as a pair of packages and the choice between them is a deployment decision, not a feature one. The full package is Melanchall.DryWetMidi and the variant without native code is Melanchall.DryWetMidi.Nativeless, and the project publishes both to NuGet and both to the Unity Asset Store at version v8.0.3. The repository root shows where the native part lives: a separate targets file, Melanchall.DryWetMidi.Native.targets, sits next to the solution and the main library directory.

That split exists because the library covers two rather different jobs. Working with MIDI files is pure data handling and needs nothing from the operating system, while sending events to a MIDI output device and receiving from an input device means talking to a native MIDI API. The nativeless package is the one you want in environments where native libraries are awkward: a web assembly target, a locked down server, a platform the native build does not cover.

The default branch is develop rather than master, and the source sits under DryWetMidi with a separate DryWetMidi.Tests project and a Docs directory, so the documentation is part of the repository rather than a separate site that can drift.

One practical warning from the project itself: if you plan to open an issue or a discussion, the README asks you to read the Support article first. That is a small signal about how the project handles incoming reports.

## Every file error gets its own exception class

The reading and writing side of the library is built around Standard MIDI Files, with RMID support as well, where an SMF file is wrapped in a RIFF chunk. The notable design decision is error granularity. Instead of a general failure, all possible errors in a file are presented as separate exception classes, so a caller can catch exactly the problem it knows how to deal with.

That choice pays off in the situations that actually break in production. The documentation states that you can read corrupted files and repair them, and that you can build MIDI file validators. A validator is only practical if it can tell you which rule was violated, and one exception type for the whole file would force a developer to parse error strings or re-implement the parsing to work out the cause. With per error classes, a repair pass can look for a specific failure and patch that specific thing.

Custom meta events and custom chunks are also supported and can be written to and read back from files, so a private data format can travel inside a MIDI file without the library treating it as an unknown chunk to discard.

The RMID path is worth calling out separately because it is the kind of thing that is either supported or is not. A file that wraps an SMF inside a RIFF container reads through the same file reading surface, which means a tool that ingests RMID collections does not need a second parser.

## The same MIDI data has a low level and a high level view

The central idea in the API is that MIDI data can be handled at two altitudes. At the bottom there are low level objects such as a MIDI event, and at the top there are high level ones such as a note, and the two use different representations of time and length. A note on an off pair are separate events in the raw stream, and a note is the interval they describe.

The two views also differ in how time is expressed. MIDI files store delta times, which are relative and depend on the preceding event, so a raw timestamp tells you very little on its own. The high level model works with a tempo map, and durations can be read in metric form, which is hours, minutes and seconds, or in ticks, or in the other representations the library provides.

Here is the shape of the first example in the documentation, which reads a file, builds its tempo map, walks the notes and prints each one with its time and length in metric form:

```csharp
var midiFile = MidiFile.Read("MyFile.mid");
var tempoMap = midiFile.GetTempoMap();
 
foreach (var note in midiFile.GetNotes())
{
    var time = note.TimeAs<MetricTimeSpan>(tempoMap);
    var length = note.LengthAs<MetricTimeSpan>(tempoMap);
    Console.WriteLine($"{note} at {time} with length of {length}");
```

The tempo map is what makes the metric conversion correct, because a file can change tempo part way through, and a note at tick 4096 means different wall clock times in different files. Building the map once and passing it into the conversion is the pattern the example teaches, and it is worth following rather than converting each note in isolation.

## Pattern builds music in code instead of a piano roll

The composing side of the library is the Pattern page in the documentation, and it exists because generating MIDI by hand is tedious. A pattern describes musical content in code, so a melody or a drum figure is a sequence of statements rather than a file you assemble with a tool. That is the difference in approach between DryWetMIDI and a general purpose MIDI file writer: this one treats composition as something a program can do at runtime.

The same code-first thinking shows up in the music theory API, which the documentation presents as its own overview article. Music theory in code means intervals, scales and progressions are values you compute with rather than shapes you draw.

The Tools page collects the operations that come up in practice: quantizing, splitting notes, and converting a MIDI file to a CSV representation. Quantizing is the one every generator needs, because a programmatically produced note lands on an arbitrary tick and sounds wrong against a grid unless it is snapped. Splitting notes is the counterpart, cutting long notes into shorter ones.

The CSV export is the feature that tells you something about the intended audience. MIDI data is awkward to inspect by eye, and a CSV that you can open in a spreadsheet, diff, and check by hand is how you check that a transformation did what you meant. For a library used to build tools rather than to play a single file, that kind of inspection path matters more than a nicer playback API.

## Devices, playback and recording sit on top of the same event model

The device half of the library is where the native dependency comes back. You can send MIDI events to an output device and receive events from an input device, and the documentation has separate articles for each. Playback and recording are built on that: you can play MIDI data and record it, with a separate overview page for each.

The design point worth understanding is that device traffic and file data share the same event vocabulary. A message that is read from a file can be sent to a port, and a message captured from a keyboard can be written into a file, because the underlying object is the same MIDI event type. That is what makes a library useful for a MIDI processing utility that has to do both, rather than being a file format reader with a thin device layer bolted on.

The alternative approaches divide cleanly into two camps, and neither matches this. A hand written parser gives you full control over bytes and no dependency, but you write chunk iteration, delta time accumulation and tempo handling yourself, and a file that is almost valid will crash your code instead of telling you why. A platform specific MIDI API gives you hardware access with excellent timing and no file handling at all, and the platform API is also a different object model on every operating system, so anything you build on it does not travel.

DryWetMIDI sits in the middle: a portable .NET API, one exception type per failure, and a file model rich enough that you can repair and extend a file rather than only read it. What it does not claim is anything about realtime scheduling or timing jitter, and the documentation does not describe timing guarantees for output.

## Version 8.x is current, with a support policy and a public survey

Maintenance looks healthy for a library with a long history. The last push was on 2026-10-19, and the recent releases are v8.0.3 from 2025-12-16, v8.0.2 from 2025-07-31 and v8.0.1 from 2025-06-23. That cadence, three point releases across the middle and end of 2025 plus work continuing afterwards, is consistent with an actively maintained library rather than an archived one.

The licence is MIT, which keeps the commercial question simple for a library that ships inside other products, and the README asks projects that use it to link back in their about section.

Adoption is visible in the form of a list of projects using the library, which includes a rhythm game, a sound to light controller for DMX lighting, a MIDI controller extension, virtual instruments with real MIDI output, a collaborative music tool and an app that uses MIDI both as a note source and as a chaotic delay. Reading that list tells you more about the library than the feature bullets do. These are applications that need to both read and write musical data and talk to hardware, and they are the kind of project that breaks if the file model and the device model do not fit together.

The project also runs a public survey asking for feedback on how to improve the library, and it is linked from an important notice at the top of the README. An open survey is a sign that the maintainer wants direction on the roadmap, and it is a reasonable place to vote if a feature you need is missing.

## Conclusion

Adopt DryWetMIDI if you are building on .NET and need to read, repair, transform or emit Standard MIDI Files, or to talk to hardware MIDI ports, and you value an API that lets you work at note level instead of hand parsing chunks. Do not adopt it if your project must target an environment where the native package will not load, or where you need realtime priority scheduling, since the library offers device output and input but the documentation does not describe timing guarantees. Before committing, install Melanchall.DryWetMidi.Nativeless rather than the full package, run the file reading example against a real MIDI file, and read the Support article the project asks every issue reporter to read first.

## FAQ

### Which DryWetMIDI package should I install?

Melanchall.DryWetMidi is the full package and Melanchall.DryWetMidi.Nativeless is the variant that carries no native code, so the nativeless one suits environments where the native MIDI dependency cannot be loaded. Both are on NuGet and in the Unity Asset Store.

### Can DryWetMIDI read a corrupted MIDI file?

Yes. The documentation states that the reading and writing process can be adjusted so that corrupted files can be read and repaired, and that you can build MIDI file validators, which is easier because every possible error in a file is reported as its own exception class.

### How do I get note times in seconds instead of ticks in DryWetMIDI?

Build a tempo map from the file with GetTempoMap and pass it to the conversion, then read the time and length with TimeAs and LengthAs for a MetricTimeSpan. The tempo map is what keeps the conversion correct when a file changes tempo part way through.

### Does DryWetMIDI work with MIDI hardware devices?

Yes, the library can send events to MIDI output devices and receive events from input devices, and it can play and record MIDI data. The nativeless package omits the native code, so pick the full package when you need device access.

### What is the latest DryWetMIDI version?

The recent releases are v8.0.3 from 2025-12-16, v8.0.2 from 2025-07-31 and v8.0.1 from 2025-06-23, and the last push to the repository was on 2026-10-19.

## Sources

- [License: MIT](https://github.com/melanchall/drywetmidi/blob/develop/LICENSE)
- [melanchall/drywetmidi on GitHub](https://github.com/melanchall/drywetmidi)
- [Project website](https://melanchall.github.io/drywetmidi)
- [README](https://github.com/melanchall/drywetmidi/blob/develop/README.md)
- [Releases](https://github.com/melanchall/drywetmidi/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/melanchall-drywetmidi
