#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ PyNSMClient - A New Session Manager Client-Library in one file. The Non-Session-Manager by Jonathan Moore Liles : http://non.tuxfamily.org/nsm/ New Session Manager, by LinuxAudio.org: https://github.com/linuxaudio/new-session-manager With help from code fragments from https://github.com/attwad/python-osc ( DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE v2 ) MIT License Copyright (c) since 2014: Laborejo Software Suite , All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import sys #for qt app args. from os import getpid #we use this as jack meta data from PyQt5 import QtWidgets, QtCore #jack only import ctypes from random import uniform #to generate samples between -1.0 and 1.0 for Jack. #nsm only from nsmclient import NSMClient ######################################################################## #Prepare the Qt Window ######################################################################## class Main(QtWidgets.QWidget): def __init__(self, qtApp): super().__init__() self.qtApp = qtApp self.layout = QtWidgets.QVBoxLayout() self.layout.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) self.setLayout(self.layout) niceTitle = "PyNSM v2 Example - JACK Noise" self.title = QtWidgets.QLabel("") self.saved = QtWidgets.QLabel("") self._value = QtWidgets.QSlider(orientation = 1) #horizontal self._value.setMinimum(0) self._value.setMaximum(100) self._value.setValue(50) #only visible for the first start. self.valueLabel = QtWidgets.QLabel("Noise Volume: " + str(self._value.value())) self._value.valueChanged.connect(lambda new: self.valueLabel.setText("Noise Volume: " + str(new))) self.layout.addWidget(self.title) self.layout.addWidget(self.saved) self.layout.addWidget(self._value) self.layout.addWidget(self.valueLabel) #Prepare the NSM Client #This has to be done as soon as possible because NSM provides paths and names for us. #and may quit if NSM environment var was not found. self.nsmClient = NSMClient(prettyName = niceTitle, #will raise an error and exit if this example is not run from NSM. supportsSaveStatus = True, saveCallback = self.saveCallback, openOrNewCallback = self.openOrNewCallback, exitProgramCallback = self.exitCallback, loggingLevel = "info", #"info" for development or debugging, "error" for production. default is error. ) #If NSM did not start up properly the program quits here with an error message from NSM. #No JACK client gets created, no Qt window can be seen. self.title.setText("" + self.nsmClient.ourClientNameUnderNSM + "") self.eventLoop = QtCore.QTimer() self.eventLoop.start(100) #10ms-20ms is smooth for "real time" feeling. 100ms is still ok. self.eventLoop.timeout.connect(self.nsmClient.reactToMessage) #self.show is called as the new/open callback. @property def value(self): return str(self._value.value()) @value.setter def value(self, new): new = int(new) self._value.setValue(new) def saveCallback(self, ourPath, sessionName, ourClientNameUnderNSM): #parameters are filled in by NSM. if self.value: with open(ourPath, "w") as f: #ourpath is taken as a filename here. We have this path name at our disposal and we know we only want one file. So we don't make a directory. This way we don't have to create a dir first. f.write(self.value) def openOrNewCallback(self, ourPath, sessionName, ourClientNameUnderNSM): #parameters are filled in by NSM. try: with open(ourPath, "r") as f: savedValue = f.read() #a string self.saved.setText("{}: {}".format(ourPath, savedValue)) self.value = savedValue #internal casting to int. Sets the slider. except FileNotFoundError: self.saved.setText("{}: No save file found. Normal for first start.".format(ourPath)) finally: self.show() def exitCallback(self, ourPath, sessionName, ourClientNameUnderNSM): """This function is a callback for NSM. We have a chance to close our clients and open connections here. If not nsmclient will just kill us no matter what """ cjack.jack_remove_properties(ctypesJackClient, ctypesJackUuid) #clean our metadata cjack.jack_client_close(ctypesJackClient) #omitting this introduces problems. in Jack1 this would mute all jack clients for several seconds. exit() #or get SIGKILLed through NSM def closeEvent(self, event): """Qt likes to quits on its own. For example when the window manager closes the main window. Ignore that request and instead send a roundtrip through NSM""" self.nsmClient.serverSendExitToSelf() event.ignore() #Prepare the window instance. Gets executed at the end of this file. qtApp = QtWidgets.QApplication(sys.argv) ourClient = Main(qtApp) ######################################################################## #Prepare the JACK Client #We need the client name from NSM first. ######################################################################## cjack = ctypes.cdll.LoadLibrary("libjack.so.0") clientName = ourClient.nsmClient.prettyName #the nsm client is in the qt instance here. But in your program it can be anywhere. options = 0 status = None class jack_client_t(ctypes.Structure): _fields_ = [] cjack.jack_client_open.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)] #the two ints are enum and pointer to enum. #http://jackaudio.org/files/docs/html/group__ClientFunctions.html#gab8b16ee616207532d0585d04a0bd1d60 cjack.jack_client_open.restype = ctypes.POINTER(jack_client_t) ctypesJackClient = cjack.jack_client_open(clientName.encode("ascii"), options, status) #Create one output port class jack_port_t(ctypes.Structure): _fields_ = [] JACK_DEFAULT_AUDIO_TYPE = "32 bit float mono audio".encode("ascii") #http://jackaudio.org/files/docs/html/types_8h.html JACK_PORT_IS_OUTPUT = 0x2 #http://jackaudio.org/files/docs/html/types_8h.html portname = "output".encode("ascii") cjack.jack_port_register.argtypes = [ctypes.POINTER(jack_client_t), ctypes.c_char_p, ctypes.c_char_p, ctypes.c_ulong, ctypes.c_ulong] #http://jackaudio.org/files/docs/html/group__PortFunctions.html#ga3e21d145c3c82d273a889272f0e405e7 cjack.jack_port_register.restype = ctypes.POINTER(jack_port_t) outputPort = cjack.jack_port_register(ctypesJackClient, portname, JACK_DEFAULT_AUDIO_TYPE, JACK_PORT_IS_OUTPUT, 0) cjack.jack_client_close.argtypes = [ctypes.POINTER(jack_client_t),] #Create the callback #http://jackaudio.org/files/docs/html/group__ClientCallbacks.html#gafb5ec9fb4b736606d676c135fb97888b jack_nframes_t = ctypes.c_uint32 cjack.jack_port_get_buffer.argtypes = [ctypes.POINTER(jack_port_t), jack_nframes_t] cjack.jack_port_get_buffer.restype = ctypes.POINTER(ctypes.c_float) #this is only valid for audio, not for midi. C Jack has a pointer to void here. def pythonJackCallback(nframes, void): #types: jack_nframes_t (ctypes.c_uint32), pointer to void """http://jackaudio.org/files/docs/html/simple__client_8c.html#a01271cc6cf692278ae35d0062935d7ae""" out = cjack.jack_port_get_buffer(outputPort, nframes) #out should be a pointer to jack_default_audio_sample_t (float, ctypes.POINTER(ctypes.c_float)) #For each required sample for i in range(nframes): factor = ourClient._value.value() / 100 val = ctypes.c_float(round(uniform(-0.5, 0.5) * factor, 10)) out[i]= val return 0 # 0 on success, otherwise a non-zero error code, causing JACK to remove that client from the process() graph. JACK_CALLBACK_TYPE = ctypes.CFUNCTYPE(ctypes.c_int, jack_nframes_t, ctypes.c_void_p) #the first parameter is the return type, the following are input parameters callbackFunction = JACK_CALLBACK_TYPE(pythonJackCallback) cjack.jack_set_process_callback.argtypes = [ctypes.POINTER(jack_client_t), JACK_CALLBACK_TYPE, ctypes.c_void_p] cjack.jack_set_process_callback.restype = ctypes.c_uint32 #I think this is redundant since ctypes has int as default result type cjack.jack_set_process_callback(ctypesJackClient, callbackFunction, 0) #Ready. Activate the client. cjack.jack_activate(ctypesJackClient) #The Jack Processing functions gets called by jack in another thread. We just have to keep this program itself running. Qt does the job. #Jack Metadata - Inform the jack server about our program. Optional but has benefits when used with other programs that rely on metadata. #http://jackaudio.org/files/docs/html/group__Metadata.html jack_uuid_t = ctypes.c_uint64 cjack.jack_set_property.argtypes = [ctypes.POINTER(jack_client_t), jack_uuid_t, ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_char_p)] #client(we), subject/uuid,key,value/data,type cjack.jack_remove_properties.argtypes = [ctypes.POINTER(jack_client_t), jack_uuid_t] #for cleaning up when the program stops. the jack server can do it in newer jack versions, but this is safer. cjack.jack_get_uuid_for_client_name.argtypes = [ctypes.POINTER(jack_client_t), ctypes.c_char_p] cjack.jack_get_uuid_for_client_name.restype = ctypes.c_char_p ourJackUuid = cjack.jack_get_uuid_for_client_name(ctypesJackClient, clientName.encode("ascii")) ourJackUuid = int(ourJackUuid.decode("UTF-8")) ctypesJackUuid = jack_uuid_t(ourJackUuid) cjack.jack_set_property(ctypesJackClient, ctypesJackUuid, ctypes.c_char_p(b"pid"), ctypes.c_char_p(str(getpid()).encode()), None) ################## #Start everything qtApp.exec_()