0

I'm working on a kivy app which is intended to parametrically produce ear training exercises on the spot and play them. The music is described using the mingus module, and played via its fluidsynth implementation. I have an exercise playing function, which works as intended when normally called, but since with that method everything else hangs while the drill is running (on an infinite while loop waiting to be manually stopped), that does not result in acceptable behaviour. I assigned the function to its own process using Process(), and based on the the output to the terminal it is looping as intended, but there's no sound. Another point worthy of note is that multiple presses to the start button results in multiple processes running in parallel, judging by the rhythms of the terminal activity. Here's the code of the python side of the interface:

from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleview import RecycleView
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.config import Config
from classes import *
from multiprocessing import Process
import yaml
Config.set('graphics', 'width', '400')
Config.set('graphics', 'height', '700')

class Welcome(Screen):
    pass

class Builtin(Screen):
    pass

class DrillScreen(Screen):
    def __init__(self, **kwargs):
        super(DrillScreen, self).__init__(**kwargs)
        self.options = kwargs['options']
        self.desc = self.options.des
        self.thedrill = drill(self.options)
        desc = Label(
                text=self.options.des,
                size_hint_x=0.9,
                size_hint_y=1)
        button = Button(
                text = 'Start',
                size_hint=[0.4,0.1])
        button.bind(on_release=self.startdrill())
        self.add_widget(desc)
        self.add_widget(button)

    def startdrill(self):
        def starter(*args):
            proc = Process(target=self.thedrill.run)
            proc.start()
        return starter



class BuiltinView(RecycleView):
    def __init__(self, **kwargs):
        super(BuiltinView, self).__init__(**kwargs)
        with open('builtin.yaml', 'r') as builtin:
            self.drills = yaml.load(builtin)
        self.data = [ {'text': self.drills[i]['name'],
            'on_release': self.startermaker(i,self.drills[i])}
            for i in sorted(self.drills.keys()) ]

    def startermaker(self,num,options):
        def startdrill():
            options = drilloptions(self.drills[num])
            drillscreen = DrillScreen(name='drill', options=options)
            app = App.get_running_app()
            app.root.add_widget(drillscreen)
            app.root.current = 'drill'
            #drillscreen.thedrill.run()
        return startdrill


class Manager(ScreenManager):
    pass

class TestingApp(App):
    def build(self):
        return Manager()

TestingApp().run()

The drill class holds some parameters in its attributes and enters a loop to endlessly generate exercises within those parameters when its .run() method is called. In the button.bind() statement of the DrillScreen class, if I directly put on_release=self.thedrill.run , I get the function working no problem. I don't know what I'm missing here. I can post code from other components of the project if needed. Thank you.

aplainzetakind
  • 490
  • 2
  • 8
  • I am very willing to help you, but can you please try to isolate your problem to less than, say, 100 lines of runnable code? By that I mean I will be able to copy-paste it in my IDE and run it and play around with it right away. – Edvardas Dlugauskas Jun 05 '17 at 18:20
  • Thank you. The example I attempted to write turned out to work as I want it to. There, for the sake of brevity, I called the fluidsynth play function on a fixed musical entity. I think I'm lacking some understanding on what the bound functions get passed as arguments. For instance, I put the `*args` into the definition of `starter()` as a miserable workaround to it being passed a `Button` instance in a way I couldn't grasp. The `.run()` method of the `drill` class relies on many attributes, and if `self`'s get mixed up somehow it might break. I'll still try to write a small but broken example. – aplainzetakind Jun 05 '17 at 20:38
  • Yes, you are on the right path - perhaps this will help you locate the problem. You can then edit your answer and say that the problem is on line X with 100% certainty. – Edvardas Dlugauskas Jun 05 '17 at 20:57

1 Answers1

0

Well, in trying to produce a small broken example, I stumbled upon a fix. I'm not really sure why it works one way and not the other. Previously fluidsynth was initialized in the .__init__() method of the drill class upon creation, I moved it to the .run() method. Very roughly in almost pseudocode:

Before:

class drill:
    def __init__(self, opt):
        #Some attribute setting
        self.ex = None #This will instantiate a class describing a single question
        fluidsynth.init(args)

    def run(self):
        while True:
            #Determining randomly the arguments to create Exercise instance

            self.ex = Exercise(args)
            self.ex.play()

After:

class drill:
    def __init__(self, opt):
        #Some attribute setting
        self.ex = None

    def run(self):
        fluidsynth.init(args)
        while True:

            #Determining randomly the arguments to create Exercise instance

            self.ex = Exercise(args)
            self.ex.play()

When I call .run() directly with the button.bind(), both versions work. When I assign it to its own process and hand that process to button.bind(), only the second version produces sound. So it seems that roughly what's happening is that an initialized fluidsynth isn't accessible from different processes.

I will delay accepting this answer in case someone delivers non-speculative understanding on the matter.

aplainzetakind
  • 490
  • 2
  • 8
  • Did you try putting the line *before* the loop? Multiple initialisations could play a role here, or it may be a question of timing. – Tshirtman Jun 07 '17 at 08:23
  • @Tshirtman Yes that's what I'm doing after all. I misplaced it while typing a sketch of it here (there's much irrelevant stuff in the actual code). Edited, thanks. – aplainzetakind Jun 07 '17 at 09:51