1. Summary
  2. Files
  3. Support
  4. Report Spam
  5. Create account
  6. Log in

Python API in CsoundQt

From qutecsound

Revision as of 06:07, 4 May 2013 by Tarmoj (Talk | contribs)
Jump to: navigation, search

Written by Tarmo Johannes, based on "Python Scripting in QuteCsound" [1] by Andrés Cabrera


If CsoundQt is built with PythonQt support (see [2] or [3] for some hints), it enables a lot of new possibilities, mostly in three main fields: interaction with CsoundQt interface, interaction with widgets and using classes from Qt libraries to build custom interfaces in python.

If you start CsoundQt and can open panes "Python console" and "Python scratchpad", you are ready to go.


Contents

The QuteCsound Python Object

The QuteCsound object (called PyQcsObject in the sources) is the interface for scripting QuteCsound. All declarations of the class can be found in the file pyqcsobject.h: [4] in the sources.

It enables to control a large part of CsoundQt possibilities from python interpreter, python scratchpad, scripts or from inside of a running csound csd via python opcodes[5].

By default, a PyQcsObject is already available in the python interpreter of qcs called “q”. To use any of its methods, use form like

q.stopAll()


The methods can be divided into four groups: CsoundQt interface (open, close files, start stop etc), editing open csd, managing widgets, interfacing with running Csound engine.

top


Interface

It is possible to create or open (load) a csd file, run, pause or stop it with the following methods:

int newDocument(QString name)
int loadDocument(QString name, bool runNow = false)

play(int index = -1, realtime = true)
pause(int index = -1)
stop(int index = -1)
stopAll()

newDocument adnd loadDocument return the index of the created/opened document or -1, if it fails (the file already exists for newDocument or it can't be found for loadDocument)

For play, pause and stop, omitting 'index' parameter or setting it to -1 means the current document.


Method

int getDocument(QString name = "")
returns document index. -1 if not currently open

Use

setDocument(index)

to set a particular document referred by its index as active. The document will become visible, as if the tab for it had been clicked.


Examples:

# open "test.csd" if it already exists, create, if not:
if (q.loadDocument("test.csd")==-1):
   myIndex=q.newDocument("test.csd")

#show first.csd
q.setDocument(q.getDocument("first.csd")

#but play test.csd
q.play(myIndex)

top

Editor

Operations on text for open files can be done through the API, for example:

insertText(text, index = -1, section = -1)

will insert the text on the current cursor position, for document 'index'. The 'section' parameter has currently no effect yet.

Text can also be inserted to individual sections using the functions:

setCsd(text, index = -1);
setFullText(text, index = -1)
setOrc(text, index = -1)
setSco(text, index = -1)
setWidgetsText(text, index = -1)
setPresetsText(text, index = -1)
setOptionsText(text, index = -1)

Note that the whole section will be overwritten with the string in parameter text. And text can also be queried with the functions:

getSelectedText(index = -1, int section = -1)
getCsd(index = -1)
getFullText(index = -1)
getOrc(index = -1)
getSco(index = -1)
getSelectedWidgetsText(int index = -1);
getWidgetsText(index = -1)
getPresetsText(index = -1)
getOptionsText(index = -1)

So it is possible put together a new csd taking orchestra from one open document, score from other or generate it, make string manipulations to pre- existing templates, add widgets from third one etc.

Additionally information about the file being edited can be queried with:

getFileName(index = -1)
getFilePath(index = -1)

top

Widgets

Creation

To create a new widget, use method in form createNew<WidgetName>(int x, int y, QString channelName, int index = -1) All of the arguments are optional. If used without any arguments or just with the x and y coordinates, a dialog opens, where it is possible to set all necessary parameters and the widget will be created in active document. For example: q.createNewLabel()

With the the first two parameters it is possible to determine, in which point the widget will be created. If the third one, the channelName, is set, no dialog will be opened - it is especially useful for scripts creating many widgets. The last argument, index, determines, in which document the widget will be created. -1 means active document. All the createNew-methods return uuid - an unique string - of the widget that can be later used to control or delete the widget (see below).

Here is the list of the widget creation methods:

QString createNewLabel(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewDisplay(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewScrollNumber(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewLineEdit(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewSpinBox(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewSlider(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewButton(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewKnob(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewCheckBox(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewMenu(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewMeter(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewConsole(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewGraph(int x = 0, int y = 0, QString channel = QString(), int index = -1);
QString createNewScope(int x = 0, int y = 0, QString channel = QString(), int index = -1);

Examples:

# create a new slider for channel level at position 10,10 in document test.csd (must be open)
q.createNewSlider(10,10,"level",q.getDocument("test.csd"))

#create 10 knobs with channels partial0, partial1 etc, add according labels 
for no in range(10):
        q.createNewKnob(100*no,5,"partial"+str(no) )
        q.createNewLabel(100*no+5,90,"Amplitude "+str(no) )

top

Controlling widgets

Any of the properties of a widget can be changed via method

setWidgetProperty(QString widgetid, QString property, QVariant value, int index= -1) 

'widgetid' can be either the channel name of the widget (more practical when typing to console) OR the uuid of the widget. The latter is more safe since two different widgets can have same channel name (like a slider and a display). So it always wise to store the uuids of created widgets for latter control. Extending the previous example:

knobs = []
labels = []

for no in range(10):
        knobs.append(q.createNewKnob(100*no,5,"partial"+str(no) ) )
        labels.append(q.createNewLabel(100*no+10,90,"Amplitude "+str(no) ) )


To get uuids of all the widgets use method

getWidgetUuids(index index = -1)


To list all possible properties, use method

listWidgetProperties(QString widgetid, int index = -1)

For example

q.listWidgetProperties("partial0")

returns:

(u'QCS_x', u'QCS_y', u'QCS_uuid', u'QCS_visible', u'QCS_midichan', u'QCS_midicc', u'QCS_minimum', u'QCS_maximum', u'QCS_value', u'QCS_mode', u'QCS_mouseControl',
 u'QCS_mouseControlAct', u'QCS_resolution', u'QCS_randomizable', u'QCS_randomizableGroup', u'QCS_width', u'QCS_height', u'QCS_objectName')

These are most typical properties to all widgets and all of them can be tweaked.

To get a value of a certain property use

getWidgetProperty(QString widgetid, QString property, int index= -1) 

For example, to move first knob created above 200 points downward:

q.setWidgetProperty( "partial0", "QCS_y", q.getWidgetProperty("partial0","QCS_y")+200 )


Example: modify the maximum of each knob so that the higher partials would have smaller absolute range but the knob operations would be the same (turn maxs to 1, 0.9, 0.8 etc):

for a in range(10):
        q.setWidgetProperty(knobs[a],"QCS_maximum",1-a/10.0)

top

Deleting widgets

To delete a widget use method

destroyWidget(QString widgetid)<pre>

Example: delete all kobs created in the example above:
<pre>
for w in knobs:
    q.destroyWidget(w)

Delete all widgets of the active document:

for w in q.getWidgetUuids():
    q.destroyWidget(w)

top

Widget values and presets

Widget values can be changed and queried through the API for any of the open documents with the functions:

setChannelValue(channel, value, index = -1)
getChannelValue(channel, index = -1)
setChannelString(channel, stringvalue, int index = -1)
getChannelString(channel, index = -1)
setWidgetProperty(channel, property, value, index= -1)
getWidgetProperty(channel, property, index= -1)

Now it is also possible to load a previously stored widget preset (widget panel -> right click -> Store preset) referring to its index number. The method is:

loadPreset(int preSetIndex, int index = -1)

Currently only available in the git development repository.

It can be especially handy to use it from inside a csd to switch all the widgets to other predefined state. Example:

instr loadPreset
	index = p4
	pycalli "q.loadPreset", index
endin


top

Csound functions

Several functions can interact with the Csound engine, for example to query information about it:

getVersion() # QuteCsound API version
getSampleRate(int index)
getKsmps(int index)
getNumChannels(int index)
opcodeExists(QString opcodeName)

There are objects to send score events to any running document, without having to switch to it:

sendEvent(int index, QString events)
sendEvent(QString events)

There is a group of functions that enable to reach csound channels directly, independently from widgets (currently only in git). They are useful when testing a csd for use with csound API (in another application, a csLapdsa or Cabbage plugin, Android application) or otherwise. The functions are, hopefully selfexplanatory.:

getCsChannel(QString channel, int index = -1) # returns double
getCsStringChannel(QString channel, int index = -1) # returns string
setCsChannel(QString channel, double value, int index = -1)
setCsChannel(QString channel, QString value, int index = -1)

And there is a function which can register a Python function as a callback to be executed in between processing blocks for Csound. The first argument should be the text that should be called on every pass. It can include arguments or variables which will be evaluated every time. You can also set a number of periods to skip to avoid.

registerProcessCallback(QString func, int skipPeriods = 0)

You can register the python text to be executed on every Csound control block callback, so you can execute a block of code, or call any function which is already defined.


top

Creating simple GUIs

Use the widget panel

The easiest way to write some kind of user interface without getting into much programming is to use the CsoundQt's native widget panel. It is enough for most simpler tasks, it is easy to create, design and change the overall lookout. You can use label, lineedit, scrollnumber, menu, button and other widgets, set their sizes, colors, backgrounds, fonts, alignment etc as you wish without any coding.


Use getChannelValue for numeric and getChannelString for string (text) input to read the values from widgets. The widgets don't have to be in the same csd as your working csd or script, and the csd of the widgets does not have to be running. You can also read values from many open document (use the index parameter of the methods) and also you can make one active if needed with setDocument(index).


Example: Let's say you want to insert a line to score of "sound.csd" and ask from user for the instrument number, duration and parameter 1, that has to be a string. Suppose, sound.csd converts the texts somehow to sound. File [6] has three widgets (a spinbox - channel "instrument", scrollnumber - channel "duration" and lineedit "stringinput")

w_index=q.getDocument("widgets1.csd") 
s_index=q.getDocument("sound.csd")
q.setSco("i " + str(int(q.getChannelValue("instrument",w_index))) + " 0 " +q.getChannelString("duration",w_index) +
 " \"" + q.getChannelString("stringinput",w_index) + "\"", s_index )

Every time you run the script or execute the commands in python console or scratchpad, user input is read and a new line is written to the score.

This example is not very practical but gives an idea, how to use similar principles for more useful tasks.


top


Single question

Sometimes it is practical to ask from user just one question - number or name of something and then execute the rest f the code (it can be done also inside a csd with python opcodes).

If CsoundQt is compiled with PythonQt qt support, it is possible to reach all the means of Qt programming framework. In Qt, the class to create a dialog for one question is called QInputDialog.

To use this or any other Qt calsses, it is necessary to import The PythonQt and its Qt submodules. In most cases it is enough to add line:

from PythonQt.Qt import *

or

from PythonQt.QtGui import *

At first an object of QInputDialog must be defined, then you can use its methods getInt, getDouble, getItem or getText to read the input in the form you need.

Example 1:

from PythonQt.Qt import *

inpdia = QInputDialog()
myInt = inpdia.getInt(inpdia,"Your title","How many?")
print myInt # or do what you need

There are many parameters that you can finetune by QInputDialog, see the documentation of Qt.

Example 2: use the dialog inside csd to evaluate a global variable

<CsoundSynthesizer>
<CsOptions>
-n
</CsOptions>
<CsInstruments>

pyinit
pyruni {{
from PythonQt.Qt import *
dia = QInputDialog()
dia.setDoubleDecimals(4)
}}

giNumber pyevali {{
dia.getDouble(dia,"CS question","Enter number: ") 
}} ; initalize the number from Qt dialog

instr 1
	print giNumber
endin

</CsInstruments>
<CsScore>
i 1 0 0

</CsScore>
</CsoundSynthesizer>


top

Simple gui with buttons

Sometimes using just the CsoundQt widgets for UI is not enough. For example you want to have the window of the UI always active and you want to perform some certain actions when user presses to a button. Let's rewrite the example above about turning text into sound with the means of PythonQt. To make the example more useful, let's add a button to send a live event formed from the input fields to running csound instance (naturally, "play" has to be pressed before).

The code is fully commented and should be selfexplanatory. For more information about Qt and its python implementation, please refer to documentation [7] and [8]

The sound.csd that is run by the script, can be found here

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# simple PythonQt GUI example
# Tarmo Johannes tarmo@otsakool.edu.ee

from PythonQt.Qt import *

# FUNCTIONS==============================

def insert(): # read input from UI and insert a line to score of csd file, open in CsoundQt with index csdIndex
    scoreLine = "f0 3600\n" + "i " + instrSpinBox.text + " 0 " + durSpinBox.text + ' "' + par1LineEdit.text + "\"" 
    print scoreLine
    q.setSco(scoreLine, csdIndex)

	
def play(): # play file with index csdIndex
    print "PLAY"
    q.play(csdIndex)	

def send(): # read input from UI send live event
    scoreLine = "i " + instrSpinBox.text + " 0 " + durSpinBox.text + ' "' + par1LineEdit.text + "\"" 
    print scoreLine
    q.sendEvent(csdIndex, scoreLine)
    
def stopAndClose(): #stop csdIndex, close UI
    print "STOP"
    q.stop(csdIndex)
    window.delete()
        
    

# MAIN ====================================

window = QWidget() # window as main widget
layout = QGridLayout(window) # use gridLayout - the most flexible one - to place the widgets in a table-like structure
window.setLayout(layout) 
window.setWindowTitle("PythonQt inteface example")

instrLabel = QLabel("Select instrument")
layout.addWidget(instrLabel,0,0) # first row, first column

instrSpinBox = QSpinBox(window)
instrSpinBox.setMinimum(1)
instrSpinBox.setMaximum(3)
layout.addWidget(instrSpinBox, 0, 1) # first row, second column

durLabel = QLabel("Duration: ")
layout.addWidget(durLabel,1,0)  # etc

durSpinBox = QDoubleSpinBox(window)
durSpinBox.setMinimum(0.0)
durSpinBox.setMaximum(20)
durSpinBox.setDecimals(3)
durSpinBox.setValue(2.5)
layout.addWidget(durSpinBox, 1, 1)

par1Label = QLabel("Enter string for parameter 1: ")
layout.addWidget(par1Label,2,0)

par1LineEdit = QLineEdit(window)
par1LineEdit.setMaxLength(30) # don't allow too long strings
par1LineEdit.setText("type here") 
layout.addWidget(par1LineEdit,2,1)

insertButton = QPushButton("Insert",window)
layout.addWidget(insertButton, 3,0)

playButton = QPushButton("Play",window)
layout.addWidget(playButton, 3,1)

sendButton = QPushButton("Send event",window)
layout.addWidget(sendButton, 4,0)

closeButton = QPushButton("Close",window)
layout.addWidget(closeButton, 4,1)

# connect buttons and functions  ================
#NB! function names must be  without parenthesis!
# number and type of arguments of the signal and slot (called function) must match

insertButton.connect(SIGNAL("clicked()"),insert ) # when clicked, run function insert()
playButton.connect(SIGNAL("clicked()"),play)  #etc
sendButton.connect(SIGNAL("clicked()"),send)
closeButton.connect(SIGNAL("clicked()"),stopAndClose)

csdIndex = q.loadDocument("sound.csd") # open document and/or get index
if (csdIndex == -1):
    message = QMessageBox() # create object to output message
    message.setText("Could not open \"sound.csd\". Sorry.");
    message.exec_() # must be with underscore due to python reserved words
    exit
else: 
	window.show() # if everything is fine, show the window and wait for clicks on buttons


top

Example: color-controller

To illustrate how to use power of Qt together with CsoundQt, the following example uses color picking dialog of Qt. When user moves cursor around in the RGB palette frame, the current red-green-blue values are forwarded to CsoundQt as floats in 0..1, visualized as colored meters and used as controlling parameters for sound.

Qt's object QColorDialog emits signal currentColorChanged(QColor) every time when any of the values of RGB values in the colorbox has changed. The script connects the signal to function that forwards the color values to csound. So with one mouse movement, three parameters can be controlled instantly.

In the csound implementation of this example I used - thinking on the colors - three instruments from Richard Boulanger's "Trapped in convert" - red, green and blue. The RGB values of the dialog box control the mix between these three instruments.

The csd can be found here, the python code looks like:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Demonstarion of Qt integration with csound over CsoundQt python API

# read cuurent RGB values from Qt color dialog as control values for sound 
# csound code in "rgb-widgets.csd" must be in the same directory as the python script
# Tarmo Johannes tarmo@otsakool.edu.ee

from PythonQt.Qt import *

def getColors(currentColor): # write the current RGB values as floats 0..1 to according channels of "rgb-widgets.csd"
    q.setChannelValue("red",currentColor.redF(),csd) 
    q.setChannelValue("green",currentColor.greenF(),csd)
    q.setChannelValue("blue",currentColor.blueF(),csd)

# main-----------
cdia = QColorDialog() #create QColorDiaog object
csd = q.loadDocument("rgb-widgets.csd") # get the index of csd. Returns the index if already open. 
if ( csd == -1): # report error, if opening failed
    print "Could not find rgb-widgets.csd. Exiting"
    exit(-1)
q.setDocument(csd)# set focus to the csd
cdia.connect(SIGNAL("currentColorChanged(QColor)"),getColors) # create connection between  color changes in the dialog window and function getColors
cdia.show() # show the dialog window,
q.play(csd) # and play the csd


Happy experimenting with CsounQt's python capabilities!


Create GUI with QtDesigner

QtDesigner is a powerful frontend of Qt development tools and allows to build quickly and efficiently beautiful and enhanced GUIs. The output of QtDesigner is a xml file that is primarily meant to be included in a Qt C++ project, but it can be converted to python code - that can be used in CsoundQt - with an utility pyuic4 that comes with package python-qt4-devel or similar.


In the following example QtDesigner 4.8.4 and pyuic4 4.9.6 were used

To learn more about using QtDesigner, see http://qt-project.org/doc/qt-4.8/designer-manual.html


As a very simple example, lets create a small simple dialog with a double spinbox for frequency and a button that later will play instrument 1:


Open QtDesigner, dialog New Form will appear. Simplest choice is to choose Widget as the main widget, but it can be whatever meets your needs, of course. (For designing dialogs with a bit more widgets I find Form Layout most comfortable - you can easily inset labels for questions and widgets that gathet the info when you just double click to the form layout area).


Drag a Double Spin Box from Widget Box to the canvas. Double click it and name to freqSpinBox for clarity. You can then change its attributes in Property Editor panel. (for example minimum, maximum and single step)


You could add also a label, explaining what the spinbox is for. Drag Label to canvas and edit it.


Then add a Push Button close to other widgets. Let's change its text to 'Play' and the object's name (either with right click or in Property Editor) to 'playButton'.


Then we need to define, what happens if one presses the button. It is called connecting signal of a widget to a slot (a function that handles the action). Click to button "Edit signals/slots" in the main toolbar or use Signals/Slots editor panel. Click to the your playButton, a red line with an arrow like earthing sign will appear. Drag the arrow to the object where the handling function will be written to. Most typically it is the main widget, the Form (the gray background of the window). Dialog 'Configure Connection' will appear. In the right side (Form) click 'Edit' to add a new custom slot (your own function that you will write later). In the 'Slots' tab click 'Add'. A new slot named slot1() will be places to the list. You can rename it to play() for example. Then select the signal 'clicked()' and slot 'play()' and click 'Ok'. The connection is done and according line of code will be generated for you later.

NB! The arguments of signal and slot must match! For example if you connect signal stateChanged(int) the slot must have one int argument, like sendState2Csound(int)


When everything is ready, save the form to your working directory. The default extension is .ui for 'user interface'.


To convert the ui file to python code navigate to the folder from command line execute

pyuic4 youform.ui -o your_pythoncode.py

It is wise to generate the code for execution too (the -__main__ function) - you will get lines about defining an object using the form and showing the form. Then add also flag -x

pyuic4 youform.ui  -x -o your_pythoncode.py 


To use the code within CsoundQt open it CsoundQt as a python file or copy to your csd into pyruni {{ }} block

You need to make some replacements to make it work: - replace 'PyQt4' with 'PythonQt' in the import sentence

- in the connection lines like

QtCore.QObject.connect(self.playButton, QtCore.SIGNAL(_fromUtf8("clicked()")), Form.play)

replace 'Form' or whatever widget yoyu chose with 'self' - it is easiest to define the slots as funcstions of the same UI calss where all the UI is defined

- remove or comment out line

QtCore.QMetaObject.connectSlotsByName(Form)

since PythonQt.QrCore does not have object QMetaObject. The line does not seem to be necessary


- define the slot to the class Ui_Form:

  def play(self): # self must always be the first argument of methods of an object
        freq = self.buttonSpinBox.value # get the freq from widget
        q.sendEvent("i1 0 1 "+str(freq))


- remove lines from __main__ function that execute the application (lines using the sys module) (since Pythonqt works always in another app already) thus the remaining lines are:

if __name__ == "__main__":
    Form = QtGui.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()


Now let's write a simple instrument to test the interaction with the UI:

giSine ftgen 0, 0, 4096, 10, 1

instr 1
	ifreq = p4
	aenv linseg 1,0.1,p3,0.1
	asig poscil aenv, ifreq, giSine
	outs asig, asig
endin


If this all is done, press Run and the UI should appear.


You can find the ui, py and csd file (all named as qtd2.*) on: [9]

Personal tools