This project has moved. For the latest updates, please go here.

NAudio MIDI editing questions

Nov 12, 2013 at 1:20 AM
Hi there. I've been using NAudio for its MIDI editing capabilities and I have to say I'm surprised at how much I've accomplished with it so far.
Now i'm stuck with a few issues that I would love some assistance with, since I have tried all I can think of and have gotten nowhere.

First, let me make it clear. I am NOT using NAudio for midi playback or midi capture from a sequencer. I'm ONLY using it to read existing MIDI files, modify them, and spit them back out as MIDI files again with my modifications.

First issue / question, is there a way to change the DeltaTicksPerQuarterNote value on an existing MIDI? Some of the threads I found when researching the issue here talk about setting the value, which I imagine is during the creation of a MIDI file from scratch. I can read the DeltaTicksPerQuarterNote value, but when I try to change it, I'm told that it has no setter function and I can't change it. For my needs, if the MIDI has a DelterTicksPerQuarterNote value greater than 480, I need to change it to 480. Anyway to make this work would be appreciated.

Second issue i'm having is in reading some MIDI files. For my latest tool, which relies exclusively on NAudio, out of 22 test MIDI files, it consistently fails on the same 5. Out of a second batch of 73 test MIDI files, it consistently fails on the same 6.

So that's 11 different MIDIs I have to test that NAudio refuses to read. The errors are always the same, and there's only four kinds of errors:
"Invalid sequence number length"
"Object reference not set to an instance of an object."
"Read too far 11196+21775!=35811"
"Invalid SMPTE Offset length: Got 127, expected 5"

Note that those errors are happening at the
var dirtyMIDI = new MidiFile(file, false);
step that works consistently with the other 84 test MIDIs.

In speaking with a buddy who has extensive MIDI experience but in C, he believes at least one of those errors is a bug in NAudio because the "Invalid sequence number length" error is thrown when there is a PC event at 0 ms with the track sequence name. I edited one of the MIDI files throwing that error in a professional DAW, removed the PC events, and then tried to read the MIDI file with NAudio and it switched to the "Object reference not set to an instance of an object." error.

I don't see a way to force reading of MIDI files beyond what i've already done. By setting the strict checking variable to false I can now read MIDI files with missing / problematic NoteOff events, but I don't see how to read past these other problems.

I have spent quite a bit of time with this, so I would love some assistance with this.

Thank you!
Coordinator
Nov 12, 2013 at 4:31 PM
hi, the MIDI capabilities of NAudio were mainly developed to help me create MIDI file mapper (https://midifilemapper.codeplex.com/) so it may be of benefit for you to look and see how the code works there. It has been quite a few years since I did anything with this though, so it's not very fresh in my mind.

As for the error, it is quite possible that there is a MIDI event that NAudio is not properly reading. It might be a good idea for you to take a copy of the NAudio source, and you can step through the code to see what is going on (easiest way might be to debug using AudioFileInspector). If you can provide example MIDI files, I might be able to test myself.

NAudio lets you use whatever DeltaTicksPerQuarterNote when you create a MIDI file. You'd just need to make sure that you update the deltas for each note to reflect your new value.
Nov 12, 2013 at 7:09 PM
Thanks for the reply.

I'll try taking a look at the source code and see if I get anywhere with that.

In the meantime, here's an archive with the 5 problem midis and a text file with their respective error codes thrown by NAudio. This is what I had sent my C buddy I mentioned above. If you can figure out what's going on before I post a reply on here i'd love to hear it.

http://www.keepitfishy.com/rb3/problemmidis.rar

Thank you again.
Nov 12, 2013 at 10:05 PM
Edited Nov 12, 2013 at 10:08 PM
Ok. I got the source code, gutted it, cleaned it, and integrated it right into my program. I can now get the same exact results I was getting before with the code in my project rather than using the compiled DLL, so I can start testing things.

First, the "Object reference not set to an instance of an object" was an error being generated by my own code because I was assuming that all MIDI events with MidiCommandCode NoteOn would be a NoteOn event. I stepped through the code on one of the MIDI files throwing that error, and it turns out the library is reading NoteOff events as NoteOn events, so when my code was comparing the NoteLength value, it was throwing a null exception because it didn't have it...

Image

As you can see from the screenshot, that should not be happening. Since I realized this was causing the problem, I made sure that any time my code called the NoteLength value, that the event in question had a velocity greater than 0. This effectively removed the "Object reference not set to an instance of an object" and my program was able to finish doing its thing.

Then I tried opening the new MIDI file again, and got a different error: "Note number must be in the range 0-127" ... in stepping through the code, that MIDI that had just been processed had events with note numbers beyond 127, at least one was 131. Nowhere in my code do I set a note value that high, and my understanding is that NAudio doesn't allow it either. If I change the code to instead of throwing the exception, to change the NoteNumber to 127, then the error changes to "Read too far 11196+21775!=35811" ... so that wasn't a good solution to anything.

"Invalid SMPTE Offset length: Got 127, expected 5" error is thrown by SmpteOffsetEvent.cs whenever the length is different than 5. For some reason these MIDIs have values of 0 and 127 respectively. I'm assuming this is a byproduct of erroneous binary reading?

Lastly, "Invalid sequence number length" is thrown by TrackSequenceNumberEvent.cs if the length is not 2. The problem MIDIs i'm testing with have a value of 0, which your code is annotated with
// TODO: there is a form of the TrackSequenceNumberEvent that
// has a length of zero
I'm hoping you could tell me how I need to change that line of code to account for a value of 0 instead of always reading as it does now assuming a value of 2. By just changing the code to accept either 0 or 2, the error code changes from "Invalid sequence number length" to "Invalid SMPTE Offset length: Got 0, expected 5"

Anyways, that's enough rambling. As you can see I'm determined to get this to work and would appreciate some help. Now that I have the source code i'll contact my C buddy to see if he can suggest how I can fix this in case you can't get around to looking at all this.

Thanks in advance.
Coordinator
Nov 13, 2013 at 2:37 PM
I've had a very quick look. It seems that the problem is getting an unrecognised type of MetaEvent. It happens to also be a "running" event following a text meta event in the example I looked at, so perhaps there is some quirk of the MIDI spec I am overlooking in that case.
Nov 13, 2013 at 2:51 PM
Yeah my C buddy was telling me about running events. I added a catch to my code to see just how many times it was happening. On the MIDIs that I can parse and process fine, there are 0 instances...whereas in the problem MIDI I've been trying to work this out with, it had 9967 instances of a NoteOn event with velocity 0...

At this point I think it's something at a library level that needs addressing, and I don't expect you to drop what you're doing and fix it. So i'll keep a look out for a future update to NAudio and hope that these issues are worked out by then.

Thanks for your time!
Coordinator
Nov 13, 2013 at 3:09 PM
yes, I guess it's possible that I never actually got running events going because none of my test files used them. There is a little bit of code in there that tries to handle them, but I don't know if it ever got exercised or not.
Nov 13, 2013 at 3:14 PM
Edited Nov 13, 2013 at 3:15 PM
What about this part of the problem? It seems like it may be an easy fix for someone who understands what and why the library is doing as it reads the binary of the MIDI file.

// TODO: there is a form of the TrackSequenceNumberEvent that
// has a length of zero
if(length != 2)
{
throw new FormatException("Invalid sequence number length");
}
sequenceNumber = (ushort) ((br.ReadByte() << 8) + br.ReadByte());

Even being able to read MIDIs with a value of 0 would help reduce the "problem" MIDIs that I can't support with my tool.
Coordinator
Nov 13, 2013 at 3:17 PM
possibly, although it might be that your running event problem means that this isn't a TrackSequenceNumberEvent at all, and we're just reading nonsense out the file at this point. Are there some MIDI files you have where making only this change fixes them completely?
Nov 13, 2013 at 3:21 PM
I do not. All my problem test midis are in that file I linked to earlier. The reason we know it's down to this library is because my buddy can process them on his program, which is coded in C and I don't know what library he uses, may even be his own. And I can and have opened and edited those "problem" midis in REAPER, which is one of the DAWs we work with with no problem. They're not "corrupt" per se, but something in their construction is throwing NAudio off.

The two midis that have the problem with the tracksequencenumberevent being a value of 0 I have never been able to "fix" because just by allowing 0 and doing the same reading your code does when the value is 2 then causes problems, and i'm assuming that is because the binary sequence that must be read when it's a value of 0 is different.
Coordinator
Nov 13, 2013 at 3:28 PM
there's an article here http://home.roadrunner.com/~jgglatt/tech/midispec/run.htm that might be useful. The strange thing about the one test file I looked at was that the running message seemed to come straight after a text event, which ought not to be possible, since meta events cancel running status. So might need to dig a bit deeper to work out what is going wrong
Nov 13, 2013 at 5:05 PM
I've been working with trojannemo on this issue. That article mentions Sysex messages (events 0xF0 and 0xF7) cancel running status, but clarifies that meta events have no effect on a running status in progress. When I originally wrote my own MIDI parser from scratch I had made the same assumption that meta events would end a running status and would run into occasional trouble with some of these MIDI files. This was probably because other online information about running status have some misinformation about this detail. All I had to do to fix this was to keep a variable tracking the event type of the last normal MIDI event, and update that each time a normal MIDI event was parsed. This could explain some of the strange parsing behavior, where NAudio would identify malformed instances of events that don't actually exist in the MIDI file.
Nov 13, 2013 at 5:19 PM
Edited Nov 13, 2013 at 5:20 PM
I have little experience with object oriented programming, but it seems ReadNextEvent() is parsing running status events by fetching previous.CommandCode, and I'm guessing that variable is allowed to be updated even in instances when it shouldn't be (such as when a meta event is parsed). If you don't alter this variable except for when parsing regular MIDI events, or if you use a separate variable that is conditionally updated based on what type of event is being parsed, it seems like it would match my parser's handling of meta events interleaved with running status events.
Coordinator
Nov 14, 2013 at 2:52 PM
thanks raynebc, I'll give that a try next time I get a chance to work on this. I think part of the reason the running event handling is flakey in NAudio is that I never encountered it. Most sequencers don't use it when saving out MIDI files.
Nov 14, 2013 at 5:33 PM
So I got some news.

I was testing things and realized along the way I broke something. So I went back to using the compiled library instead of the source code.
With my new code in place to avoid running into the pitfalls of identifying a running even NoteOff as a NoteOn and asking for its NoteLength, two of my problem MIDIs with that issue are now fixed.

So while perhaps you may want to update the library to avoid throwing a null error in those instances, it can be worked around by the user code, as I did.

Still got the following issues that I can't work around, but this is at least an improvement.

"Invalid sequence number length" ----- this is the whole sequence number length being 0 instead of 2 that you have marked as TO DO in the code
"Read too far 11196+21775!=35811"
"Invalid SMPTE Offset length: Got 127, expected 5"

Thanks for the continued responses. Very much appreciated.
Coordinator
Nov 18, 2013 at 11:09 AM
thanks for the information. I'm not actively working on the MIDI code at the moment, but I've bookmarked this discussion for next time I am.
Jan 14, 2014 at 8:01 PM
Hey torjannemo / markheath:

I want to read events from a MIDI file and make my game characters jump/run/dance/animate to beats/notes so it looks like game objects are synchronized with music. Since this is the only thread that gives me some hope that its possible, I wanted to know if you could point me in the right direction. Where should I start? The scope of what I want to do is probably very limited as compared to what NAudio provides. Basically I will be playing a music file (mp3/wav) and reading MIDI file to direct animations/actions in the game. No interfacing with hardware or any magic of that sort. Maybe I dont even need the NAudio library for this but just the part that reads a MIDI file and gives me information on events/notes in the file at specified music-time on the timeline.
Coordinator
Jan 15, 2014 at 10:55 AM
NAudio can read out MIDI events and their timing information. Have a look at the AudioFileInspector source code that comes with the NAudio code, and see how that displays information about a MIDI file.
Dec 31, 2014 at 7:49 AM
Mark,

I wanted to point out something. I've continued to use NAudio all this time (thanks for the great tool), and even tonight was working on fixing something that's been bugging me. In REAPER I could see the tempo events were double values, but the string value of the tempo events being returned by NAudio when it read the MIDI file only displayed the integer value. This means small variations in timing, minimal for regular midi usage but problematic for rhythm game uses. It also made my friend's fixed 197 BPM file report as 196 BPM. I got your source code and just changed the values in TempoEvent.cs from int to double, and bam, much more accurate results with a simple fix. Maybe something to consider for your next revision, if one is forthcoming.

If there's a reason you were going with the int value and not double, please let me know if I've opened myself up for failure by doing this. So far nothing seems broken and results seem more accurate. But I know very little about MIDI specs.
Dec 31, 2014 at 5:41 PM
Integer is definitely not accurate enough to store a value in beats per minute. The only suitable way the tempo can be stored as an integer is as the original microseconds per quarter note value, which is how it is encoded in the MIDI file. I don't know if NAudio makes any assumptions for the BPM variable having been rounded to an int value though.
Dec 31, 2014 at 5:51 PM
Edited Dec 31, 2014 at 5:51 PM
The original relevant code was:

private int microsecondsPerQuarterNote;

public double Tempo
{
get { return (60000000.0/microsecondsPerQuarterNote); }
set { microsecondsPerQuarterNote = (int) (60000000.0/value); }
}
my changes:
private double microsecondsPerQuarterNote;

public double Tempo
{
get { return (60000000.0/microsecondsPerQuarterNote); }
set { microsecondsPerQuarterNote = 60000000.0/value; }
}
Seems to be working as intended and more accurately than before. Any reason to worry about this change?
Coordinator
Jan 9, 2015 at 10:22 PM
what other changes did you make to get it to compile? Ultimately microsecondsPerQuarterNote is stored as a three byte integer value in a MIDI event, so when it is written to a file or sent over a network, it can't have a fractional part.

We're you actually just meaning the ToString method doesn't show fractions? That can be fixed quite easily without any changes to microsecondsPerQuarterNote