简体   繁体   中英

Python threading only working when called from another module

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM