| @@ -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 | |||
| @@ -0,0 +1,61 @@ | |||
| # --- INSTALL for Carla --- | |||
| To install Carla, simply run as usual: <br/> | |||
| `$ make` <br/> | |||
| `$ [sudo] make install` | |||
| You can run it without installing, by using instead: <br/> | |||
| `$ make` <br/> | |||
| `$ python3 source/carla.py` | |||
| Packagers can make use of the 'PREFIX' and 'DESTDIR' variable during install, like this: <br/> | |||
| `$ make install PREFIX=/usr DESTDIR=./test-dir` | |||
| <br/> | |||
| ===== BUILD DEPENDENCIES ===== | |||
| -------------------------------- | |||
| The required build dependencies are: <i>(devel packages of these)</i> | |||
| - 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: <br/> | |||
| `$ sudo apt-get install libjack-dev liblo-dev libqt4-dev libfluidsynth-dev qt4-dev-tools` <br/> | |||
| `$ sudo apt-get install libgtk2.0-dev libgtk-3-dev libsuil-dev` <br/> | |||
| `$ sudo apt-get install libfftw3-dev libmxml-dev` <br/> | |||
| `$ 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. <br/> | |||
| <br/> | |||
| 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. <br/> | |||
| After install, Carla will still work on distros with python2 as default, without any additional work. | |||
| <br/> | |||
| @@ -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/ | |||
| @@ -0,0 +1,12 @@ | |||
| # --- README for Carla --- | |||
| Carla is an audio plugin host, with support for many audio drivers and plugin formats. <br/> | |||
| 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. <br/> | |||
| 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).<br/> | |||
| Supports controlling main UI components (Dry/Wet, Volume and Balance), and all plugins parameters. <br/> | |||
| Peak values and control outputs are displayed as well. | |||
| @@ -0,0 +1,9 @@ | |||
| [Project] | |||
| Manager=KDevGenericManager | |||
| Name=Carla | |||
| [Filters] | |||
| Excludes=*/.*,*/*~,*/*.pyc,*/ui_*.py,*/__pycache__/ | |||
| [Project] | |||
| VersionControlSupport=kdevgit | |||
| @@ -0,0 +1,520 @@ | |||
| #!/usr/bin/env python3 | |||
| # -*- coding: utf-8 -*- | |||
| # Common Carla code | |||
| # Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com> | |||
| # | |||
| # 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(""","\"") | |||
| @@ -188,7 +188,7 @@ intptr_t VSTCALLBACK vstHostCallback(AEffect* const effect, const int32_t opcode | |||
| break; | |||
| case audioMasterGetNumAutomatableParameters: | |||
| ret = carla_min<int32_t>(effect->numParams, MAX_PARAMETERS, 0); | |||
| ret = carla_min<int32_t>(effect->numParams, MAX_DEFAULT_PARAMETERS, 0); | |||
| break; | |||
| case audioMasterGetParameterQuantization: | |||
| @@ -2,7 +2,7 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # Digital Peak Meter, a custom Qt4 widget | |||
| # Copyright (C) 2011-2012 Filipe Coelho <falktx@falktx.com> | |||
| # Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com> | |||
| # | |||
| # 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) | |||
| @@ -2,7 +2,7 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # Pixmap Button, a custom Qt4 widget | |||
| # Copyright (C) 2011-2012 Filipe Coelho <falktx@falktx.com> | |||
| # Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com> | |||
| # | |||
| # 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() | |||
| @@ -1,13 +1,32 @@ | |||
| #!/usr/bin/env python | |||
| # -*- coding: utf-8 -*- | |||
| # Parameter SpinBox, a custom Qt4 widget | |||
| # Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com> | |||
| # | |||
| # 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: | |||
| @@ -2,7 +2,7 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # Pixmap Dial, a custom Qt4 widget | |||
| # Copyright (C) 2011-2012 Filipe Coelho <falktx@falktx.com> | |||
| # Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com> | |||
| # | |||
| # 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) | |||
| @@ -2,7 +2,7 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # Pixmap Keyboard, a custom Qt4 widget | |||
| # Copyright (C) 2011-2012 Filipe Coelho <falktx@falktx.com> | |||
| # Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com> | |||
| # | |||
| # 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)) | |||