Trouble making an interactive console app (GUI blocking console)

59 views
Skip to first unread message

James B.

unread,
Aug 27, 2022, 5:40:34 PM8/27/22
to pyo-discuss
Hi all, thank you for taking a look.

My Goal:
  • I am leaning OOP in python.
  • I want to make an interactive console app that will create different instances of synths based on what I type. 
My Issue:
  • It only works when self.s.gui(locals()) is called after creating the synth. This is a problem because then the console is blocked from further input.
  • If I comment that line out, I get no sound at all.
The Code:
import sys
from pyo import *

class Audio(object):

    def __init__(self):
        #pm_list_devices()
        print('~~~~~~ LOADING UP!')
        if sys.platform == "win32":
            import ctypes
            ctypes.windll.kernel32.SetConsoleTitleW('                            S        L         O     F   I')
            self.s = Server(buffersize=1024, duplex=0, winhost='mme',nchnls=2) #port audio = pa # audio='pa', duplex=0
            print('~~~~~~ Windows Server!')
        else:
            self.s = Server()
            print('~~~~~~ Linux Server!')
        self.s.setMidiInputDevice(99)
        self.s.boot().start()
        print('~~~~~~ Audio Server Booted!')
        self.mid = Notein(scale=1)
        #self.mid = Notein(scale=1)
       

    def synth(self,k):
        # sineLoop Osc

        at = .001
        env = MidiAdsr(self.mid['velocity'], attack=at, decay=at*4, sustain=.4, release=1)
        a = SineLoop(freq=self.mid['pitch'], feedback=.1, mul=env)
        b = SineLoop(freq=self.mid['pitch']*1.005, feedback=.1, mul=env)
        a.out()
        b.out(1)

        '''
        * With gui it works, but then the console stops working.
        * Without gui, no sound at all.
        * launching with -i does nothing (tested in linux).
        '''
        self.s.gui(locals())

    def control(self):
        self.s.gui(locals()) # Prevents immediate script termination


audio = Audio()

while True:
    print('\n\nType the letter \'O\' to add a new synth:')
    x = input()
    if x == 'O':
        audio.synth('O')
        print('~~~~~~ Midi Synth Created!')

print('~~~~~~ END')

James B.

unread,
Sep 3, 2022, 5:41:16 PM9/3/22
to pyo-discuss
I've tried probably 5+ different ideas and haven't found a solution. 

I think the only way may be through multithreading. Something I'll have to do a lot of research on.
If anyone can think of a simpler way, please let me know, thank you!

Again, the goal is to be able to add and remove different instances of synths. This is a simplistic example to a larger idea that I am working on.

For reference, this is my closest attempt:

import sys
from pyo import *

class Audio(object):

    def __init__(self):
        pm_list_devices()
        print('~~~~~~ LOADING UP!')
        if sys.platform == "win32":
            import ctypes
            ctypes.windll.kernel32.SetConsoleTitleW('                            S        L         O     F   I')
            self.s = Server(buffersize=1024, duplex=0, winhost='mme',nchnls=2) #port audio = pa # audio='pa', duplex=0
            print('~~~~~~ Windows Server!')
        else:
            self.s = Server()
            print('~~~~~~ Linux Server!')
        self.s.setMidiInputDevice(99)
        self.s.boot().start()
        print('~~~~~~ Audio Server Booted!')
        self.mid = Notein(scale=1)
       

class synth(object):
    def __init__(self,audio):
        self.audio = audio
        self.synth1()
       
   
    def __del__(self):
        print('deleted')

    def synth1(self):
        at = .001
        env = MidiAdsr(self.audio.mid['velocity'], attack=at, decay=at*4, sustain=.4, release=1)
        a = SineLoop(freq=self.audio.mid['pitch'], feedback=.1, mul=env)
        b = SineLoop(freq=self.audio.mid['pitch']*1.005, feedback=.1, mul=env)
        a.out()
        b.out(1)
        print("~~ Synth Created")

        #keeps it open till del.
        while True:
                pass



audio = Audio()

while True:
    print('\n\nType \'[instance],O\' to add a synth, \'[instance],D\'  to remove instance:')
    x = input()
   
    x = x.split(",")
    if x[1] == 'O':
        x[0] = synth(audio)
    elif x[1] == 'D':
        del x[0]
print('~~~~~~ END')


Erwin Engelsma

unread,
Sep 4, 2022, 12:59:24 PM9/4/22
to pyo-discuss
Hi James,  I ran into more less this problem a while back. I found two different solutions. One is to use multithreaded(see program below) and the other is to use networking. The advantage of networking (pyoscreceive and send) is that you can run two separate programs whereas with multithreading you are not truly working in parallel. But here is a multithreaded example: 
from pyo import Server
from pyo import Sine
from pyo import LinTable
from pyo import Osc
from threading import Thread
import tkinter as tk
from tkinter import ttk
import tkinter.font as tkFont

class pyoClass(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.runProgram = True
        self.pyoServer = Server(buffersize=1024)
        self.pyoServer.setInOutDevice(14) # The device index returned by portaudio go here
        self.pyoServer.boot()  
        #a = Input(chnl=0, mul=.7)# 0 is the first channel of the device (give [0,1] to get stereo input)
        #spec = Spectrum(a, size=4096)
        self.frequency =1.0
        self.change = True
        sine = Sine(freq = self.frequency).out(chnl = 0)
        self.pyoServer.start()
       
    def run(self):
        self.theShape = LinTable([(0,-0.8), (4000, 0.8), (8191,-0.8)])   #, size = 16        
        while self.runProgram == True:
            if self.change:

              myOscil = Osc(self.theShape, freq=self.frequency, mul=0.8)
              myOscil.out(chnl=0)
              self.change = False
        self.pyoServer.stop()
       
    def setFreq(self, freqIn):
        self.frequency = freqIn
        self.change = True
       
    def setShape(self, pos, amplitude):
        self.theShape = LinTable([(0,-0.8), (pos,amplitude), (8191, -0.8)])
        self.change = True  
       
    def stopPyo(self):
        self.runProgram = False

class App(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.grid()
        self.create_widgets()
        self.update()
        self.myPyo= pyoClass()
        self.myPyo.start()

    def create_widgets(self):        
        self.freqVar = tk.StringVar()
        self.posVar = tk.StringVar()    
        self.ampVar = tk.StringVar()            
        self.btn_font = tkFont.Font(family="Helvetica", size=12, weight='bold')
        self.ShowData_text = ttk.Entry(self, textvariable = self.freqVar)
        self.ShowData_text.grid(column = 3, row = 1)      
        self.sendFreqButton = tk.Button(self, font = self.btn_font,text="Send Freq",  command=self.sendFreq)
        self.sendFreqButton.grid(column = 3, row = 50, pady = 5, sticky = 'we')
        self.ShowPos_text = ttk.Entry(self, textvariable = self.posVar)
        self.ShowPos_text.grid(column = 4, row = 1)      
        self.ShowAmp_text = ttk.Entry(self, textvariable = self.ampVar)
        self.ShowAmp_text.grid(column = 5, row = 1)          
        self.sendShapeButton = tk.Button(self, font = self.btn_font,text="Send Shape", command=self.sendShape)
        self.sendShapeButton.grid(column = 4, row = 50, pady = 5, sticky = 'we')                
        self.stopButton = tk.Button(self, font = self.btn_font,text="Stop It", command=self.stopIt)
        self.stopButton.grid(column = 4, row = 60, pady = 5, sticky = 'we')        

    def mainloop(self):
        super().mainloop()
        self.update()
        self.destroy()  

    def sendFreq(self):
        freq = float(self.ShowData_text.get())
        self.myPyo.setFreq(freq)

    def sendShape(self):
        pos = int(self.ShowPos_text.get())
        ampl =  float(self.ShowAmp_text.get())
        self.myPyo.setShape(pos, ampl)        

    def stopIt(self):
        self.myPyo.stopPyo()
        tk.Frame.quit(self)
        self.destroy()
       
def main():
    app = App()
    app.master.title('Spectrum Analyzer')
    app.master.geometry('900x980')
    app.mainloop()

if __name__ == '__main__':
    main()

I prefer networking because you can develop the programs separately and even run them on two different computers.
In stead of multithreading you might consider asyncio. 

I hope this helps
Erwin

barmin

unread,
Sep 4, 2022, 3:54:38 PM9/4/22
to pyo-d...@googlegroups.com
Hello,

AFAICT the problem is not where you think it is.

You're just replicating a very common mistake when beginning with pyo: when calling `Audio.synth()`, your objects a and b are created, and immediately garbage-collected when the method terminates. So you don't hear anything unless you manage to stay in your method (which is what your gui() does).

Try simply replacing

a = SineLoop(freq=self.mid['pitch'], feedback=.1, mul=env)

with

self.a = SineLoop(freq=self.mid['pitch'], feedback=.1, mul=env)

That will keep your `a` alive as long as your `audio` object is alive. That should solve your problem.

Here is a quick proof of concept, loosely based on your code:


~~~

import pyo

class Sound(object):

def __init__(self, freq):
self.sound = pyo.Sine(freq).out()

s = pyo.Server().boot().start()

sounds = []

while x := input('Frequency (<enter> to quit): '):
sounds.append(Sound(int(x)))

~~~

You don't need to explicitly multithread. Pyo is already running the audio engine in a separate thread, so even blocking IO like an input() won't block the audio.

Hope this helps!

Matthieu

Le 27.08.22 à 23:40, James B. a écrit :
> Hi all, thank you for taking a look.
>
> My Goal:
>
> * I am leaning OOP in python.
> * I want to make an interactive console app that will create different instances of synths based on what I type.
>
> My Issue:
>
> * It only works when self.s.gui(locals()) is called after creating the synth. This is a problem because then the console is blocked from further input.
> * If I comment that line out, I get no sound at all.

Erwin Engelsma

unread,
Sep 5, 2022, 11:51:49 AM9/5/22
to pyo-discuss
Hi Matthieu, 
thanks for this info, I tried your outline and it works nicely. Just a question. Does this solution in your experience also work if you make a GUI in TKinter, but also at the same time open a few graphs (for instance because you use a table?)
I use multithreaded specifically for that situation as I did not get it to work otherwise (graphs would disappear).
Erwin

James B.

unread,
Sep 5, 2022, 3:14:42 PM9/5/22
to pyo-discuss
Thank you Matthieu and Erwin for your input!

Matthieu, sure enough your suggestion solved my issue, thank you so much for that simple solution. It looks like my problem all along was not quite knowing what I'm doing with OOP ;-)  

I'm excited that I won't have to go looking into multithreading just yet. 


barmin

unread,
Sep 6, 2022, 3:55:22 AM9/6/22
to pyo-d...@googlegroups.com
Hi,

> thanks for this info, I tried your outline and it works nicely. Just a question. Does this solution in your experience also work if you make a GUI in TKinter, but also at the same time open a few graphs (for instance because you use a table?)
> I use multithreaded specifically for that situation as I did not get it to work otherwise (graphs would disappear).

I usually don't use a GUI so I'm no expert. However, I seem to remember there's an option to tell pyo's windows (at least the ctrl() ones) that you won't use the server main window:
http://ajaxsoundstudio.com/pyodoc/api/classes/_core.html?highlight=ctrl#pyo.PyoObject.ctrl

Does it help to add `wxnoserver=True`?

Cheers,

Matthieu

barmin

unread,
Sep 6, 2022, 4:10:50 AM9/6/22
to pyo-d...@googlegroups.com
> Matthieu, sure enough your suggestion solved my issue, thank you so much for that simple solution. It looks like my problem all along was not quite knowing what I'm doing with OOP ;-)

Glad it helped! :-)

Matthieu

Erwin Engelsma

unread,
Sep 6, 2022, 9:19:25 AM9/6/22
to pyo-discuss
Matthieu, I did not know this wxnoserver possibility existed, thanks for pointing it out. Anyway, for what I envision at the moment my network solution feels the best, because I get a clear division between the sound generation and the generation/acquisition of the parameters that control it. And I like the flexibility that offers me.
Reply all
Reply to author
Forward
0 new messages