Giter Club home page Giter Club logo

Comments (11)

tdhsmith avatar tdhsmith commented on June 17, 2024

Well the key to iteration is this bit from the docs:

A MIDI file is represented as a hierarchical set of objects. At the top is a Pattern, which contains a list of Tracks, and a Track is is a list of MIDI Events.

So after importing you're going to have a loop something like this:

pattern = midi.read_midifile('...')
for track in pattern:
    for event in track:
        if isinstance(event, midi.NoteEvent):
            # some action for note events...

Changing the pitch up or down by a half step is as simple as event.pitch += 1 or event.pitch -= 1 (assuming you have checked it is a NoteEvent as above; other event types won't have a pitch property). However you're going to need some logic to find the NoteOffEvent associated with each NoteOnEvent you modify in order to avoid issues where you're turning off a note of a different pitch and leaving your new pitch playing.

If you don't need both the modified and original tracks in one file, it will be easier to just modify the events in place and then write the original pattern object to a new file, rather than bothering to make entirely new pattern and track objects.

I don't think there are any sort of wrapper/callback options here (unless they've been added recently). But it wouldn't be terribly hard to write your own helper function. The events are sequential, and you can convert directly from ticks to beats, so figuring out what beat you're at is pretty easy if you're in absolute tick mode. (I may be wrong here, but I think the MIDI reader always makes Patterns in relative tick mode. There is a helper function Pattern.make_ticks_abs())

from python-midi.

tleyden avatar tleyden commented on June 17, 2024

Thanks @tdhsmith for the help! This is really useful. I have a few follow up questions below.

However you're going to need some logic to find the NoteOffEvent associated with each NoteOnEvent you modify in order to avoid issues where you're turning off a note of a different pitch and leaving your new pitch playing.

Hmmm.. not quite sure I follow this. Can you give a concrete example? By the way I'm not planning to "turn off" any notes, just change their pitch up or down a half-step.

it will be easier to just modify the events in place and then write the original pattern object to a new file

Sounds like good advice, I'll take that approach!

so figuring out what beat you're at is pretty easy if you're in absolute tick mode.

I don't understand the relationship between ticks and beats. Can you explain it or point me to some docs?

from python-midi.

tdhsmith avatar tdhsmith commented on June 17, 2024

Hmmm.. not quite sure I follow this. Can you give a concrete example? By the way I'm not planning to "turn off" any notes, just change their pitch up or down a half-step.

Well MIDI is designed primarily as a real-time communication protocol, so it doesn't transmit the "length" or "duration" of a note (because in real-time it doesn't know how long you're going to hold it for). Instead it breaks note signals into note-on and note-off events. Most instruments will keep playing every note-on they receive until they get a corresponding note-off with the same track, channel, & pitch values.

So if you are moving along the midi file, and you change the pitch of a NoteOnEvent, it will no longer be on the same pitch as the signal that is supposed to turn the sound off, and all of your note durations will go haywire.

I'm not quite sure how I would handle this situation myself. The easiest (but not efficient) method that comes to mind right now would be to make a helper function findMatchingNoteOff(track, noteOn) that scanned for the first instance in track of a NoteOffEvent on with the same channel and pitch as noteOn, and occurring after its absolute tick value. Then you can use that function to modify the noteOff in the same manner that you modified the noteOn.

If you need efficiency too (since this is going to scan the whole track again for every note), you may have to rethink your iterator structure and maybe use enumerate and indices so you can start your search at the right location.

Of course the whole thing would probably be better handled by a library that handles notes from a musical standpoint, but that introduces a lot of complexity and this project seems to aim for reproducing MIDI and not a whole musical notation system.

from python-midi.

tdhsmith avatar tdhsmith commented on June 17, 2024

I should note some special cases:

  • My statement about note length goes for most instruments. Instruments playing fixed-length samples or using decay will stop producing sound regardless of whether the MIDI note continues. There may be other situations where the system cuts off a note regardless of a signal (i.e. hitting an instrument's polyphony limit or receiving an override from another logic). Sometimes people take shortcuts and might not use note-offs in these cases. About the only place I've ever seen it is in percussion tracks, but it's something to be aware of.
  • Some software chooses not to use note-offs anywhere and instead transmits another note-on with velocity 0. I think this is obnoxious, but it is fairly common in the files I've dealt with. This is something you may have to incorporate into your helper function. Apparently I already wrote about this, but totally forgot.
  • Moving notes around like this is fine and good for simple files where each voice takes its own track or channel, but be aware of how you can make really weird situations by modifying notes non-uniformly. You may cause errors by colliding notes that used to be on different pitches.
before:
C>  *  *  ON -  OFF *  *
B>  *  ON -  OFF *  *  *

after moving B up half step:
C> *  ON ON OFF OFF *  *

It's hard to say what this will sound like on a particular instrument, or what you'd even want to happen in this situation. And it would certainly take a rather complex solution to address it cleanly. The best solution in my mind is to try to adjust your system to avoid the situation entirely, and if it's necessary in a few spots, just fix them by hand later. 😉

from python-midi.

tdhsmith avatar tdhsmith commented on June 17, 2024

Oh and there's a bit about ticks in the main readme. I think just dividing ticks by resolution will get you the current beat number, but I always have to test it a few ways before I remember and feel confident haha.

from python-midi.

tleyden avatar tleyden commented on June 17, 2024

The easiest (but not efficient) method that comes to mind right now would be to make a helper function findMatchingNoteOff(track, noteOn) that scanned for the first instance in track of a NoteOffEvent on with the same channel and pitch as noteOn, and occurring after its absolute tick value

Ok, that makes sense. Also, the caveats you mentioned about:

  • missing NoteOff events
  • NoteOn events with velocity 0 instead of NoteOff

also make sense, and seem like they won't be too much of an issue.

But I got lost here:

Moving notes around like this is fine and good for simple files where each voice takes its own track or channel, but be aware of how you can make really weird situations by modifying notes non-uniformly.

When you say "non-uniformly", do you mean in the context of a single track, making a mistake where the wrong NoteOff (or NoteOn with velocity 0) had it's pitch adjusted?

I read this description of midi tracks vs channels and think I have a grasp on it, ut I'm confused about what you mean by "voice" here. Maybe an example would be useful. Is this midi file a "simple file" that you mentioned? What about this one, which seems to have a left and a right track for each channel? Since I can hand pick the input files for this application, as long as I know how to only pick "simple" files, I think I can avoid the issues you mentioned.

before:
C>  *  *  ON -  OFF *  *
B>  *  ON -  OFF *  *  *

after moving B up half step:
C> *  ON ON OFF OFF *  *

^^ Scratching my head on this one .. not really sure what this is depicting exactly.

The best solution in my mind is to try to adjust your system to avoid the situation entirely

I definitely don't want to be doing any hand-fixing, so if you can provide more details on how to avoid the situation, that would be helpful.

from python-midi.

tdhsmith avatar tdhsmith commented on June 17, 2024

Sorry I was intentionally using the terms vaguely. I didn't mean to draw you into the precise definitions of channel/track/voice. I still confuse myself there. 😓

When you say "non-uniformly", do you mean in the context of a single track, making a mistake where the wrong NoteOff (or NoteOn with velocity 0) had it's pitch adjusted?

A little broader than that. I'm using uniform editing to mean a change applied to an entire MIDI unit at once (like a track). So non-uniform is any edit where you define your own 'chunks' (like measures, or time durations, or certain pitches or event types).

I bring it up because you could be matching the on-off pairs correctly and still run into "collisions" where you now have pitches overlapping, or other kinds of event cues in places they don't belong.

^^ Scratching my head on this one .. not really sure what this is depicting exactly.

Hypothetically, say you're changing all the A's in a track to C's. Then this situation is hard to resolve:

Music notation for staggered chord of an A&C where both notes are held for two beats, but the C starts a beat after the A does

Afterward you should only have C's in this measure, right? But the ordering of MIDI events will be bad, because first the 'old A' turns on, then the 'old C' does, then the 'old A' turns off, then the 'old C' turns off. So in that one pitch line, your instrument will see the sequence On On Off Off. This doesn't really make sense to it because it doesn't have a concept of multiple voices within a single pitch in a single track. The first Off will turn "both" off, and you'll probably end up with only 2 beats of sound instead of 3.

So the results of collisions can be hard to predict (and software might interpret the file different than you expect).

Ultimately all I was trying to get at is to be aware that the "boundaries" between what you edit and what you don't can cause unexpected behavior. You're working with pure MIDI data, so it's up to you to handle any higher-level "musical logic" about how notes and their properties might interact.

from python-midi.

tdhsmith avatar tdhsmith commented on June 17, 2024

Really though, even if you end up having these collisions, I'd guess that most instruments & software would handle it fine and you wouldn't even notice the difference. And since your project involves moving measure blocks by pitch, I'd really only expect to encounter pitch overlaps like in the example I gave, which have pretty innocuous side effects.

from python-midi.

tleyden avatar tleyden commented on June 17, 2024

But the ordering of MIDI events will be bad, because first the 'old A' turns on, then the 'old C' does, then the 'old A' turns off, then the 'old C' turns off.

Aha, this makes sense now! I'm not going to worry about that "edge case" in the first pass, but it's good to know about in case I get unexpected results. Thanks again for taking the time! I'm planning to send some Pull Requests for any helper code I write that feels like it might be reusable.

from python-midi.

tleyden avatar tleyden commented on June 17, 2024

Changing the pitch up or down by a half step is as simple as event.pitch += 1 or event.pitch -= 1

What are the boundary conditions here? Eg, what is the minimum pitch value and the maximum pitch value? (0-128?)

from python-midi.

vishnubob avatar vishnubob commented on June 17, 2024

Correct, most data fields within MIDI are 7-bit. The high-order bit is reserved as a flag to indicate variable length data (such as SysEx messages, delta time values, etc). The MIDI standard was drafted in 1983 and many of its short-comings are an artifact of its age. When writing code to manipulate MIDI, it's helpful to read up on the MIDI file format and specification.

Wikipedia
MIDI file format
MIDI Specification

from python-midi.

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.