Yikai-Liao / symusic

A cross platform note level midi decoding library with lightening speed, based on minimidi.
https://yikai-liao.github.io/symusic/
MIT License
108 stars 8 forks source link

Make `Score.clip` include the ongoing tempo/time signature/key signature at `start` #35

Closed Natooz closed 3 months ago

Natooz commented 3 months ago

Hello,

I have a small feature request for the clip method: make it include the current tempo, time signature and key signature at the tick start. The current implementation keeps the events of these types occurring within the clip section (i.e. changes), but we might not know what are the values of these features at the beginning of the clip.

Describe the solution you'd like

Make clip determine the latest tempo/time signature/key signature occurring before the start tick, and add Tempo/TimeSignature/KeySignature elements in the returned Score chunk with time values at start_tick. This could set as an option controlled by a method attribute.

Describe alternatives you've considered

Here is how I implemented it in Python:

def extract_chunk_from_midi(
    midi: Score, tick_start: int, tick_end: int, clip_end: bool = False
) -> Score:
    """
    Extract a chunk of a ``Score``.

    The returned chunk will have a starting time at tick 0, i.e. the times of its
    events will be shifted by ``-tick_start``.

    :param midi: object to extract a chunk from.
    :param tick_start: starting tick of the chunk to extract.
    :param tick_end: ending tick of the chunk to extract.
    :param clip_end: if given ``True``, the chunk at ``tick_end + 1`` and thus include
        the events occurring at ``tick_end``. (default: ``False``)
    :return: chunk of the ``midi`` starting at ``tick_start and ending at ``tick_end``.
    """
    # Get the tempo, time sig and key sig at the beginning of the chunk to extract
    # There might not be default key signatures in the Score
    tempo, time_signature, key_signature = None, None, KeySignature(0, 0, 0)
    for tempo_ in midi.tempos:
        if tempo_.time > tick_start:
            break
        tempo = tempo_
    for time_signature_ in midi.time_signatures:
        if time_signature_.time > tick_start:
            break
        time_signature = time_signature_
    for key_signature_ in midi.key_signatures:
        if key_signature_.time > tick_start:
            break
        key_signature = key_signature_
    tempo.time = time_signature.time = key_signature.time = 0

    # Clip the MIDI and append the global attributes
    midi_split = midi.clip(tick_start, tick_end, clip_end=clip_end).shift_time(
        -tick_start
    )
    midi_split.tempos.append(tempo)
    midi_split.time_signatures.append(time_signature)
    midi_split.key_signatures.append(key_signature)

    return midi_split

I finally got back doing some c++ and got a glimpse of nanobind. If you want, I can try to implement this.

Additional context

That's something I'm implementing for this MidiTok PR: https://github.com/Natooz/MidiTok/pull/148

Yikai-Liao commented 3 months ago

I am recently preparing for the 3.24 gre exam. Sorry for not having time to read your requirements carefully. But you can try to modify the function if you are happy to do so.

Yikai-Liao commented 3 months ago

@Natooz I understand this issue. This probably won't involve a lot of modifications. I'll make the changes as soon as I can.

Yikai-Liao commented 3 months ago

Oh, there is still one thing about notes. For example, how do we deal with a note(start = 0, duration = 10) when clip with start = 5, end = 10? Do we need to create a new note starting at 5?

Natooz commented 3 months ago

Thank you for looking into this!

I do not think creating new notes in this case would be desired, as they originally started previously. Creating new notes would not provide any information about their original onset position. This is not so problematic with tempos/time signatures as these are "long-term" attributes that affect the whole music.

If one wanted to keep in the clip the parts of notes after their onset (before start), they might prefer use the pianoroll methods.

Yikai-Liao commented 3 months ago

I agree with you

Yikai-Liao commented 3 months ago

@Natooz I have modified the clip function. Does this work as what you expected?

截图_20240327150316

Natooz commented 3 months ago

Absolutely! Thank you for these changes!