Comments (14)
Hi @f3flight ,
Thanks for using the library! The library has clear conception of "levels of abstraction". Smf namespace contains raw MIDI structures as they are presented in a MIDI file, without any processing. Smf.Interaction provides high level objects which are useful for real tasks. So it is correct that there are only low level objects inside MidiFile
.
Note On event hasn't length as well as all other events. You need to get corresponding Note Off event and take difference between times of both events as length of the note constructed from those events. You can take a look at CreateNotes private method of the NotesManager
class which constructs notes from TimedEvent
's.
So for every TimedEvent
which wraps NoteOnEvent
you need to find corresponding TimedEvent
which wraps NoteOffEvent
with the same note's number and channel as NoteOnEvent
and take difference between times of timed events.
Probably I'll add some extension method like GetNoteOffEvent
for NoteOnEvent
or something like that. Thank you for idea and your question!
from drywetmidi.
@f3flight Did you solve your problem?
from drywetmidi.
Nope not yet, didn't touch. Well I have a code which does the trick when using MidiSharp lib, but wanted to switch to something esle bcz that guy didnt care about my pull request. You can check the only pull request in his github repo. Not the cleanest way to do things but worked ok.
from drywetmidi.
from drywetmidi.
First of all, it is wrong to add length to event. While it can be useful for some tasks it is conceptually wrong. MIDI event is just a point in time, event is momentary thing and has no length. From my practice when you start changing code to satisfy particulat task ignoring conceptions of subject domain, you'll encounter problems sooner or later with maintaining and changing of the codebase.
I can suggest you a solution that will keep things clear. First of all define some class that will attach length to TimedEvent
:
public sealed class LengthedEvent
{
public LengthedEvent(TimedEvent timedEvent)
{
TimedEvent = timedEvent;
}
public TimedEvent TimedEvent { get; }
public long? Length { get; set; }
}
Now you can process TimedEvent
s with this code:
public static IEnumerable<LengthedEvent> ProcessTimedEvents(IEnumerable<TimedEvent> timedEvents)
{
var result = new List<LengthedEvent>();
var noteOnTimedEvents = new List<TimedEvent>();
foreach (var timedEvent in timedEvents)
{
var midiEvent = timedEvent.Event;
if (midiEvent is NoteOnEvent)
{
noteOnTimedEvents.Add(timedEvent);
result.Add(new LengthedEvent(timedEvent));
continue;
}
var noteOffEvent = midiEvent as NoteOffEvent;
if (noteOffEvent != null)
{
var channel = noteOffEvent.Channel;
var noteNumber = noteOffEvent.NoteNumber;
var noteOnTimedEvent = noteOnTimedEvents.FirstOrDefault(e => IsAppropriateNoteOnTimedEvent(e, channel, noteNumber));
if (noteOnTimedEvent == null)
continue;
noteOnTimedEvents.Remove(noteOnTimedEvent);
var lengthedEvent = result.FirstOrDefault(e => e.TimedEvent == noteOnTimedEvent);
lengthedEvent.Length = timedEvent.Time - noteOnTimedEvent.Time;
continue;
}
result.Add(new LengthedEvent(timedEvent));
}
return result;
}
private static bool IsAppropriateNoteOnTimedEvent(TimedEvent timedEvent, FourBitNumber channel, SevenBitNumber noteNumber)
{
var noteOnEvent = timedEvent.Event as NoteOnEvent;
return noteOnEvent != null &&
noteOnEvent.Channel == channel &&
noteOnEvent.NoteNumber == noteNumber;
}
ProcessTimedEvents
will return original TimedEvent
s without Note Off events and with Length
set to some long
value to all TimedEvent
s that wrap NoteOnEvent
. All other events will have Length
set to null
. So to get LengthedEvent
's from a MIDI file you need to execute this code:
var midiFile = MidiFile.Read("Some MIDI file.mid");
var timedEvents = midiFile.GetTimedEvents();
var lengtehedEvents = ProcessTimedEvents(timedEvents);
Also note that DryWetMIDI provides a way to convert raw long
value to another representations. For example, to get Length
as minutes and seconds, write this:
TempoMap tempoMap = midiFile.GetTempoMap();
MetricTimeSpan metricLength = LengthConverter.ConvertTo<MetricTimeSpan>(lengthEvent.Length, lengthedEvent.TimedEvent.Time, tempoMap);
MetricTimeSpan
has properties like Minutes
and Seconds
and also can be casted to standard TimeSpan
. Visit Time and length Wiki page to learn more.
Is seems interesting to add some extension method like GetTimedEventsAndNotes
which will return IEnumerable<ITimedObject>
where element can be TimedEvent
or Note
. I'll think about implementing such method in the nearest release of the library and let you know when it is implemented :)
from drywetmidi.
OK, I've added ExtractNotes
method which is an extension method for IEnumerable<TimedEvent>
. This method returns IEnumerable<ITimedObject>
where an element can be either TimedEvent
or Note
. So all Note On/Note Off pairs will be returned as Note
objects. All other events will be returned without processing.
Usage of the method:
IEnumerable<ITimedObject> timedObjects = midiFile.GetTimedEvents().ExtractNotes();
At now the method exist only in develop branch. Nearest release will have it.
@f3flight Any news on your task?
from drywetmidi.
@melanchall thas good news, thank you! Unfortunately the folks Im trying to do my project with are lacking in enthusiasm, and i always suck at doing stuff solely by myself. If youre interested in some C# + Unity + VR programming for fun and self improvement - pm me (fb, vk, telegram - f3flight), i really need someone who will want to work together with me on this.
from drywetmidi.
I've tested this, looks like there's a bug.
Midi attached. test.zip
Debug output from a test app: https://pastebin.com/TLN4LTVY
As you can see, at around "122214 Event at 155040: Note On [0] (46, 40)" it breaks and starts throwing in TimedEvent instead of Note.
The code of the test app:
Stream stream = System.IO.FileStream("C:\\test.mid",System.IO.FileMode.Open,System.IO.FileAccess.Read);
midiFile = MidiFile.Read(stream);
IEnumerable<ITimedObject> timedObjectsAndNotes = midiFile.GetTimedEvents().ExtractNotes();
TempoMap tmap = midiFile.GetTempoMap();
foreach (ITimedObject x in timedObjectsAndNotes) {
MetricTimeSpan t = x.TimeAs<MetricTimeSpan>(tmap);
Debug.Write((t.TotalMicroseconds / 1000) + " " + x);
Melanchall.DryWetMidi.Smf.Interaction.Note note = x as Melanchall.DryWetMidi.Smf.Interaction.Note;
if (note != null) {
MetricTimeSpan l = note.LengthAs<MetricTimeSpan>(tmap);
Debug.Write(" " + note.NoteName + " " + note.Octave + " " + (l.TotalMicroseconds / 1000));
}
Debug.WriteLine("");
}
Debug.WriteLine("finished");
Another concern I have is that the absolute time in seconds is a bit off from what I got when working with this file via my own calculating functions...
Hm yes I checked with MidiEditor and it also shows a slightly longer time. drywetmidi gives 0:6:50:356 for the last event, MidiEditor shows 6.51.55 for the last NoteOn.
. So I'm pretty sure there's a small error accumulating in the algorithm. But that would be a separate issue.
from drywetmidi.
@melanchall I checked with my older code, I get 410.3503 seconds which mostly matches your 6:50:356, just 6ms diff, maybe because I'm using float in my code. So there's no issue with time calc.
Let me know if you find why ExtractNotes fails midway through this midi file, I will try to check as well if I have time.
from drywetmidi.
@melanchall I'm pretty sure the issue is due to lack of handling of some of these situations:
- note is already ringing (double-on). Assuming a missing note off event...
- note on event actually indicating note off
- note is not ringing now (double-off). Ignoring.
basically in this particular file (and probably some others in the wild), there's a mess with noteOns, so I guess that's what breaks it. Looks like 2 is covered as the file pretty much has no noteoff events, but maybe 1 is still an issue.
I did a bit of debugging and found that noteEventDescriptors starts growing after some moment, and keeps accumulating notes, up to ~5000 at the point when TimedEvent (noteOnEvent) start being yielded instead of "Note" - which happens around "Event at 155040"
from drywetmidi.
Thanks for investigation! I'm a little busy so cannot handle the issue quickly :( As for special cases:
- Yes, that can be a problem.
- It's handled already on
MidiFile.Read
. Note On s with zero velocity are treated as Note Off s by default (that behavior can be adjusted viaReadingSettings
), so there is no issues. - At now Note Off s without corresponding Note On s ignored. And... this is wrong and should be fixed. Such events should be returned as just
TimedEvent
s.
The bug is critical so I'll look into it as soon as possible. Thank you again.
from drywetmidi.
@f3flight I've found the bug and fixed it. Please take latest version and check ExtractNotes
method. Thank you for pointing out to the issue.
from drywetmidi.
@melanchall thanks I'll test! I recommend to put link to issue inside commit message, then it will be automatically shown in the thread. I'll put this for reference - c8af797
from drywetmidi.
Looks like it's fixed and works well! I'll close this issue.
from drywetmidi.
Related Issues (20)
- Can't sync midi file notes with audio file HOT 12
- HELP !!! Notes in panel to midi file?? HOT 9
- please help a noob HOT 6
- Remove all notes example from the README not working HOT 9
- Using wetdrymidi in C# project causes error CS0009
- InputDevice event listening crash HOT 2
- Crash when running in Unity on M2 MacBook HOT 3
- MidiDeviceException Internal error HOT 9
- Android Support
- Unity cannot exit after use HOT 10
- MidiClock sync problem HOT 6
- MIDI Tempo changes count returns 1 but the song has 6 tempo changes. HOT 1
- add il2cpp mode HOT 5
- CustomChunk no mididata throw error HOT 4
- CSharp Code Error HOT 2
- MIDI File returns error: InvalidMetaEventParameterValueException HOT 1
- Unable to send Note-On event to device HOT 8
- Cannot get track name to change HOT 10
- How to use DryWetMidi in Visual Basic (VS 2022) ? HOT 20
- Plans for MIDI 2.0 Support? HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from drywetmidi.