Giter Club home page Giter Club logo

Comments (14)

melanchall avatar melanchall commented on June 20, 2024

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.

melanchall avatar melanchall commented on June 20, 2024

@f3flight Did you solve your problem?

from drywetmidi.

f3flight avatar f3flight commented on June 20, 2024

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.

f3flight avatar f3flight commented on June 20, 2024

stephentoub/MidiSharp#5

from drywetmidi.

melanchall avatar melanchall commented on June 20, 2024

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 TimedEvents 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 TimedEvents without Note Off events and with Length set to some long value to all TimedEvents 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.

melanchall avatar melanchall commented on June 20, 2024

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.

f3flight avatar f3flight commented on June 20, 2024

@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.

f3flight avatar f3flight commented on June 20, 2024

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.
image. So I'm pretty sure there's a small error accumulating in the algorithm. But that would be a separate issue.

from drywetmidi.

f3flight avatar f3flight commented on June 20, 2024

@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.

f3flight avatar f3flight commented on June 20, 2024

@melanchall I'm pretty sure the issue is due to lack of handling of some of these situations:

  1. note is already ringing (double-on). Assuming a missing note off event...
  2. note on event actually indicating note off
  3. 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.

melanchall avatar melanchall commented on June 20, 2024

@f3flight

Thanks for investigation! I'm a little busy so cannot handle the issue quickly :( As for special cases:

  1. Yes, that can be a problem.
  2. 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 via ReadingSettings), so there is no issues.
  3. 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 TimedEvents.

The bug is critical so I'll look into it as soon as possible. Thank you again.

from drywetmidi.

melanchall avatar melanchall commented on June 20, 2024

@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.

f3flight avatar f3flight commented on June 20, 2024

@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.

f3flight avatar f3flight commented on June 20, 2024

Looks like it's fixed and works well! I'll close this issue.

from drywetmidi.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.