Binding a Pyo Oscillator output to a WX Event - python

I am building a simple signal generator in Python based on the Pyo and WX libraries.
I have ran through the simple tutorials for each and have successfully bound buttons in WX to WX functions. I am now trying to generate a simple sine wave(at 440 hz) for 1 second by pressing the button labeled "Oscillator 1"; however, when the main() function executes, the sine tone is played and while the button is displayed in the wx frame I am unable to retrigger the sine tone. Both of these symptoms are unwanted.
Why does the sine tone play immediately on program execution? Why does the firstOSC button seemingly not work?
import wx
from pyo import *
import time
pyoServer = Server().boot()
pyoServer.start()
class MainWindow(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent,title=title, size = (640,640))
self.CreateStatusBar() # A StatusBar in the bottom of the window
# Signal Generator controls
oscillator = SoundOutput()
firstOSC = wx.Button(self, wx.ID_YES,"Oscillator 1 " + str(oscillator.str_osc1State))
self.Bind(wx.EVT_BUTTON, oscillator.OnOff1(440), firstOSC)
#Menus
filemenu = wx.Menu()
menuExit = filemenu.Append(wx.ID_EXIT,"&Exit","Terminate the program")
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File")
self.SetMenuBar(menuBar)
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
self.Show(True)
def OnExit(self,e):
self.Close(True)
class SoundOutput(object):
def __init__(self):
self.osc1State = False
self.str_osc1State = "Off"
self.a = Sine(440, 0, 0.1)
def OnOff1(self, frequency):
self.a.freq = frequency
self.a.out()
time.sleep(1)
self.osc1State = True
def Main():
app = wx.App(False)
frame = MainWindow(None,"Signal Generator")
app.MainLoop()

I solved this by investigating how WX handles events. As it turns out, for some reason calling a method in a nested or separate instance of a class caused the tone to play at runtime instead of on the event. I fixed this by making a method for the MainWindow class that serves as the binded event handler for firstOSC. This method then calls the requisite methods for the actual oscillator class.
Here is the new code:
# Signal Generator controls
self.fOscillator = SoundOutput()
self.fOscillatorstatus = False
self.firstOSC = wx.Button(self, wx.ID_ANY,"Oscillator 1 On")
self.firstOSC.Bind(wx.EVT_BUTTON, self.OnFirstOSC)
def OnFirstOSC(self,e):
if not self.fOscillatorstatus:
self.fOscillator.OnOff1(440)
self.fOscillatorstatus = True
self.firstOSC.SetLabel("Oscillator 1 Off")
elif self.fOscillatorstatus:
self.fOscillator.OnOff1(0)
self.firstOSC.SetLabel("Oscillator 1 On")
self.fOscillatorstatus = False

Related

How do I detect closing wxPython?

I am using wxpython...How can I detect and perform a function if someone clicks the Red 'X' on the top right corner ( Close button )? What is the code? Can someone please help me? Thanks!
You are looking for EVT_CLOSE.
e.g.
import wx
class Test(wx.Frame):
def __init__(self,parent):
wx.Frame.__init__(self,parent,title="Main Window",size = (300,200))
panel = wx.Panel(self)
menubar=wx.MenuBar()
firstm=wx.Menu()
fm1 = wx.MenuItem(firstm, -1, 'Quit\tAlt+Q')
firstm.Append(fm1)
self.Bind(wx.EVT_MENU, self.OnExit, id=fm1.GetId())
# Catch Clicking on the Corner X to close
self.Bind(wx.EVT_CLOSE, self.OnExit)
menubar.Append(firstm,"File")
self.SetMenuBar(menubar)
t = wx.StaticText(panel,-1,"Testing 1 2 3 ....", pos=(10,20))
def OnExit(self, event):
# To discover how you got here,
# you can either test the event type or define a separate function for EVT_CLOSE,
# most of the time you don't care
if event.GetEventType() == wx.EVT_CLOSE.typeId:
print("Close using X")
else:
print("Close using Menu or Alt+Q")
self.Destroy()
if __name__=='__main__':
app=wx.App()
frame=Test(None)
frame.Show()
app.MainLoop()

Pause the code at some point

I want to pause the code at exact position and wait for the different input plus the start button clicked. However, if it is not achievable, how can I add another button into the start button to make it work?
import wx
import time
import RPi.GPIO as GPIO
global Total_Length
Total_Length = 500
global Input_Length
Input_Length = 0
a = 0
class tyler(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Lyquid Crystal Laser Control',size=(500,200))
panel=wx.Panel(self)
#global Text_1
self.Text_1 = wx.TextCtrl(panel,-1,"0",(350,30),(50,30))
self.Text_1.Bind(wx.EVT_TEXT_ENTER,self.Start)
self.Text_1.Bind(wx.EVT_KILL_FOCUS,self.Start)
self.timer = wx.Timer(self)
#self.Bind(wx.EVT_TIMER, self.timer)
button_1=wx.Button(panel,label="Start",pos=(400,80),size=(80,30))
button_2=wx.Button(panel,label="Stop",pos=(400,120),size=(80,30))
self.Bind(wx.EVT_BUTTON, self.Start, button_1)
#self.Bind(wx.EVT_BUTTON, self.Stop, button_2)
def Start(self,event):
global a
Input_Length=float(self.Text_1.GetValue())
#print(Input_Length)
#a = Input_Length
#print(Input_Length)
dc=float(100*Input_Length/Total_Length)
GPIO.setmode(GPIO.BCM)
GPIO.setup(18,GPIO.OUT)
GPIO.setwarnings(False)
p = GPIO.PWM(18,1150)
p.start(0)
p.ChangeDutyCycle(dc)
p.ChangeFrequency(1150)
#I wanted to pause the code at here, until the input changes, and the start button clicked, so I add timer in below, however, the output is only a pulse but the square wave is what I wanted
if a == dc:
self.timer.Start(1000)
else:
a = dc
self.timer.Stop()
#def Stop(self,event):
GPIO.cleanup()
if __name__=='__main__':
app=wx.PySimpleApp()
frame=tyler(parent=None,id=-1)
frame.Show()
app.MainLoop()
"Pause and wait" and "event-driven GUI programming" do not go together. As long as the main GUI thread is blocked waiting for something, then other events can't be processed and the program will appear to be frozen. Your options are to change how you "wait" (by not actually waiting) or to use another thread.
This answer to another question applies just as well here, and will give you more information and pointers.

Which EvtHandler to use for PostEvent in wxPython

This question addresses the very specific question of the EvtHandler required to post en event using wxPython.
Background
I'm using Python 2.7.
In the example below I have two kinds of events:
StartMeausuringEvent is triggered within a wx.Panel derived object (DisplayPanel), hence I use self.GetEventHandler(), this works, even though the binding is in the parent object.
NewResultEvent is triggered from a threading.Thread derived object (MeasurementThread), and it has no event handler, hence I have been forced to send an event handler along, and I chose the event handler of the wx.Frame derived object (MeasurementFrame), as this is also the object that "cathes" the event eventually.
Question
WHY does the first one work, since the object ? And more generally, how tight has the connection between the event handler and the object that "catches" the event has to be?
Code example
import wx
import time
import threading
import numpy as np
import wx.lib.newevent
# Create two new event types
StartMeasuringEvent, EVT_START_MEASURING = wx.lib.newevent.NewCommandEvent()
NewResultEvent, EVT_NEW_RESULT = wx.lib.newevent.NewEvent()
class MeasurementFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="Lets measure!", size=(300, 300))
# Layout
self.view = DisplayPanel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.view, 1, wx.ALIGN_CENTER)
self.SetSizer(sizer)
self.SetMinSize((300, 300))
self.CreateStatusBar()
# Create a new measuring device object to embody a physical measuring device
self.device = MeasuringDevice(self.GetEventHandler(), amplification=10)
# Bind events to the proper handlers
self.Bind(EVT_START_MEASURING, self.OnStartMeasurement)
self.Bind(EVT_NEW_RESULT, self.OnNewResult)
def OnStartMeasurement(self, evt):
self.view.SetStatus("Measuring")
self.device.start_measurement()
def OnNewResult(self, evt):
self.view.SetStatus("New Result!")
print evt.result_data
class DisplayPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# Attributes
self._result_display = wx.StaticText(self, label="0")
self._result_display.SetFont(wx.Font(16, wx.MODERN, wx.NORMAL, wx.NORMAL))
self._status_display = wx.StaticText(self, label="Ready!")
self._status_display.SetFont(wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL))
# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
button1 = wx.Button(self, wx.NewId(), "Increment Counter")
# button2 = wx.Button(self, wx.NewId(), "Decrease Counter")
sizer.AddMany([(button1, 0, wx.ALIGN_CENTER),
# (button2, 0, wx.ALIGN_CENTER),
((15, 15), 0),
(self._result_display, 0, wx.ALIGN_CENTER),
(self._status_display, 0, wx.ALIGN_LEFT)])
self.SetSizer(sizer)
# Event Handlers
button1.Bind(wx.EVT_BUTTON, self.OnButton)
def OnButton(self, evt):
""" Send an event ... but to where? """
wx.PostEvent(self.GetEventHandler(), StartMeasuringEvent(self.GetId()))
def SetStatus(self, status=""):
""" Set status text in the window"""
self._status_display.SetLabel(status)
class MeasuringDevice:
def __init__(self, event_handler, amplification=10):
self.amplification = amplification
self.event_handler = event_handler # The object to which all event are sent
def start_measurement(self, repetitions=1):
""" Start a thread that takes care of obtaining a measurement """
for n in range(repetitions):
worker = MeasurementThread(self.event_handler, self.amplification)
worker.start()
class MeasurementThread(threading.Thread):
def __init__(self, event_handler, amplification):
threading.Thread.__init__(self)
self.event_handler = event_handler
self.amplification = amplification
def run(self):
print("Beginning simulated measurement")
time.sleep(1) # My simulated calculation time
result = np.random.randn()*self.amplification
evt = NewResultEvent(result_data=result)
wx.PostEvent(self.event_handler, evt)
print("Simulated Measurement done!")
if __name__ == '__main__':
my_app = wx.App(False)
my_frame = MeasurementFrame(None)
my_frame.Show()
my_app.MainLoop()
The first one works because command events automatically propagate up the containment hierarchy (a.k.a the window parent/child connections) until there is a matching binding found or until it reaches a top-level parent window like a frame or dialog. See http://wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind and also http://wxpython.org/OSCON2006/wxPython-intro-OSCON2006.pdf starting at slide 53 for more explanation.
There is a specific path that the event processor will search when looking for a matching event binding. In a nutshell: as mentioned above, event types that derive directly or indirectly from wx.CommandEvent will continue searching up through the parents until a match is found, and for other types of events it will only look for bindings in the window that the event was sent to and will not propagate to parent windows. If a handler calls event.Skip() then when the handler returns the event processor will continue looking for another matching handler (still limited to the same window for non-command events.) There is a lot more to it than that, (see slide 52 in the PDF linked above) but if you understand this much then it will be enough for almost every event binding/handling situation you'll likely encounter.
BTW, the wx.Window class derives from wx.EvtHandler so unless you've done something like PushEventHandler then window.GetEventHandler() will return the window itself. So unless you need to push new event handler instances (most Python programs don't, it is used more often in C++) then you can save some typing by just using window instead of window.GetEventHandler().

wxPython: Show Frame while doing something in other thread

in my GUI with wxPython if have to do some calculations which can take some time. So I want to start them in a seperate Thread and show a window in the GUI that prints that the program is calculating. The main windows should be disabled during this.
So that's my code:
import time
import threading
import wx
def calculate():
# Simulates the calculation
time.sleep(5)
return True
class CalcFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent=None, id=-1, title="Calculate")
# Normally here are some intctrls, but i dont show them to keep it easy
self.panel = wx.Panel(parent=self, id=-1)
self.createButtons()
def createButtons(self):
button = wx.Button(parent=self.panel, id=-1, label="Calculate")
button.Bind(wx.EVT_BUTTON, self.onCalculate)
def onCalculate(self, event):
calcThread = threading.Thread(target=calculate)
checkThread = threading.Thread(target=self.checkThread, args=(calcThread,))
self.createWaitingFrame()
self.waitingFrame.Show(True)
self.Disable()
calcThread.run()
checkThread.run()
def createWaitingFrame(self):
self.waitingFrame = wx.MiniFrame(parent=self, title="Please wait")
panel = wx.Panel(parent=self.waitingFrame)
waitingText = wx.StaticText(parent=panel, label="Please wait - Calculating", style=wx.ALIGN_CENTER)
def checkThread(self, thread):
while thread.is_alive():
pass
print "Fertig"
self.waitingFrame.Destroy()
self.Enable()
app = wx.PySimpleApp()
frame = CalcFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
But my problem is now, that if i press the "Calculate" button, the waitingFrame isnt't shown right, I can't see the text. I also cant move/maximize/minimize the main window.
Can you help me? Thank you in advance :)
you should never update the gui in a thread other than the main thread .... Im pretty sure the docs for wxPython mention this in several places .. it is not thread safe and your gui gets in broken states ...
instead I believe you are supposed to do something like
def thread1():
time.sleep(5)
return True
def thread2(t1,gui):
while thread.is_alive():
pass
print "Fertig"
wx.CallAfter(gui.ThreadDone)
class MyFrame(wx.Frame):
def startThread(self):
calcThread = threading.Thread(target=thread1)
checkThread = threading.Thread(target=thread2, args=(calcThread,self))
def ThreadDone(self):
print "Both threads done???"
print "Now modify gui from main thread(here!)"

Opening a wx.Frame in Python via a new thread

I have a frame that exists as a start up screen for the user to make a selection before the main program starts. After the user makes a selection I need the screen to stay up as a sort of splash screen until the main program finishes loading in back.
I've done this by creating an application and starting a thread:
class App(wx.App):
'''
Creates the main frame and displays it
Returns true if successful
'''
def OnInit(self):
try:
'''
Initialization
'''
self.newFile = False
self.fileName = ""
self.splashThread = Splash.SplashThread(logging, self)
self.splashThread.start()
#...More to the class
which launches a frame:
class SplashThread(threading.Thread):
def __init__(self, logger, app):
threading.Thread.__init__(self)
self.logger = logger
self.app = app
def run(self):
frame = Frame(self.logger, self.app)
frame.Show()
The app value is needed as it contains the callback which allows the main program to continue when the user makes their selection. The problem is that the startup screen only flashes for a millisecond then goes away, not allowing the user to make a selection and blocking the rest of start up.
Any ideas? Thanks in advance!
You don't need threads for this. The drawback is that the splash window will block while loading but that is an issue only if you want to update it's contents (animate it) or if you want to be able to drag it. An issue that can be solved by periodically calling wx.SafeYield for example.
import time
import wx
class Loader(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
self.btn1 = wx.Button(self, label="Option 1")
self.btn2 = wx.Button(self, label="Option 2")
sizer.Add(self.btn1, flag=wx.EXPAND)
sizer.Add(self.btn2, flag=wx.EXPAND)
self.btn1.Bind(wx.EVT_BUTTON, self.OnOption1)
self.btn2.Bind(
wx.EVT_BUTTON, lambda e: wx.MessageBox("There is no option 2")
)
def OnOption1(self, event):
self.btn1.Hide()
self.btn2.Hide()
self.Sizer.Add(
wx.StaticText(self, label="Loading Option 1..."),
1, wx.ALL | wx.EXPAND, 15
)
self.Layout()
self.Update()
AppFrame(self).Show()
class AppFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
time.sleep(3)
parent.Hide()
# the top window (Loader) is hidden so the app needs to be told to exit
# when this window is closed
self.Bind(wx.EVT_CLOSE, lambda e: wx.GetApp().ExitMainLoop())
app = wx.PySimpleApp()
app.TopWindow = Loader()
app.TopWindow.Show()
app.MainLoop()

Categories