diff --git a/.gitignore b/.gitignore
index 326ed27f..529fa05b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,6 @@ src/dist/
# Other
data/templates/energyXT.xt
+
+# IDEs
+.idea/
\ No newline at end of file
diff --git a/data/cadence-pulse2jack b/data/cadence-pulse2jack
index 5266cb9f..4243a0a0 100755
--- a/data/cadence-pulse2jack
+++ b/data/cadence-pulse2jack
@@ -1,44 +1,48 @@
-#!/bin/bash
+#! /usr/bin/env bash
# Script to bridge/start pulseaudio into JACK mode
INSTALL_PREFIX="X-PREFIX-X"
+PULSE_CONFIG_DIR=${PULSE_CONFIG_DIR:-"$HOME/.pulse"}
+JACK_CONNFILE="$PULSE_CONFIG_DIR/jack-connections"
+PA_CTLFILE="$PULSE_CONFIG_DIR/ctl.pa"
+
# ----------------------------------------------
-if [ ! -d ~/.pulse ]; then
- mkdir -p ~/.pulse
+if [ ! -d $PULSE_CONFIG_DIR ]; then
+ mkdir -p $PULSE_CONFIG_DIR
fi
-if [ ! -f ~/.pulse/client.conf ]; then
- echo "autospawn = no" > ~/.pulse/client.conf
+if [ ! -f $PULSE_CONFIG_DIR/client.conf ]; then
+ echo "autospawn = no" > $PULSE_CONFIG_DIR/client.conf
else
- if (! cat ~/.pulse/client.conf | grep "autospawn = no" > /dev/null); then
- sed -i '/autospawn =/d' ~/.pulse/client.conf
- echo "autospawn = no" >> ~/.pulse/client.conf
+ if (! cat $PULSE_CONFIG_DIR/client.conf | grep "autospawn = no" > /dev/null); then
+ sed -i '/autospawn =/d' $PULSE_CONFIG_DIR/client.conf
+ echo "autospawn = no" >> $PULSE_CONFIG_DIR/client.conf
fi
fi
-if [ ! -f ~/.pulse/daemon.conf ]; then
- echo "default-sample-format = float32le" > ~/.pulse/daemon.conf
- echo "realtime-scheduling = yes" >> ~/.pulse/daemon.conf
- echo "rlimit-rttime = -1" >> ~/.pulse/daemon.conf
- echo "exit-idle-time = -1" >> ~/.pulse/daemon.conf
+if [ ! -f $PULSE_CONFIG_DIR/daemon.conf ]; then
+ echo "default-sample-format = float32le" > $PULSE_CONFIG_DIR/daemon.conf
+ echo "realtime-scheduling = yes" >> $PULSE_CONFIG_DIR/daemon.conf
+ echo "rlimit-rttime = -1" >> $PULSE_CONFIG_DIR/daemon.conf
+ echo "exit-idle-time = -1" >> $PULSE_CONFIG_DIR/daemon.conf
else
- if (! cat ~/.pulse/daemon.conf | grep "default-sample-format = float32le" > /dev/null); then
- sed -i '/default-sample-format = /d' ~/.pulse/daemon.conf
- echo "default-sample-format = float32le" >> ~/.pulse/daemon.conf
+ if (! cat $PULSE_CONFIG_DIR/daemon.conf | grep "default-sample-format = float32le" > /dev/null); then
+ sed -i '/default-sample-format = /d' $PULSE_CONFIG_DIR/daemon.conf
+ echo "default-sample-format = float32le" >> $PULSE_CONFIG_DIR/daemon.conf
fi
- if (! cat ~/.pulse/daemon.conf | grep "realtime-scheduling = yes" > /dev/null); then
- sed -i '/realtime-scheduling = /d' ~/.pulse/daemon.conf
- echo "realtime-scheduling = yes" >> ~/.pulse/daemon.conf
+ if (! cat $PULSE_CONFIG_DIR/daemon.conf | grep "realtime-scheduling = yes" > /dev/null); then
+ sed -i '/realtime-scheduling = /d' $PULSE_CONFIG_DIR/daemon.conf
+ echo "realtime-scheduling = yes" >> $PULSE_CONFIG_DIR/daemon.conf
fi
- if (! cat ~/.pulse/daemon.conf | grep "rlimit-rttime = -1" > /dev/null); then
- sed -i '/rlimit-rttime =/d' ~/.pulse/daemon.conf
- echo "rlimit-rttime = -1" >> ~/.pulse/daemon.conf
+ if (! cat $PULSE_CONFIG_DIR/daemon.conf | grep "rlimit-rttime = -1" > /dev/null); then
+ sed -i '/rlimit-rttime =/d' $PULSE_CONFIG_DIR/daemon.conf
+ echo "rlimit-rttime = -1" >> $PULSE_CONFIG_DIR/daemon.conf
fi
- if (! cat ~/.pulse/daemon.conf | grep "exit-idle-time = -1" > /dev/null); then
- sed -i '/exit-idle-time =/d' ~/.pulse/daemon.conf
- echo "exit-idle-time = -1" >> ~/.pulse/daemon.conf
+ if (! cat $PULSE_CONFIG_DIR/daemon.conf | grep "exit-idle-time = -1" > /dev/null); then
+ sed -i '/exit-idle-time =/d' $PULSE_CONFIG_DIR/daemon.conf
+ echo "exit-idle-time = -1" >> $PULSE_CONFIG_DIR/daemon.conf
fi
fi
@@ -56,7 +60,7 @@ echo "usage: $0 [command]
--dummy Don't do anything, just create the needed files
NOTE:
- When runned with no arguments, pulse2jack will
+ When ran with no arguments, pulse2jack will
activate PulseAudio with both playback and record modes.
"
exit
@@ -68,18 +72,56 @@ exit
-p|--p|--play)
PLAY_ONLY="yes"
-FILE=$INSTALL_PREFIX/share/cadence/pulse2jack/play.pa
;;
*)
-FILE=$INSTALL_PREFIX/share/cadence/pulse2jack/play+rec.pa
;;
esac
+TEMPLATE_PA_FILE=$INSTALL_PREFIX/share/cadence/pulse2jack/template.pa
+
# ----------------------------------------------
-IsPulseAudioRunning()
-{
+addJackConnectionsToPAFile() {
+ PAFILE=$1
+ OUTFILE=$2
+ cp $PAFILE $OUTFILE
+ tac $JACK_CONNFILE | while IFS=\| read name type channels connect; do
+ sed -i "/### Load Jack modules/a load-module module-jack-$type channels=$channels connect=$connect client_name=\"$name\"" $OUTFILE
+ done
+}
+
+loadConnectionsIntoPA() {
+ CONNTYPE=$1
+ while IFS=\| read name type channels connect; do
+ if [ $CONNTYPE == "$type" ] ; then
+ pactl load-module module-jack-$type channels=$channels connect=$connect client_name="$name" > /dev/null
+ fi
+ done < $JACK_CONNFILE
+}
+
+addDefaultSink() {
+ INFILE=$1
+ sed -i "/### Make Jack default/a set-default-sink jack_out" $INFILE
+}
+
+addDefaultSource() {
+ INFILE=$1
+ sed -i "/### Make Jack default/a set-default-source jack_in" $INFILE
+}
+
+if [ ! -f $PULSE_CONFIG_DIR/jack-connections ] ; then
+ # safety in case there's no config generated yet from GUI
+ sed "/### Load Jack modules/a load-module module-jack-sink
+ /### Load Jack modules/a load-module module-jack-source" $TEMPLATE_PA_FILE > $PA_CTLFILE
+else
+ addJackConnectionsToPAFile $TEMPLATE_PA_FILE $PA_CTLFILE
+fi
+
+addDefaultSource $PA_CTLFILE
+addDefaultSink $PA_CTLFILE
+
+IsPulseAudioRunning() {
PROCESS=`ps -u $USER | grep pulseaudio`
if [ "$PROCESS" == "" ]; then
false
@@ -89,39 +131,29 @@ IsPulseAudioRunning()
}
if (IsPulseAudioRunning); then
-{
- if (`jack_lsp | grep "PulseAudio JACK Sink:" > /dev/null`); then
- {
+ # get the first sink name from the table
+ FIRST_SINK_NAME=$(grep '|sink|' $JACK_CONNFILE | head -1 | cut -d\| -f1)
+ if ($(jack_lsp 2>/dev/null | grep "$FIRST_SINK_NAME" > /dev/null)); then
echo "PulseAudio is already running and bridged to JACK"
- }
else
- {
echo "PulseAudio is already running, bridge it..."
if [ "$PLAY_ONLY" == "yes" ]; then
- {
- pactl load-module module-jack-sink > /dev/null
+ loadConnectionsIntoPA "sink"
pacmd set-default-source jack_in > /dev/null
- }
else
- {
- pactl load-module module-jack-sink > /dev/null
- pactl load-module module-jack-source > /dev/null
+ loadConnectionsIntoPA "source"
+ loadConnectionsIntoPA "sink"
pacmd set-default-sink jack_out > /dev/null
pacmd set-default-source jack_in > /dev/null
- }
fi
echo "Done"
- }
fi
-}
else
-{
- if (`pulseaudio --daemonize --high-priority --realtime --exit-idle-time=-1 --file=$FILE -n`); then
+ if ($(pulseaudio --daemonize --high-priority --realtime --exit-idle-time=-1 --file=$PA_CTLFILE -n)); then
echo "Initiated PulseAudio successfully!"
else
echo "Failed to initialize PulseAudio!"
fi
-}
fi
diff --git a/data/pulse2jack/play.pa b/data/pulse2jack/template.pa
old mode 100644
new mode 100755
similarity index 96%
rename from data/pulse2jack/play.pa
rename to data/pulse2jack/template.pa
index 3fc04dd6..4c6a1d5a
--- a/data/pulse2jack/play.pa
+++ b/data/pulse2jack/template.pa
@@ -28,7 +28,6 @@ load-module module-stream-restore
load-module module-card-restore
### Load Jack modules
-load-module module-jack-sink
### Load unix protocol
load-module module-native-protocol-unix
@@ -47,4 +46,4 @@ load-module module-rescue-streams
load-module module-always-sink
### Make Jack default
-set-default-sink jack_out
+
diff --git a/resources/ui/cadence.ui b/resources/ui/cadence.ui
index 267f04d2..2932804e 100644
--- a/resources/ui/cadence.ui
+++ b/resources/ui/cadence.ui
@@ -6,8 +6,8 @@
0
0
- 740
- 564
+ 822
+ 659
@@ -240,770 +240,1019 @@
-
-
-
-
- JACK Status
+
+
+ 1
-
-
-
-
-
-
-
-
-
-
-
-
- :/16x16/dialog-ok-apply.png
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Sample Rate:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- DSP Load:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- TextLabel
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Realtime:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
-
-
- :/16x16/dialog-ok-apply.png
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Buffer Size:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- TextLabel
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Xruns:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- TextLabel
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Server Status:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Block Latency:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- TextLabel
-
-
-
- -
-
-
- TextLabel
-
-
-
- -
-
-
- TextLabel
-
-
-
- -
-
-
- TextLabel
-
-
-
-
-
-
- -
-
-
- 0
-
-
- 1
-
-
- Qt::Horizontal
-
-
-
- -
-
-
-
-
-
- Start
-
-
-
- -
-
-
- Stop
-
-
-
- -
-
-
- Force Restart
-
-
-
- -
-
-
- Configure
-
-
-
- -
-
-
- Switch Master
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Preferred
-
-
-
- 38
- 20
-
-
-
-
- -
-
-
- Auto-start JACK or LADISH at login
-
-
-
- -
-
-
- ...
-
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- JACK Bridges
-
-
-
- 2
-
-
-
-
-
-
- 0
- 0
-
-
-
- QFrame::StyledPanel
-
-
- QFrame::Sunken
-
-
- 0
-
-
- 1
-
-
-
-
- 0
- 0
- 360
- 100
-
+
+
+ Jack Status
+
+
+
-
+
+
+
-
- ALSA Audio
-
-
-
- 2
-
-
- 0
-
-
- 2
-
-
- 2
-
-
-
-
-
- No bridge in use
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 10
- 20
-
-
-
-
- -
-
-
- Start
-
-
-
- -
-
-
- Stop
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 10
- 20
-
-
-
-
-
-
+
-
-
-
-
-
-
- Bridge Type:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
-
+
+
+
-
+
+
+
+
+
+ :/16x16/dialog-ok-apply.png
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Sample Rate:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ DSP Load:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
- (None)
+ Realtime:
-
- -
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
- ALSA -> Loop -> JACK
+
+
+
+ :/16x16/dialog-ok-apply.png
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
-
- -
- ALSA -> JACK (Plugin)
+ Buffer Size:
-
- -
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
- ALSA -> PulseAudio -> JACK (Plugin)
+ TextLabel
-
-
-
- -
-
-
- ...
-
-
-
-
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Xruns:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ TextLabel
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Server Status:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Block Latency:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ TextLabel
+
+
+
+ -
+
+
+ TextLabel
+
+
+
+ -
+
+
+ TextLabel
+
+
+
+ -
+
+
+ TextLabel
+
+
+
+
+
-
-
-
- Qt::Vertical
-
-
- QSizePolicy::Preferred
+
+
+ 0
-
-
- 20
- 20
-
-
-
-
-
-
-
-
-
- 0
- 0
- 360
- 97
-
-
-
- ALSA MIDI
-
-
-
- 2
-
-
- 0
-
-
- 2
-
-
- 2
-
- -
-
-
- ALSA MIDI State
+
+ 1
-
- Qt::AlignCenter
+
+ Qt::Horizontal
-
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 10
- 20
-
-
-
-
- -
-
+
+
-
+
Start
- -
-
+
-
+
Stop
- -
-
-
- Qt::Horizontal
+
-
+
+
+ Force Restart
-
- QSizePolicy::Fixed
+
+
+ -
+
+
+ Configure
-
-
- 10
- 20
-
+
+
+ -
+
+
+ Switch Master
-
+
-
-
- -
-
-
-
-
+
-
+
Qt::Horizontal
+
+ QSizePolicy::Preferred
+
- 40
+ 38
20
- -
-
+
-
+
- Export hardware ports
+ Auto-start JACK or LADISH at login
- -
-
+
-
+
- Start with Jack
+ ...
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Preferred
-
-
-
- 20
- 20
-
-
-
-
-
-
-
- 0
- 0
- 360
- 97
-
+
+ -
+
+
+ Qt::Vertical
-
- PulseAudio
-
-
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+ Jack Bridges
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+
- 2
+ 4
- 0
+ 4
- 2
+ 4
2
-
-
-
-
- PulseAudio state
+
-
+
+
+
+ 0
+ 0
+
-
- Qt::AlignCenter
+
+ QFrame::StyledPanel
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 10
- 20
-
-
-
-
- -
-
-
- Start
+
+ QFrame::Sunken
+
+
+ 0
+
+
+ 1
+
+
+ 1
+
+
+ 0
+
+
+
+
+ 0
+ 0
+ 458
+ 424
+
+
+
+ ALSA
+
+
+
+ 4
-
-
- -
-
-
- Stop
+
+ 4
-
-
- -
-
-
- Qt::Horizontal
+
+ 2
-
- QSizePolicy::Fixed
+
+ 4
-
-
- 10
- 20
-
+
+ 4
-
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
+
-
+
+
+ 4
+
+
+ 4
+
+
+ 6
+
+
-
+
+
+ ALSA Audio
+
+
+
+ -
+
+
+ No bridge in use
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+ Start
+
+
+
+ -
+
+
+ Stop
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Bridge Type:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
-
+
+ (None)
+
+
+ -
+
+ ALSA -> Loop -> JACK
+
+
+ -
+
+ ALSA -> JACK (Plugin)
+
+
+ -
+
+ ALSA -> PulseAudio -> JACK (Plugin)
+
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+
+
+ -
+
+
+ 4
+
+
+ 4
+
+
+ 6
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ ALSA MIDI
+
+
+
+ -
+
+
+ ALSA MIDI State
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+ Start
+
+
+
+ -
+
+
+ Stop
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Export hardware ports
+
+
+
+ -
+
+
+ Start with Jack
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Preferred
+
+
+
+ 20
+ 140
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+ 458
+ 424
+
+
+
+ PulseAudio
+
+
+
+ 2
-
-
- 40
- 20
-
+
+ 0
-
-
- -
-
-
- Auto-start at login
+
+ 2
-
-
- -
-
-
- ...
+
+ 2
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Preferred
-
-
-
- 20
- 20
-
-
-
+
-
+
+
+ PulseAudio state
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ 4
+
+
+ 4
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+ Start
+
+
+
+ -
+
+
+ Stop
+
+
+
+ -
+
+
+ Auto-start at login
+
+
+
+ -
+
+
+ ...
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Minimum
+
+
+
+ 20
+ 5
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Minimum
+
+
+
+ 20
+ 5
+
+
+
+
+ -
+
+
+ 4
+
+
-
+
+
+ PulseAudio Sinks and Sources
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed
+
+
+ QAbstractItemView::SingleSelection
+
+
+ QAbstractItemView::SelectRows
+
+
+ 4
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ true
+
+
+
+
+
+
+
+ -
+
+
+ 6
+
+
+ 0
+
+
+ 4
+
+
+ 4
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 50
+
+
+
+ +
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 50
+
+
+
+ -
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Preferred
+
+
+
+ 160
+ 20
+
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Undo
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Save
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Defaults
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Preferred
-
-
-
- 20
- 40
-
-
-
-
@@ -1572,7 +1821,7 @@
Audio Plugins PATH
- AlignHCenter|AlignVCenter|AlignCenter
+ AlignCenter
ItemIsSelectable|ItemIsEnabled
@@ -1583,7 +1832,7 @@
Default Applications
- AlignHCenter|AlignVCenter|AlignCenter
+ AlignCenter
ItemIsSelectable|ItemIsEnabled
@@ -1594,7 +1843,7 @@
WineASIO
- AlignHCenter|AlignVCenter|AlignCenter
+ AlignCenter
ItemIsSelectable|ItemIsEnabled
@@ -1613,7 +1862,16 @@
-
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
0
-
@@ -1681,8 +1939,8 @@
0
0
- 416
- 334
+ 490
+ 385
@@ -1711,8 +1969,8 @@
0
0
- 94
- 66
+ 86
+ 72
@@ -1741,8 +1999,8 @@
0
0
- 94
- 66
+ 86
+ 72
@@ -1771,8 +2029,8 @@
0
0
- 94
- 66
+ 86
+ 72
@@ -1832,12 +2090,21 @@
-
- 20
+
+ 0
+
+
+ 0
+
+
+ 0
-
+
0
+
+ 20
+
-
@@ -2063,7 +2330,16 @@
-
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
0
-
@@ -2297,6 +2573,11 @@ Default is off
QLabel
+
+ BridgeSourceSink
+ QTableWidget
+
+
diff --git a/src/bridgesourcesink.py b/src/bridgesourcesink.py
new file mode 100755
index 00000000..c61e5cab
--- /dev/null
+++ b/src/bridgesourcesink.py
@@ -0,0 +1,271 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Custom QTableWidget that handles pulseaudio source and sinks
+# Copyright (C) 2011-2018 Filipe Coelho
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# For a full copy of the GNU General Public License see the COPYING file
+
+# ---------------------------------------------------------------------
+# Imports (Global)
+
+from collections import namedtuple
+
+from PyQt5.QtCore import Qt, QRegExp, pyqtSignal
+from PyQt5.QtGui import QRegExpValidator
+from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem, QHeaderView, QComboBox, QLineEdit, QSpinBox, QPushButton, QCheckBox, QHBoxLayout, QWidget
+from shared import *
+from shared_cadence import GlobalSettings
+
+# Python3/4 function name normalisation
+try:
+ range = xrange
+except NameError:
+ pass
+
+PULSE_USER_CONFIG_DIR = os.getenv("PULSE_USER_CONFIG_DIR")
+if not PULSE_USER_CONFIG_DIR:
+ PULSE_USER_CONFIG_DIR = os.path.join(HOME, ".pulse")
+
+if not os.path.exists(PULSE_USER_CONFIG_DIR):
+ os.path.mkdir(PULSE_USER_CONFIG_DIR)
+
+# a data class to hold the Sink/Source Data. Use strings in tuple for easy map(_make)
+# but convert to type in table for editor
+SSData = namedtuple('SSData', 'name s_type channels connected')
+
+
+# ---------------------------------------------------------------------
+# Extend QTableWidget to hold Sink/Source data
+
+class BridgeSourceSink(QTableWidget):
+ defaultPASourceData = SSData(
+ name="PulseAudio JACK Source",
+ s_type="source",
+ channels="2",
+ connected="True")
+
+ defaultPASinkData = SSData(
+ name="PulseAudio JACK Sink",
+ s_type="sink",
+ channels="2",
+ connected="True")
+
+ customChanged = pyqtSignal()
+
+ def __init__(self, parent):
+ QTableWidget.__init__(self, parent)
+ self.bridgeData = []
+ if not GlobalSettings.contains("Pulse2JACK/PABridges"):
+ self.initialise_settings()
+ self.load_from_settings()
+
+ def load_data_into_cells(self):
+ self.setHorizontalHeaderLabels(['Name', 'Type', 'Channels', 'Conn?'])
+ self.setRowCount(0)
+
+ for data in self.bridgeData:
+ row = self.rowCount()
+ self.insertRow(row)
+
+ # Name
+ name_col = QLineEdit()
+ name_col.setText(data.name)
+ name_col.textChanged.connect(self.customChanged.emit)
+ rx = QRegExp("[^|]+")
+ validator = QRegExpValidator(rx, self)
+ name_col.setValidator(validator)
+ self.setCellWidget(row, 0, name_col)
+
+ # Type
+ combo_box = QComboBox()
+
+ microphone_icon = QIcon.fromTheme('audio-input-microphone')
+ if microphone_icon.isNull():
+ microphone_icon = QIcon.fromTheme('microphone')
+
+ loudspeaker_icon = QIcon.fromTheme('audio-volume-high')
+ if loudspeaker_icon.isNull():
+ loudspeaker_icon = QIcon.fromTheme('player-volume')
+
+ combo_box.addItem(microphone_icon, "source")
+ combo_box.addItem(loudspeaker_icon, "sink")
+
+ combo_box.setCurrentIndex(0 if data.s_type == "source" else 1)
+ combo_box.currentTextChanged.connect(self.customChanged.emit)
+ self.setCellWidget(row, 1, combo_box)
+
+ # Channels
+ chan_col = QSpinBox()
+ chan_col.setValue(int(data.channels))
+ chan_col.setMinimum(1)
+ chan_col.setAlignment(Qt.AlignCenter)
+ chan_col.valueChanged.connect(self.customChanged.emit)
+ self.setCellWidget(row, 2, chan_col)
+
+ # Auto connect?
+ auto_cb = QCheckBox()
+ auto_cb.setObjectName("auto_cb")
+ auto_cb.setCheckState(Qt.Checked if data.connected in ['true', 'True', 'TRUE'] else Qt.Unchecked)
+ auto_cb.stateChanged.connect(self.customChanged.emit)
+ widget = QWidget()
+ h_layout = QHBoxLayout(widget)
+ h_layout.addWidget(auto_cb)
+ h_layout.setAlignment(Qt.AlignCenter)
+ h_layout.setContentsMargins(0, 0, 0, 0)
+ widget.setLayout(h_layout)
+ self.setCellWidget(row, 3, widget)
+ self.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed)
+
+ def defaults(self):
+ self.bridgeData = [self.defaultPASourceData, self.defaultPASinkData]
+ self.load_data_into_cells()
+ self.customChanged.emit()
+
+ def undo(self):
+ self.load_from_settings()
+ self.load_data_into_cells()
+ self.customChanged.emit()
+
+ def initialise_settings(self):
+ GlobalSettings.setValue(
+ "Pulse2JACK/PABridges",
+ self.encode_bridge_data([self.defaultPASourceData, self.defaultPASinkData]))
+
+ def load_from_settings(self):
+ bridgeDataText = GlobalSettings.value("Pulse2JACK/PABridges")
+ self.bridgeData = self.decode_bridge_data(bridgeDataText)
+
+ def hasChanges(self)->bool:
+ bridgeDataText = GlobalSettings.value("Pulse2JACK/PABridges")
+ saved_data = self.decode_bridge_data(bridgeDataText)
+
+ if self.rowCount() != len(saved_data):
+ return True
+
+ for row in range(self.rowCount()):
+ orig_data = saved_data[row]
+
+ name = self.cellWidget(row, 0).text()
+ if name != orig_data[0]:
+ return True
+
+ type = self.cellWidget(row, 1).currentText()
+ if type != orig_data[1]:
+ return True
+
+ channels = self.cellWidget(row, 2).value()
+ if channels != int(orig_data[2]):
+ return True
+
+ auto_cb = self.cellWidget(row, 3).findChild(QCheckBox, "auto_cb")
+ connected = auto_cb.isChecked()
+ if connected != bool(orig_data[3]):
+ return True
+
+ return False
+
+ def hasValidValues(self)->bool:
+ used_names = []
+
+ row_count = self.rowCount()
+ # Prevent save without any bridge
+ if not row_count:
+ return False
+
+ for row in range(row_count):
+ line_edit = self.cellWidget(row, 0)
+ name = line_edit.text()
+
+ if not name or name in used_names:
+ # prevent double name entries
+ return False
+
+ used_names.append(name)
+
+ return True
+
+ def add_row(self):
+ # first, search in table which bridge exists
+ # to add the most pertinent new bridge
+ has_source = False
+ has_sink = False
+
+ for row in range(self.rowCount()):
+ cell_widget = self.cellWidget(row, 1)
+
+ group_type = ""
+ if cell_widget:
+ group_type = cell_widget.currentText()
+
+ if group_type == "source":
+ has_source = True
+ elif group_type == "sink":
+ has_sink = True
+
+ if has_source and has_sink:
+ break
+
+ ss_data = SSData(name="", s_type="source", channels="2", connected="False")
+ if not has_sink:
+ ss_data = self.defaultPASinkData
+ elif not has_source:
+ ss_data = self.defaultPASourceData
+
+ self.bridgeData.append(ss_data)
+ self.load_data_into_cells()
+ self.editItem(self.item(self.rowCount() - 1, 0))
+ self.customChanged.emit()
+
+ def remove_row(self):
+ del self.bridgeData[self.currentRow()]
+ self.load_data_into_cells()
+ self.customChanged.emit()
+
+ def save_bridges(self):
+ self.bridgeData = []
+ for row in range(0, self.rowCount()):
+ new_name = self.cellWidget(row, 0).property("text")
+ new_type = self.cellWidget(row, 1).currentText()
+ new_channels = self.cellWidget(row, 2).value()
+ auto_cb = self.cellWidget(row, 3).findChild(QCheckBox, "auto_cb")
+ new_conn = auto_cb.checkState() == Qt.Checked
+
+ self.bridgeData.append(
+ SSData(name=new_name,
+ s_type=new_type,
+ channels=new_channels,
+ connected=str(new_conn)))
+ GlobalSettings.setValue("Pulse2JACK/PABridges", self.encode_bridge_data(self.bridgeData))
+ conn_file_path = os.path.join(PULSE_USER_CONFIG_DIR, "jack-connections")
+ conn_file = open(conn_file_path, "w")
+ conn_file.write("\n".join(self.encode_bridge_data(self.bridgeData)))
+ # Need an extra line at the end
+ conn_file.write("\n")
+ conn_file.close()
+ self.customChanged.emit()
+
+ # encode and decode from tuple so it isn't stored in the settings file as a type, and thus the
+ # configuration is backwards compatible with versions that don't understand SSData types.
+ # Uses PIPE symbol as separator
+ def encode_bridge_data(self, data):
+ return list(map(lambda s: s.name + "|" + s.s_type + "|" + str(s.channels) + "|" + str(s.connected), data))
+
+ def decode_bridge_data(self, data):
+ return list(map(lambda d: SSData._make(d.split("|")), data))
+
+ def resizeEvent(self, event):
+ self.setColumnWidth(0, int(self.width() * 0.49))
+ self.setColumnWidth(1, int(self.width() * 0.17))
+ self.setColumnWidth(2, int(self.width() * 0.17))
+ self.setColumnWidth(3, int(self.width() * 0.17))
diff --git a/src/cadence.py b/src/cadence.py
index 8b5192f7..369c7462 100755
--- a/src/cadence.py
+++ b/src/cadence.py
@@ -23,10 +23,10 @@
if True:
from PyQt5.QtCore import QFileSystemWatcher, QThread, QSemaphore
- from PyQt5.QtWidgets import QApplication, QDialogButtonBox, QLabel, QMainWindow, QSizePolicy
+ from PyQt5.QtWidgets import QApplication, QDialogButtonBox, QLabel, QMainWindow #, QSizePolicy, QTableWidget, QTableWidgetItem, QHeaderView
else:
from PyQt4.QtCore import QFileSystemWatcher, QThread, QSemaphore
- from PyQt4.QtGui import QApplication, QDialogButtonBox, QLabel, QMainWindow, QSizePolicy
+ from PyQt4.QtGui import QApplication, QDialogButtonBox, QLabel, QMainWindow # , QSizePolicy, QTableWidget, QTableWidgetItem, QHeaderView
# ------------------------------------------------------------------------------------------------------------
# Imports (Custom Stuff)
@@ -982,6 +982,8 @@ def __init__(self, parent=None):
if self.cb_app_browser.count() == 0:
self.ch_app_browser.setEnabled(False)
+ self.tableSinkSourceData.load_data_into_cells()
+
mimeappsPath = os.path.join(HOME, ".local", "share", "applications", "mimeapps.list")
if os.path.exists(mimeappsPath):
@@ -1132,6 +1134,14 @@ def __init__(self, parent=None):
self.b_pulse_stop.clicked.connect(self.slot_PulseAudioBridgeStop)
self.tb_pulse_options.clicked.connect(self.slot_PulseAudioBridgeOptions)
+ self.b_bridge_add.clicked.connect(self.slot_PulseAudioBridgeAdd)
+ self.b_bridge_remove.clicked.connect(self.slot_PulseAudioBridgeRemove)
+ self.b_bridge_undo.clicked.connect(self.slot_PulseAudioBridgeUndo)
+ self.b_bridge_save.clicked.connect(self.slot_PulseAudioBridgeSave)
+ self.b_bridge_defaults.clicked.connect(self.slot_PulseAudioBridgeDefaults)
+ self.tableSinkSourceData.customChanged.connect(self.slot_PulseAudioBridgeTableChanged)
+ self.tableSinkSourceData.doubleClicked.connect(self.slot_PulseAudioBridgeTableChanged)
+
self.pic_catia.clicked.connect(self.func_start_catia)
self.pic_claudia.clicked.connect(self.func_start_claudia)
self.pic_meter_in.clicked.connect(self.func_start_jackmeter_in)
@@ -1226,11 +1236,13 @@ def DBusReconnect(self):
except:
version, groups, conns = (list(), list(), list())
+ pa_first_group_name = self.getFirstPulseAudioGroupName()
+
for group_id, group_name, ports in groups:
if group_name == "alsa2jack":
global jackClientIdALSA
jackClientIdALSA = group_id
- elif group_name == "PulseAudio JACK Sink":
+ elif group_name in (pa_first_group_name, "PulseAudio JACK Sink"):
global jackClientIdPulse
jackClientIdPulse = group_id
@@ -1405,6 +1417,14 @@ def a2jStopped(self):
self.systray.setActionEnabled("a2j_stop", False)
self.label_bridge_a2j.setText(self.tr("ALSA MIDI Bridge is stopped"))
+ def getFirstPulseAudioGroupName(self)->str:
+ # search PulseAudio JACK first group in settings
+ pa_bridges_settings = GlobalSettings.value("Pulse2JACK/PABridges", type=list)
+ if pa_bridges_settings:
+ return pa_bridges_settings[0].partition('|')[0]
+
+ return "PulseAudio JACK Sink"
+
def checkAlsaAudio(self):
asoundrcFile = os.path.join(HOME, ".asoundrc")
@@ -1616,11 +1636,13 @@ def slot_DBusJackServerStoppedCallback(self):
@pyqtSlot(int, str)
def slot_DBusJackClientAppearedCallback(self, group_id, group_name):
+ pa_first_group_name = self.getFirstPulseAudioGroupName()
+
if group_name == "alsa2jack":
global jackClientIdALSA
jackClientIdALSA = group_id
self.checkAlsaAudio()
- elif group_name == "PulseAudio JACK Sink":
+ elif group_name in ("PulseAudio JACK Sink", pa_first_group_name):
global jackClientIdPulse
jackClientIdPulse = group_id
self.checkPulseAudio()
@@ -1797,6 +1819,33 @@ def slot_PulseAudioBridgeStart(self):
def slot_PulseAudioBridgeStop(self):
os.system("pulseaudio -k")
+ @pyqtSlot()
+ def slot_PulseAudioBridgeTableChanged(self):
+ has_changes = self.tableSinkSourceData.hasChanges()
+ has_valid_values = self.tableSinkSourceData.hasValidValues()
+ self.b_bridge_save.setEnabled(has_changes and has_valid_values)
+ self.b_bridge_undo.setEnabled(has_changes)
+
+ @pyqtSlot()
+ def slot_PulseAudioBridgeAdd(self):
+ self.tableSinkSourceData.add_row()
+
+ @pyqtSlot()
+ def slot_PulseAudioBridgeRemove(self):
+ self.tableSinkSourceData.remove_row()
+
+ @pyqtSlot()
+ def slot_PulseAudioBridgeUndo(self):
+ self.tableSinkSourceData.undo()
+
+ @pyqtSlot()
+ def slot_PulseAudioBridgeSave(self):
+ self.tableSinkSourceData.save_bridges()
+
+ @pyqtSlot()
+ def slot_PulseAudioBridgeDefaults(self):
+ self.tableSinkSourceData.defaults()
+
@pyqtSlot()
def slot_PulseAudioBridgeOptions(self):
ToolBarPADialog(self).exec_()
diff --git a/src/claudia_launcher.py b/src/claudia_launcher.py
index 64af08e3..77d0e145 100755
--- a/src/claudia_launcher.py
+++ b/src/claudia_launcher.py
@@ -168,7 +168,7 @@ def __init__(self, parent):
self.clearInfo_DAW()
self.clearInfo_Host()
- self.clearInfo_Intrument()
+ self.clearInfo_Instrument()
self.clearInfo_Bristol()
self.clearInfo_Plugin()
self.clearInfo_Effect()
@@ -547,7 +547,7 @@ def clearInfo_Host(self):
self.frame_Host.setEnabled(False)
self.showDoc_Host("", "")
- def clearInfo_Intrument(self):
+ def clearInfo_Instrument(self):
self.ico_app_ins.setPixmap(self.getIcon("start-here").pixmap(48, 48))
self.label_name_ins.setText("App Name")
self.ico_builtin_fx_ins.setPixmap(self.getIconForYesNo(False).pixmap(16, 16))
@@ -983,7 +983,7 @@ def slot_checkSelectedInstrument(self, row):
Docs0 = Docs[0] if (os.path.exists(Docs[0].replace("file://", ""))) else ""
self.showDoc_Instrument(Docs0, Docs[1])
else:
- self.clearInfo_Intrument()
+ self.clearInfo_Instrument()
self.callback_checkGUI(row >= 0)