Browse Source

Add files

tags/1.9.4
falkTX 12 years ago
parent
commit
ca083b9a78
13 changed files with 2012 additions and 92 deletions
  1. +7
    -4
      .gitignore
  2. +61
    -0
      INSTALL.md
  3. +166
    -0
      Makefile
  4. +12
    -0
      README.md
  5. +9
    -0
      source/carla.kdev4
  6. +1098
    -0
      source/carla_backend.py
  7. +520
    -0
      source/carla_shared.py
  8. +1
    -1
      source/discovery/carla-discovery.cpp
  9. +9
    -3
      source/widgets/digitalpeakmeter.py
  10. +11
    -5
      source/widgets/ledbutton.py
  11. +26
    -1
      source/widgets/paramspinbox.py
  12. +49
    -43
      source/widgets/pixmapdial.py
  13. +43
    -35
      source/widgets/pixmapkeyboard.py

+ 7
- 4
.gitignore View File

@@ -2,6 +2,7 @@
.directory .directory
.fuse-* .fuse-*
.*.kate-swp .*.kate-swp
.kdev_include_paths
.kdev4/ .kdev4/


# Temp files # Temp files
@@ -29,10 +30,12 @@ ui_*.h
# Python files # Python files
*.pyc *.pyc
ui_*.py 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 # Binaries
carla-bridge-qtcreator carla-bridge-qtcreator


+ 61
- 0
INSTALL.md View File

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

+ 166
- 0
Makefile View File

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

+ 12
- 0
README.md View File

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

+ 9
- 0
source/carla.kdev4 View File

@@ -0,0 +1,9 @@
[Project]
Manager=KDevGenericManager
Name=Carla

[Filters]
Excludes=*/.*,*/*~,*/*.pyc,*/ui_*.py,*/__pycache__/

[Project]
VersionControlSupport=kdevgit

+ 1098
- 0
source/carla_backend.py
File diff suppressed because it is too large
View File


+ 520
- 0
source/carla_shared.py View File

@@ -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("&", "&amp;").replace("<","&lt;").replace(">","&gt;").replace("'","&apos;").replace("\"","&quot;")
else:
return string.replace("&amp;", "&").replace("&lt;","<").replace("&gt;",">").replace("&apos;","'").replace("&quot;","\"")

+ 1
- 1
source/discovery/carla-discovery.cpp View File

@@ -188,7 +188,7 @@ intptr_t VSTCALLBACK vstHostCallback(AEffect* const effect, const int32_t opcode
break; break;


case audioMasterGetNumAutomatableParameters: case audioMasterGetNumAutomatableParameters:
ret = carla_min<int32_t>(effect->numParams, MAX_PARAMETERS, 0);
ret = carla_min<int32_t>(effect->numParams, MAX_DEFAULT_PARAMETERS, 0);
break; break;


case audioMasterGetParameterQuantization: case audioMasterGetParameterQuantization:


+ 9
- 3
source/widgets/digitalpeakmeter.py View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# Digital Peak Meter, a custom Qt4 widget # 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 # 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 # 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, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # 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. # GNU General Public License for more details.
# #
# For a full copy of the GNU General Public License see the COPYING file # For a full copy of the GNU General Public License see the COPYING file


# ------------------------------------------------------------------------------------------------------------
# Imports (Global) # Imports (Global)

from PyQt4.QtCore import qCritical, Qt, QTimer, QSize from PyQt4.QtCore import qCritical, Qt, QTimer, QSize
from PyQt4.QtGui import QColor, QLinearGradient, QPainter, QWidget from PyQt4.QtGui import QColor, QLinearGradient, QPainter, QWidget


# ------------------------------------------------------------------------------------------------------------
# Widget Class # Widget Class

class DigitalPeakMeter(QWidget): class DigitalPeakMeter(QWidget):
# enum Orientation # enum Orientation
HORIZONTAL = 1 HORIZONTAL = 1
@@ -60,7 +64,7 @@ class DigitalPeakMeter(QWidget):
self.m_channelsData[meter-1] = level self.m_channelsData[meter-1] = level


def setChannels(self, channels): def setChannels(self, channels):
if (channels < 0):
if channels < 0:
return qCritical("DigitalPeakMeter::setChannels(%i) - 'channels' must be a positive integer" % channels) return qCritical("DigitalPeakMeter::setChannels(%i) - 'channels' must be a positive integer" % channels)


self.m_channels = channels self.m_channels = channels
@@ -228,6 +232,8 @@ class DigitalPeakMeter(QWidget):
painter.setPen(QColor(110, 15, 15, 100)) painter.setPen(QColor(110, 15, 15, 100))
painter.drawLine(2, lsmall - (lsmall * 0.96), lfull-2, lsmall - (lsmall * 0.96)) painter.drawLine(2, lsmall - (lsmall * 0.96), lfull-2, lsmall - (lsmall * 0.96))


event.accept()

def resizeEvent(self, event): def resizeEvent(self, event):
self.updateSizes() self.updateSizes()
QWidget.resizeEvent(self, event) QWidget.resizeEvent(self, event)

+ 11
- 5
source/widgets/ledbutton.py View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# Pixmap Button, a custom Qt4 widget # 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 # 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 # 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, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # 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. # GNU General Public License for more details.
# #
# For a full copy of the GNU General Public License see the COPYING file # For a full copy of the GNU General Public License see the COPYING file


# ------------------------------------------------------------------------------------------------------------
# Imports (Global) # Imports (Global)

from PyQt4.QtCore import qCritical, QRectF from PyQt4.QtCore import qCritical, QRectF
from PyQt4.QtGui import QPainter, QPixmap, QPushButton from PyQt4.QtGui import QPainter, QPixmap, QPushButton


# ------------------------------------------------------------------------------------------------------------
# Widget Class # Widget Class

class LEDButton(QPushButton): class LEDButton(QPushButton):
BLUE = 1 BLUE = 1
GREEN = 2 GREEN = 2
@@ -32,7 +36,7 @@ class LEDButton(QPushButton):
QPushButton.__init__(self, parent) QPushButton.__init__(self, parent)


self.m_pixmap = QPixmap() 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.setCheckable(True)
self.setText("") self.setText("")
@@ -52,7 +56,7 @@ class LEDButton(QPushButton):
self.setPixmapSize(size) self.setPixmapSize(size)


def setPixmapSize(self, 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.setMinimumWidth(size)
self.setMaximumWidth(size) self.setMaximumWidth(size)
@@ -83,4 +87,6 @@ class LEDButton(QPushButton):
else: else:
return 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()

+ 26
- 1
source/widgets/paramspinbox.py View File

@@ -1,13 +1,32 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- 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) # Imports (Global)

from PyQt4.QtCore import pyqtSlot, Qt, QTimer, SIGNAL, SLOT 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 QAbstractSpinBox, QComboBox, QCursor, QDialog, QInputDialog, QMenu, QPainter, QProgressBar, QValidator
from PyQt4.QtGui import QStyleFactory from PyQt4.QtGui import QStyleFactory
from math import isnan from math import isnan


# ------------------------------------------------------------------------------------------------------------
# Imports (Custom) # Imports (Custom)

import ui_inputdialog_value import ui_inputdialog_value


def fix_value(value, minimum, maximum): def fix_value(value, minimum, maximum):
@@ -25,7 +44,9 @@ def fix_value(value, minimum, maximum):


#QPlastiqueStyle = QStyleFactory.create("Plastique") #QPlastiqueStyle = QStyleFactory.create("Plastique")


# ------------------------------------------------------------------------------------------------------------
# Custom InputDialog with Scale Points support # Custom InputDialog with Scale Points support

class CustomInputDialog(QDialog, ui_inputdialog_value.Ui_Dialog): class CustomInputDialog(QDialog, ui_inputdialog_value.Ui_Dialog):
def __init__(self, parent, label, current, minimum, maximum, step, scalePoints): def __init__(self, parent, label, current, minimum, maximum, step, scalePoints):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
@@ -59,7 +80,9 @@ class CustomInputDialog(QDialog, ui_inputdialog_value.Ui_Dialog):
QDialog.done(self, r) QDialog.done(self, r)
self.close() self.close()


# ------------------------------------------------------------------------------------------------------------
# Progress-Bar used for ParamSpinBox # Progress-Bar used for ParamSpinBox

class ParamProgressBar(QProgressBar): class ParamProgressBar(QProgressBar):
def __init__(self, parent): def __init__(self, parent):
QProgressBar.__init__(self, parent) QProgressBar.__init__(self, parent)
@@ -138,7 +161,9 @@ class ParamProgressBar(QProgressBar):


QProgressBar.paintEvent(self, event) QProgressBar.paintEvent(self, event)


# ------------------------------------------------------------------------------------------------------------
# Special SpinBox used for parameters # Special SpinBox used for parameters

class ParamSpinBox(QAbstractSpinBox): class ParamSpinBox(QAbstractSpinBox):
def __init__(self, parent): def __init__(self, parent):
QAbstractSpinBox.__init__(self, parent) QAbstractSpinBox.__init__(self, parent)
@@ -205,7 +230,7 @@ class ParamSpinBox(QAbstractSpinBox):
self._step = 0.001 self._step = 0.001
else: else:
self._step = value self._step = value
if self._step_small > value: if self._step_small > value:
self._step_small = value self._step_small = value
if self._step_large < value: if self._step_large < value:


+ 49
- 43
source/widgets/pixmapdial.py View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# Pixmap Dial, a custom Qt4 widget # 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 # 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 # 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, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # 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. # GNU General Public License for more details.
# #
# For a full copy of the GNU General Public License see the COPYING file # For a full copy of the GNU General Public License see the COPYING file


# ------------------------------------------------------------------------------------------------------------
# Imports (Global) # Imports (Global)

from math import floor from math import floor
from PyQt4.QtCore import Qt, QPointF, QRectF, QTimer, QSize, SLOT from PyQt4.QtCore import Qt, QPointF, QRectF, QTimer, QSize, SLOT
from PyQt4.QtGui import QColor, QConicalGradient, QDial, QFontMetrics, QLinearGradient, QPainter, QPainterPath, QPen, QPixmap from PyQt4.QtGui import QColor, QConicalGradient, QDial, QFontMetrics, QLinearGradient, QPainter, QPainterPath, QPen, QPixmap


# ------------------------------------------------------------------------------------------------------------
# Widget Class # Widget Class

class PixmapDial(QDial): class PixmapDial(QDial):
# enum Orientation # enum Orientation
HORIZONTAL = 0 HORIZONTAL = 0
@@ -41,11 +45,11 @@ class PixmapDial(QDial):
QDial.__init__(self, parent) QDial.__init__(self, parent)


self.m_pixmap = QPixmap(":/bitmaps/dial_01d.png") 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(): if self.m_pixmap.width() > self.m_pixmap.height():
self.m_orientation = self.HORIZONTAL self.m_orientation = self.HORIZONTAL
@@ -53,10 +57,10 @@ class PixmapDial(QDial):
self.m_orientation = self.VERTICAL self.m_orientation = self.VERTICAL


self.m_label = "" 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: if self.palette().window().color().lightness() > 100:
# Light background # Light background
@@ -76,12 +80,12 @@ class PixmapDial(QDial):
return self.p_size return self.p_size


def setCustomPaint(self, paint): def setCustomPaint(self, paint):
self.m_custom_paint = paint
self.m_customPaint = paint
self.update() self.update()


def setEnabled(self, enabled): def setEnabled(self, enabled):
if self.isEnabled() != 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.updateSizes()
self.update() self.update()
QDial.setEnabled(self, enabled) QDial.setEnabled(self, enabled)
@@ -89,25 +93,25 @@ class PixmapDial(QDial):
def setLabel(self, label): def setLabel(self, label):
self.m_label = 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() self.update()


def setPixmap(self, pixmapId): 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(): if self.m_pixmap.width() > self.m_pixmap.height():
self.m_orientation = self.HORIZONTAL self.m_orientation = self.HORIZONTAL
@@ -140,19 +144,19 @@ class PixmapDial(QDial):
self.p_size = self.p_width self.p_size = self.p_width
self.p_count = self.p_height / 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): def enterEvent(self, event):
self.m_hovered = True 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) QDial.enterEvent(self, event)


def leaveEvent(self, event): def leaveEvent(self, event):
self.m_hovered = False 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) QDial.leaveEvent(self, event)


def paintEvent(self, event): def paintEvent(self, event):
@@ -161,11 +165,11 @@ class PixmapDial(QDial):


if self.m_label: if self.m_label:
painter.setPen(self.m_color2) 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.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(): if self.isEnabled():
current = float(self.value() - self.minimum()) current = float(self.value() - self.minimum())
@@ -190,10 +194,10 @@ class PixmapDial(QDial):
painter.drawPixmap(target, self.m_pixmap, source) painter.drawPixmap(target, self.m_pixmap, source)


# Custom knobs (Dry/Wet and Volume) # 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 # 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 # draw small circle
ballRect = QRectF(8.0, 8.0, 15.0, 15.0) ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
@@ -208,7 +212,7 @@ class PixmapDial(QDial):
startAngle = 216*16 startAngle = 216*16
spanAngle = -252*16*value 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.setBrush(colorBlue)
painter.setPen(QPen(colorBlue, 0)) painter.setPen(QPen(colorBlue, 0))
painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) 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) painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)


# Custom knobs (L and R) # 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 # 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 # draw small circle
ballRect = QRectF(7.0, 8.0, 11.0, 12.0) 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)) painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0))


# draw arc # draw arc
if self.m_custom_paint == self.CUSTOM_PAINT_CARLA_L:
if self.m_customPaint == self.CUSTOM_PAINT_CARLA_L:
startAngle = 216*16 startAngle = 216*16
spanAngle = -252.0*16*value 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 startAngle = 324.0*16
spanAngle = 252.0*16*(1.0-value) spanAngle = 252.0*16*(1.0-value)
else: else:
@@ -264,14 +268,16 @@ class PixmapDial(QDial):
painter.setPen(QPen(color, 2)) painter.setPen(QPen(color, 2))
painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle) 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()")) QTimer.singleShot(20, self, SLOT("update()"))


else: else:
target = QRectF(0.0, 0.0, self.p_size, self.p_size) target = QRectF(0.0, 0.0, self.p_size, self.p_size)
painter.drawPixmap(target, self.m_pixmap, target) painter.drawPixmap(target, self.m_pixmap, target)


event.accept()

def resizeEvent(self, event): def resizeEvent(self, event):
self.updateSizes() self.updateSizes()
QDial.resizeEvent(self, event) QDial.resizeEvent(self, event)

+ 43
- 35
source/widgets/pixmapkeyboard.py View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


# Pixmap Keyboard, a custom Qt4 widget # 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 # 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 # 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, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # 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. # GNU General Public License for more details.
# #
# For a full copy of the GNU General Public License see the COPYING file # For a full copy of the GNU General Public License see the COPYING file


# ------------------------------------------------------------------------------------------------------------
# Imports (Global) # Imports (Global)

from PyQt4.QtCore import pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer, SIGNAL, SLOT from PyQt4.QtCore import pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer, SIGNAL, SLOT
from PyQt4.QtGui import QFont, QPainter, QPixmap, QWidget from PyQt4.QtGui import QFont, QPainter, QPixmap, QWidget


# ------------------------------------------------------------------------------------------------------------

midi_key2rect_map_horizontal = { midi_key2rect_map_horizontal = {
'0': QRectF(0, 0, 18, 64), # C '0': QRectF(0, 0, 18, 64), # C
'1': QRectF(13, 0, 11, 42), # C# '1': QRectF(13, 0, 11, 42), # C#
@@ -79,7 +83,9 @@ midi_keyboard2key_map = {
'%i' % Qt.Key_U: 71, '%i' % Qt.Key_U: 71,
} }


# ------------------------------------------------------------------------------------------------------------
# MIDI Keyboard, using a pixmap for painting # MIDI Keyboard, using a pixmap for painting

class PixmapKeyboard(QWidget): class PixmapKeyboard(QWidget):
# enum Color # enum Color
COLOR_CLASSIC = 0 COLOR_CLASSIC = 0
@@ -146,15 +152,15 @@ class PixmapKeyboard(QWidget):
return self.setMode(mode) return self.setMode(mode)


if mode == self.HORIZONTAL: 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.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_width = self.m_pixmap.width()
self.p_height = self.m_pixmap.height() / 2 self.p_height = self.m_pixmap.height() / 2
elif mode == self.VERTICAL: 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.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_width = self.m_pixmap.width() / 2
self.p_height = self.m_pixmap.height() self.p_height = self.m_pixmap.height()
else: else:
@@ -170,23 +176,23 @@ class PixmapKeyboard(QWidget):
octaves = 8 octaves = 8
self.m_octaves = octaves 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.setMinimumSize(self.p_width * self.m_octaves, self.p_height)
self.setMaximumSize(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.setMinimumSize(self.p_width, self.p_height * self.m_octaves)
self.setMaximumSize(self.p_width, self.p_height * self.m_octaves) self.setMaximumSize(self.p_width, self.p_height * self.m_octaves)


self.update() self.update()


def handleMousePos(self, pos): 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: if pos.x() < 0 or pos.x() > self.m_octaves * 144:
return return
posX = pos.x() - 1 posX = pos.x() - 1
octave = int(posX / self.p_width) octave = int(posX / self.p_width)
n_pos = QPointF(posX % self.p_width, pos.y()) 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: if pos.y() < 0 or pos.y() > self.m_octaves * 144:
return return
posY = pos.y() - 1 posY = pos.y() - 1
@@ -197,29 +203,29 @@ class PixmapKeyboard(QWidget):


octave += 3 octave += 3


if self.m_midi_map['1'].contains(n_pos): # C#
if self.m_midiMap['1'].contains(n_pos): # C#
note = 1 note = 1
elif self.m_midi_map['3'].contains(n_pos): # D#
elif self.m_midiMap['3'].contains(n_pos): # D#
note = 3 note = 3
elif self.m_midi_map['6'].contains(n_pos): # F#
elif self.m_midiMap['6'].contains(n_pos): # F#
note = 6 note = 6
elif self.m_midi_map['8'].contains(n_pos): # G#
elif self.m_midiMap['8'].contains(n_pos): # G#
note = 8 note = 8
elif self.m_midi_map['10'].contains(n_pos):# A#
elif self.m_midiMap['10'].contains(n_pos):# A#
note = 10 note = 10
elif self.m_midi_map['0'].contains(n_pos): # C
elif self.m_midiMap['0'].contains(n_pos): # C
note = 0 note = 0
elif self.m_midi_map['2'].contains(n_pos): # D
elif self.m_midiMap['2'].contains(n_pos): # D
note = 2 note = 2
elif self.m_midi_map['4'].contains(n_pos): # E
elif self.m_midiMap['4'].contains(n_pos): # E
note = 4 note = 4
elif self.m_midi_map['5'].contains(n_pos): # F
elif self.m_midiMap['5'].contains(n_pos): # F
note = 5 note = 5
elif self.m_midi_map['7'].contains(n_pos): # G
elif self.m_midiMap['7'].contains(n_pos): # G
note = 7 note = 7
elif self.m_midi_map['9'].contains(n_pos): # A
elif self.m_midiMap['9'].contains(n_pos): # A
note = 9 note = 9
elif self.m_midi_map['11'].contains(n_pos):# B
elif self.m_midiMap['11'].contains(n_pos):# B
note = 11 note = 11
else: else:
note = -1 note = -1
@@ -271,9 +277,9 @@ class PixmapKeyboard(QWidget):
# Paint clean keys (as background) # Paint clean keys (as background)


for octave in range(self.m_octaves): 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) 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) target = QRectF(0, self.p_height * octave, self.p_width, self.p_height)
else: else:
return return
@@ -316,13 +322,13 @@ class PixmapKeyboard(QWidget):
# cannot paint this note either # cannot paint this note either
continue continue


if self.m_pixmap_mode == self.VERTICAL:
if self.m_pixmapMode == self.VERTICAL:
octave = self.m_octaves - octave - 1 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()) target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height())
source = QRectF(pos.x(), self.p_height, 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()) 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()) source = QRectF(self.p_width, pos.y(), pos.width(), pos.height())
else: else:
@@ -338,10 +344,10 @@ class PixmapKeyboard(QWidget):
for octave in range(self.m_octaves): for octave in range(self.m_octaves):
for note in (1, 3, 6, 8, 10): for note in (1, 3, 6, 8, 10):
pos = self._getRectFromMidiNote(note) 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()) target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height())
source = QRectF(pos.x(), 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()) target = QRectF(pos.x(), pos.y() + (self.p_height * octave), pos.width(), pos.height())
source = QRectF(0, pos.y(), pos.width(), pos.height()) source = QRectF(0, pos.y(), pos.width(), pos.height())
else: else:
@@ -382,13 +388,13 @@ class PixmapKeyboard(QWidget):
# cannot paint this note either # cannot paint this note either
continue continue


if self.m_pixmap_mode == self.VERTICAL:
if self.m_pixmapMode == self.VERTICAL:
octave = self.m_octaves - octave - 1 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()) target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height())
source = QRectF(pos.x(), self.p_height, 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()) 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()) source = QRectF(self.p_width, pos.y(), pos.width(), pos.height())
else: else:
@@ -401,11 +407,13 @@ class PixmapKeyboard(QWidget):
painter.setPen(Qt.black) painter.setPen(Qt.black)


for i in range(self.m_octaves): 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)) 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)) painter.drawText(45, (self.m_octaves * 144) - (i * 144) - 16, 18, 18, Qt.AlignCenter, "C%i" % int(i + 2))


event.accept()

@pyqtSlot() @pyqtSlot()
def slot_updateOnce(self): def slot_updateOnce(self):
if self.m_needsUpdate: if self.m_needsUpdate:
@@ -417,4 +425,4 @@ class PixmapKeyboard(QWidget):
return bool(baseNote in (1, 3, 6, 8, 10)) return bool(baseNote in (1, 3, 6, 8, 10))


def _getRectFromMidiNote(self, note): def _getRectFromMidiNote(self, note):
return self.m_midi_map.get(str(note % 12))
return self.m_midiMap.get(str(note % 12))

Loading…
Cancel
Save