From ca083b9a78aa201311b1b1cf5cad201802673e14 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 19 Jan 2013 16:43:37 +0000 Subject: [PATCH] Add files --- .gitignore | 11 +- INSTALL.md | 61 ++ Makefile | 166 ++++ README.md | 12 + source/carla.kdev4 | 9 + source/carla_backend.py | 1098 ++++++++++++++++++++++++++ source/carla_shared.py | 520 ++++++++++++ source/discovery/carla-discovery.cpp | 2 +- source/widgets/digitalpeakmeter.py | 12 +- source/widgets/ledbutton.py | 16 +- source/widgets/paramspinbox.py | 27 +- source/widgets/pixmapdial.py | 92 ++- source/widgets/pixmapkeyboard.py | 78 +- 13 files changed, 2012 insertions(+), 92 deletions(-) create mode 100644 INSTALL.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 source/carla.kdev4 create mode 100644 source/carla_backend.py create mode 100644 source/carla_shared.py diff --git a/.gitignore b/.gitignore index feb5bfe49..6ce38bd50 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .directory .fuse-* .*.kate-swp +.kdev_include_paths .kdev4/ # Temp files @@ -29,10 +30,12 @@ ui_*.h # Python files *.pyc ui_*.py - -# Resource files -src/resources_rc.py -qrc_resources*.cpp +source/digitalpeakmeter.py +source/ledbutton.py +source/paramspinbox.py +source/pixmapdial.py +source/pixmapkeyboard.py +source/resources_rc.py # Binaries carla-bridge-qtcreator diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 000000000..297fb9c83 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,61 @@ +# --- INSTALL for Carla --- + +To install Carla, simply run as usual:
+`$ make`
+`$ [sudo] make install` + +You can run it without installing, by using instead:
+`$ make`
+`$ python3 source/carla.py` + +Packagers can make use of the 'PREFIX' and 'DESTDIR' variable during install, like this:
+`$ make install PREFIX=/usr DESTDIR=./test-dir` + +
+ +===== BUILD DEPENDENCIES ===== +-------------------------------- +The required build dependencies are: (devel packages of these) + + - JACK + - liblo + - Qt4 + - PyQt4 + - OpenGL + +Optional but recommended: + + - FluidSynth + - LinuxSampler + +Optional for extended LV2 UIs support: + + - Gtk2 + - Gtk3 + - Suil + +Optional for native zynaddsubfx plugin: + - fftw3 + - mxml + +On Debian and Ubuntu, use these commands to install all dependencies:
+`$ sudo apt-get install libjack-dev liblo-dev libqt4-dev libfluidsynth-dev qt4-dev-tools`
+`$ sudo apt-get install libgtk2.0-dev libgtk-3-dev libsuil-dev`
+`$ sudo apt-get install libfftw3-dev libmxml-dev`
+`$ sudo apt-get install python3-pyqt4 pyqt4-dev-tools` + +NOTE: linuxsampler is not packaged in either Debian or Ubuntu, but it's available in KXStudio.
+
+ +To run all the Carla-Control, you'll additionally need: + + - python3-liblo + +Optional but recommended: + + - python3-rdflib (for LADSPA-RDF support) + +The python version used and tested is python3.2.
+After install, Carla will still work on distros with python2 as default, without any additional work. + +
diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..70c44a959 --- /dev/null +++ b/Makefile @@ -0,0 +1,166 @@ +#!/usr/bin/make -f +# Makefile for Carla # +# ---------------------- # +# Created by falkTX +# + +PREFIX = /usr/local +DESTDIR = + +SED_PREFIX = $(shell echo $(PREFIX) | sed "s/\//\\\\\\\\\//g") + +LINK = ln -s +PYUIC = pyuic4 +PYRCC = pyrcc4 -py3 + + +all: CPP RES UI WIDGETS + + +# ------------------------------------------------------------------------------------------------------------------------------------------------------ + +CPP: discovery + +discovery: + $(MAKE) -C source/discovery + +# ------------------------------------------------------------------------------------------------------------------------------------------------------ + +RES = source/resources_rc.py + +RES: $(RES) + +source/resources_rc.py: resources/resources.qrc + $(PYRCC) $< -o $@ + +# ------------------------------------------------------------------------------------------------------------------------------------------------------ + +UIs = source/ui_carla.py source/ui_carla_control.py\ + source/ui_carla_about.py source/ui_carla_database.py source/ui_carla_edit.py source/ui_carla_parameter.py source/ui_carla_plugin.py source/ui_carla_refresh.py \ + source/ui_inputdialog_value.py + +UI: $(UIs) + +source/ui_%.py: resources/ui/%.ui + $(PYUIC) $< -o $@ + +# ------------------------------------------------------------------------------------------------------------------------------------------------------ + +WIDGETS = source/digitalpeakmeter.py source/ledbutton.py source/paramspinbox.py source/pixmapdial.py source/pixmapkeyboard.py + +WIDGETS: $(WIDGETS) + +source/%.py: source/widgets/%.py + $(LINK) widgets/$*.py $@ + +# ------------------------------------------------------------------------------------------------------------------------------------------------------ + +debug: + $(MAKE) DEBUG=true + +# doxygen: +# $(MAKE) doxygen -C source/backend + +# ------------------------------------------------------------------------------------------------------------------------------------------------------ + +posix32: + $(MAKE) -C source/bridge posix32 + $(MAKE) -C source/discovery posix32 + +posix64: + $(MAKE) -C source/bridge posix64 + $(MAKE) -C source/discovery posix64 + +win32: + $(MAKE) -C source/bridge win32 + $(MAKE) -C source/discovery win32 + +win64: + $(MAKE) -C source/bridge win64 + $(MAKE) -C source/discovery win64 + +wine32: + $(MAKE) -C source/jackbridge wine32 + $(LINK) source/libs/jackbridge/libcarla-jackbridge-win32.dll.so source/bridge/libcarla-jackbridge-win32.dll + +wine64: + $(MAKE) -C source/jackbridge wine64 + $(LINK) source/libs/jackbridge/libcarla-jackbridge-win64.dll.so source/bridge/libcarla-jackbridge-win64.dll + +# ------------------------------------------------------------------------------------------------------------------------------------------------------ + +clean: + $(MAKE) clean -C source/discovery + rm -f $(RES) + rm -f $(UIs) + rm -f $(WIDGETS) + rm -f *~ source/*~ source/*.pyc +# rm -rf source/*/doxygen + +install: + # Create directories + install -d $(DESTDIR)$(PREFIX)/bin/ + install -d $(DESTDIR)$(PREFIX)/lib/carla/ + install -d $(DESTDIR)$(PREFIX)/share/applications/ + install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ + install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ + install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ + install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ + install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ + install -d $(DESTDIR)$(PREFIX)/share/carla/ + + # Install script files and binaries + install -m 755 \ + data/carla \ + data/carla-control \ + data/carla-standalone \ + $(DESTDIR)$(PREFIX)/bin/ + + # Install desktop files + install -m 644 data/*.desktop $(DESTDIR)$(PREFIX)/share/applications/ + + # Install icons, 16x16 + install -m 644 resources/16x16/carla.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ + install -m 644 resources/16x16/carla-control.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/16x16/apps/ + + # Install icons, 48x48 + install -m 644 resources/48x48/carla.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ + install -m 644 resources/48x48/carla-control.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/48x48/apps/ + + # Install icons, 128x128 + install -m 644 resources/128x128/carla.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ + install -m 644 resources/128x128/carla-control.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/128x128/apps/ + + # Install icons, 256x256 + install -m 644 resources/256x256/carla.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ + install -m 644 resources/256x256/carla-control.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/ + + # Install icons, scalable + install -m 644 resources/scalable/carla.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ + install -m 644 resources/scalable/carla-control.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/ + + # Install main code + install -m 755 source/*.py $(DESTDIR)$(PREFIX)/share/carla/ + + install -m 755 \ + source/backend/*.so \ + source/bridge/carla-bridge-* \ + source/discovery/carla-discovery-* \ + $(DESTDIR)$(PREFIX)/lib/cadence/ + + # Adjust PREFIX value in script files + sed -i "s/X-PREFIX-X/$(SED_PREFIX)/" \ + $(DESTDIR)$(PREFIX)/bin/carla \ + $(DESTDIR)$(PREFIX)/bin/carla-control \ + $(DESTDIR)$(PREFIX)/bin/carla-standalone \ + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/carla* + rm -f $(DESTDIR)$(PREFIX)/share/applications/carla.desktop + rm -f $(DESTDIR)$(PREFIX)/share/applications/carla-control.desktop + rm -f $(DESTDIR)$(PREFIX)/share/icons/hicolor/*/apps/carla.png + rm -f $(DESTDIR)$(PREFIX)/share/icons/hicolor/*/apps/carla-control.png + rm -f $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/carla.svg + rm -f $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/carla-control.svg + rm -rf $(DESTDIR)$(PREFIX)/lib/carla/ + rm -rf $(DESTDIR)$(PREFIX)/share/carla/ diff --git a/README.md b/README.md new file mode 100644 index 000000000..65f67b7be --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# --- README for Carla --- + +Carla is an audio plugin host, with support for many audio drivers and plugin formats.
+It's being developed by falkTX, using C++, Python3 and Qt4. + +It has some nice features like automation of parameters via MIDI CCs (and send control outputs back to MIDI too) and full OSC control.
+Currently supports LADSPA (including LRDF), DSSI, LV2, and VST plugin formats, with additional GIG, SF2 and SFZ file support via FluidSynth and LinuxSampler. +It uses JACK as the default and preferred audio driver, but also supports native system drivers by using RtAudio+RtMidi. + +Carla-Control is an OSC Control GUI for Carla (you get the OSC address from the Carla's about dialog, and connect to it).
+Supports controlling main UI components (Dry/Wet, Volume and Balance), and all plugins parameters.
+Peak values and control outputs are displayed as well. diff --git a/source/carla.kdev4 b/source/carla.kdev4 new file mode 100644 index 000000000..b774e5f33 --- /dev/null +++ b/source/carla.kdev4 @@ -0,0 +1,9 @@ +[Project] +Manager=KDevGenericManager +Name=Carla + +[Filters] +Excludes=*/.*,*/*~,*/*.pyc,*/ui_*.py,*/__pycache__/ + +[Project] +VersionControlSupport=kdevgit diff --git a/source/carla_backend.py b/source/carla_backend.py new file mode 100644 index 000000000..5e64e7c13 --- /dev/null +++ b/source/carla_backend.py @@ -0,0 +1,1098 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Carla Backend code +# Copyright (C) 2011-2013 Filipe Coelho +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# For a full copy of the GNU General Public License see the COPYING file + +# ------------------------------------------------------------------------------------------------------------ +# Imports (Global) + +import os +from ctypes import * +from subprocess import Popen, PIPE + +# ------------------------------------------------------------------------------------------------------------ +# Imports (Custom) + +from carla_shared import * + +try: + import ladspa_rdf + haveLRDF = True +except: + print("LRDF Support not available (LADSPA-RDF will be disabled)") + haveLRDF = False + +# ------------------------------------------------------------------------------------------------------------ +# Convert a ctypes c_char_p into a python string + +def cString(value): + if not value: + return "" + if isinstance(value, str): + return value + return value.decode("utf-8", errors="ignore") + +# ------------------------------------------------------------------------------------------------------------ +# Convert a ctypes struct into a python dict + +def structToDict(struct): + return dict((attr, getattr(struct, attr)) for attr, value in struct._fields_) + +# ------------------------------------------------------------------------------------------------------------ +# Default Plugin Folders + +if WINDOWS: + splitter = ";" + APPDATA = os.getenv("APPDATA") + PROGRAMFILES = os.getenv("PROGRAMFILES") + PROGRAMFILESx86 = os.getenv("PROGRAMFILES(x86)") + COMMONPROGRAMFILES = os.getenv("COMMONPROGRAMFILES") + + # Small integrity tests + if not APPDATA: + print("APPDATA variable not set, cannot continue") + sys.exit(1) + + if not PROGRAMFILES: + print("PROGRAMFILES variable not set, cannot continue") + sys.exit(1) + + if not COMMONPROGRAMFILES: + print("COMMONPROGRAMFILES variable not set, cannot continue") + sys.exit(1) + + DEFAULT_LADSPA_PATH = [ + os.path.join(APPDATA, "LADSPA"), + os.path.join(PROGRAMFILES, "LADSPA") + ] + + DEFAULT_DSSI_PATH = [ + os.path.join(APPDATA, "DSSI"), + os.path.join(PROGRAMFILES, "DSSI") + ] + + DEFAULT_LV2_PATH = [ + os.path.join(APPDATA, "LV2"), + os.path.join(COMMONPROGRAMFILES, "LV2") + ] + + DEFAULT_VST_PATH = [ + os.path.join(PROGRAMFILES, "VstPlugins"), + os.path.join(PROGRAMFILES, "Steinberg", "VstPlugins") + ] + + DEFAULT_GIG_PATH = [ + os.path.join(APPDATA, "GIG") + ] + + DEFAULT_SF2_PATH = [ + os.path.join(APPDATA, "SF2") + ] + + DEFAULT_SFZ_PATH = [ + os.path.join(APPDATA, "SFZ") + ] + + if PROGRAMFILESx86: + DEFAULT_LADSPA_PATH.append(os.path.join(PROGRAMFILESx86, "LADSPA")) + DEFAULT_DSSI_PATH.append(os.path.join(PROGRAMFILESx86, "DSSI")) + DEFAULT_VST_PATH.append(os.path.join(PROGRAMFILESx86, "VstPlugins")) + DEFAULT_VST_PATH.append(os.path.join(PROGRAMFILESx86, "Steinberg", "VstPlugins")) + +elif HAIKU: + splitter = ":" + + DEFAULT_LADSPA_PATH = [ + # TODO + ] + + DEFAULT_DSSI_PATH = [ + # TODO + ] + + DEFAULT_LV2_PATH = [ + # TODO + ] + + DEFAULT_VST_PATH = [ + # TODO + ] + + DEFAULT_GIG_PATH = [ + # TODO + ] + + DEFAULT_SF2_PATH = [ + # TODO + ] + + DEFAULT_SFZ_PATH = [ + # TODO + ] + +elif MACOS: + splitter = ":" + + DEFAULT_LADSPA_PATH = [ + os.path.join(HOME, "Library", "Audio", "Plug-Ins", "LADSPA"), + os.path.join("/", "Library", "Audio", "Plug-Ins", "LADSPA") + ] + + DEFAULT_DSSI_PATH = [ + os.path.join(HOME, "Library", "Audio", "Plug-Ins", "DSSI"), + os.path.join("/", "Library", "Audio", "Plug-Ins", "DSSI") + ] + + DEFAULT_LV2_PATH = [ + os.path.join(HOME, "Library", "Audio", "Plug-Ins", "LV2"), + os.path.join("/", "Library", "Audio", "Plug-Ins", "LV2") + ] + + DEFAULT_VST_PATH = [ + os.path.join(HOME, "Library", "Audio", "Plug-Ins", "VST"), + os.path.join("/", "Library", "Audio", "Plug-Ins", "VST") + ] + + DEFAULT_GIG_PATH = [ + # TODO + ] + + DEFAULT_SF2_PATH = [ + # TODO + ] + + DEFAULT_SFZ_PATH = [ + # TODO + ] + +else: + splitter = ":" + + DEFAULT_LADSPA_PATH = [ + os.path.join(HOME, ".ladspa"), + os.path.join("/", "usr", "lib", "ladspa"), + os.path.join("/", "usr", "local", "lib", "ladspa") + ] + + DEFAULT_DSSI_PATH = [ + os.path.join(HOME, ".dssi"), + os.path.join("/", "usr", "lib", "dssi"), + os.path.join("/", "usr", "local", "lib", "dssi") + ] + + DEFAULT_LV2_PATH = [ + os.path.join(HOME, ".lv2"), + os.path.join("/", "usr", "lib", "lv2"), + os.path.join("/", "usr", "local", "lib", "lv2") + ] + + DEFAULT_VST_PATH = [ + os.path.join(HOME, ".vst"), + os.path.join("/", "usr", "lib", "vst"), + os.path.join("/", "usr", "local", "lib", "vst") + ] + + DEFAULT_GIG_PATH = [ + os.path.join(HOME, ".sounds"), + os.path.join("/", "usr", "share", "sounds", "gig") + ] + + DEFAULT_SF2_PATH = [ + os.path.join(HOME, ".sounds"), + os.path.join("/", "usr", "share", "sounds", "sf2") + ] + + DEFAULT_SFZ_PATH = [ + os.path.join(HOME, ".sounds"), + os.path.join("/", "usr", "share", "sounds", "sfz") + ] + +# ------------------------------------------------------------------------------------------------------------ +# Default Plugin Folders (set) + +global LADSPA_PATH, DSSI_PATH, LV2_PATH, VST_PATH, GIG_PATH, SF2_PATH, SFZ_PATH + +LADSPA_PATH_env = os.getenv("LADSPA_PATH") +DSSI_PATH_env = os.getenv("DSSI_PATH") +LV2_PATH_env = os.getenv("LV2_PATH") +VST_PATH_env = os.getenv("VST_PATH") +GIG_PATH_env = os.getenv("GIG_PATH") +SF2_PATH_env = os.getenv("SF2_PATH") +SFZ_PATH_env = os.getenv("SFZ_PATH") + +if LADSPA_PATH_env: + LADSPA_PATH = LADSPA_PATH_env.split(splitter) +else: + LADSPA_PATH = DEFAULT_LADSPA_PATH + +if DSSI_PATH_env: + DSSI_PATH = DSSI_PATH_env.split(splitter) +else: + DSSI_PATH = DEFAULT_DSSI_PATH + +if LV2_PATH_env: + LV2_PATH = LV2_PATH_env.split(splitter) +else: + LV2_PATH = DEFAULT_LV2_PATH + +if VST_PATH_env: + VST_PATH = VST_PATH_env.split(splitter) +else: + VST_PATH = DEFAULT_VST_PATH + +if GIG_PATH_env: + GIG_PATH = GIG_PATH_env.split(splitter) +else: + GIG_PATH = DEFAULT_GIG_PATH + +if SF2_PATH_env: + SF2_PATH = SF2_PATH_env.split(splitter) +else: + SF2_PATH = DEFAULT_SF2_PATH + +if SFZ_PATH_env: + SFZ_PATH = SFZ_PATH_env.split(splitter) +else: + SFZ_PATH = DEFAULT_SFZ_PATH + +if haveLRDF: + LADSPA_RDF_PATH_env = os.getenv("LADSPA_RDF_PATH") + if LADSPA_RDF_PATH_env: + ladspa_rdf.set_rdf_path(LADSPA_RDF_PATH_env.split(splitter)) + del LADSPA_RDF_PATH_env + +del LADSPA_PATH_env +del DSSI_PATH_env +del LV2_PATH_env +del VST_PATH_env +del GIG_PATH_env +del SF2_PATH_env +del SFZ_PATH_env + +# ------------------------------------------------------------------------------------------------------------ +# Search for Carla library and tools + +global carla_library_path +carla_library_path = "" + +carla_discovery_native = "" +carla_discovery_posix32 = "" +carla_discovery_posix64 = "" +carla_discovery_win32 = "" +carla_discovery_win64 = "" + +carla_bridge_native = "" +carla_bridge_posix32 = "" +carla_bridge_posix64 = "" +carla_bridge_win32 = "" +carla_bridge_win64 = "" + +carla_bridge_lv2_gtk2 = "" +carla_bridge_lv2_gtk3 = "" +carla_bridge_lv2_qt4 = "" +carla_bridge_lv2_qt5 = "" +carla_bridge_lv2_cocoa = "" +carla_bridge_lv2_windows = "" +carla_bridge_lv2_x11 = "" + +carla_bridge_vst_cocoa = "" +carla_bridge_vst_hwnd = "" +carla_bridge_vst_x11 = "" + +if WINDOWS: + carla_libname = "carla_backend.dll" +elif MACOS: + carla_libname = "carla_backend.dylib" +else: + carla_libname = "carla_backend.so" + +CWD = sys.path[0] +CWDpp = os.path.join(CWD, "..", "c++") + +# make it work with cxfreeze +if CWD.endswith("%scarla" % os.sep): + CWD = CWD.rsplit("%scarla" % os.sep, 1)[0] + CWDpp = CWD + +# find carla_library_path +if os.path.exists(os.path.join(CWDpp, "carla-backend", carla_libname)): + carla_library_path = os.path.join(CWDpp, "carla-backend", carla_libname) +else: + if WINDOWS: + CARLA_PATH = (os.path.join(PROGRAMFILES, "Cadence", "carla"),) + elif MACOS: + CARLA_PATH = ("/opt/local/lib", "/usr/local/lib/", "/usr/lib") + else: + CARLA_PATH = ("/usr/local/lib/", "/usr/lib") + + for path in CARLA_PATH: + if os.path.exists(os.path.join(path, "cadence", carla_libname)): + carla_library_path = os.path.join(path, "cadence", carla_libname) + break + +# find any tool +def findTool(tdir, tname): + if os.path.exists(os.path.join(CWDpp, tdir, tname)): + return os.path.join(CWDpp, tdir, tname) + + for p in PATH: + if os.path.exists(os.path.join(p, tname)): + return os.path.join(p, tname) + + return "" + +# find wine/windows tools +carla_discovery_win32 = findTool("carla-discovery", "carla-discovery-win32.exe") +carla_discovery_win64 = findTool("carla-discovery", "carla-discovery-win64.exe") +carla_bridge_win32 = findTool("carla-bridge", "carla-bridge-win32.exe") +carla_bridge_win64 = findTool("carla-bridge", "carla-bridge-win64.exe") + +# find native and posix only tools +if not WINDOWS: + carla_discovery_native = findTool("carla-discovery", "carla-discovery-native") + carla_discovery_posix32 = findTool("carla-discovery", "carla-discovery-posix32") + carla_discovery_posix64 = findTool("carla-discovery", "carla-discovery-posix64") + carla_bridge_native = findTool("carla-bridge", "carla-bridge-native") + carla_bridge_posix32 = findTool("carla-bridge", "carla-bridge-posix32") + carla_bridge_posix64 = findTool("carla-bridge", "carla-bridge-posix64") + +# find windows only tools +if WINDOWS: + carla_bridge_lv2_windows = findTool("carla-bridge", "carla-bridge-lv2-windows.exe") + carla_bridge_vst_hwnd = findTool("carla-bridge", "carla-bridge-vst-hwnd.exe") + +# find mac os only tools +elif MACOS: + carla_bridge_lv2_cocoa = findTool("carla-bridge", "carla-bridge-lv2-cocoa") + carla_bridge_vst_cocoa = findTool("carla-bridge", "carla-bridge-vst-cocoa") + +# find generic tools +else: + carla_bridge_lv2_gtk2 = findTool("carla-bridge", "carla-bridge-lv2-gtk2") + carla_bridge_lv2_gtk3 = findTool("carla-bridge", "carla-bridge-lv2-gtk3") + carla_bridge_lv2_qt4 = findTool("carla-bridge", "carla-bridge-lv2-qt4") + carla_bridge_lv2_qt5 = findTool("carla-bridge", "carla-bridge-lv2-qt5") + +# find linux only tools +if LINUX: + carla_bridge_lv2_x11 = os.path.join("carla-bridge", "carla-bridge-lv2-x11") + carla_bridge_vst_x11 = os.path.join("carla-bridge", "carla-bridge-vst-x11") + +# ------------------------------------------------------------------------------------------------------------ +# Plugin Query (helper functions) + +def findBinaries(bPATH, OS): + binaries = [] + + if OS == "WINDOWS": + extensions = (".dll",) + elif OS == "MACOS": + extensions = (".dylib", ".so") + else: + extensions = (".so", ".sO", ".SO", ".So") + + for root, dirs, files in os.walk(bPATH): + for name in [name for name in files if name.endswith(extensions)]: + binaries.append(os.path.join(root, name)) + + return binaries + +def findLV2Bundles(bPATH): + bundles = [] + extensions = (".lv2", ".lV2", ".LV2", ".Lv2") if not WINDOWS else (".lv2",) + + for root, dirs, files in os.walk(bPATH): + for dir_ in [dir_ for dir_ in dirs if dir_.endswith(extensions)]: + bundles.append(os.path.join(root, dir_)) + + return bundles + +def findSoundKits(bPATH, stype): + soundfonts = [] + + if stype == "gig": + extensions = (".gig", ".giG", ".gIG", ".GIG", ".GIg", ".Gig") if not WINDOWS else (".gig",) + elif stype == "sf2": + extensions = (".sf2", ".sF2", ".SF2", ".Sf2") if not WINDOWS else (".sf2",) + elif stype == "sfz": + extensions = (".sfz", ".sfZ", ".sFZ", ".SFZ", ".SFz", ".Sfz") if not WINDOWS else (".sfz",) + else: + return [] + + for root, dirs, files in os.walk(bPATH): + for name in [name for name in files if name.endswith(extensions)]: + soundfonts.append(os.path.join(root, name)) + + return soundfonts + +def findDSSIGUI(filename, name, label): + pluginDir = filename.rsplit(".", 1)[0] + shortName = os.path.basename(pluginDir) + guiFilename = "" + + checkName = name.replace(" ", "_") + checkLabel = label + checkSName = shortName + + if checkName[-1] != "_": checkName += "_" + if checkLabel[-1] != "_": checkLabel += "_" + if checkSName[-1] != "_": checkSName += "_" + + for root, dirs, files in os.walk(pluginDir): + guiFiles = files + break + else: + guiFiles = [] + + for gui in guiFiles: + if gui.startswith(checkName) or gui.startswith(checkLabel) or gui.startswith(checkSName): + guiFilename = os.path.join(pluginDir, gui) + break + + return guiFilename + +# ------------------------------------------------------------------------------------------------------------ +# Plugin Query + +PLUGIN_QUERY_API_VERSION = 1 + +PyPluginInfo = { + 'API': PLUGIN_QUERY_API_VERSION, + 'build': 0, # BINARY_NONE + 'type': 0, # PLUGIN_NONE + 'hints': 0x0, + 'binary': "", + 'name': "", + 'label': "", + 'maker': "", + 'copyright': "", + 'unique_id': 0, + 'audio.ins': 0, + 'audio.outs': 0, + 'audio.totals': 0, + 'midi.ins': 0, + 'midi.outs': 0, + 'midi.totals': 0, + 'parameters.ins': 0, + 'parameters.outs': 0, + 'parameters.total': 0, + 'programs.total': 0 +} + +def runCarlaDiscovery(itype, stype, filename, tool, isWine=False): + fakeLabel = os.path.basename(filename).rsplit(".", 1)[0] + plugins = [] + command = [] + + if LINUX or MACOS: + command.append("env") + command.append("LANG=C") + if isWine: + command.append("WINEDEBUG=-all") + + command.append(tool) + command.append(stype) + command.append(filename) + + Ps = Popen(command, stdout=PIPE) + Ps.wait() + output = Ps.stdout.read().decode("utf-8", errors="ignore").split("\n") + + pinfo = None + + for line in output: + line = line.strip() + if line == "carla-discovery::init::-----------": + pinfo = deepcopy(PyPluginInfo) + pinfo['type'] = itype + pinfo['binary'] = filename + + elif line == "carla-discovery::end::------------": + if pinfo != None: + plugins.append(pinfo) + pinfo = None + + elif line == "Segmentation fault": + print("carla-discovery::crash::%s crashed during discovery" % filename) + + elif line.startswith("err:module:import_dll Library"): + print(line) + + elif line.startswith("carla-discovery::error::"): + print("%s - %s" % (line, filename)) + + elif line.startswith("carla-discovery::"): + if pinfo == None: + continue + + prop, value = line.replace("carla-discovery::", "").split("::", 1) + + if prop == "name": + pinfo['name'] = value if value else fakeLabel + elif prop == "label": + pinfo['label'] = value if value else fakeLabel + elif prop == "maker": + pinfo['maker'] = value + elif prop == "copyright": + pinfo['copyright'] = value + elif prop == "unique_id": + if value.isdigit(): pinfo['unique_id'] = int(value) + elif prop == "hints": + if value.isdigit(): pinfo['hints'] = int(value) + elif prop == "audio.ins": + if value.isdigit(): pinfo['audio.ins'] = int(value) + elif prop == "audio.outs": + if value.isdigit(): pinfo['audio.outs'] = int(value) + elif prop == "audio.total": + if value.isdigit(): pinfo['audio.total'] = int(value) + elif prop == "midi.ins": + if value.isdigit(): pinfo['midi.ins'] = int(value) + elif prop == "midi.outs": + if value.isdigit(): pinfo['midi.outs'] = int(value) + elif prop == "midi.total": + if value.isdigit(): pinfo['midi.total'] = int(value) + elif prop == "parameters.ins": + if value.isdigit(): pinfo['parameters.ins'] = int(value) + elif prop == "parameters.outs": + if value.isdigit(): pinfo['parameters.outs'] = int(value) + elif prop == "parameters.total": + if value.isdigit(): pinfo['parameters.total'] = int(value) + elif prop == "programs.total": + if value.isdigit(): pinfo['programs.total'] = int(value) + elif prop == "build": + if value.isdigit(): pinfo['build'] = int(value) + + # Additional checks + for pinfo in plugins: + if itype == PLUGIN_DSSI: + if findDSSIGUI(pinfo['binary'], pinfo['name'], pinfo['label']): + pinfo['hints'] |= PLUGIN_HAS_GUI + + return plugins + +def checkPluginInternal(desc): + plugins = [] + + pinfo = deepcopy(PyPluginInfo) + pinfo['type'] = PLUGIN_INTERNAL + pinfo['name'] = cString(desc['name']) + pinfo['label'] = cString(desc['label']) + pinfo['maker'] = cString(desc['maker']) + pinfo['copyright'] = cString(desc['copyright']) + pinfo['hints'] = int(desc['hints']) + pinfo['build'] = BINARY_NATIVE + + plugins.append(pinfo) + return plugins + +def checkPluginLADSPA(filename, tool, isWine=False): + return runCarlaDiscovery(PLUGIN_LADSPA, "LADSPA", filename, tool, isWine) + +def checkPluginDSSI(filename, tool, isWine=False): + return runCarlaDiscovery(PLUGIN_DSSI, "DSSI", filename, tool, isWine) + +def checkPluginLV2(filename, tool, isWine=False): + return runCarlaDiscovery(PLUGIN_LV2, "LV2", filename, tool, isWine) + +def checkPluginVST(filename, tool, isWine=False): + return runCarlaDiscovery(PLUGIN_VST, "VST", filename, tool, isWine) + +def checkPluginGIG(filename, tool): + return runCarlaDiscovery(PLUGIN_GIG, "GIG", filename, tool) + +def checkPluginSF2(filename, tool): + return runCarlaDiscovery(PLUGIN_SF2, "SF2", filename, tool) + +def checkPluginSFZ(filename, tool): + return runCarlaDiscovery(PLUGIN_SFZ, "SFZ", filename, tool) + +# ------------------------------------------------------------------------------------------------------------ +# Backend C++ -> Python variables + +c_enum = c_int +c_nullptr = None + +if is64bit: + c_uintptr = c_uint64 +else: + c_uintptr = c_uint32 + +class ParameterData(Structure): + _fields_ = [ + ("type", c_enum), + ("index", c_int32), + ("rindex", c_int32), + ("hints", c_int32), + ("midiChannel", c_uint8), + ("midiCC", c_int16) + ] + +class ParameterRanges(Structure): + _fields_ = [ + ("def", c_float), + ("min", c_float), + ("max", c_float), + ("step", c_float), + ("stepSmall", c_float), + ("stepLarge", c_float) + ] + +class MidiProgramData(Structure): + _fields_ = [ + ("bank", c_uint32), + ("program", c_uint32), + ("label", c_char_p) + ] + +class CustomData(Structure): + _fields_ = [ + ("type", c_char_p), + ("key", c_char_p), + ("value", c_char_p) + ] + +class PluginInfo(Structure): + _fields_ = [ + ("type", c_enum), + ("category", c_enum), + ("hints", c_uint), + ("binary", c_char_p), + ("name", c_char_p), + ("label", c_char_p), + ("maker", c_char_p), + ("copyright", c_char_p), + ("uniqueId", c_long) + ] + +class PortCountInfo(Structure): + _fields_ = [ + ("ins", c_uint32), + ("outs", c_uint32), + ("total", c_uint32) + ] + +class ParameterInfo(Structure): + _fields_ = [ + ("name", c_char_p), + ("symbol", c_char_p), + ("unit", c_char_p), + ("scalePointCount", c_uint32) + ] + +class ScalePointInfo(Structure): + _fields_ = [ + ("value", c_double), + ("label", c_char_p) + ] + +class GuiInfo(Structure): + _fields_ = [ + ("type", c_enum), + ("resizable", c_bool), + ] + +CallbackFunc = CFUNCTYPE(None, c_void_p, c_enum, c_ushort, c_int, c_int, c_double, c_char_p) + +# ------------------------------------------------------------------------------------------------------------ +# Backend C++ -> Python object + +class Host(object): + def __init__(self, lib_prefix_arg): + object.__init__(self) + + global carla_library_path + + if lib_prefix_arg: + carla_library_path = os.path.join(lib_prefix_arg, "lib", "cadence", carla_libname) + + if not carla_library_path: + self.lib = None + return + + self.lib = cdll.LoadLibrary(carla_library_path) + + self.lib.get_extended_license_text.argtypes = None + self.lib.get_extended_license_text.restype = c_char_p + + self.lib.get_engine_driver_count.argtypes = None + self.lib.get_engine_driver_count.restype = c_uint + + self.lib.get_engine_driver_name.argtypes = [c_uint] + self.lib.get_engine_driver_name.restype = c_char_p + + self.lib.get_internal_plugin_count.argtypes = None + self.lib.get_internal_plugin_count.restype = c_uint + + self.lib.get_internal_plugin_info.argtypes = [c_uint] + self.lib.get_internal_plugin_info.restype = POINTER(PluginInfo) + + self.lib.engine_init.argtypes = [c_char_p, c_char_p] + self.lib.engine_init.restype = c_bool + + self.lib.engine_close.argtypes = None + self.lib.engine_close.restype = c_bool + + self.lib.is_engine_running.argtypes = None + self.lib.is_engine_running.restype = c_bool + + self.lib.add_plugin.argtypes = [c_enum, c_enum, c_char_p, c_char_p, c_char_p, c_void_p] + self.lib.add_plugin.restype = c_short + + self.lib.remove_plugin.argtypes = [c_ushort] + self.lib.remove_plugin.restype = c_bool + + self.lib.get_plugin_info.argtypes = [c_ushort] + self.lib.get_plugin_info.restype = POINTER(PluginInfo) + + self.lib.get_audio_port_count_info.argtypes = [c_ushort] + self.lib.get_audio_port_count_info.restype = POINTER(PortCountInfo) + + self.lib.get_midi_port_count_info.argtypes = [c_ushort] + self.lib.get_midi_port_count_info.restype = POINTER(PortCountInfo) + + self.lib.get_parameter_count_info.argtypes = [c_ushort] + self.lib.get_parameter_count_info.restype = POINTER(PortCountInfo) + + self.lib.get_parameter_info.argtypes = [c_ushort, c_uint32] + self.lib.get_parameter_info.restype = POINTER(ParameterInfo) + + self.lib.get_parameter_scalepoint_info.argtypes = [c_ushort, c_uint32, c_uint32] + self.lib.get_parameter_scalepoint_info.restype = POINTER(ScalePointInfo) + + self.lib.get_gui_info.argtypes = [c_ushort] + self.lib.get_gui_info.restype = POINTER(GuiInfo) + + self.lib.get_parameter_data.argtypes = [c_ushort, c_uint32] + self.lib.get_parameter_data.restype = POINTER(ParameterData) + + self.lib.get_parameter_ranges.argtypes = [c_ushort, c_uint32] + self.lib.get_parameter_ranges.restype = POINTER(ParameterRanges) + + self.lib.get_midi_program_data.argtypes = [c_ushort, c_uint32] + self.lib.get_midi_program_data.restype = POINTER(MidiProgramData) + + self.lib.get_custom_data.argtypes = [c_ushort, c_uint32] + self.lib.get_custom_data.restype = POINTER(CustomData) + + self.lib.get_chunk_data.argtypes = [c_ushort] + self.lib.get_chunk_data.restype = c_char_p + + self.lib.get_parameter_count.argtypes = [c_ushort] + self.lib.get_parameter_count.restype = c_uint32 + + self.lib.get_program_count.argtypes = [c_ushort] + self.lib.get_program_count.restype = c_uint32 + + self.lib.get_midi_program_count.argtypes = [c_ushort] + self.lib.get_midi_program_count.restype = c_uint32 + + self.lib.get_custom_data_count.argtypes = [c_ushort] + self.lib.get_custom_data_count.restype = c_uint32 + + self.lib.get_parameter_text.argtypes = [c_ushort, c_uint32] + self.lib.get_parameter_text.restype = c_char_p + + self.lib.get_program_name.argtypes = [c_ushort, c_uint32] + self.lib.get_program_name.restype = c_char_p + + self.lib.get_midi_program_name.argtypes = [c_ushort, c_uint32] + self.lib.get_midi_program_name.restype = c_char_p + + self.lib.get_real_plugin_name.argtypes = [c_ushort] + self.lib.get_real_plugin_name.restype = c_char_p + + self.lib.get_current_program_index.argtypes = [c_ushort] + self.lib.get_current_program_index.restype = c_int32 + + self.lib.get_current_midi_program_index.argtypes = [c_ushort] + self.lib.get_current_midi_program_index.restype = c_int32 + + self.lib.get_default_parameter_value.argtypes = [c_ushort, c_uint32] + self.lib.get_default_parameter_value.restype = c_double + + self.lib.get_current_parameter_value.argtypes = [c_ushort, c_uint32] + self.lib.get_current_parameter_value.restype = c_double + + self.lib.get_input_peak_value.argtypes = [c_ushort, c_ushort] + self.lib.get_input_peak_value.restype = c_double + + self.lib.get_output_peak_value.argtypes = [c_ushort, c_ushort] + self.lib.get_output_peak_value.restype = c_double + + self.lib.set_active.argtypes = [c_ushort, c_bool] + self.lib.set_active.restype = None + + self.lib.set_drywet.argtypes = [c_ushort, c_double] + self.lib.set_drywet.restype = None + + self.lib.set_volume.argtypes = [c_ushort, c_double] + self.lib.set_volume.restype = None + + self.lib.set_balance_left.argtypes = [c_ushort, c_double] + self.lib.set_balance_left.restype = None + + self.lib.set_balance_right.argtypes = [c_ushort, c_double] + self.lib.set_balance_right.restype = None + + self.lib.set_parameter_value.argtypes = [c_ushort, c_uint32, c_double] + self.lib.set_parameter_value.restype = None + + self.lib.set_parameter_midi_cc.argtypes = [c_ushort, c_uint32, c_int16] + self.lib.set_parameter_midi_cc.restype = None + + self.lib.set_parameter_midi_channel.argtypes = [c_ushort, c_uint32, c_uint8] + self.lib.set_parameter_midi_channel.restype = None + + self.lib.set_program.argtypes = [c_ushort, c_uint32] + self.lib.set_program.restype = None + + self.lib.set_midi_program.argtypes = [c_ushort, c_uint32] + self.lib.set_midi_program.restype = None + + self.lib.set_custom_data.argtypes = [c_ushort, c_char_p, c_char_p, c_char_p] + self.lib.set_custom_data.restype = None + + self.lib.set_chunk_data.argtypes = [c_ushort, c_char_p] + self.lib.set_chunk_data.restype = None + + self.lib.set_gui_container.argtypes = [c_ushort, c_uintptr] + self.lib.set_gui_container.restype = None + + self.lib.show_gui.argtypes = [c_ushort, c_bool] + self.lib.show_gui.restype = None + + self.lib.idle_guis.argtypes = None + self.lib.idle_guis.restype = None + + self.lib.send_midi_note.argtypes = [c_ushort, c_uint8, c_uint8, c_uint8] + self.lib.send_midi_note.restype = None + + self.lib.prepare_for_save.argtypes = [c_ushort] + self.lib.prepare_for_save.restype = None + + self.lib.get_buffer_size.argtypes = None + self.lib.get_buffer_size.restype = c_uint32 + + self.lib.get_sample_rate.argtypes = None + self.lib.get_sample_rate.restype = c_double + + self.lib.get_last_error.argtypes = None + self.lib.get_last_error.restype = c_char_p + + self.lib.get_host_osc_url.argtypes = None + self.lib.get_host_osc_url.restype = c_char_p + + self.lib.set_callback_function.argtypes = [CallbackFunc] + self.lib.set_callback_function.restype = None + + self.lib.set_option.argtypes = [c_enum, c_int, c_char_p] + self.lib.set_option.restype = None + + self.lib.nsm_announce.argtypes = [c_char_p, c_int] + self.lib.nsm_announce.restype = None + + self.lib.nsm_reply_open.argtypes = None + self.lib.nsm_reply_open.restype = None + + self.lib.nsm_reply_save.argtypes = None + self.lib.nsm_reply_save.restype = None + + def get_extended_license_text(self): + return self.lib.get_extended_license_text() + + def get_engine_driver_count(self): + return self.lib.get_engine_driver_count() + + def get_engine_driver_name(self, index): + return self.lib.get_engine_driver_name(index) + + def get_internal_plugin_count(self): + return self.lib.get_internal_plugin_count() + + def get_internal_plugin_info(self, index): + return structToDict(self.lib.get_internal_plugin_info(index).contents) + + def engine_init(self, driverName, clientName): + return self.lib.engine_init(driverName.encode("utf-8"), clientName.encode("utf-8")) + + def engine_close(self): + return self.lib.engine_close() + + def is_engine_running(self): + return self.lib.is_engine_running() + + def add_plugin(self, btype, ptype, filename, name, label, extraStuff): + return self.lib.add_plugin(btype, ptype, filename.encode("utf-8"), name.encode("utf-8") if name else c_nullptr, label.encode("utf-8"), cast(extraStuff, c_void_p)) + + def remove_plugin(self, pluginId): + return self.lib.remove_plugin(pluginId) + + def get_plugin_info(self, pluginId): + return structToDict(self.lib.get_plugin_info(pluginId).contents) + + def get_audio_port_count_info(self, pluginId): + return structToDict(self.lib.get_audio_port_count_info(pluginId).contents) + + def get_midi_port_count_info(self, pluginId): + return structToDict(self.lib.get_midi_port_count_info(pluginId).contents) + + def get_parameter_count_info(self, pluginId): + return structToDict(self.lib.get_parameter_count_info(pluginId).contents) + + def get_parameter_info(self, pluginId, parameterId): + return structToDict(self.lib.get_parameter_info(pluginId, parameterId).contents) + + def get_parameter_scalepoint_info(self, pluginId, parameterId, scalePointId): + return structToDict(self.lib.get_parameter_scalepoint_info(pluginId, parameterId, scalePointId).contents) + + def get_parameter_data(self, pluginId, parameterId): + return structToDict(self.lib.get_parameter_data(pluginId, parameterId).contents) + + def get_parameter_ranges(self, pluginId, parameterId): + return structToDict(self.lib.get_parameter_ranges(pluginId, parameterId).contents) + + def get_midi_program_data(self, pluginId, midiProgramId): + return structToDict(self.lib.get_midi_program_data(pluginId, midiProgramId).contents) + + def get_custom_data(self, pluginId, customDataId): + return structToDict(self.lib.get_custom_data(pluginId, customDataId).contents) + + def get_chunk_data(self, pluginId): + return self.lib.get_chunk_data(pluginId) + + def get_gui_info(self, pluginId): + return structToDict(self.lib.get_gui_info(pluginId).contents) + + def get_parameter_count(self, pluginId): + return self.lib.get_parameter_count(pluginId) + + def get_program_count(self, pluginId): + return self.lib.get_program_count(pluginId) + + def get_midi_program_count(self, pluginId): + return self.lib.get_midi_program_count(pluginId) + + def get_custom_data_count(self, pluginId): + return self.lib.get_custom_data_count(pluginId) + + def get_parameter_text(self, pluginId, parameterId): + return self.lib.get_parameter_text(pluginId, parameterId) + + def get_program_name(self, pluginId, programId): + return self.lib.get_program_name(pluginId, programId) + + def get_midi_program_name(self, pluginId, midiProgramId): + return self.lib.get_midi_program_name(pluginId, midiProgramId) + + def get_real_plugin_name(self, pluginId): + return self.lib.get_real_plugin_name(pluginId) + + def get_current_program_index(self, pluginId): + return self.lib.get_current_program_index(pluginId) + + def get_current_midi_program_index(self, pluginId): + return self.lib.get_current_midi_program_index(pluginId) + + def get_default_parameter_value(self, pluginId, parameterId): + return self.lib.get_default_parameter_value(pluginId, parameterId) + + def get_current_parameter_value(self, pluginId, parameterId): + return self.lib.get_current_parameter_value(pluginId, parameterId) + + def get_input_peak_value(self, pluginId, portId): + return self.lib.get_input_peak_value(pluginId, portId) + + def get_output_peak_value(self, pluginId, portId): + return self.lib.get_output_peak_value(pluginId, portId) + + def set_active(self, pluginId, onOff): + self.lib.set_active(pluginId, onOff) + + def set_drywet(self, pluginId, value): + self.lib.set_drywet(pluginId, value) + + def set_volume(self, pluginId, value): + self.lib.set_volume(pluginId, value) + + def set_balance_left(self, pluginId, value): + self.lib.set_balance_left(pluginId, value) + + def set_balance_right(self, pluginId, value): + self.lib.set_balance_right(pluginId, value) + + def set_parameter_value(self, pluginId, parameterId, value): + self.lib.set_parameter_value(pluginId, parameterId, value) + + def set_parameter_midi_cc(self, pluginId, parameterId, cc): + self.lib.set_parameter_midi_cc(pluginId, parameterId, cc) + + def set_parameter_midi_channel(self, pluginId, parameterId, channel): + self.lib.set_parameter_midi_channel(pluginId, parameterId, channel) + + def set_program(self, pluginId, programId): + self.lib.set_program(pluginId, programId) + + def set_midi_program(self, pluginId, midiProgramId): + self.lib.set_midi_program(pluginId, midiProgramId) + + def set_custom_data(self, pluginId, type_, key, value): + self.lib.set_custom_data(pluginId, type_.encode("utf-8"), key.encode("utf-8"), value.encode("utf-8")) + + def set_chunk_data(self, pluginId, chunkData): + self.lib.set_chunk_data(pluginId, chunkData.encode("utf-8")) + + def set_gui_container(self, pluginId, guiAddr): + self.lib.set_gui_container(pluginId, guiAddr) + + def show_gui(self, pluginId, yesNo): + self.lib.show_gui(pluginId, yesNo) + + def idle_guis(self): + self.lib.idle_guis() + + def send_midi_note(self, pluginId, channel, note, velocity): + self.lib.send_midi_note(pluginId, channel, note, velocity) + + def prepare_for_save(self, pluginId): + self.lib.prepare_for_save(pluginId) + + def set_callback_function(self, func): + self.callback = CallbackFunc(func) + self.lib.set_callback_function(self.callback) + + def set_option(self, option, value, valueStr): + self.lib.set_option(option, value, valueStr.encode("utf-8")) + + def get_last_error(self): + return self.lib.get_last_error() + + def get_host_osc_url(self): + return self.lib.get_host_osc_url() + + def get_buffer_size(self): + return self.lib.get_buffer_size() + + def get_sample_rate(self): + return self.lib.get_sample_rate() + + def nsm_announce(self, url, pid): + self.lib.nsm_announce(url.encode("utf-8"), pid) + + def nsm_reply_open(self): + self.lib.nsm_reply_open() + + def nsm_reply_save(self): + self.lib.nsm_reply_save() diff --git a/source/carla_shared.py b/source/carla_shared.py new file mode 100644 index 000000000..488fb8307 --- /dev/null +++ b/source/carla_shared.py @@ -0,0 +1,520 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Common Carla code +# Copyright (C) 2011-2013 Filipe Coelho +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# For a full copy of the GNU General Public License see the COPYING file + +# ------------------------------------------------------------------------------------------------------------ +# Imports (Global) + +import os +#import platform +import sys +from codecs import open as codecopen +from copy import deepcopy +#from decimal import Decimal +from PyQt4.QtCore import qWarning +#pyqtSlot, qFatal, Qt, QSettings, QTimer +#from PyQt4.QtGui import QColor, QCursor, QDialog, QFontMetrics, QFrame, QGraphicsScene, QInputDialog, QLinearGradient, QMenu, QPainter, QPainterPath, QVBoxLayout, QWidget +#from PyQt4.QtXml import QDomDocument + +# ------------------------------------------------------------------------------------------------------------ +# Imports (Custom) + +#import ui_carla_about +#import ui_carla_edit +#import ui_carla_parameter +#import ui_carla_plugin + +# ------------------------------------------------------------------------------------------------------------ +# Try Import Signal + +try: + from signal import signal, SIGINT, SIGTERM, SIGUSR1, SIGUSR2 + haveSignal = True +except: + haveSignal = False + +# ------------------------------------------------------------------------------------------------------------ +# Set Platform + +if sys.platform == "darwin": + from PyQt4.QtGui import qt_mac_set_menubar_icons + qt_mac_set_menubar_icons(False) + HAIKU = False + LINUX = False + MACOS = True + WINDOWS = False +elif "haiku" in sys.platform: + HAIKU = True + LINUX = False + MACOS = False + WINDOWS = False +elif "linux" in sys.platform: + HAIKU = False + LINUX = True + MACOS = False + WINDOWS = False +elif sys.platform in ("win32", "win64", "cygwin"): + WINDIR = os.getenv("WINDIR") + HAIKU = False + LINUX = False + MACOS = False + WINDOWS = True +else: + HAIKU = False + LINUX = False + MACOS = False + WINDOWS = False + +# ------------------------------------------------------------------------------------------------------------ +# Set Version + +VERSION = "0.5.0" + +# ------------------------------------------------------------------------------------------------------------ +# Set TMP + +TMP = os.getenv("TMP") + +if TMP is None: + if WINDOWS: + qWarning("TMP variable not set") + TMP = os.path.join(WINDIR, "temp") + else: + TMP = "/tmp" + +# ------------------------------------------------------------------------------------------------------------ +# Set HOME + +HOME = os.getenv("HOME") + +if HOME is None: + HOME = os.path.expanduser("~") + + if LINUX or MACOS: + qWarning("HOME variable not set") + +if not os.path.exists(HOME): + qWarning("HOME does not exist") + HOME = TMP + +# ------------------------------------------------------------------------------------------------------------ +# Set PATH + +PATH = os.getenv("PATH") + +if PATH is None: + qWarning("PATH variable not set") + + if MACOS: + PATH = ("/opt/local/bin", "/usr/local/bin", "/usr/bin", "/bin") + elif WINDOWS: + PATH = (os.path.join(WINDIR, "system32"), WINDIR) + else: + PATH = ("/usr/local/bin", "/usr/bin", "/bin") + +else: + PATH = PATH.split(os.pathsep) + +# ------------------------------------------------------------------------------------------------------------ +# 64bit check + +is64bit = False #bool(platform.architecture()[0] == "64bit" and sys.maxsize > 2**32) + +# ------------------------------------------------------------------------------------------------------------ +# Check if a value is a number (float support) + +def isNumber(value): + try: + float(value) + return True + except: + return False + +# ------------------------------------------------------------------------------------------------------------ +# Unicode open + +def uopen(filename, mode="r"): + return codecopen(filename, encoding="utf-8", mode=mode) + +# ------------------------------------------------------------------------------------------------ +# Backend defines + +MAX_DEFAULT_PLUGINS = 99 +MAX_RACK_PLUGINS = 16 +MAX_PATCHBAY_PLUGINS = 999 +MAX_DEFAULT_PARAMETERS = 200 + +# Plugin Hints +PLUGIN_IS_BRIDGE = 0x001 +PLUGIN_IS_RTSAFE = 0x002 +PLUGIN_IS_SYNTH = 0x004 +PLUGIN_HAS_GUI = 0x010 +PLUGIN_USES_CHUNKS = 0x020 +PLUGIN_USES_SINGLE_THREAD = 0x040 +PLUGIN_CAN_DRYWET = 0x100 +PLUGIN_CAN_VOLUME = 0x200 +PLUGIN_CAN_BALANCE = 0x400 +PLUGIN_CAN_FORCE_STEREO = 0x800 + +# Parameter Hints +PARAMETER_IS_BOOLEAN = 0x01 +PARAMETER_IS_INTEGER = 0x02 +PARAMETER_IS_LOGARITHMIC = 0x04 +PARAMETER_IS_ENABLED = 0x08 +PARAMETER_IS_AUTOMABLE = 0x10 +PARAMETER_USES_SAMPLERATE = 0x20 +PARAMETER_USES_SCALEPOINTS = 0x40 +PARAMETER_USES_CUSTOM_TEXT = 0x80 + +# FIXME +# Custom Data types +CUSTOM_DATA_INVALID = None +CUSTOM_DATA_CHUNK = "http://kxstudio.sf.net/ns/carla/chunk" +CUSTOM_DATA_STRING = "http://kxstudio.sf.net/ns/carla/string" + +# Binary Type +BINARY_NONE = 0 +BINARY_POSIX32 = 1 +BINARY_POSIX64 = 2 +BINARY_WIN32 = 3 +BINARY_WIN64 = 4 +BINARY_OTHER = 5 + +# Plugin Type +PLUGIN_NONE = 0 +PLUGIN_INTERNAL = 1 +PLUGIN_LADSPA = 2 +PLUGIN_DSSI = 3 +PLUGIN_LV2 = 4 +PLUGIN_VST = 5 +PLUGIN_GIG = 6 +PLUGIN_SF2 = 7 +PLUGIN_SFZ = 8 + +# Plugin Category +PLUGIN_CATEGORY_NONE = 0 +PLUGIN_CATEGORY_SYNTH = 1 +PLUGIN_CATEGORY_DELAY = 2 # also Reverb +PLUGIN_CATEGORY_EQ = 3 +PLUGIN_CATEGORY_FILTER = 4 +PLUGIN_CATEGORY_DYNAMICS = 5 # Amplifier, Compressor, Gate +PLUGIN_CATEGORY_MODULATOR = 6 # Chorus, Flanger, Phaser +PLUGIN_CATEGORY_UTILITY = 7 # Analyzer, Converter, Mixer +PLUGIN_CATEGORY_OTHER = 8 # used to check if a plugin has a category + +# Parameter Type +PARAMETER_UNKNOWN = 0 +PARAMETER_INPUT = 1 +PARAMETER_OUTPUT = 2 +PARAMETER_LATENCY = 3 +PARAMETER_SAMPLE_RATE = 4 +PARAMETER_LV2_FREEWHEEL = 5 +PARAMETER_LV2_TIME = 6 + +# Internal Parameters Index +PARAMETER_NULL = -1 +PARAMETER_ACTIVE = -2 +PARAMETER_DRYWET = -3 +PARAMETER_VOLUME = -4 +PARAMETER_BALANCE_LEFT = -5 +PARAMETER_BALANCE_RIGHT = -6 +PARAMETER_PANNING = -7 + +# Options Type +OPTION_PROCESS_NAME = 0 +OPTION_PROCESS_MODE = 1 +OPTION_PROCESS_HIGH_PRECISION = 2 +OPTION_FORCE_STEREO = 3 +OPTION_PREFER_PLUGIN_BRIDGES = 4 +OPTION_PREFER_UI_BRIDGES = 5 +OPTION_USE_DSSI_VST_CHUNKS = 6 +OPTION_MAX_PARAMETERS = 7 +OPTION_OSC_UI_TIMEOUT = 8 +OPTION_PREFERRED_BUFFER_SIZE = 9 +OPTION_PREFERRED_SAMPLE_RATE = 10 +OPTION_PATH_BRIDGE_NATIVE = 11 +OPTION_PATH_BRIDGE_POSIX32 = 12 +OPTION_PATH_BRIDGE_POSIX64 = 13 +OPTION_PATH_BRIDGE_WIN32 = 14 +OPTION_PATH_BRIDGE_WIN64 = 15 +OPTION_PATH_BRIDGE_LV2_GTK2 = 16 +OPTION_PATH_BRIDGE_LV2_GTK3 = 17 +OPTION_PATH_BRIDGE_LV2_QT4 = 18 +OPTION_PATH_BRIDGE_LV2_QT5 = 19 +OPTION_PATH_BRIDGE_LV2_COCOA = 20 +OPTION_PATH_BRIDGE_LV2_WINDOWS = 21 +OPTION_PATH_BRIDGE_LV2_X11 = 22 +OPTION_PATH_BRIDGE_VST_COCOA = 23 +OPTION_PATH_BRIDGE_VST_HWND = 24 +OPTION_PATH_BRIDGE_VST_X11 = 25 + +# Callback Type +CALLBACK_DEBUG = 0 +CALLBACK_PARAMETER_VALUE_CHANGED = 1 +CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED = 2 +CALLBACK_PARAMETER_MIDI_CC_CHANGED = 3 +CALLBACK_PROGRAM_CHANGED = 4 +CALLBACK_MIDI_PROGRAM_CHANGED = 5 +CALLBACK_NOTE_ON = 6 +CALLBACK_NOTE_OFF = 7 +CALLBACK_SHOW_GUI = 8 +CALLBACK_UPDATE = 9 +CALLBACK_RELOAD_INFO = 10 +CALLBACK_RELOAD_PARAMETERS = 11 +CALLBACK_RELOAD_PROGRAMS = 12 +CALLBACK_RELOAD_ALL = 13 +CALLBACK_NSM_ANNOUNCE = 14 +CALLBACK_NSM_OPEN1 = 15 +CALLBACK_NSM_OPEN2 = 16 +CALLBACK_NSM_SAVE = 17 +CALLBACK_ERROR = 18 +CALLBACK_QUIT = 19 + +# Process Mode Type +PROCESS_MODE_SINGLE_CLIENT = 0 +PROCESS_MODE_MULTIPLE_CLIENTS = 1 +PROCESS_MODE_CONTINUOUS_RACK = 2 +PROCESS_MODE_PATCHBAY = 3 + +# Set BINARY_NATIVE +if HAIKU or LINUX or MACOS: + BINARY_NATIVE = BINARY_POSIX64 if is64bit else BINARY_POSIX32 +elif WINDOWS: + BINARY_NATIVE = BINARY_WIN64 if is64bit else BINARY_WIN32 +else: + BINARY_NATIVE = BINARY_OTHER + +# ------------------------------------------------------------------------------------------------------------ +# Carla Host object + +class CarlaHostObject(object): + __slots__ = [ + 'host', + 'gui', + 'isControl', + 'processMode', + 'maxParameters' + ] + +Carla = CarlaHostObject() +Carla.host = None +Carla.gui = None +Carla.isControl = False +Carla.processMode = PROCESS_MODE_CONTINUOUS_RACK +Carla.maxParameters = MAX_RACK_PLUGINS + +# ------------------------------------------------------------------------------------------------------------ +# Carla GUI stuff + +ICON_STATE_NULL = 0 +ICON_STATE_WAIT = 1 +ICON_STATE_OFF = 2 +ICON_STATE_ON = 3 + +PALETTE_COLOR_NONE = 0 +PALETTE_COLOR_WHITE = 1 +PALETTE_COLOR_RED = 2 +PALETTE_COLOR_GREEN = 3 +PALETTE_COLOR_BLUE = 4 +PALETTE_COLOR_YELLOW = 5 +PALETTE_COLOR_ORANGE = 6 +PALETTE_COLOR_BROWN = 7 +PALETTE_COLOR_PINK = 8 + +CarlaStateParameter = { + 'index': 0, + 'name': "", + 'symbol': "", + 'value': 0.0, + 'midiChannel': 1, + 'midiCC': -1 +} + +CarlaStateCustomData = { + 'type': CUSTOM_DATA_INVALID, + 'key': "", + 'value': "" +} + +CarlaSaveState = { + 'type': "", + 'name': "", + 'label': "", + 'binary': "", + 'uniqueId': 0, + 'active': False, + 'dryWet': 1.0, + 'volume': 1.0, + 'balanceLeft': -1.0, + 'balanceRight': 1.0, + 'pannning': 0.0, + 'parameterList': [], + 'currentProgramIndex': -1, + 'currentProgramName': "", + 'currentMidiBank': -1, + 'currentMidiProgram': -1, + 'customDataList': [], + 'chunk': None +} + +# ------------------------------------------------------------------------------------------------------------ +# Carla XML helpers + +def getSaveStateDictFromXML(xmlNode): + saveState = deepcopy(CarlaSaveState) + + node = xmlNode.firstChild() + + while not node.isNull(): + # ------------------------------------------------------ + # Info + + if node.toElement().tagName() == "Info": + xmlInfo = node.toElement().firstChild() + + while not xmlInfo.isNull(): + tag = xmlInfo.toElement().tagName() + text = xmlInfo.toElement().text().strip() + + if tag == "Type": + saveState["type"] = text + elif tag == "Name": + saveState["name"] = xmlSafeString(text, False) + elif tag in ("Label", "URI"): + saveState["label"] = xmlSafeString(text, False) + elif tag == "Binary": + saveState["binary"] = xmlSafeString(text, False) + elif tag == "UniqueID": + if text.isdigit(): saveState["uniqueId"] = int(text) + + xmlInfo = xmlInfo.nextSibling() + + # ------------------------------------------------------ + # Data + + elif node.toElement().tagName() == "Data": + xmlData = node.toElement().firstChild() + + while not xmlData.isNull(): + tag = xmlData.toElement().tagName() + text = xmlData.toElement().text().strip() + + # ---------------------------------------------- + # Internal Data + + if tag == "Active": + saveState['active'] = bool(text == "Yes") + elif tag == "DryWet": + if isNumber(text): saveState["dryWet"] = float(text) + elif tag == "Volume": + if isNumber(text): saveState["volume"] = float(text) + elif tag == "Balance-Left": + if isNumber(text): saveState["balanceLeft"] = float(text) + elif tag == "Balance-Right": + if isNumber(text): saveState["balanceRight"] = float(text) + elif tag == "Panning": + if isNumber(text): saveState["pannning"] = float(text) + + # ---------------------------------------------- + # Program (current) + + elif tag == "CurrentProgramIndex": + if text.isdigit(): saveState["currentProgramIndex"] = int(text) + elif tag == "CurrentProgramName": + saveState["currentProgramName"] = xmlSafeString(text, False) + + # ---------------------------------------------- + # Midi Program (current) + + elif tag == "CurrentMidiBank": + if text.isdigit(): saveState["currentMidiBank"] = int(text) + elif tag == "CurrentMidiProgram": + if text.isdigit(): saveState["currentMidiProgram"] = int(text) + + # ---------------------------------------------- + # Parameters + + elif tag == "Parameter": + stateParameter = deepcopy(CarlaStateParameter) + + xmlSubData = xmlData.toElement().firstChild() + + while not xmlSubData.isNull(): + pTag = xmlSubData.toElement().tagName() + pText = xmlSubData.toElement().text().strip() + + if pTag == "Index": + if pText.isdigit(): stateParameter["index"] = int(pText) + elif pTag == "Name": + stateParameter["name"] = xmlSafeString(pText, False) + elif pTag == "Symbol": + stateParameter["symbol"] = xmlSafeString(pText, False) + elif pTag == "Value": + if isNumber(pText): stateParameter["value"] = float(pText) + elif pTag == "MidiChannel": + if pText.isdigit(): stateParameter["midiChannel"] = int(pText) + elif pTag == "MidiCC": + if pText.isdigit(): stateParameter["midiCC"] = int(pText) + + xmlSubData = xmlSubData.nextSibling() + + saveState["parameterList"].append(stateParameter) + + # ---------------------------------------------- + # Custom Data + + elif tag == "CustomData": + stateCustomData = deepcopy(CarlaStateCustomData) + + xmlSubData = xmlData.toElement().firstChild() + + while not xmlSubData.isNull(): + cTag = xmlSubData.toElement().tagName() + cText = xmlSubData.toElement().text().strip() + + if cTag == "Type": + stateCustomData["type"] = xmlSafeString(cText, False) + elif cTag == "Key": + stateCustomData["key"] = xmlSafeString(cText, False) + elif cTag == "Value": + stateCustomData["value"] = xmlSafeString(cText, False) + + xmlSubData = xmlSubData.nextSibling() + + saveState["customDataList"].append(stateCustomData) + + # ---------------------------------------------- + # Chunk + + elif tag == "Chunk": + saveState["chunk"] = xmlSafeString(text, False) + + # ---------------------------------------------- + + xmlData = xmlData.nextSibling() + + # ------------------------------------------------------ + + node = node.nextSibling() + + return saveState + +def xmlSafeString(string, toXml): + if toXml: + return string.replace("&", "&").replace("<","<").replace(">",">").replace("'","'").replace("\"",""") + else: + return string.replace("&", "&").replace("<","<").replace(">",">").replace("'","'").replace(""","\"") diff --git a/source/discovery/carla-discovery.cpp b/source/discovery/carla-discovery.cpp index e07f7e25f..722001f90 100644 --- a/source/discovery/carla-discovery.cpp +++ b/source/discovery/carla-discovery.cpp @@ -188,7 +188,7 @@ intptr_t VSTCALLBACK vstHostCallback(AEffect* const effect, const int32_t opcode break; case audioMasterGetNumAutomatableParameters: - ret = carla_min(effect->numParams, MAX_PARAMETERS, 0); + ret = carla_min(effect->numParams, MAX_DEFAULT_PARAMETERS, 0); break; case audioMasterGetParameterQuantization: diff --git a/source/widgets/digitalpeakmeter.py b/source/widgets/digitalpeakmeter.py index e7afd0079..adb751ff8 100644 --- a/source/widgets/digitalpeakmeter.py +++ b/source/widgets/digitalpeakmeter.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Digital Peak Meter, a custom Qt4 widget -# Copyright (C) 2011-2012 Filipe Coelho +# Copyright (C) 2011-2013 Filipe Coelho # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -11,16 +11,20 @@ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # For a full copy of the GNU General Public License see the COPYING file +# ------------------------------------------------------------------------------------------------------------ # Imports (Global) + from PyQt4.QtCore import qCritical, Qt, QTimer, QSize from PyQt4.QtGui import QColor, QLinearGradient, QPainter, QWidget +# ------------------------------------------------------------------------------------------------------------ # Widget Class + class DigitalPeakMeter(QWidget): # enum Orientation HORIZONTAL = 1 @@ -60,7 +64,7 @@ class DigitalPeakMeter(QWidget): self.m_channelsData[meter-1] = level def setChannels(self, channels): - if (channels < 0): + if channels < 0: return qCritical("DigitalPeakMeter::setChannels(%i) - 'channels' must be a positive integer" % channels) self.m_channels = channels @@ -228,6 +232,8 @@ class DigitalPeakMeter(QWidget): painter.setPen(QColor(110, 15, 15, 100)) painter.drawLine(2, lsmall - (lsmall * 0.96), lfull-2, lsmall - (lsmall * 0.96)) + event.accept() + def resizeEvent(self, event): self.updateSizes() QWidget.resizeEvent(self, event) diff --git a/source/widgets/ledbutton.py b/source/widgets/ledbutton.py index 7d3056c4c..2010f2314 100644 --- a/source/widgets/ledbutton.py +++ b/source/widgets/ledbutton.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Pixmap Button, a custom Qt4 widget -# Copyright (C) 2011-2012 Filipe Coelho +# Copyright (C) 2011-2013 Filipe Coelho # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -11,16 +11,20 @@ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # For a full copy of the GNU General Public License see the COPYING file +# ------------------------------------------------------------------------------------------------------------ # Imports (Global) + from PyQt4.QtCore import qCritical, QRectF from PyQt4.QtGui import QPainter, QPixmap, QPushButton +# ------------------------------------------------------------------------------------------------------------ # Widget Class + class LEDButton(QPushButton): BLUE = 1 GREEN = 2 @@ -32,7 +36,7 @@ class LEDButton(QPushButton): QPushButton.__init__(self, parent) self.m_pixmap = QPixmap() - self.m_pixmap_rect = QRectF(0, 0, 0, 0) + self.m_pixmapRect = QRectF(0, 0, 0, 0) self.setCheckable(True) self.setText("") @@ -52,7 +56,7 @@ class LEDButton(QPushButton): self.setPixmapSize(size) def setPixmapSize(self, size): - self.m_pixmap_rect = QRectF(0, 0, size, size) + self.m_pixmapRect = QRectF(0, 0, size, size) self.setMinimumWidth(size) self.setMaximumWidth(size) @@ -83,4 +87,6 @@ class LEDButton(QPushButton): else: return - painter.drawPixmap(self.m_pixmap_rect, self.m_pixmap, self.m_pixmap_rect) + painter.drawPixmap(self.m_pixmapRect, self.m_pixmap, self.m_pixmapRect) + + event.accept() diff --git a/source/widgets/paramspinbox.py b/source/widgets/paramspinbox.py index 73f32f39f..686987c79 100644 --- a/source/widgets/paramspinbox.py +++ b/source/widgets/paramspinbox.py @@ -1,13 +1,32 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# Parameter SpinBox, a custom Qt4 widget +# Copyright (C) 2011-2013 Filipe Coelho +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# For a full copy of the GNU General Public License see the COPYING file + +# ------------------------------------------------------------------------------------------------------------ # Imports (Global) + from PyQt4.QtCore import pyqtSlot, Qt, QTimer, SIGNAL, SLOT from PyQt4.QtGui import QAbstractSpinBox, QComboBox, QCursor, QDialog, QInputDialog, QMenu, QPainter, QProgressBar, QValidator from PyQt4.QtGui import QStyleFactory from math import isnan +# ------------------------------------------------------------------------------------------------------------ # Imports (Custom) + import ui_inputdialog_value def fix_value(value, minimum, maximum): @@ -25,7 +44,9 @@ def fix_value(value, minimum, maximum): #QPlastiqueStyle = QStyleFactory.create("Plastique") +# ------------------------------------------------------------------------------------------------------------ # Custom InputDialog with Scale Points support + class CustomInputDialog(QDialog, ui_inputdialog_value.Ui_Dialog): def __init__(self, parent, label, current, minimum, maximum, step, scalePoints): QDialog.__init__(self, parent) @@ -59,7 +80,9 @@ class CustomInputDialog(QDialog, ui_inputdialog_value.Ui_Dialog): QDialog.done(self, r) self.close() +# ------------------------------------------------------------------------------------------------------------ # Progress-Bar used for ParamSpinBox + class ParamProgressBar(QProgressBar): def __init__(self, parent): QProgressBar.__init__(self, parent) @@ -138,7 +161,9 @@ class ParamProgressBar(QProgressBar): QProgressBar.paintEvent(self, event) +# ------------------------------------------------------------------------------------------------------------ # Special SpinBox used for parameters + class ParamSpinBox(QAbstractSpinBox): def __init__(self, parent): QAbstractSpinBox.__init__(self, parent) @@ -205,7 +230,7 @@ class ParamSpinBox(QAbstractSpinBox): self._step = 0.001 else: self._step = value - + if self._step_small > value: self._step_small = value if self._step_large < value: diff --git a/source/widgets/pixmapdial.py b/source/widgets/pixmapdial.py index 41e0d5120..983f63e57 100644 --- a/source/widgets/pixmapdial.py +++ b/source/widgets/pixmapdial.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Pixmap Dial, a custom Qt4 widget -# Copyright (C) 2011-2012 Filipe Coelho +# Copyright (C) 2011-2013 Filipe Coelho # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -11,17 +11,21 @@ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # For a full copy of the GNU General Public License see the COPYING file +# ------------------------------------------------------------------------------------------------------------ # Imports (Global) + from math import floor from PyQt4.QtCore import Qt, QPointF, QRectF, QTimer, QSize, SLOT from PyQt4.QtGui import QColor, QConicalGradient, QDial, QFontMetrics, QLinearGradient, QPainter, QPainterPath, QPen, QPixmap +# ------------------------------------------------------------------------------------------------------------ # Widget Class + class PixmapDial(QDial): # enum Orientation HORIZONTAL = 0 @@ -41,11 +45,11 @@ class PixmapDial(QDial): QDial.__init__(self, parent) self.m_pixmap = QPixmap(":/bitmaps/dial_01d.png") - self.m_pixmap_n_str = "01" - self.m_custom_paint = self.CUSTOM_PAINT_NULL + self.m_pixmapNum = "01" + self.m_customPaint = self.CUSTOM_PAINT_NULL - self.m_hovered = False - self.m_hover_step = self.HOVER_MIN + self.m_hovered = False + self.m_hoverStep = self.HOVER_MIN if self.m_pixmap.width() > self.m_pixmap.height(): self.m_orientation = self.HORIZONTAL @@ -53,10 +57,10 @@ class PixmapDial(QDial): self.m_orientation = self.VERTICAL self.m_label = "" - self.m_label_pos = QPointF(0.0, 0.0) - self.m_label_width = 0 - self.m_label_height = 0 - self.m_label_gradient = QLinearGradient(0, 0, 0, 1) + self.m_labelPos = QPointF(0.0, 0.0) + self.m_labelWidth = 0 + self.m_labelHeight = 0 + self.m_labelGradient = QLinearGradient(0, 0, 0, 1) if self.palette().window().color().lightness() > 100: # Light background @@ -76,12 +80,12 @@ class PixmapDial(QDial): return self.p_size def setCustomPaint(self, paint): - self.m_custom_paint = paint + self.m_customPaint = paint self.update() def setEnabled(self, enabled): if self.isEnabled() != enabled: - self.m_pixmap.load(":/bitmaps/dial_%s%s.png" % (self.m_pixmap_n_str, "" if enabled else "d")) + self.m_pixmap.load(":/bitmaps/dial_%s%s.png" % (self.m_pixmapNum, "" if enabled else "d")) self.updateSizes() self.update() QDial.setEnabled(self, enabled) @@ -89,25 +93,25 @@ class PixmapDial(QDial): def setLabel(self, label): self.m_label = label - self.m_label_width = QFontMetrics(self.font()).width(label) - self.m_label_height = QFontMetrics(self.font()).height() + self.m_labelWidth = QFontMetrics(self.font()).width(label) + self.m_labelHeight = QFontMetrics(self.font()).height() - self.m_label_pos.setX(float(self.p_size)/2 - float(self.m_label_width)/2) - self.m_label_pos.setY(self.p_size + self.m_label_height) + self.m_labelPos.setX(float(self.p_size)/2 - float(self.m_labelWidth)/2) + self.m_labelPos.setY(self.p_size + self.m_labelHeight) - self.m_label_gradient.setColorAt(0.0, self.m_color1) - self.m_label_gradient.setColorAt(0.6, self.m_color1) - self.m_label_gradient.setColorAt(1.0, self.m_color2) + self.m_labelGradient.setColorAt(0.0, self.m_color1) + self.m_labelGradient.setColorAt(0.6, self.m_color1) + self.m_labelGradient.setColorAt(1.0, self.m_color2) - self.m_label_gradient.setStart(0, float(self.p_size)/2) - self.m_label_gradient.setFinalStop(0, self.p_size + self.m_label_height + 5) + self.m_labelGradient.setStart(0, float(self.p_size)/2) + self.m_labelGradient.setFinalStop(0, self.p_size + self.m_labelHeight + 5) - self.m_label_gradient_rect = QRectF(float(self.p_size)/8, float(self.p_size)/2, float(self.p_size)*6/8, self.p_size+self.m_label_height+5) + self.m_labelGradient_rect = QRectF(float(self.p_size)/8, float(self.p_size)/2, float(self.p_size)*6/8, self.p_size+self.m_labelHeight+5) self.update() def setPixmap(self, pixmapId): - self.m_pixmap_n_str = "%02i" % pixmapId - self.m_pixmap.load(":/bitmaps/dial_%s%s.png" % (self.m_pixmap_n_str, "" if self.isEnabled() else "d")) + self.m_pixmapNum = "%02i" % pixmapId + self.m_pixmap.load(":/bitmaps/dial_%s%s.png" % (self.m_pixmapNum, "" if self.isEnabled() else "d")) if self.m_pixmap.width() > self.m_pixmap.height(): self.m_orientation = self.HORIZONTAL @@ -140,19 +144,19 @@ class PixmapDial(QDial): self.p_size = self.p_width self.p_count = self.p_height / self.p_width - self.setMinimumSize(self.p_size, self.p_size + self.m_label_height + 5) - self.setMaximumSize(self.p_size, self.p_size + self.m_label_height + 5) + self.setMinimumSize(self.p_size, self.p_size + self.m_labelHeight + 5) + self.setMaximumSize(self.p_size, self.p_size + self.m_labelHeight + 5) def enterEvent(self, event): self.m_hovered = True - if self.m_hover_step == self.HOVER_MIN: - self.m_hover_step += 1 + if self.m_hoverStep == self.HOVER_MIN: + self.m_hoverStep += 1 QDial.enterEvent(self, event) def leaveEvent(self, event): self.m_hovered = False - if self.m_hover_step == self.HOVER_MAX: - self.m_hover_step -= 1 + if self.m_hoverStep == self.HOVER_MAX: + self.m_hoverStep -= 1 QDial.leaveEvent(self, event) def paintEvent(self, event): @@ -161,11 +165,11 @@ class PixmapDial(QDial): if self.m_label: painter.setPen(self.m_color2) - painter.setBrush(self.m_label_gradient) - painter.drawRect(self.m_label_gradient_rect) + painter.setBrush(self.m_labelGradient) + painter.drawRect(self.m_labelGradient_rect) painter.setPen(self.m_colorT[0 if self.isEnabled() else 1]) - painter.drawText(self.m_label_pos, self.m_label) + painter.drawText(self.m_labelPos, self.m_label) if self.isEnabled(): current = float(self.value() - self.minimum()) @@ -190,10 +194,10 @@ class PixmapDial(QDial): painter.drawPixmap(target, self.m_pixmap, source) # Custom knobs (Dry/Wet and Volume) - if self.m_custom_paint in (self.CUSTOM_PAINT_CARLA_WET, self.CUSTOM_PAINT_CARLA_VOL): + if self.m_customPaint in (self.CUSTOM_PAINT_CARLA_WET, self.CUSTOM_PAINT_CARLA_VOL): # knob color - colorGreen = QColor(0x5D, 0xE7, 0x3D, 191 + self.m_hover_step*7) - colorBlue = QColor(0x3E, 0xB8, 0xBE, 191 + self.m_hover_step*7) + colorGreen = QColor(0x5D, 0xE7, 0x3D, 191 + self.m_hoverStep*7) + colorBlue = QColor(0x3E, 0xB8, 0xBE, 191 + self.m_hoverStep*7) # draw small circle ballRect = QRectF(8.0, 8.0, 15.0, 15.0) @@ -208,7 +212,7 @@ class PixmapDial(QDial): startAngle = 216*16 spanAngle = -252*16*value - if self.m_custom_paint == self.CUSTOM_PAINT_CARLA_WET: + if self.m_customPaint == self.CUSTOM_PAINT_CARLA_WET: painter.setBrush(colorBlue) painter.setPen(QPen(colorBlue, 0)) painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) @@ -234,9 +238,9 @@ class PixmapDial(QDial): painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle) # Custom knobs (L and R) - elif self.m_custom_paint in (self.CUSTOM_PAINT_CARLA_L, self.CUSTOM_PAINT_CARLA_R): + elif self.m_customPaint in (self.CUSTOM_PAINT_CARLA_L, self.CUSTOM_PAINT_CARLA_R): # knob color - color = QColor(0xAD + self.m_hover_step*5, 0xD5 + self.m_hover_step*4, 0x4B + self.m_hover_step*5) + color = QColor(0xAD + self.m_hoverStep*5, 0xD5 + self.m_hoverStep*4, 0x4B + self.m_hoverStep*5) # draw small circle ballRect = QRectF(7.0, 8.0, 11.0, 12.0) @@ -252,10 +256,10 @@ class PixmapDial(QDial): painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0)) # draw arc - if self.m_custom_paint == self.CUSTOM_PAINT_CARLA_L: + if self.m_customPaint == self.CUSTOM_PAINT_CARLA_L: startAngle = 216*16 spanAngle = -252.0*16*value - elif self.m_custom_paint == self.CUSTOM_PAINT_CARLA_R: + elif self.m_customPaint == self.CUSTOM_PAINT_CARLA_R: startAngle = 324.0*16 spanAngle = 252.0*16*(1.0-value) else: @@ -264,14 +268,16 @@ class PixmapDial(QDial): painter.setPen(QPen(color, 2)) painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle) - if self.HOVER_MIN < self.m_hover_step < self.HOVER_MAX: - self.m_hover_step += 1 if self.m_hovered else -1 + if self.HOVER_MIN < self.m_hoverStep < self.HOVER_MAX: + self.m_hoverStep += 1 if self.m_hovered else -1 QTimer.singleShot(20, self, SLOT("update()")) else: target = QRectF(0.0, 0.0, self.p_size, self.p_size) painter.drawPixmap(target, self.m_pixmap, target) + event.accept() + def resizeEvent(self, event): self.updateSizes() QDial.resizeEvent(self, event) diff --git a/source/widgets/pixmapkeyboard.py b/source/widgets/pixmapkeyboard.py index bccaac528..d03ec67b4 100644 --- a/source/widgets/pixmapkeyboard.py +++ b/source/widgets/pixmapkeyboard.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Pixmap Keyboard, a custom Qt4 widget -# Copyright (C) 2011-2012 Filipe Coelho +# Copyright (C) 2011-2013 Filipe Coelho # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -11,15 +11,19 @@ # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # For a full copy of the GNU General Public License see the COPYING file +# ------------------------------------------------------------------------------------------------------------ # Imports (Global) + from PyQt4.QtCore import pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer, SIGNAL, SLOT from PyQt4.QtGui import QFont, QPainter, QPixmap, QWidget +# ------------------------------------------------------------------------------------------------------------ + midi_key2rect_map_horizontal = { '0': QRectF(0, 0, 18, 64), # C '1': QRectF(13, 0, 11, 42), # C# @@ -79,7 +83,9 @@ midi_keyboard2key_map = { '%i' % Qt.Key_U: 71, } +# ------------------------------------------------------------------------------------------------------------ # MIDI Keyboard, using a pixmap for painting + class PixmapKeyboard(QWidget): # enum Color COLOR_CLASSIC = 0 @@ -146,15 +152,15 @@ class PixmapKeyboard(QWidget): return self.setMode(mode) if mode == self.HORIZONTAL: - self.m_midi_map = midi_key2rect_map_horizontal + self.m_midiMap = midi_key2rect_map_horizontal self.m_pixmap.load(":/bitmaps/kbd_h_%s.png" % self.m_colorStr) - self.m_pixmap_mode = self.HORIZONTAL + self.m_pixmapMode = self.HORIZONTAL self.p_width = self.m_pixmap.width() self.p_height = self.m_pixmap.height() / 2 elif mode == self.VERTICAL: - self.m_midi_map = midi_key2rect_map_vertical + self.m_midiMap = midi_key2rect_map_vertical self.m_pixmap.load(":/bitmaps/kbd_v_%s.png" % self.m_colorStr) - self.m_pixmap_mode = self.VERTICAL + self.m_pixmapMode = self.VERTICAL self.p_width = self.m_pixmap.width() / 2 self.p_height = self.m_pixmap.height() else: @@ -170,23 +176,23 @@ class PixmapKeyboard(QWidget): octaves = 8 self.m_octaves = octaves - if self.m_pixmap_mode == self.HORIZONTAL: + if self.m_pixmapMode == self.HORIZONTAL: self.setMinimumSize(self.p_width * self.m_octaves, self.p_height) self.setMaximumSize(self.p_width * self.m_octaves, self.p_height) - elif self.m_pixmap_mode == self.VERTICAL: + elif self.m_pixmapMode == self.VERTICAL: self.setMinimumSize(self.p_width, self.p_height * self.m_octaves) self.setMaximumSize(self.p_width, self.p_height * self.m_octaves) self.update() def handleMousePos(self, pos): - if self.m_pixmap_mode == self.HORIZONTAL: + if self.m_pixmapMode == self.HORIZONTAL: if pos.x() < 0 or pos.x() > self.m_octaves * 144: return posX = pos.x() - 1 octave = int(posX / self.p_width) n_pos = QPointF(posX % self.p_width, pos.y()) - elif self.m_pixmap_mode == self.VERTICAL: + elif self.m_pixmapMode == self.VERTICAL: if pos.y() < 0 or pos.y() > self.m_octaves * 144: return posY = pos.y() - 1 @@ -197,29 +203,29 @@ class PixmapKeyboard(QWidget): octave += 3 - if self.m_midi_map['1'].contains(n_pos): # C# + if self.m_midiMap['1'].contains(n_pos): # C# note = 1 - elif self.m_midi_map['3'].contains(n_pos): # D# + elif self.m_midiMap['3'].contains(n_pos): # D# note = 3 - elif self.m_midi_map['6'].contains(n_pos): # F# + elif self.m_midiMap['6'].contains(n_pos): # F# note = 6 - elif self.m_midi_map['8'].contains(n_pos): # G# + elif self.m_midiMap['8'].contains(n_pos): # G# note = 8 - elif self.m_midi_map['10'].contains(n_pos):# A# + elif self.m_midiMap['10'].contains(n_pos):# A# note = 10 - elif self.m_midi_map['0'].contains(n_pos): # C + elif self.m_midiMap['0'].contains(n_pos): # C note = 0 - elif self.m_midi_map['2'].contains(n_pos): # D + elif self.m_midiMap['2'].contains(n_pos): # D note = 2 - elif self.m_midi_map['4'].contains(n_pos): # E + elif self.m_midiMap['4'].contains(n_pos): # E note = 4 - elif self.m_midi_map['5'].contains(n_pos): # F + elif self.m_midiMap['5'].contains(n_pos): # F note = 5 - elif self.m_midi_map['7'].contains(n_pos): # G + elif self.m_midiMap['7'].contains(n_pos): # G note = 7 - elif self.m_midi_map['9'].contains(n_pos): # A + elif self.m_midiMap['9'].contains(n_pos): # A note = 9 - elif self.m_midi_map['11'].contains(n_pos):# B + elif self.m_midiMap['11'].contains(n_pos):# B note = 11 else: note = -1 @@ -271,9 +277,9 @@ class PixmapKeyboard(QWidget): # Paint clean keys (as background) for octave in range(self.m_octaves): - if self.m_pixmap_mode == self.HORIZONTAL: + if self.m_pixmapMode == self.HORIZONTAL: target = QRectF(self.p_width * octave, 0, self.p_width, self.p_height) - elif self.m_pixmap_mode == self.VERTICAL: + elif self.m_pixmapMode == self.VERTICAL: target = QRectF(0, self.p_height * octave, self.p_width, self.p_height) else: return @@ -316,13 +322,13 @@ class PixmapKeyboard(QWidget): # cannot paint this note either continue - if self.m_pixmap_mode == self.VERTICAL: + if self.m_pixmapMode == self.VERTICAL: octave = self.m_octaves - octave - 1 - if self.m_pixmap_mode == self.HORIZONTAL: + if self.m_pixmapMode == self.HORIZONTAL: target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height()) source = QRectF(pos.x(), self.p_height, pos.width(), pos.height()) - elif self.m_pixmap_mode == self.VERTICAL: + elif self.m_pixmapMode == self.VERTICAL: target = QRectF(pos.x(), pos.y() + (self.p_height * octave), pos.width(), pos.height()) source = QRectF(self.p_width, pos.y(), pos.width(), pos.height()) else: @@ -338,10 +344,10 @@ class PixmapKeyboard(QWidget): for octave in range(self.m_octaves): for note in (1, 3, 6, 8, 10): pos = self._getRectFromMidiNote(note) - if self.m_pixmap_mode == self.HORIZONTAL: + if self.m_pixmapMode == self.HORIZONTAL: target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height()) source = QRectF(pos.x(), 0, pos.width(), pos.height()) - elif self.m_pixmap_mode == self.VERTICAL: + elif self.m_pixmapMode == self.VERTICAL: target = QRectF(pos.x(), pos.y() + (self.p_height * octave), pos.width(), pos.height()) source = QRectF(0, pos.y(), pos.width(), pos.height()) else: @@ -382,13 +388,13 @@ class PixmapKeyboard(QWidget): # cannot paint this note either continue - if self.m_pixmap_mode == self.VERTICAL: + if self.m_pixmapMode == self.VERTICAL: octave = self.m_octaves - octave - 1 - if self.m_pixmap_mode == self.HORIZONTAL: + if self.m_pixmapMode == self.HORIZONTAL: target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height()) source = QRectF(pos.x(), self.p_height, pos.width(), pos.height()) - elif self.m_pixmap_mode == self.VERTICAL: + elif self.m_pixmapMode == self.VERTICAL: target = QRectF(pos.x(), pos.y() + (self.p_height * octave), pos.width(), pos.height()) source = QRectF(self.p_width, pos.y(), pos.width(), pos.height()) else: @@ -401,11 +407,13 @@ class PixmapKeyboard(QWidget): painter.setPen(Qt.black) for i in range(self.m_octaves): - if self.m_pixmap_mode == self.HORIZONTAL: + if self.m_pixmapMode == self.HORIZONTAL: painter.drawText(i * 144, 48, 18, 18, Qt.AlignCenter, "C%i" % int(i + 2)) - elif self.m_pixmap_mode == self.VERTICAL: + elif self.m_pixmapMode == self.VERTICAL: painter.drawText(45, (self.m_octaves * 144) - (i * 144) - 16, 18, 18, Qt.AlignCenter, "C%i" % int(i + 2)) + event.accept() + @pyqtSlot() def slot_updateOnce(self): if self.m_needsUpdate: @@ -417,4 +425,4 @@ class PixmapKeyboard(QWidget): return bool(baseNote in (1, 3, 6, 8, 10)) def _getRectFromMidiNote(self, note): - return self.m_midi_map.get(str(note % 12)) + return self.m_midiMap.get(str(note % 12))