-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Request for example of playing a midi file #77
Comments
There's no built-in support (yet) in ipytone for playing midi files, but you could parse the midi file using, e.g., mido to create import ipytone
import mido # convert MIDI note numbers to notes (string notation)
# modified from https://gist.github.com/devxpy/063968e0a2ef9b6db0bd6af8079dad2a
NOTES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
OCTAVES = list(range(-2, 9))
NOTES_IN_OCTAVE = len(NOTES)
def number_to_note(number):
octave = number // NOTES_IN_OCTAVE - 2
assert octave in OCTAVES, errors['notes']
assert 0 <= number <= 127, errors['notes']
note = NOTES[number % NOTES_IN_OCTAVE]
return note + str(octave) # read midi file and extract 1st track
mid = mido.MidiFile("filename.mid")
track = mid.tracks[0]
# use 120 BPM by default (possible to read it from the midi file)
tempo = 500_000
ipytone.transport.bpm.value = mido.tempo2bpm(tempo)
# parse midi note messages and create ipytone.Part events
current_time = 0.
current_notes = {}
events = []
for msg in track:
current_time += mido.tick2second(msg.time, mid.ticks_per_beat, tempo)
if msg.type == "note_on":
current_notes[msg.note] = {
"time": current_time,
"note": number_to_note(msg.note),
"velocity": msg.velocity / 127,
}
elif msg.type == "note_off":
event = current_notes.pop(msg.note)
event["duration"] = current_time - event["time"]
events.append(event) Then create the partition and play it (e.g., with an msynth = ipytone.PolySynth(volume=-8).to_destination()
def clb(time, note):
msynth.trigger_attack_release(
note.note, note.duration, time=time, velocity=note.velocity
)
part = ipytone.Part(callback=clb, events=events) part.start()
ipytone.transport.start() Not sure that we should add |
In the mid/long term, it would be nice to have features like GUI widgets to visualize (#12) and/or edit partitions as well as widgets to connect MIDI devices using https://webmidijs.org/ (a bit like https://github.com/jupyter-widgets/midicontrols but generalized to any device), probably in a 3rd party package. |
Thanks for the example! That alone is great, I was just curious how to make it work, with or without the support of another package. |
I'll have to play with it more but the example appears to be working well, only tweak I had to make was to the: current_notes.pop(msg.note) line. The midi I tested on apparently had note off without a corresponding note on which threw a KeyError which I just caught and skipped the event with. |
I don't have much experience with midi files, maybe this could happen when starting to record a performance while a key is already pressed down? I guess a "note on" without a "note off" could happen as well, then? Probably a better approach would be to use the instrument
|
With #104 and #105 (available in the next release), creating a events = []
for msg in track:
current_time += mido.tick2second(msg.time, mid.ticks_per_beat, tempo)
if msg.type not in ["note_on", "note_off"]:
continue
event = {
"time": current_time,
"note": number_to_note(msg.note),
"velocity": msg.velocity / 127,
"trigger_type": "attack" if msg.type == "note_on" else "release"
}
events.append(event) msynth = ipytone.PolySynth(volume=-8).to_destination()
def clb(time, note):
msynth.trigger_note(note, time)
part = ipytone.Part(callback=clb, events=events) I think this solution should work without needing any additional check. One drawback is when |
I wasn't sure if playing midi files is something ipytone even supports, but if it is, I'd love an example of loading and playing a midi file.
The text was updated successfully, but these errors were encountered: