@@ -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)) |