I am working on an app that accepts MIDI keyboard input (using Mido) from within a Kivy application. The goal is to have one thread that constantly polls the MIDI input and routes events to pyfluidsynth, while a conventional Kivy app is running in parallel. I need some sort of parallel process, or else the Kivy UI freezes for as long as the midi poll while loop is running.
After much fiddling, I got it to work, but I'm a bit concerned about the code. I tried starting the threads under [if name == " main "], but was only ever able to run one process, followed by the other.
Then by accident I was able to get the desired effect by leaving in the last 2 lines of code in mido_midi.py, which were originally just for testing. Now, when I run main.py, I get the app plus the keyboard input. Other than some ugly behaviour when I close the app, things appear to be working the way I wanted.
My concern is that I can't seem to get the threading to work by calling everything from main. Since things are working, and I don't understand why, and it looks wrong to me. I thought I'd throw it to smarter people for insight.
Am I doing it right? If not, what do I need to change? Thanks.
main.py:
from kivy.app import App
from kivy.uix.widget import Widget
from mido_midi import start_synth, KeyboardInput
class MyApp(App):
def build(self):
return GameWidget()
class GameWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
...
self.midikeyboard = KeyboardInput()
...
if __name__ == "__main__":
app = MyApp()
app.run()
mido_midi.py:
import mido
import fluidsynth
import threading
def start_synth(driver, sound, channel=0):
fs = fluidsynth.Synth(samplerate=24000, gain=0.8)
fs.start(driver)
sfid = fs.sfload(sound)
fs.program_select(channel, sfid, 0, 0)
# print("Midi loaded...")
return fs
class KeyboardInput(threading.Thread):
def __init__(self, device='Keystation 88 Port 1', driver='coreaudio', sound='sounds/Wii_Grand_Piano.sf2', channel=0):
super(KeyboardInput, self).__init__()
self.played_notes = []
self.device = device
self.driver = driver
self.sound = sound
self.channel = channel
self.inport = mido.open_input(self.device)
self.fs = start_synth(self.driver, self.sound)
def run(self):
for msg in self.inport:
print(msg)
note = msg.note
velocity = msg.velocity
self.fs.noteon(self.channel, note, velocity)
# Code outside of the class, intended for testing
m = KeyboardInput()
m.start()
Your code is starting the KeyboardInput
thread when you do the from mido_midi import start_synth, KeyboardInput
and the "testing" lines are executed at that time. The if __name__ == "__main__":
is a construct designed to prevent exactly that from happening when a file containing that construct is imported. Also, note that you have two different instance of KeyboardInput
. Not sure if that is your intention.
You should be able to start the thread inside your if __name__ == "__main__":
block by just adding the same two lines inside that block:
m = KeyboardInput()
m.start()
If you really only want one instance of KeyboardInput
, you should be able to do
self.midikeyboard.start()
inside your __init__()
method in GameWidget
.
Also, if you want easier shutdown add daemon=True
to the constructor call, either:
m = KeyboardInput(daemon=True)
or
self.midikeyboard = KeyboardInput(daemon=True)
The daemon=True
means that the thread will be killed as soon as the app is finished.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.