bspaans / python-mingus

Mingus is a music package for Python
GNU General Public License v3.0
859 stars 167 forks source link

Notes spanning across bars not possible #34

Open lrbigdata opened 8 years ago

lrbigdata commented 8 years ago

From what I can tell, it is not possible to create notes that span across bars. This occurs often in real music compositions, so I find it strange that this library has this limitation. I am going to try to use a different library for generating MIDI files because of this limitation. I would make the changes myself and open a PR but the project I'm working on has a super short timeline.

Please correct me if this is not the case.

lrbigdata commented 8 years ago

How hard would it be to add support for ties?

b1a0 commented 4 years ago

I know this is very old, but I wanted to leave this here, in case someone else has the problem: I think that you are right and this is not possible, but I realized that for my purposes (MIDI file sound generation) it can actually be done by using polyphony with different meters. If you, for example, want to have a melody for two (4,4) bars and a base note that plays through all of these 8 notes, generate one track with two (4,4) bars and one track with one (2,1) bar that contains just one note. In this way, all compositions are possible. I have not tried exporting this to a sheet music program, but from the sound perspective it does what it should do.

jkeesh commented 4 years ago

Hi - I found this library to be really helpful, thank you for making it! I was also running into this problem and couldn't figure out how to have a note span across bars through the docs, and finding this issue it seems like it is not possible.

I also don't yet understand the midi file format enough to see how to adjust this. What kind of syntax/notation would be needed in this library to support it? Would it just be that a Bar needs to know that it is starting with a note from the previous Bar? I'm also not sure how this is then encoded in the midi file generation using the write_Track method.

It seems the prior comment suggests one solution, but I'm wondering how this could be handled generally, or is handled in other music libraries. This already handles a lot of it

If I have a Track t, and

    result = t.add_notes(note, duration=duration)

returns False - it seems it won't add the note because it doesn't fit into the Bar. It seems you could add a parameter to force-add it ... or say you want to add it and it can just start in the new Bar

Anyways curious if people have ideas on how this can be done or what changes are needed

jkeesh commented 4 years ago

My current hack/workaround for small snippets is that I can change the time signature to something like 100/4 so that the "Bar" doesn't run out. This effectively does generate some MIDI files but does not yet work as a full workaround for setting up the actual bars or real time signature with a tie across measures

b1a0 commented 4 years ago

I'm not a developer for this library, but I also was thinking about this problem. I described my hack/workaround above, this can generate what you would need as sound (also for long snippets), but would probably still be a problem if you wanted to export this to sheet music - since it is unfortunately not "correct" music notation. But let's brainstorm :)

I had a quick look and I am not sure that the change would have to be very drastic. It could probably even suffice to give Bar an additional boolean attribute is_continued, which tells us, whether the last note of this bar should be considered to be continued in the next bar and defaults to False, for backwards compatibility. This attribute could then be used, while looping through bars (in MIDI generation and sheet music generation).

In play_Track, we normally start with self.delay = 0, and then continuously update this delay when we add bars. Maybe we could just do something with this attribute in this function, for example not stopping the playing of the NoteContainer - details would have to be thought through of course.

Let me know what you think.

jkeesh commented 4 years ago

One question that I was wondering here was how does LilyPond handle notating ties across measures? I hadn't done an in depth search but I'm not sure if they just automatically figure that out if a note doesn't fit.

Party I wanted to be able to abstract away the bars so I could just add a note and it would do whatever is needed. It seems that this idea of an is_continued field on Bar would work. Then, yes it just needs to be handled in the midi file generation. It seems if you are playing a note you always add a note_off attribute on midi to end it, and midi files themselves don't really have notions of bars, just time signatures. Basically it seems the fix on midi generation would be to just not turn off the note in the first measure, and then not start the note in the second measure. So maybe is_continued on Bar and is_continuation on the next Bar? Or the Bar can inspect the previous bar?

For example (q is just a quarter note)

Bar b1:  | q q q q4...|
Bar b2: |..q5 q q q |

b1.is_continued = True
b1.is_continuation = False

b2.is_continued = False
b2.is_continuation = True

The only notes that need to be generated differently are q4 and q5, which instead of q4 then q5 would just be a new q6 for the duration of the two notes.

The key method seems to be here: https://github.com/bspaans/python-mingus/blob/master/mingus/midi/midi_track.py#L87 in play_Bar.

It seems the changes would be something like:

https://github.com/bspaans/python-mingus/blob/master/mingus/midi/midi_track.py#L105

if not self.is_continuation:
  self.play_NoteContainer(x[2])

self.set_deltatime(self.int_to_varbyte(tick))

if not self.is_continued:
  self.stop_NoteContainer(x[2])

These names also might be a bit confusing and could be something like Bar.continues_to_next or Bar.continued_from_previous

b1a0 commented 4 years ago

I have never worked with lilypond, but a quick search suggests, that they just append a tilde to the previous note: http://lilypond.org/doc/v2.20/Documentation/learning/ties-and-slurs If don't have a lot of time right now, but I don't think that the current version of mingus, especially the from_Bar method can export ties to lilypond - I might be wrong, though. https://github.com/bspaans/python-mingus/blob/master/mingus/extra/lilypond.py

Your suggestion seems good to me - if you want to implement it, I would be happy to test it, but again, I have no ties to this library, I am just curious to see this in action.