├── src ├── images │ ├── Key - Enter.png │ ├── Key - Space.png │ ├── Key - Backspace.png │ ├── Key - Left Arrow.png │ └── Key - Right Arrow.png ├── images.qrc ├── qml.qrc ├── xfm2.pro ├── CustomDial.qml ├── main.cpp ├── ArpeggiatorPage.qml ├── LFOPage.qml ├── PopupMessage.qml ├── ModulationPage.qml ├── xfmoperator.h ├── CommonPage.qml ├── main.qml ├── EffectsPage2.qml ├── xfmoperator.cpp ├── xfm2.h ├── EffectsPage.qml ├── AlgorithmPage.qml ├── SynthModel.h ├── CustomKeyboard.qml └── EnvelopePage.qml └── README.md /src/images/Key - Enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ataristdude/xfm2controller/HEAD/src/images/Key - Enter.png -------------------------------------------------------------------------------- /src/images/Key - Space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ataristdude/xfm2controller/HEAD/src/images/Key - Space.png -------------------------------------------------------------------------------- /src/images/Key - Backspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ataristdude/xfm2controller/HEAD/src/images/Key - Backspace.png -------------------------------------------------------------------------------- /src/images/Key - Left Arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ataristdude/xfm2controller/HEAD/src/images/Key - Left Arrow.png -------------------------------------------------------------------------------- /src/images/Key - Right Arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ataristdude/xfm2controller/HEAD/src/images/Key - Right Arrow.png -------------------------------------------------------------------------------- /src/images.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/Key - Space.png 4 | images/Key - Right Arrow.png 5 | images/Key - Left Arrow.png 6 | images/Key - Enter.png 7 | images/Key - Backspace.png 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | OperatorPage.qml 5 | CommonPage.qml 6 | EnvelopePage.qml 7 | AlgorithmPage.qml 8 | PopupMessage.qml 9 | LFOPage.qml 10 | ModulationPage.qml 11 | ArpeggiatorPage.qml 12 | EffectsPage.qml 13 | EffectsPage2.qml 14 | CustomDial.qml 15 | CustomKeyboard.qml 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xfm2controller 2 | A controller for the XFM2 Synthesizer using Qt 5.12. 3 | XFM2 is a fantastic FPGA based synthesizer created by Futur3soundz see https://www.futur3soundz.com/xfm2 4 | I do not own XFM2. This project is just a controller for that synth. 5 | 6 | I designed this controller for a Raspberry Pi running an 800x480 touch screen but it will also work for the Desktop. 7 | Here's what you need to know: 8 | 1. The application uses the USB serial port to control the synth. This is set up with a SERIALPORT define in the SynthModel.cpp file. 9 | 2. Patch names are stored by the application. The filename is specified in the PATCHFILE define in the SynthModel.cpp file. 10 | 3. On the desktop you probably won't want to run it as a full sceeen app. Comment out the visibility property in Main.qml 11 | 12 | Enjoy! 13 | -------------------------------------------------------------------------------- /src/xfm2.pro: -------------------------------------------------------------------------------- 1 | QT += quick serialport 2 | 3 | CONFIG += c++11 4 | 5 | # The following define makes your compiler emit warnings if you use 6 | # any Qt feature that has been marked deprecated (the exact warnings 7 | # depend on your compiler). Refer to the documentation for the 8 | # deprecated API to know how to port your code away from it. 9 | DEFINES += QT_DEPRECATED_WARNINGS 10 | 11 | # You can also make your code fail to compile if it uses deprecated APIs. 12 | # In order to do so, uncomment the following line. 13 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 14 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 15 | 16 | SOURCES += \ 17 | SynthModel.cpp \ 18 | main.cpp \ 19 | xfmoperator.cpp 20 | 21 | RESOURCES += qml.qrc \ 22 | images.qrc 23 | 24 | # Additional import path used to resolve QML modules in Qt Creator's code model 25 | QML_IMPORT_PATH = 26 | 27 | # Additional import path used to resolve QML modules just for Qt Quick Designer 28 | QML_DESIGNER_IMPORT_PATH = 29 | 30 | # Default rules for deployment. 31 | qnx: target.path = /tmp/$${TARGET}/bin 32 | else: unix:!android: target.path = /opt/$${TARGET}/bin 33 | !isEmpty(target.path): INSTALLS += target 34 | 35 | HEADERS += \ 36 | SynthModel.h \ 37 | xfm2.h \ 38 | xfmoperator.h 39 | -------------------------------------------------------------------------------- /src/CustomDial.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | import QtQuick.Controls.Styles 1.4 29 | 30 | Dial { 31 | id: control 32 | style: DialStyle { 33 | handle: Rectangle { 34 | width: control.width/10 35 | height: control.width/10 36 | color: "black" 37 | radius: control.width/20 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "SynthModel.h" 29 | 30 | 31 | int main(int argc, char *argv[]) 32 | { 33 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 34 | 35 | QGuiApplication app(argc, argv); 36 | 37 | // Register the FM Operator class 38 | qmlRegisterType("Xfm.Synth", 1, 0, "XFMOperator"); 39 | 40 | // Set the app's default font. This is important for 41 | // correct scaling as some of the Qt forms are reliant 42 | // on point size. We assume Ubuntu as the default font 43 | QFont font("Ubuntu"); 44 | app.setFont(font); 45 | 46 | QQmlApplicationEngine engine; 47 | 48 | // Create and register the synth model object so it's visible to QML 49 | SynthModel *synthModel = new SynthModel(); 50 | 51 | engine.rootContext()->setContextProperty("synthModel", synthModel); 52 | 53 | const QUrl url(QStringLiteral("qrc:/main.qml")); 54 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { 55 | if (!obj && url == objUrl) { 56 | QCoreApplication::exit(-1); 57 | } 58 | }, Qt::QueuedConnection); 59 | 60 | engine.load(url); 61 | 62 | return app.exec(); 63 | } 64 | -------------------------------------------------------------------------------- /src/ArpeggiatorPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | 29 | 30 | Item { 31 | id: arpeggiatorPage 32 | width: 800 33 | height: 357 34 | 35 | Connections { 36 | target: synthModel 37 | onPatchNumberChanged: { 38 | updatePage(); 39 | } 40 | } 41 | 42 | function updatePage() 43 | { 44 | spinArpMode.value=synthModel.arpeggiatorMode; 45 | spinTempo.value=synthModel.arpeggiatorTempo === 0 ? 0 : synthModel.arpeggiatorTempo-49; 46 | spinMultiplier.value=synthModel.arpeggiatorTempoMultiplier; 47 | spinOctaveRange.value=synthModel.arpeggiatorOctaveRange; 48 | } 49 | 50 | 51 | Rectangle { 52 | anchors.fill: parent 53 | color: "#505050" 54 | } 55 | 56 | SpinBox { 57 | id: spinArpMode 58 | x: 110 59 | y: 24 60 | width: 160 61 | height: 40 62 | to: 5 63 | onValueChanged: { 64 | synthModel.arpeggiatorMode=spinArpMode.value; 65 | } 66 | textFromValue: function(value, locale) { 67 | var vals=["OFF", "UP", "DOWN", "UP/DOWN", "PLAYED", "RANDOM"]; 68 | 69 | return vals[value]; 70 | } 71 | } 72 | 73 | Label { 74 | id: label 75 | x: 49 76 | y: 35 77 | color: "#ffffff" 78 | text: qsTr("Mode") 79 | font.pixelSize: 16 80 | } 81 | 82 | SpinBox { 83 | id: spinTempo 84 | x: 110 85 | y: 98 86 | width: 160 87 | height: 40 88 | to: 206 89 | onValueChanged: { 90 | if (spinTempo.value === 0) { 91 | synthModel.arpeggiatorTempo=0; 92 | } else { 93 | synthModel.arpeggiatorTempo=spinTempo.value+49; 94 | } 95 | } 96 | textFromValue: function(value, locale) { 97 | if (value === 0) { 98 | return "MIDI"; 99 | } 100 | 101 | return (value+49).toString(); 102 | } 103 | } 104 | 105 | Label { 106 | id: label1 107 | x: 41 108 | y: 110 109 | color: "#ffffff" 110 | text: qsTr("Tempo") 111 | font.pixelSize: 16 112 | } 113 | 114 | SpinBox { 115 | id: spinMultiplier 116 | x: 444 117 | y: 98 118 | to: 255 119 | onValueChanged: { 120 | synthModel.arpeggiatorTempoMultiplier=spinMultiplier.value; 121 | } 122 | } 123 | 124 | Label { 125 | id: label2 126 | x: 310 127 | y: 110 128 | color: "#ffffff" 129 | text: qsTr("Tempo Multiplier") 130 | font.pixelSize: 16 131 | } 132 | 133 | SpinBox { 134 | id: spinOctaveRange 135 | x: 110 136 | y: 170 137 | to: 9 138 | onValueChanged: { 139 | synthModel.arpeggiatorOctaveRange=spinOctaveRange.value; 140 | } 141 | } 142 | 143 | Label { 144 | id: label3 145 | x: 17 146 | y: 182 147 | color: "#ffffff" 148 | text: qsTr("Oct Range") 149 | font.pixelSize: 16 150 | } 151 | 152 | 153 | Component.onCompleted: { 154 | updatePage(); 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/LFOPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | 29 | 30 | Item { 31 | id: lfoPage 32 | width: 800 33 | height: 357 34 | 35 | 36 | Rectangle { 37 | anchors.fill: parent 38 | color: "#505050" 39 | } 40 | 41 | Connections { 42 | target: synthModel 43 | onPatchNumberChanged: { 44 | updatePage(); 45 | } 46 | } 47 | 48 | function updatePage() 49 | { 50 | spinWave.value=synthModel.lfoWave; 51 | spinSync.value=synthModel.lfoSync; 52 | dialSpeed.value=synthModel.lfoSpeed; 53 | dialFade.value=synthModel.lfoFade; 54 | dialPitch.value=synthModel.lfoDepthPitch; 55 | dialAmp.value=synthModel.lfoDepthAmplitude; 56 | } 57 | 58 | Label { 59 | id: label 60 | x: 27 61 | y: 37 62 | color: "#ffffff" 63 | text: qsTr("Wave") 64 | font.pixelSize: 16 65 | } 66 | 67 | CustomDial { 68 | id: dialSpeed 69 | x: 51 70 | y: 130 71 | width: 136 72 | height: 136 73 | maximumValue: 255 74 | onValueChanged: { 75 | synthModel.lfoSpeed=dialSpeed.value; 76 | } 77 | } 78 | 79 | SpinBox { 80 | id: spinWave 81 | x: 84 82 | y: 26 83 | width: 167 84 | height: 40 85 | to: 5 86 | onValueChanged: { 87 | synthModel.lfoWave=spinWave.value 88 | } 89 | textFromValue: function(value, locale) { 90 | var vals=["TRI", "SQR", "SAW UP", "SAW DN", "SIN", "RND"]; 91 | 92 | return vals[value]; 93 | } 94 | } 95 | 96 | Label { 97 | id: label1 98 | x: 98 99 | y: 280 100 | width: 42 101 | height: 17 102 | color: "#ffffff" 103 | text: qsTr("Rate") 104 | font.pixelSize: 16 105 | } 106 | 107 | Label { 108 | id: label2 109 | x: 304 110 | y: 37 111 | color: "#ffffff" 112 | text: qsTr("Sync") 113 | font.pixelSize: 16 114 | } 115 | 116 | SpinBox { 117 | id: spinSync 118 | x: 353 119 | y: 26 120 | width: 202 121 | height: 40 122 | to: 3 123 | onValueChanged: { 124 | synthModel.lfoSync=spinSync.value; 125 | } 126 | 127 | textFromValue: function(value, locale) { 128 | var vals=["SINGLE FREE", "SINGLE KEY", "MULTI FREE", "MULTI KEY"]; 129 | 130 | return vals[value]; 131 | } 132 | } 133 | 134 | CustomDial { 135 | id: dialFade 136 | x: 222 137 | y: 130 138 | width: 136 139 | height: 136 140 | maximumValue: 255 141 | onValueChanged: { 142 | synthModel.lfoFade=synthModel.lfoFade; 143 | } 144 | } 145 | 146 | Label { 147 | id: label3 148 | x: 269 149 | y: 280 150 | width: 42 151 | height: 17 152 | color: "#ffffff" 153 | text: qsTr("Fade") 154 | font.pixelSize: 16 155 | } 156 | 157 | CustomDial { 158 | id: dialPitch 159 | x: 431 160 | y: 130 161 | width: 136 162 | height: 136 163 | maximumValue: 255 164 | onValueChanged: { 165 | synthModel.lfoDepthPitch=dialPitch.value; 166 | } 167 | } 168 | 169 | Label { 170 | id: label4 171 | x: 455 172 | y: 280 173 | width: 89 174 | height: 17 175 | color: "#ffffff" 176 | text: qsTr("Depth Pitch") 177 | font.pixelSize: 16 178 | } 179 | 180 | CustomDial { 181 | id: dialAmp 182 | x: 603 183 | y: 130 184 | width: 136 185 | height: 136 186 | maximumValue: 255 187 | onValueChanged: { 188 | synthModel.lfoDepthAmplitude=dialAmp.value; 189 | } 190 | } 191 | 192 | Label { 193 | id: label5 194 | x: 604 195 | y: 280 196 | width: 134 197 | height: 17 198 | color: "#ffffff" 199 | text: qsTr("Depth Amplitude") 200 | font.pixelSize: 16 201 | horizontalAlignment: Text.AlignHCenter 202 | } 203 | 204 | Component.onCompleted: { 205 | updatePage(); 206 | } 207 | 208 | } 209 | 210 | /*##^## 211 | Designer { 212 | D{i:1;anchors_height:400;anchors_x:0;anchors_y:0}D{i:2;anchors_height:400;anchors_x:0;anchors_y:0} 213 | } 214 | ##^##*/ 215 | -------------------------------------------------------------------------------- /src/PopupMessage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.12 26 | 27 | Popup { 28 | id: rootPopup 29 | width: 600 30 | height: 240 31 | x: (800 - width) / 2 32 | y: (480 - height) / 2 33 | dim: true 34 | background: Rectangle { color: "#00ffffff" } 35 | closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside 36 | clip: true 37 | 38 | signal okClicked() 39 | signal cancelClicked() 40 | 41 | property alias titleText: popupLabel.text; 42 | property alias detailText: confirmLabel.text; 43 | property alias okButtonText: okBtnLabel.text; 44 | property alias cancelButtonText: cancelBtnLabel.text; 45 | property alias okButton: okBtn; 46 | property alias cancelButton: cancelBtn; 47 | 48 | // popup content 49 | Rectangle { 50 | id: popupRect 51 | anchors.fill: parent 52 | radius: 0 53 | clip: true 54 | border.color: "white" 55 | 56 | Label { 57 | id: popupLabel 58 | x: 1 59 | y: 1 60 | width: 598 61 | height: 40 62 | color: "#ffffff" 63 | text: "Title" 64 | clip: true 65 | anchors.right: parent.right 66 | anchors.rightMargin: 0 67 | anchors.left: parent.left 68 | anchors.leftMargin: 0 69 | verticalAlignment: Text.AlignVCenter 70 | font.pixelSize: 30 71 | anchors.top: parent.top 72 | anchors.topMargin: 0 73 | horizontalAlignment: Text.AlignHCenter 74 | background: Rectangle { 75 | color: "#808080" 76 | } 77 | } 78 | 79 | Label { 80 | id: confirmLabel 81 | width: 571 82 | height: 91 83 | text: "Detailed text" 84 | verticalAlignment: Text.AlignVCenter 85 | wrapMode: Text.WordWrap 86 | font.pixelSize: 22 87 | anchors.top: popupLabel.bottom 88 | anchors.topMargin: 10 89 | horizontalAlignment: Text.AlignHCenter 90 | anchors.horizontalCenter: parent.horizontalCenter 91 | } 92 | 93 | Rectangle { 94 | id: horizontalLine 95 | x: 1 96 | y: 145 97 | width: parent.width 98 | height: 1 99 | anchors.top : popupLabel.bottom 100 | anchors.topMargin: 110 101 | color: "lightgray" 102 | } 103 | 104 | Rectangle { 105 | id: cancelBtn 106 | y: 154 107 | width: 298 108 | height: 64 109 | radius: 10.0 110 | border.color: "#00000000" 111 | anchors.top: horizontalLine.bottom 112 | anchors.left: parent.left 113 | anchors.leftMargin: 1 114 | Label { 115 | id: cancelBtnLabel 116 | text: "Cancel" 117 | font.pixelSize: 25 118 | anchors.horizontalCenter: parent.horizontalCenter 119 | anchors.verticalCenter: parent.verticalCenter 120 | horizontalAlignment: Text.AlignHCenter 121 | } 122 | 123 | TapHandler { 124 | gesturePolicy: TapHandler.ReleaseWithinBounds 125 | onTapped: { 126 | rootPopup.cancelClicked(); 127 | rootPopup.close() 128 | } 129 | onCanceled: { 130 | } 131 | } 132 | 133 | } 134 | 135 | Rectangle { 136 | id: verticalLine 137 | width: 1 138 | height: 64 139 | anchors.top : horizontalLine.bottom 140 | anchors.horizontalCenter: parent.horizontalCenter 141 | color: "lightgray" 142 | } 143 | 144 | Rectangle { 145 | id: okBtn 146 | y: 154 147 | width: parent.width / 2 - 2 148 | height: 64 149 | radius: 10.0 150 | border.color: "#00000000" 151 | anchors.top: horizontalLine.bottom 152 | anchors.right: parent.right 153 | anchors.rightMargin: 1 154 | 155 | Label { 156 | id: okBtnLabel 157 | text: "Ok" 158 | font.pixelSize: 25 159 | anchors.horizontalCenter: parent.horizontalCenter 160 | anchors.verticalCenter: parent.verticalCenter 161 | horizontalAlignment: Text.AlignHCenter 162 | } 163 | TapHandler { 164 | gesturePolicy: TapHandler.ReleaseWithinBounds 165 | onTapped: { 166 | rootPopup.okClicked(); 167 | rootPopup.close() 168 | } 169 | onCanceled: { 170 | } 171 | } 172 | 173 | } 174 | } 175 | } 176 | 177 | -------------------------------------------------------------------------------- /src/ModulationPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | 29 | 30 | Item { 31 | id: modulationPage 32 | width: 800 33 | height: 357 34 | 35 | Connections { 36 | target: synthModel 37 | onPatchNumberChanged: { 38 | updatePage(); 39 | } 40 | } 41 | 42 | function updatePage() 43 | { 44 | dialPitchAftertouch.value=synthModel.modPitchAftertouch; 45 | dialPitchLFOAftertouch.value=synthModel.modPitchLFOAftertouch; 46 | dialAmplitudeLFOAftertouch.value=synthModel.modAmpLFOAftertouch; 47 | dialEGBiasAftertouch.value=synthModel.modEnvelopeBiasAftertouch; 48 | 49 | dialPitchRandom.value=synthModel.modPitchRandom; 50 | dialPitchLFOWheel.value=synthModel.modPitchLFOWheel; 51 | dialAmplitudeLFOWheel.value=synthModel.modAmpLFOWheel; 52 | dialEGBiasWheel.value=synthModel.modEnvelopeBiasWheel; 53 | 54 | dialPitchBreath.value=synthModel.modPitchBreath; 55 | dialPitchLFOBreath.value=synthModel.modPitchLFOBreath; 56 | dialAmplitudeLFOBreath.value=synthModel.modAmpLFOBreath; 57 | dialEGBiasBreath.value=synthModel.modEnvelopeBiasBreath; 58 | 59 | dialPitchFoot.value=synthModel.modPitchFoot; 60 | dialPitchLFOFoot.value=synthModel.modPitchLFOFoot; 61 | dialAmplitudeLFOFoot.value=synthModel.modAmpLFOFoot; 62 | dialEGBiasFoot.value=synthModel.modEnvelopeBiasFoot; 63 | } 64 | 65 | Rectangle { 66 | anchors.fill: parent 67 | color: "#505050" 68 | } 69 | 70 | 71 | Label { 72 | id: label 73 | x: 144 74 | y: 10 75 | color: "#ffffff" 76 | text: qsTr("Aftertouch") 77 | font.pixelSize: 16 78 | } 79 | 80 | Label { 81 | id: label1 82 | x: 81 83 | y: 60 84 | color: "#ffffff" 85 | text: qsTr("Pitch") 86 | font.pixelSize: 16 87 | } 88 | 89 | CustomDial { 90 | id: dialPitchAftertouch 91 | x: 141 92 | y: 33 93 | width: 80 94 | height: 70 95 | maximumValue: 255 96 | onValueChanged: { 97 | synthModel.modPitchAftertouch=dialPitchAftertouch.value; 98 | } 99 | } 100 | 101 | CustomDial { 102 | id: dialPitchLFOAftertouch 103 | x: 141 104 | y: 109 105 | width: 80 106 | height: 70 107 | maximumValue: 255 108 | onValueChanged: { 109 | synthModel.modPitchLFOAftertouch=dialPitchLFOAftertouch.value; 110 | } 111 | } 112 | 113 | CustomDial { 114 | id: dialAmplitudeLFOAftertouch 115 | x: 141 116 | y: 185 117 | width: 80 118 | height: 70 119 | maximumValue: 255 120 | onValueChanged: { 121 | synthModel.modAmpLFOAftertouch=dialAmplitudeLFOAftertouch.value; 122 | } 123 | } 124 | 125 | CustomDial { 126 | id: dialEGBiasAftertouch 127 | x: 141 128 | y: 261 129 | width: 80 130 | height: 70 131 | maximumValue: 255 132 | onValueChanged: { 133 | synthModel.modEnvelopeBiasAftertouch=dialEGBiasAftertouch.value; 134 | } 135 | } 136 | 137 | Label { 138 | id: label2 139 | x: 50 140 | y: 136 141 | color: "#ffffff" 142 | text: qsTr("Pitch LFO") 143 | font.pixelSize: 16 144 | } 145 | 146 | Label { 147 | id: label3 148 | x: 13 149 | y: 212 150 | color: "#ffffff" 151 | text: qsTr("Amplitude LFO") 152 | font.pixelSize: 16 153 | } 154 | 155 | Label { 156 | id: label4 157 | x: 65 158 | y: 288 159 | color: "#ffffff" 160 | text: qsTr("EG Bias") 161 | font.pixelSize: 16 162 | } 163 | 164 | Label { 165 | id: label5 166 | x: 297 167 | y: 10 168 | color: "#ffffff" 169 | text: qsTr("Wheel/RND") 170 | font.pixelSize: 16 171 | } 172 | 173 | CustomDial { 174 | id: dialPitchRandom 175 | x: 297 176 | y: 33 177 | width: 80 178 | height: 70 179 | maximumValue: 255 180 | onValueChanged: { 181 | synthModel.modPitchRandom=dialPitchRandom.value; 182 | } 183 | } 184 | 185 | CustomDial { 186 | id: dialPitchLFOWheel 187 | x: 297 188 | y: 109 189 | width: 80 190 | height: 70 191 | maximumValue: 255 192 | onValueChanged: { 193 | synthModel.modPitchLFOWheel=dialPitchLFOWheel.value; 194 | } 195 | } 196 | 197 | CustomDial { 198 | id: dialAmplitudeLFOWheel 199 | x: 297 200 | y: 185 201 | width: 80 202 | height: 70 203 | maximumValue: 255 204 | onValueChanged: { 205 | synthModel.modAmpLFOWheel=dialAmplitudeLFOWheel.value; 206 | } 207 | } 208 | 209 | CustomDial { 210 | id: dialEGBiasWheel 211 | x: 297 212 | y: 261 213 | width: 80 214 | height: 70 215 | maximumValue: 255 216 | onValueChanged: { 217 | synthModel.modEnvelopeBiasWheel=dialEGBiasWheel.value; 218 | } 219 | } 220 | 221 | Label { 222 | id: label6 223 | x: 470 224 | y: 10 225 | color: "#ffffff" 226 | text: qsTr("Breath") 227 | font.pixelSize: 16 228 | } 229 | 230 | CustomDial { 231 | id: dialPitchBreath 232 | x: 453 233 | y: 33 234 | width: 80 235 | height: 70 236 | maximumValue: 255 237 | onValueChanged: { 238 | synthModel.modPitchBreath=dialPitchBreath.value; 239 | } 240 | } 241 | 242 | CustomDial { 243 | id: dialPitchLFOBreath 244 | x: 453 245 | y: 110 246 | width: 80 247 | height: 70 248 | maximumValue: 255 249 | onValueChanged: { 250 | synthModel.modPitchLFOBreath=dialPitchLFOBreath.value; 251 | } 252 | } 253 | 254 | CustomDial { 255 | id: dialAmplitudeLFOBreath 256 | x: 453 257 | y: 185 258 | width: 80 259 | height: 70 260 | maximumValue: 255 261 | onValueChanged: { 262 | synthModel.modAmpLFOBreath=dialAmplitudeLFOBreath.value; 263 | } 264 | } 265 | 266 | CustomDial { 267 | id: dialEGBiasBreath 268 | x: 453 269 | y: 262 270 | width: 80 271 | height: 70 272 | maximumValue: 255 273 | onValueChanged: { 274 | synthModel.modEnvelopeBiasBreath=dialEGBiasBreath.value; 275 | } 276 | } 277 | 278 | Label { 279 | id: label7 280 | x: 633 281 | y: 10 282 | color: "#ffffff" 283 | text: qsTr("Foot") 284 | font.pixelSize: 16 285 | } 286 | 287 | CustomDial { 288 | id: dialPitchFoot 289 | x: 609 290 | y: 33 291 | width: 80 292 | height: 70 293 | maximumValue: 255 294 | onValueChanged: { 295 | synthModel.modPitchFoot=dialPitchFoot.value; 296 | } 297 | } 298 | 299 | CustomDial { 300 | id: dialPitchLFOFoot 301 | x: 609 302 | y: 109 303 | width: 80 304 | height: 70 305 | maximumValue: 255 306 | onValueChanged: { 307 | synthModel.modPitchLFOFoot=dialPitchLFOFoot.value; 308 | } 309 | } 310 | 311 | CustomDial { 312 | id: dialAmplitudeLFOFoot 313 | x: 609 314 | y: 185 315 | width: 80 316 | height: 70 317 | maximumValue: 255 318 | onValueChanged: { 319 | synthModel.modAmpLFOFoot=dialAmplitudeLFOFoot.value; 320 | } 321 | } 322 | 323 | CustomDial { 324 | id: dialEGBiasFoot 325 | x: 609 326 | y: 262 327 | width: 80 328 | height: 70 329 | maximumValue: 255 330 | onValueChanged: { 331 | synthModel.modEnvelopeBiasFoot=dialEGBiasFoot.value; 332 | } 333 | } 334 | 335 | Component.onCompleted: { 336 | updatePage(); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/xfmoperator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | #ifndef XFMOPERATOR_H 25 | #define XFMOPERATOR_H 26 | 27 | #include 28 | #include 29 | #include "xfm2.h" 30 | 31 | /* 32 | * This is a class for a single XFM2 Operator 33 | * It makes it easy to manipulate the operator's parameters 34 | */ 35 | 36 | class XFMOperator : public QObject { 37 | Q_OBJECT 38 | 39 | // The operator number keeps track of which operator this is 40 | Q_PROPERTY(int operatorNumber READ operatorNumber WRITE setOperatorNumber NOTIFY operatorNumberChanged) 41 | 42 | // Algorithm, feedback and frequency ratio 43 | Q_PROPERTY(int algorithm READ algorithm WRITE setAlgorithm NOTIFY algorithmChanged) 44 | Q_PROPERTY(int feedback READ feedback WRITE setFeedback NOTIFY feedbackChanged) 45 | Q_PROPERTY(int ratio READ ratio WRITE setRatio NOTIFY ratioChanged) 46 | Q_PROPERTY(int ratioFine READ ratioFine WRITE setRatioFine NOTIFY ratioFineChanged) 47 | 48 | // Waveform and phase 49 | Q_PROPERTY(int wave1 READ wave1 WRITE setWave1 NOTIFY wave1Changed) 50 | Q_PROPERTY(int wave2 READ wave2 WRITE setWave2 NOTIFY wave2Changed) 51 | Q_PROPERTY(int oscillatorMode READ oscillatorMode WRITE setOscillatorMode NOTIFY oscillatorModeChanged) 52 | Q_PROPERTY(int oscillatorRatio READ oscillatorRatio WRITE setOscillatorRatio NOTIFY oscillatorRatioChanged) 53 | Q_PROPERTY(int phase READ phase WRITE setPhase NOTIFY phaseChanged) 54 | 55 | // Fine tuning 56 | Q_PROPERTY(int fine READ fine WRITE setFine NOTIFY fineChanged) 57 | 58 | // Level 59 | Q_PROPERTY(int level READ level WRITE setLevel NOTIFY levelChanged) 60 | Q_PROPERTY(int levelLeft READ levelLeft WRITE setLevelLeft NOTIFY levelLeftChanged) 61 | Q_PROPERTY(int levelRight READ levelRight WRITE setLevelRight NOTIFY levelRightChanged) 62 | 63 | // Keyboard control. The breakpoint sets the split between "left" (lower) and "right" (upper) 64 | Q_PROPERTY(int velocitySensitivity READ velocitySensitivity WRITE setVelocitySensitivity NOTIFY velocitySensitivityChanged) 65 | Q_PROPERTY(int keyboardBreakpoint READ keyboardBreakpoint WRITE setKeyboardBreakpoint NOTIFY keyboardBreakpointChanged) 66 | Q_PROPERTY(int keyboardScaleLeft READ keyboardScaleLeft WRITE setKeyboardScaleLeft NOTIFY keyboardScaleLeftChanged) 67 | Q_PROPERTY(int keyboardScaleRight READ keyboardScaleRight WRITE setKeyboardScaleRight NOTIFY keyboardScaleRightChanged) 68 | Q_PROPERTY(int keyboardCurveLeft READ keyboardCurveLeft WRITE setKeyboardCurveLeft NOTIFY keyboardCurveLeftChanged) 69 | Q_PROPERTY(int keyboardCurveRight READ keyboardCurveRight WRITE setKeyboardCurveRight NOTIFY keyboardCurveRightChanged) 70 | Q_PROPERTY(int rateKey READ rateKey WRITE setRateKey NOTIFY rateKeyChanged) 71 | 72 | // Levels 0-5 73 | Q_PROPERTY(int L0 READ L0 WRITE setL0 NOTIFY L0Changed) 74 | Q_PROPERTY(int L1 READ L1 WRITE setL1 NOTIFY L1Changed) 75 | Q_PROPERTY(int L2 READ L2 WRITE setL2 NOTIFY L2Changed) 76 | Q_PROPERTY(int L3 READ L3 WRITE setL3 NOTIFY L3Changed) 77 | Q_PROPERTY(int L4 READ L4 WRITE setL4 NOTIFY L4Changed) 78 | Q_PROPERTY(int L5 READ L5 WRITE setL5 NOTIFY L5Changed) 79 | 80 | // Rates 0-5 81 | Q_PROPERTY(int R0 READ R0 WRITE setR0 NOTIFY R0Changed) 82 | Q_PROPERTY(int R1 READ R1 WRITE setR1 NOTIFY R1Changed) 83 | Q_PROPERTY(int R2 READ R2 WRITE setR2 NOTIFY R2Changed) 84 | Q_PROPERTY(int R3 READ R3 WRITE setR3 NOTIFY R3Changed) 85 | Q_PROPERTY(int R4 READ R4 WRITE setR4 NOTIFY R4Changed) 86 | Q_PROPERTY(int R5 READ R5 WRITE setR5 NOTIFY R5Changed) 87 | 88 | // Modulation 89 | Q_PROPERTY(int amplitudeModulationSensitivity READ amplitudeModulationSensitivity WRITE setAmplitudeModulationSensitivity NOTIFY amplitudeModulationSensitivityChanged) 90 | Q_PROPERTY(int pitchModulationSensitivity READ pitchModulationSensitivity WRITE setPitchModulationSensitivity NOTIFY pitchModulationSensitivityChanged) 91 | 92 | public: 93 | explicit XFMOperator(QObject *parent = nullptr); 94 | 95 | 96 | signals: 97 | void operatorNumberChanged(); 98 | void algorithmChanged(); 99 | void feedbackChanged(); 100 | void ratioChanged(); 101 | void ratioFineChanged(); 102 | void fineChanged(); 103 | void levelChanged(); 104 | void levelLeftChanged(); 105 | void levelRightChanged(); 106 | void velocitySensitivityChanged(); 107 | void keyboardBreakpointChanged(); 108 | void keyboardScaleLeftChanged(); 109 | void keyboardScaleRightChanged(); 110 | void keyboardCurveLeftChanged(); 111 | void keyboardCurveRightChanged(); 112 | void L0Changed(); 113 | void L1Changed(); 114 | void L2Changed(); 115 | void L3Changed(); 116 | void L4Changed(); 117 | void L5Changed(); 118 | void R0Changed(); 119 | void R1Changed(); 120 | void R2Changed(); 121 | void R3Changed(); 122 | void R4Changed(); 123 | void R5Changed(); 124 | void rateKeyChanged(); 125 | void amplitudeModulationSensitivityChanged(); 126 | void pitchModulationSensitivityChanged(); 127 | void wave1Changed(); 128 | void wave2Changed(); 129 | void oscillatorModeChanged(); 130 | void oscillatorRatioChanged(); 131 | void phaseChanged(); 132 | 133 | public: 134 | int operatorNumber() const; 135 | void setOperatorNumber(int n); 136 | 137 | int algorithm() const; 138 | void setAlgorithm(int a); 139 | 140 | int feedback() const; 141 | void setFeedback(int f); 142 | 143 | int ratio() const; 144 | void setRatio(int v); 145 | 146 | int ratioFine() const; 147 | void setRatioFine(int v); 148 | 149 | int fine() const; 150 | void setFine(int v); 151 | 152 | int level() const; 153 | void setLevel(int v); 154 | 155 | int levelLeft() const; 156 | void setLevelLeft(int v); 157 | 158 | int levelRight() const; 159 | void setLevelRight(int v); 160 | 161 | int velocitySensitivity() const; 162 | void setVelocitySensitivity(int v); 163 | 164 | int keyboardBreakpoint() const; 165 | void setKeyboardBreakpoint(int v); 166 | 167 | int keyboardScaleLeft() const; 168 | void setKeyboardScaleLeft(int v); 169 | 170 | int keyboardScaleRight() const; 171 | void setKeyboardScaleRight(int f); 172 | 173 | int keyboardCurveLeft() const; 174 | void setKeyboardCurveLeft(int f); 175 | 176 | int keyboardCurveRight() const; 177 | void setKeyboardCurveRight(int f); 178 | 179 | // Envelope 180 | int L0() const; 181 | void setL0(int f); 182 | 183 | int L1() const; 184 | void setL1(int f); 185 | 186 | int L2() const; 187 | void setL2(int f); 188 | 189 | int L3() const; 190 | void setL3(int f); 191 | 192 | int L4() const; 193 | void setL4(int f); 194 | 195 | int L5() const; 196 | void setL5(int f); 197 | 198 | int R0() const; 199 | void setR0(int f); 200 | 201 | int R1() const; 202 | void setR1(int f); 203 | 204 | int R2() const; 205 | void setR2(int f); 206 | 207 | int R3() const; 208 | void setR3(int f); 209 | 210 | int R4() const; 211 | void setR4(int f); 212 | 213 | int R5() const; 214 | void setR5(int f); 215 | 216 | int rateKey() const; 217 | void setRateKey(int f); 218 | 219 | int amplitudeModulationSensitivity() const; 220 | void setAmplitudeModulationSensitivity(int v); 221 | 222 | int pitchModulationSensitivity() const; 223 | void setPitchModulationSensitivity(int v); 224 | 225 | int wave1() const; 226 | void setWave1(int v); 227 | 228 | int wave2() const; 229 | void setWave2(int v); 230 | 231 | int oscillatorMode() const; 232 | void setOscillatorMode(int v); 233 | 234 | int oscillatorRatio() const; 235 | void setOscillatorRatio(int v); 236 | 237 | int phase() const; 238 | void setPhase(int v); 239 | 240 | private: 241 | int m_operator; 242 | unsigned char m_algorithm; 243 | unsigned char m_feedback; 244 | unsigned char m_ratio; 245 | unsigned char m_ratiofine; 246 | unsigned char m_fine; 247 | unsigned char m_level; 248 | unsigned char m_levelLeft; 249 | unsigned char m_levelRight; 250 | unsigned char m_velocitysensitivity; 251 | unsigned char m_keyboardbreakpoint; 252 | unsigned char m_keyboardscaleleft; 253 | unsigned char m_keyboardscaleright; 254 | unsigned char m_keyboardcurveleft; 255 | unsigned char m_keyboardcurveright; 256 | unsigned char m_l0; 257 | unsigned char m_l1; 258 | unsigned char m_l2; 259 | unsigned char m_l3; 260 | unsigned char m_l4; 261 | unsigned char m_l5; 262 | unsigned char m_r0; 263 | unsigned char m_r1; 264 | unsigned char m_r2; 265 | unsigned char m_r3; 266 | unsigned char m_r4; 267 | unsigned char m_r5; 268 | unsigned char m_ratekey; 269 | unsigned char m_ams; 270 | unsigned char m_pms; 271 | unsigned char m_wave1; 272 | unsigned char m_wave2; 273 | unsigned char m_wmode; 274 | unsigned char m_wratio; 275 | unsigned char m_phase; 276 | }; 277 | 278 | 279 | #endif // XFMOPERATOR_H 280 | -------------------------------------------------------------------------------- /src/CommonPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | import QtQuick.Controls.Styles 1.4 29 | 30 | Item { 31 | id: commonPage 32 | width: 800 33 | height: 357 34 | 35 | Rectangle { 36 | anchors.fill: parent 37 | color: "#505050" 38 | } 39 | 40 | Connections { 41 | target: synthModel 42 | onPatchNumberChanged: { 43 | updatePage(); 44 | } 45 | } 46 | 47 | function updatePage() 48 | { 49 | sliderOutput.value=synthModel.outputLevel 50 | dialVolume.value=synthModel.masterVolume 51 | dialPan.value=synthModel.masterPan 52 | spinBendUp.value=synthModel.masterPitchBendUp 53 | spinBendDown.value=synthModel.masterPitchBendDown 54 | spinTranspose.value=synthModel.masterTranspose-24 55 | spinVelocityOffset.value=synthModel.masterVelocityOffset 56 | sliderPortamentoTime.value=synthModel.portamentoTime 57 | switchLegato.checked=synthModel.masterLegato != 0 58 | spinPortamentoMode.value=synthModel.portamentoMode 59 | textName.text=synthModel.patchName 60 | } 61 | 62 | TextField { 63 | id: textName 64 | x: 101 65 | y: 20 66 | width: 410 67 | height: 38 68 | verticalAlignment: Text.AlignVCenter 69 | font.pixelSize: 18 70 | cursorVisible: false 71 | 72 | background: Rectangle { 73 | border.width: 1 74 | border.color: "#000000" 75 | color: "#ffffff" 76 | radius: 4 77 | } 78 | 79 | onTextChanged: { 80 | synthModel.patchName=textName.text; 81 | } 82 | 83 | onPressed: { 84 | keyboard.visible=true; 85 | textName.cursorVisible=true; 86 | } 87 | } 88 | 89 | Slider { 90 | id: sliderOutput 91 | x: 471 92 | y: 146 93 | width: 40 94 | height: 161 95 | to: 255 96 | orientation: Qt.Vertical 97 | onValueChanged: { 98 | synthModel.outputLevel=sliderOutput.value; 99 | } 100 | } 101 | 102 | CustomDial { 103 | id: dialVolume 104 | x: 627 105 | y: 15 106 | width: 136 107 | height: 136 108 | maximumValue: 255 109 | onValueChanged: { 110 | synthModel.masterVolume=dialVolume.value; 111 | } 112 | } 113 | 114 | Label { 115 | id: label1 116 | x: 669 117 | y: 162 118 | color: "#ffffff" 119 | text: qsTr("Volume") 120 | font.pixelSize: 16 121 | } 122 | 123 | Label { 124 | id: label 125 | x: 455 126 | y: 313 127 | width: 73 128 | height: 38 129 | color: "#ffffff" 130 | text: qsTr("Output Level") 131 | font.pixelSize: 16 132 | wrapMode: Text.WordWrap 133 | horizontalAlignment: Text.AlignHCenter 134 | } 135 | 136 | CustomDial { 137 | id: dialPan 138 | x: 627 139 | y: 192 140 | width: 136 141 | height: 136 142 | maximumValue: 255 143 | onValueChanged: { 144 | synthModel.masterPan=dialPan.value; 145 | } 146 | } 147 | 148 | Label { 149 | id: label3 150 | x: 27 151 | y: 95 152 | color: "#ffffff" 153 | text: qsTr("Bend Up") 154 | font.pixelSize: 16 155 | } 156 | 157 | SpinBox { 158 | id: spinBendUp 159 | x: 101 160 | y: 84 161 | to: 127 162 | onValueChanged: { 163 | synthModel.masterPitchBendUp=spinBendUp.value 164 | } 165 | } 166 | 167 | Label { 168 | id: label2 169 | x: 682 170 | y: 332 171 | color: "#ffffff" 172 | text: qsTr("Pan") 173 | font.pixelSize: 16 174 | horizontalAlignment: Text.AlignHCenter 175 | } 176 | 177 | Label { 178 | id: label4 179 | x: 5 180 | y: 154 181 | color: "#ffffff" 182 | text: qsTr("Bend Down") 183 | font.pixelSize: 16 184 | } 185 | 186 | SpinBox { 187 | id: spinBendDown 188 | x: 101 189 | y: 143 190 | to: 127 191 | onValueChanged: { 192 | synthModel.masterPitchBendDown=spinBendDown.value 193 | } 194 | } 195 | 196 | Label { 197 | id: label5 198 | x: 282 199 | y: 95 200 | color: "#ffffff" 201 | text: qsTr("Transpose") 202 | font.pixelSize: 16 203 | } 204 | 205 | SpinBox { 206 | id: spinTranspose 207 | x: 371 208 | y: 84 209 | from: -24 210 | to: 24 211 | onValueChanged: { 212 | synthModel.masterTranspose=spinTranspose.value+24 213 | } 214 | } 215 | 216 | Label { 217 | id: label6 218 | x: 8 219 | y: 324 220 | width: 81 221 | height: 17 222 | color: "#ffffff" 223 | text: qsTr("Vel. Offset") 224 | font.pixelSize: 16 225 | } 226 | 227 | SpinBox { 228 | id: spinVelocityOffset 229 | x: 101 230 | y: 312 231 | to: 127 232 | from: 0 233 | onValueChanged: { 234 | synthModel.masterVelocityOffset=spinVelocityOffset.value 235 | } 236 | } 237 | 238 | Slider { 239 | id: sliderPortamentoTime 240 | x: 371 241 | y: 146 242 | width: 40 243 | height: 161 244 | orientation: Qt.Vertical 245 | to: 255 246 | onValueChanged: { 247 | synthModel.portamentoTime=sliderPortamentoTime.value; 248 | } 249 | } 250 | 251 | Switch { 252 | id: switchLegato 253 | x: 94 254 | y: 203 255 | text: qsTr("Mono") 256 | font.pixelSize: 16 257 | onCheckedChanged: { 258 | synthModel.masterLegato=switchLegato.checked ? 1 : 0 259 | } 260 | } 261 | 262 | Label { 263 | id: label7 264 | x: 347 265 | y: 313 266 | width: 88 267 | height: 38 268 | color: "#ffffff" 269 | text: qsTr("Portamento Time") 270 | font.pixelSize: 16 271 | horizontalAlignment: Text.AlignHCenter 272 | wrapMode: Text.WordWrap 273 | } 274 | 275 | Label { 276 | id: label8 277 | x: 58 278 | y: 213 279 | height: 17 280 | color: "#ffffff" 281 | text: qsTr("Poly") 282 | font.pixelSize: 16 283 | } 284 | 285 | Label { 286 | id: label9 287 | x: -11 288 | y: 267 289 | width: 100 290 | height: 36 291 | color: "#ffffff" 292 | text: qsTr("Portamento Mode") 293 | font.pixelSize: 16 294 | wrapMode: Text.WordWrap 295 | horizontalAlignment: Text.AlignRight 296 | } 297 | 298 | SpinBox { 299 | id: spinPortamentoMode 300 | x: 101 301 | y: 265 302 | width: 178 303 | height: 40 304 | to: 2 305 | onValueChanged: { 306 | synthModel.portamentoMode=spinPortamentoMode.value 307 | } 308 | textFromValue: function(value, locale) { 309 | var clist=["OFF", "ALWAYS", "FINGERED"]; 310 | 311 | return clist[value]; 312 | } 313 | } 314 | 315 | Label { 316 | id: label10 317 | x: 2 318 | y: 29 319 | color: "#ffffff" 320 | text: qsTr("Patch Name") 321 | font.pixelSize: 16 322 | } 323 | 324 | /* This custom keyboard is required on the Raspberry Pi/Touch screen implementation 325 | * but you won't need it for a Desktop version. 326 | */ 327 | CustomKeyboard { 328 | id: keyboard 329 | width: 800 330 | height: 260 331 | x: 0 332 | y: 97 333 | visible: false; 334 | z: 1000 335 | 336 | onKeyPressed: { 337 | if (keyval === "LEFT") { 338 | if (textName.cursorPosition > 0) { 339 | textName.cursorPosition=textName.cursorPosition-1; 340 | textName.cursorVisible=true; 341 | textName.focus=true; 342 | } 343 | } else if (keyval === "RIGHT") { 344 | if (textName.cursorPosition < textName.text.length) { 345 | textName.cursorPosition=textName.cursorPosition+1; 346 | textName.cursorVisible=true; 347 | textName.focus=true; 348 | } 349 | } else if (keyval === "DEL") { 350 | if (textName.cursorPosition > 0) { 351 | var oldpos=textName.cursorPosition; 352 | 353 | var leftText=textName.text.substring(0, oldpos-1); 354 | var rightText=textName.text.substring(oldpos, textName.text.length); 355 | 356 | textName.text=leftText+""+rightText; 357 | textName.cursorPosition=oldpos-1; 358 | textName.cursorVisible=true; 359 | textName.focus=true; 360 | } 361 | } else if (keyval === "ENTER") { // Dismiss and move to next field 362 | keyboard.visible=false; 363 | textName.cursorVisible=false; 364 | } else { 365 | var charToInsert=keyval; 366 | if (textName.cursorPosition > 0) { 367 | var prevchar=textName.text.charAt(textName.cursorPosition-1); 368 | 369 | if (prevchar !== ' ') { 370 | charToInsert=charToInsert.toLowerCase(); 371 | } 372 | } 373 | textName.insert(textName.cursorPosition, charToInsert); 374 | textName.focus=true; 375 | } 376 | } 377 | 378 | } 379 | 380 | TapHandler { 381 | onTapped: { 382 | keyboard.visible=false; 383 | textName.cursorVisible=false; 384 | } 385 | onCanceled: { 386 | } 387 | } 388 | 389 | Component.onCompleted: { 390 | updatePage(); 391 | } 392 | 393 | } 394 | 395 | /*##^## 396 | Designer { 397 | D{i:1;anchors_height:400;anchors_x:0;anchors_y:0} 398 | } 399 | ##^##*/ 400 | -------------------------------------------------------------------------------- /src/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Window 2.12 26 | import QtQuick.Controls 2.3 27 | 28 | Window { 29 | id: mainWindow 30 | visible: true 31 | width: 800 32 | height: 480 33 | title: qsTr("XFm2 Synth") 34 | 35 | // Note: This app was designed to run on a Raspberry Pi with an 800x480 touch screen 36 | // Remove the visibility line below if you want to run it as a Desktop application! 37 | visibility: "FullScreen" 38 | 39 | Rectangle { 40 | anchors.fill: parent 41 | color: "#505050" 42 | } 43 | 44 | CommonPage { 45 | id: commonPage 46 | anchors.fill: tabArea 47 | visible: true 48 | } 49 | 50 | AlgorithmPage { 51 | id: algorithmPage 52 | anchors.fill: tabArea 53 | visible: false 54 | } 55 | 56 | OperatorPage { 57 | id: operatorPage 58 | anchors.fill: tabArea 59 | visible: false 60 | } 61 | 62 | EnvelopePage { 63 | id: envelopePage 64 | anchors.fill: tabArea 65 | visible: false 66 | } 67 | 68 | LFOPage { 69 | id: lfoPage 70 | anchors.fill: tabArea 71 | visible: false 72 | } 73 | 74 | ModulationPage { 75 | id: modulationPage 76 | anchors.fill: tabArea 77 | visible: false 78 | } 79 | 80 | ArpeggiatorPage { 81 | id: arpeggiatorPage 82 | anchors.fill: tabArea 83 | visible: false 84 | } 85 | 86 | EffectsPage { 87 | id: effectsPage 88 | anchors.fill: tabArea 89 | visible: false 90 | } 91 | 92 | EffectsPage2 { 93 | id: effectsPage2 94 | anchors.fill: tabArea 95 | visible: false 96 | } 97 | 98 | Label { 99 | id: element 100 | x: 34 101 | y: 34 102 | color: "#ffb060" 103 | text: qsTr("Patch") 104 | font.pixelSize: 22 105 | } 106 | 107 | SpinBox { 108 | id: spinPatchNumber 109 | x: 107 110 | y: 27 111 | font.pixelSize: 22 112 | to: 127 113 | 114 | onValueChanged: { 115 | synthModel.patchNumber=spinPatchNumber.value; 116 | } 117 | } 118 | 119 | GroupBox { 120 | id: groupBox 121 | x: 0 122 | y: 79 123 | width: 800 124 | height: 1 125 | title: qsTr("") 126 | } 127 | 128 | TabBar { 129 | id: tabBar 130 | y: 440 131 | position: TabBar.Footer 132 | anchors.right: parent.right 133 | anchors.rightMargin: 0 134 | anchors.left: parent.left 135 | anchors.leftMargin: 0 136 | anchors.bottom: parent.bottom 137 | anchors.bottomMargin: 0 138 | 139 | TabButton { 140 | id: tabButtonCommon 141 | x: 0 142 | y: 0 143 | text: qsTr("Common") 144 | } 145 | 146 | TabButton { 147 | id: tabButtonAlgorithm 148 | text: qsTr("ALGO") 149 | } 150 | 151 | TabButton { 152 | id: tabButtonOperator 153 | text: qsTr("OPER") 154 | } 155 | 156 | TabButton { 157 | id: tabButtonEnvelope 158 | text: qsTr("ENV") 159 | } 160 | 161 | TabButton { 162 | id: tabButtonLFO 163 | text: qsTr("LFO") 164 | } 165 | 166 | TabButton { 167 | id: tabButtonModulation 168 | text: qsTr("MOD") 169 | } 170 | 171 | TabButton { 172 | id: tabButtonArpeggiator 173 | text: qsTr("ARP") 174 | } 175 | 176 | TabButton { 177 | id: tabButtonEffects 178 | text: qsTr("FX1") 179 | } 180 | 181 | TabButton { 182 | id: tabButtonEffects2 183 | text: qsTr("FX2") 184 | } 185 | 186 | onCurrentIndexChanged: { 187 | switch (tabBar.currentIndex) { 188 | case 0: // Common 189 | commonPage.visible=true; 190 | algorithmPage.visible=false; 191 | operatorPage.visible=false; 192 | envelopePage.visible=false; 193 | lfoPage.visible=false; 194 | modulationPage.visible=false; 195 | arpeggiatorPage.visible=false; 196 | effectsPage.visible=false; 197 | effectsPage2.visible=false; 198 | break; 199 | 200 | case 1: // Algorithm 201 | commonPage.visible=true; 202 | algorithmPage.visible=true; 203 | operatorPage.visible=false; 204 | envelopePage.visible=false; 205 | lfoPage.visible=false; 206 | modulationPage.visible=false; 207 | arpeggiatorPage.visible=false; 208 | effectsPage.visible=false; 209 | effectsPage2.visible=false; 210 | break; 211 | 212 | case 2: // Operator 213 | commonPage.visible=false; 214 | algorithmPage.visible=false; 215 | operatorPage.visible=true; 216 | envelopePage.visible=false; 217 | lfoPage.visible=false; 218 | modulationPage.visible=false; 219 | arpeggiatorPage.visible=false; 220 | effectsPage.visible=false; 221 | effectsPage2.visible=false; 222 | break; 223 | 224 | case 3: // Envelope 225 | commonPage.visible=false; 226 | algorithmPage.visible=false; 227 | operatorPage.visible=false; 228 | envelopePage.visible=true; 229 | lfoPage.visible=false; 230 | modulationPage.visible=false; 231 | arpeggiatorPage.visible=false; 232 | effectsPage.visible=false; 233 | effectsPage2.visible=false; 234 | break; 235 | 236 | case 4: // LFO 237 | commonPage.visible=false; 238 | algorithmPage.visible=false; 239 | operatorPage.visible=false; 240 | envelopePage.visible=false; 241 | lfoPage.visible=true; 242 | modulationPage.visible=false; 243 | arpeggiatorPage.visible=false; 244 | effectsPage.visible=false; 245 | effectsPage2.visible=false; 246 | break; 247 | 248 | case 5: // Modulation 249 | commonPage.visible=false; 250 | algorithmPage.visible=false; 251 | operatorPage.visible=false; 252 | envelopePage.visible=false; 253 | lfoPage.visible=false; 254 | modulationPage.visible=true; 255 | arpeggiatorPage.visible=false; 256 | effectsPage.visible=false; 257 | effectsPage2.visible=false; 258 | break; 259 | 260 | case 6: // Arpeggiator 261 | commonPage.visible=false; 262 | algorithmPage.visible=false; 263 | operatorPage.visible=false; 264 | envelopePage.visible=false; 265 | lfoPage.visible=false; 266 | modulationPage.visible=false; 267 | arpeggiatorPage.visible=true; 268 | effectsPage.visible=false; 269 | effectsPage2.visible=false; 270 | break; 271 | 272 | case 7: // Effects 273 | commonPage.visible=false; 274 | algorithmPage.visible=false; 275 | operatorPage.visible=false; 276 | envelopePage.visible=false; 277 | lfoPage.visible=false; 278 | modulationPage.visible=false; 279 | arpeggiatorPage.visible=false; 280 | effectsPage.visible=true; 281 | effectsPage2.visible=false; 282 | break; 283 | 284 | case 8: // Effects 2 285 | commonPage.visible=false; 286 | algorithmPage.visible=false; 287 | operatorPage.visible=false; 288 | envelopePage.visible=false; 289 | lfoPage.visible=false; 290 | modulationPage.visible=false; 291 | arpeggiatorPage.visible=false; 292 | effectsPage.visible=false; 293 | effectsPage2.visible=true; 294 | break; 295 | 296 | default: 297 | break; 298 | } 299 | } 300 | } 301 | 302 | Item { 303 | id: tabArea 304 | x: 0 305 | y: 83 306 | width: 800 307 | height: 357 308 | } 309 | 310 | Button { 311 | id: buttonWrite 312 | x: 674 313 | y: 27 314 | text: qsTr("WRITE") 315 | font.pointSize: 16 316 | onClicked: { 317 | popupWriteMessage.open(); 318 | } 319 | } 320 | 321 | Button { 322 | id: buttonReload 323 | x: 280 324 | y: 27 325 | text: qsTr("READ") 326 | font.pointSize: 16 327 | onClicked: { 328 | popupReloadMessage.open(); 329 | } 330 | } 331 | 332 | Button { 333 | id: buttonInit 334 | x: 417 335 | y: 27 336 | text: qsTr("INIT") 337 | font.pointSize: 16 338 | onClicked: { 339 | popupInitMessage.open(); 340 | } 341 | } 342 | 343 | PopupMessage { 344 | id: popupInitMessage 345 | titleText: "Initialise Patch" 346 | detailText: "Do you want to init this patch?" 347 | okButtonText: "Yes" 348 | cancelButtonText: "No" 349 | onOkClicked: { 350 | synthModel.initPatchBuffer(); 351 | } 352 | } 353 | 354 | PopupMessage { 355 | id: popupReloadMessage 356 | titleText: "Load Patch" 357 | detailText: "Do you want to reload this patch?" 358 | okButtonText: "Yes" 359 | cancelButtonText: "No" 360 | onOkClicked: { 361 | synthModel.reloadPatch(); 362 | } 363 | } 364 | 365 | PopupMessage { 366 | id: popupWriteMessage 367 | titleText: "Write Patch" 368 | detailText: "Do you want to save this patch?" 369 | okButtonText: "Yes" 370 | cancelButtonText: "No" 371 | onOkClicked: { 372 | synthModel.writePatchBuffer(-1); 373 | } 374 | } 375 | 376 | Component.onCompleted: { 377 | spinPatchNumber.value=synthModel.patchNumber; 378 | synthModel.readPatchBuffer(); 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/EffectsPage2.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | 29 | Item { 30 | id: effectsPage2 31 | width: 800 32 | height: 357 33 | 34 | 35 | Rectangle { 36 | anchors.fill: parent 37 | color: "#505050" 38 | } 39 | 40 | Connections { 41 | target: synthModel 42 | onPatchNumberChanged: { 43 | updatePage(); 44 | } 45 | } 46 | 47 | function updatePage() 48 | { 49 | dialBitCrusher.value=synthModel.fxBitCrushDepth; 50 | dialDecimator.value=synthModel.fxDecimator; 51 | dialLowPassFilter.value=synthModel.filterLoCutoff; 52 | dialHighPassFilter.value=synthModel.filterHiCutoff; 53 | 54 | dialAMDepth.value=synthModel.fxAMDepth; 55 | dialAMSpeed.value=synthModel.fxAMSpeed; 56 | dialAMRange.value=synthModel.fxAMRange; 57 | dialAMPhase.value=synthModel.fxAMPhase; 58 | 59 | dialReverbDry.value=synthModel.fxReverbDry; 60 | dialReverbWet.value=synthModel.fxReverbWet; 61 | dialReverbDecay.value=synthModel.fxReverbDecay; 62 | dialReverbDamp.value=synthModel.fxReverbDamp; 63 | spinReverbMode.value=synthModel.fxReverbMode; 64 | 65 | switchFXRoute.checked=synthModel.fxRoute !== 0; 66 | } 67 | 68 | Rectangle { 69 | id: rectangle1 70 | x: 109 71 | y: 175 72 | width: 508 73 | height: 2 74 | color: "#ffffff" 75 | border.color: "#ffb060" 76 | } 77 | 78 | Switch { 79 | id: switchFXRoute 80 | x: 623 81 | y: 218 82 | text: qsTr("Delay-Chorus") 83 | font.pixelSize: 16 84 | onCheckedChanged: { 85 | if (switchFXRoute.checked) { 86 | synthModel.fxRoute=1; 87 | } else { 88 | synthModel.fxRoute=0; 89 | } 90 | } 91 | } 92 | 93 | Rectangle { 94 | id: rectangle 95 | x: 61 96 | y: 17 97 | width: 731 98 | height: 2 99 | color: "#ffffff" 100 | border.color: "#ffb060" 101 | } 102 | 103 | Label { 104 | id: label 105 | x: 8 106 | y: 110 107 | color: "#ffffff" 108 | text: qsTr("Bit Crusher") 109 | font.pixelSize: 16 110 | } 111 | 112 | CustomDial { 113 | id: dialBitCrusher 114 | x: 14 115 | y: 40 116 | width: 64 117 | height: 64 118 | maximumValue: 24 119 | onValueChanged: { 120 | synthModel.fxBitCrushDepth=dialBitCrusher.value; 121 | } 122 | } 123 | 124 | CustomDial { 125 | id: dialDecimator 126 | x: 104 127 | y: 40 128 | width: 64 129 | height: 64 130 | maximumValue: 255 131 | onValueChanged: { 132 | synthModel.fxDecimator=dialDecimator.value; 133 | } 134 | } 135 | 136 | Label { 137 | id: label1 138 | x: 101 139 | y: 110 140 | width: 71 141 | height: 17 142 | color: "#ffffff" 143 | text: qsTr("Decimator") 144 | font.pixelSize: 16 145 | } 146 | 147 | CustomDial { 148 | id: dialLowPassFilter 149 | x: 194 150 | y: 40 151 | width: 64 152 | height: 64 153 | maximumValue: 255 154 | onValueChanged: { 155 | synthModel.filterLoCutoff=dialLowPassFilter.value; 156 | } 157 | } 158 | 159 | Label { 160 | id: label2 161 | x: 190 162 | y: 110 163 | width: 72 164 | height: 17 165 | color: "#ffffff" 166 | text: qsTr("Low Pass") 167 | font.pixelSize: 16 168 | horizontalAlignment: Text.AlignHCenter 169 | } 170 | 171 | CustomDial { 172 | id: dialHighPassFilter 173 | x: 284 174 | y: 40 175 | width: 64 176 | height: 64 177 | maximumValue: 255 178 | onValueChanged: { 179 | synthModel.filterHiCutoff=dialHighPassFilter.value; 180 | } 181 | } 182 | 183 | Label { 184 | id: label3 185 | x: 285 186 | y: 110 187 | width: 62 188 | height: 17 189 | color: "#ffffff" 190 | text: qsTr("Hi Pass") 191 | font.pixelSize: 16 192 | horizontalAlignment: Text.AlignHCenter 193 | } 194 | 195 | Label { 196 | id: label4 197 | x: 0 198 | y: 10 199 | color: "#ffb060" 200 | text: qsTr("Effects") 201 | font.pixelSize: 16 202 | } 203 | 204 | Label { 205 | id: label5 206 | x: 0 207 | y: 166 208 | color: "#ffb060" 209 | text: qsTr("Global Reverb") 210 | font.pixelSize: 16 211 | } 212 | 213 | CustomDial { 214 | id: dialAMDepth 215 | x: 419 216 | y: 40 217 | width: 64 218 | height: 64 219 | maximumValue: 255 220 | onValueChanged: { 221 | synthModel.fxAMDepth=dialAMDepth.value; 222 | } 223 | } 224 | 225 | Label { 226 | id: label6 227 | x: 420 228 | y: 110 229 | width: 62 230 | height: 35 231 | color: "#ffffff" 232 | text: qsTr("AM Depth") 233 | font.pixelSize: 16 234 | wrapMode: Text.WordWrap 235 | verticalAlignment: Text.AlignVCenter 236 | horizontalAlignment: Text.AlignHCenter 237 | } 238 | 239 | CustomDial { 240 | id: dialAMSpeed 241 | x: 509 242 | y: 40 243 | width: 64 244 | height: 64 245 | maximumValue: 255 246 | onValueChanged: { 247 | synthModel.fxAMSpeed=dialAMSpeed.value; 248 | } 249 | } 250 | 251 | Label { 252 | id: label7 253 | x: 510 254 | y: 110 255 | width: 62 256 | height: 35 257 | color: "#ffffff" 258 | text: qsTr("AM Speed") 259 | font.pixelSize: 16 260 | wrapMode: Text.WordWrap 261 | verticalAlignment: Text.AlignVCenter 262 | horizontalAlignment: Text.AlignHCenter 263 | } 264 | 265 | CustomDial { 266 | id: dialAMRange 267 | x: 599 268 | y: 40 269 | width: 64 270 | height: 64 271 | maximumValue: 255 272 | onValueChanged: { 273 | synthModel.fxAMRange=dialAMRange.value; 274 | } 275 | 276 | } 277 | 278 | Label { 279 | id: label8 280 | x: 600 281 | y: 110 282 | width: 62 283 | height: 35 284 | color: "#ffffff" 285 | text: qsTr("AM Range") 286 | font.pixelSize: 16 287 | wrapMode: Text.WordWrap 288 | verticalAlignment: Text.AlignVCenter 289 | horizontalAlignment: Text.AlignHCenter 290 | } 291 | 292 | CustomDial { 293 | id: dialAMPhase 294 | x: 689 295 | y: 40 296 | width: 64 297 | height: 64 298 | maximumValue: 255 299 | onValueChanged: { 300 | synthModel.fxAMPhase=dialAMPhase.value; 301 | } 302 | } 303 | 304 | Label { 305 | id: label9 306 | x: 690 307 | y: 110 308 | width: 62 309 | height: 35 310 | color: "#ffffff" 311 | text: qsTr("AM Phase") 312 | font.pixelSize: 16 313 | wrapMode: Text.WordWrap 314 | verticalAlignment: Text.AlignVCenter 315 | horizontalAlignment: Text.AlignHCenter 316 | } 317 | 318 | CustomDial { 319 | id: dialReverbDry 320 | x: 14 321 | y: 205 322 | width: 64 323 | height: 64 324 | maximumValue: 255 325 | onValueChanged: { 326 | synthModel.fxReverbDry=dialReverbDry.value 327 | } 328 | } 329 | 330 | Label { 331 | id: label10 332 | x: 35 333 | y: 275 334 | color: "#ffffff" 335 | text: qsTr("Dry") 336 | font.pixelSize: 16 337 | horizontalAlignment: Text.AlignHCenter 338 | } 339 | 340 | CustomDial { 341 | id: dialReverbWet 342 | x: 104 343 | y: 205 344 | width: 64 345 | height: 64 346 | maximumValue: 255 347 | onValueChanged: { 348 | synthModel.fxReverbWet=dialReverbWet.value; 349 | } 350 | } 351 | 352 | Label { 353 | id: label11 354 | x: 122 355 | y: 275 356 | width: 28 357 | height: 17 358 | color: "#ffffff" 359 | text: qsTr("Wet") 360 | font.pixelSize: 16 361 | horizontalAlignment: Text.AlignHCenter 362 | } 363 | 364 | CustomDial { 365 | id: dialReverbDecay 366 | x: 194 367 | y: 205 368 | width: 64 369 | height: 64 370 | maximumValue: 255 371 | onValueChanged: { 372 | synthModel.fxReverbDecay=dialReverbDecay.value; 373 | } 374 | } 375 | 376 | Label { 377 | id: label12 378 | x: 203 379 | y: 275 380 | width: 46 381 | height: 17 382 | color: "#ffffff" 383 | text: qsTr("Decay") 384 | font.pixelSize: 16 385 | horizontalAlignment: Text.AlignHCenter 386 | } 387 | 388 | CustomDial { 389 | id: dialReverbDamp 390 | x: 284 391 | y: 205 392 | width: 64 393 | height: 64 394 | maximumValue: 255 395 | onValueChanged: { 396 | synthModel.fxReverbDamp=dialReverbDamp.value; 397 | } 398 | } 399 | 400 | Label { 401 | id: label13 402 | x: 293 403 | y: 275 404 | width: 46 405 | height: 17 406 | color: "#ffffff" 407 | text: qsTr("Damp") 408 | font.pixelSize: 16 409 | horizontalAlignment: Text.AlignHCenter 410 | } 411 | 412 | SpinBox { 413 | id: spinReverbMode 414 | x: 440 415 | y: 217 416 | width: 160 417 | height: 40 418 | to: 1 419 | font.pointSize: 14 420 | onValueChanged: { 421 | synthModel.fxReverbMode=spinReverbMode.value; 422 | } 423 | textFromValue: function(value, locale) { 424 | var vals=["Plate", "Hall"]; 425 | 426 | return vals[value]; 427 | } 428 | } 429 | 430 | Label { 431 | id: label14 432 | x: 374 433 | y: 229 434 | width: 46 435 | height: 17 436 | color: "#ffffff" 437 | text: qsTr("Mode") 438 | font.pixelSize: 16 439 | horizontalAlignment: Text.AlignRight 440 | } 441 | 442 | Rectangle { 443 | id: rectangle2 444 | x: 698 445 | y: 175 446 | width: 90 447 | height: 2 448 | color: "#ffffff" 449 | border.color: "#ffb060" 450 | } 451 | 452 | Label { 453 | id: label15 454 | x: 628 455 | y: 166 456 | color: "#ffb060" 457 | text: qsTr("FX Route") 458 | font.pixelSize: 16 459 | } 460 | 461 | Component.onCompleted: { 462 | updatePage(); 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /src/xfmoperator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | #include 25 | #include 26 | #include "xfmoperator.h" 27 | #include "SynthModel.h" 28 | 29 | /* This is the implementation of a single FM operator for XFM2 30 | * Really the class just holds a set of properties and gives 31 | * them nice names that make it easy to use. The main SynthModel 32 | * actually talks to the hardware 33 | */ 34 | 35 | XFMOperator::XFMOperator(QObject *parent) : QObject(parent) 36 | { 37 | m_operator=0; 38 | m_algorithm=0; 39 | m_ratio=0; 40 | m_ratiofine=0; 41 | m_fine=0; 42 | m_level=0; 43 | m_levelLeft=0; 44 | m_levelRight=0; 45 | m_velocitysensitivity=0; 46 | m_keyboardbreakpoint=0; 47 | m_keyboardscaleleft=0; 48 | m_keyboardscaleright=0; 49 | m_keyboardcurveleft=0; 50 | m_keyboardcurveright=0; 51 | m_l0=0; 52 | m_l1=0; 53 | m_l2=0; 54 | m_l3=0; 55 | m_l4=0; 56 | m_l5=0; 57 | m_r0=0; 58 | m_r1=0; 59 | m_r2=0; 60 | m_r3=0; 61 | m_r4=0; 62 | m_r5=0; 63 | m_ratekey=0; 64 | m_ams=0; 65 | m_pms=0; 66 | m_wave1=0; 67 | m_wave2=0; 68 | m_wmode=0; 69 | m_wratio=0; 70 | m_phase=0; 71 | } 72 | 73 | int XFMOperator::operatorNumber() const 74 | { 75 | return m_operator; 76 | } 77 | 78 | void XFMOperator::setOperatorNumber(int n) 79 | { 80 | if (n != m_operator) { 81 | m_operator=n; 82 | emit operatorNumberChanged(); 83 | } 84 | } 85 | 86 | int XFMOperator::algorithm() const 87 | { 88 | return static_cast(m_algorithm); 89 | } 90 | 91 | void XFMOperator::setAlgorithm(int a) 92 | { 93 | unsigned char aa=static_cast(a); 94 | if (aa != m_algorithm) { 95 | m_algorithm=aa; 96 | emit algorithmChanged(); 97 | } 98 | } 99 | 100 | int XFMOperator::feedback() const 101 | { 102 | return static_cast(m_feedback); 103 | } 104 | 105 | void XFMOperator::setFeedback(int f) 106 | { 107 | unsigned char ff=static_cast(f); 108 | if (ff != m_feedback) { 109 | m_feedback=ff; 110 | emit feedbackChanged(); 111 | } 112 | } 113 | 114 | int XFMOperator::ratio() const 115 | { 116 | return static_cast(m_ratio); 117 | } 118 | 119 | void XFMOperator::setRatio(int f) 120 | { 121 | unsigned char ff=static_cast(f); 122 | if (ff != m_ratio) { 123 | m_ratio=ff; 124 | emit ratioChanged(); 125 | } 126 | } 127 | 128 | int XFMOperator::ratioFine() const 129 | { 130 | return static_cast(m_ratiofine); 131 | } 132 | 133 | void XFMOperator::setRatioFine(int f) 134 | { 135 | unsigned char ff=static_cast(f); 136 | if (ff != m_ratiofine) { 137 | m_ratiofine=ff; 138 | emit ratioFineChanged(); 139 | } 140 | } 141 | 142 | int XFMOperator::fine() const 143 | { 144 | return static_cast(m_fine); 145 | } 146 | 147 | void XFMOperator::setFine(int f) 148 | { 149 | unsigned char ff=static_cast(f); 150 | if (ff != m_fine) { 151 | m_fine=ff; 152 | emit fineChanged(); 153 | } 154 | } 155 | 156 | int XFMOperator::level() const 157 | { 158 | return static_cast(m_level); 159 | } 160 | 161 | void XFMOperator::setLevel(int f) 162 | { 163 | unsigned char ff=static_cast(f); 164 | if (ff != m_level) { 165 | m_level=ff; 166 | emit levelChanged(); 167 | } 168 | } 169 | 170 | int XFMOperator::levelLeft() const 171 | { 172 | return static_cast(m_levelLeft); 173 | } 174 | 175 | void XFMOperator::setLevelLeft(int f) 176 | { 177 | unsigned char ff=static_cast(f); 178 | if (ff != m_levelLeft) { 179 | m_levelLeft=ff; 180 | emit levelLeftChanged(); 181 | } 182 | } 183 | 184 | int XFMOperator::levelRight() const 185 | { 186 | return static_cast(m_levelRight); 187 | } 188 | 189 | void XFMOperator::setLevelRight(int f) 190 | { 191 | unsigned char ff=static_cast(f); 192 | if (ff != m_levelRight) { 193 | m_levelRight=ff; 194 | emit levelRightChanged(); 195 | } 196 | } 197 | 198 | 199 | int XFMOperator::velocitySensitivity() const 200 | { 201 | return static_cast(m_velocitysensitivity); 202 | } 203 | 204 | void XFMOperator::setVelocitySensitivity(int f) 205 | { 206 | unsigned char ff=static_cast(f); 207 | if (ff != m_velocitysensitivity) { 208 | m_velocitysensitivity=ff; 209 | emit velocitySensitivityChanged(); 210 | } 211 | } 212 | 213 | int XFMOperator::keyboardBreakpoint() const 214 | { 215 | return static_cast(m_keyboardbreakpoint); 216 | } 217 | 218 | void XFMOperator::setKeyboardBreakpoint(int f) 219 | { 220 | unsigned char ff=static_cast(f); 221 | if (ff != m_keyboardbreakpoint) { 222 | m_keyboardbreakpoint=ff; 223 | emit keyboardBreakpointChanged(); 224 | } 225 | } 226 | 227 | int XFMOperator::keyboardScaleLeft() const 228 | { 229 | return static_cast(m_keyboardscaleleft); 230 | } 231 | 232 | void XFMOperator::setKeyboardScaleLeft(int f) 233 | { 234 | unsigned char ff=static_cast(f); 235 | if (ff != m_keyboardscaleleft) { 236 | m_keyboardscaleleft=ff; 237 | emit keyboardScaleLeftChanged(); 238 | } 239 | } 240 | 241 | int XFMOperator::keyboardScaleRight() const 242 | { 243 | return static_cast(m_keyboardscaleright); 244 | } 245 | 246 | void XFMOperator::setKeyboardScaleRight(int f) 247 | { 248 | unsigned char ff=static_cast(f); 249 | if (ff != m_keyboardscaleright) { 250 | m_keyboardscaleright=ff; 251 | emit keyboardScaleRightChanged(); 252 | } 253 | } 254 | 255 | int XFMOperator::keyboardCurveLeft() const 256 | { 257 | return static_cast(m_keyboardcurveleft); 258 | } 259 | 260 | void XFMOperator::setKeyboardCurveLeft(int f) 261 | { 262 | unsigned char ff=static_cast(f); 263 | if (ff != m_keyboardcurveleft) { 264 | m_keyboardcurveleft=ff; 265 | emit keyboardCurveLeftChanged(); 266 | } 267 | } 268 | 269 | int XFMOperator::keyboardCurveRight() const 270 | { 271 | return static_cast(m_keyboardcurveright); 272 | } 273 | 274 | void XFMOperator::setKeyboardCurveRight(int f) 275 | { 276 | unsigned char ff=static_cast(f); 277 | if (ff != m_keyboardcurveright) { 278 | m_keyboardcurveright=ff; 279 | emit keyboardCurveRightChanged(); 280 | } 281 | } 282 | 283 | int XFMOperator::L0() const 284 | { 285 | return static_cast(m_l0); 286 | } 287 | 288 | void XFMOperator::setL0(int f) 289 | { 290 | unsigned char ff=static_cast(f); 291 | if (ff != m_l0) { 292 | m_l0=ff; 293 | emit L0Changed(); 294 | } 295 | } 296 | 297 | int XFMOperator::L1() const 298 | { 299 | return static_cast(m_l1); 300 | } 301 | 302 | void XFMOperator::setL1(int f) 303 | { 304 | unsigned char ff=static_cast(f); 305 | if (ff != m_l1) { 306 | m_l1=ff; 307 | emit L1Changed(); 308 | } 309 | } 310 | 311 | int XFMOperator::L2() const 312 | { 313 | return static_cast(m_l2); 314 | } 315 | 316 | void XFMOperator::setL2(int f) 317 | { 318 | unsigned char ff=static_cast(f); 319 | if (ff != m_l2) { 320 | m_l2=ff; 321 | emit L2Changed(); 322 | } 323 | } 324 | 325 | int XFMOperator::L3() const 326 | { 327 | return static_cast(m_l3); 328 | } 329 | 330 | void XFMOperator::setL3(int f) 331 | { 332 | unsigned char ff=static_cast(f); 333 | if (ff != m_l3) { 334 | m_l3=ff; 335 | emit L3Changed(); 336 | } 337 | } 338 | 339 | int XFMOperator::L4() const 340 | { 341 | return static_cast(m_l4); 342 | } 343 | 344 | void XFMOperator::setL4(int f) 345 | { 346 | unsigned char ff=static_cast(f); 347 | if (ff != m_l4) { 348 | m_l4=ff; 349 | emit L4Changed(); 350 | } 351 | } 352 | 353 | int XFMOperator::L5() const 354 | { 355 | return static_cast(m_l5); 356 | } 357 | 358 | void XFMOperator::setL5(int f) 359 | { 360 | unsigned char ff=static_cast(f); 361 | if (ff != m_l5) { 362 | m_l5=ff; 363 | emit L5Changed(); 364 | } 365 | } 366 | 367 | int XFMOperator::R0() const 368 | { 369 | return static_cast(m_r0); 370 | } 371 | 372 | void XFMOperator::setR0(int f) 373 | { 374 | unsigned char ff=static_cast(f); 375 | if (ff != m_r0) { 376 | m_r0=ff; 377 | emit R0Changed(); 378 | } 379 | } 380 | 381 | int XFMOperator::R1() const 382 | { 383 | return static_cast(m_r1); 384 | } 385 | 386 | void XFMOperator::setR1(int f) 387 | { 388 | unsigned char ff=static_cast(f); 389 | if (ff != m_r1) { 390 | m_r1=ff; 391 | emit R1Changed(); 392 | } 393 | } 394 | 395 | int XFMOperator::R2() const 396 | { 397 | return static_cast(m_r2); 398 | } 399 | 400 | void XFMOperator::setR2(int f) 401 | { 402 | unsigned char ff=static_cast(f); 403 | if (ff != m_r2) { 404 | m_r2=ff; 405 | emit R2Changed(); 406 | } 407 | } 408 | 409 | int XFMOperator::R3() const 410 | { 411 | return static_cast(m_r3); 412 | } 413 | 414 | void XFMOperator::setR3(int f) 415 | { 416 | unsigned char ff=static_cast(f); 417 | if (ff != m_r3) { 418 | m_r3=ff; 419 | emit R3Changed(); 420 | } 421 | } 422 | 423 | int XFMOperator::R4() const 424 | { 425 | return static_cast(m_r4); 426 | } 427 | 428 | void XFMOperator::setR4(int f) 429 | { 430 | unsigned char ff=static_cast(f); 431 | if (ff != m_r4) { 432 | m_r4=ff; 433 | emit R4Changed(); 434 | } 435 | } 436 | 437 | int XFMOperator::R5() const 438 | { 439 | return static_cast(m_r5); 440 | } 441 | 442 | void XFMOperator::setR5(int f) 443 | { 444 | unsigned char ff=static_cast(f); 445 | if (ff != m_r5) { 446 | m_r5=ff; 447 | emit R5Changed(); 448 | } 449 | } 450 | 451 | int XFMOperator::rateKey() const 452 | { 453 | return static_cast(m_ratekey); 454 | } 455 | 456 | void XFMOperator::setRateKey(int f) 457 | { 458 | unsigned char ff=static_cast(f); 459 | if (ff != m_ratekey) { 460 | m_ratekey=ff; 461 | emit rateKeyChanged(); 462 | } 463 | } 464 | 465 | int XFMOperator::amplitudeModulationSensitivity() const 466 | { 467 | return static_cast(m_ams); 468 | } 469 | 470 | void XFMOperator::setAmplitudeModulationSensitivity(int v) 471 | { 472 | unsigned char vv=static_cast(v); 473 | if (vv != m_ams) { 474 | m_ams=vv; 475 | emit amplitudeModulationSensitivityChanged(); 476 | } 477 | } 478 | 479 | int XFMOperator::pitchModulationSensitivity() const 480 | { 481 | return static_cast(m_pms); 482 | } 483 | 484 | void XFMOperator::setPitchModulationSensitivity(int v) 485 | { 486 | unsigned char vv=static_cast(v); 487 | if (vv != m_pms) { 488 | m_pms=vv; 489 | emit pitchModulationSensitivityChanged(); 490 | } 491 | } 492 | 493 | int XFMOperator::wave1() const 494 | { 495 | return static_cast(m_wave1); 496 | } 497 | 498 | void XFMOperator::setWave1(int v) 499 | { 500 | unsigned char vv=static_cast(v); 501 | if (vv != m_wave1) { 502 | m_wave1=vv; 503 | emit wave1Changed(); 504 | } 505 | } 506 | 507 | 508 | int XFMOperator::wave2() const 509 | { 510 | return static_cast(m_wave2); 511 | } 512 | 513 | void XFMOperator::setWave2(int v) 514 | { 515 | unsigned char vv=static_cast(v); 516 | if (vv != m_wave2) { 517 | m_wave2=vv; 518 | emit wave2Changed(); 519 | } 520 | } 521 | 522 | int XFMOperator::oscillatorMode() const 523 | { 524 | return static_cast(m_wmode); 525 | } 526 | 527 | void XFMOperator::setOscillatorMode(int v) 528 | { 529 | unsigned char vv=static_cast(v); 530 | if (vv != m_wmode) { 531 | m_wmode=vv; 532 | emit oscillatorModeChanged(); 533 | } 534 | } 535 | 536 | int XFMOperator::oscillatorRatio() const 537 | { 538 | return static_cast(m_wratio); 539 | } 540 | 541 | void XFMOperator::setOscillatorRatio(int v) 542 | { 543 | unsigned char vv=static_cast(v); 544 | if (vv != m_wratio) { 545 | m_wratio=vv; 546 | emit oscillatorRatioChanged(); 547 | } 548 | } 549 | 550 | int XFMOperator::phase() const 551 | { 552 | return static_cast(m_phase); 553 | } 554 | 555 | void XFMOperator::setPhase(int v) 556 | { 557 | unsigned char vv=static_cast(v); 558 | if (vv != m_phase) { 559 | m_phase=vv; 560 | emit phaseChanged(); 561 | } 562 | } 563 | -------------------------------------------------------------------------------- /src/xfm2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | #ifndef XFM2_H 25 | #define XFM2_H 26 | 27 | // This is a list of XFM2 parameter numbers so that 28 | // the rest of the app can reference them by name instead 29 | // of hard-coding the numbers 30 | 31 | enum XFM2Parameter { 32 | // Operators/Program 33 | ALGO1=1, // 1 = ALGO1 34 | ALGO2=2, // 2 = ALGO2 35 | ALGO3=3, // 3 = ALGO3 36 | ALGO4=4, // 4 = ALGO4 37 | ALGO5=5, // 5 = ALGO5 38 | ALGO6=6, // 6 = ALGO6 39 | OP_FEEDBACK1=7, // 7 = FEEDBACK1 40 | OP_FEEDBACK2=8, // 8 = FEEDBACK2 41 | OP_FEEDBACK3=9, // 9 = FEEDBACK3 42 | OP_FEEDBACK4=10, // 10 = FEEDBACK4 43 | OP_FEEDBACK5=11, // 11 = FEEDBACK5 44 | OP_FEEDBACK6=12, // 12 = FEEDBACK6 45 | OP_SYNC=13, // 13 = OPSYNC 46 | OP_MODE=14, // 14 = MODE 47 | OP_RATIO1=15, // 15 = OPRATIO1 48 | OP_RATIO2=16, // 16 49 | OP_RATIO3=17, // 17 50 | OP_RATIO4=18, // 18 51 | OP_RATIO5=19, // 19 52 | OP_RATIO6=20, // 20 53 | OP_RATIOFINE1=21, // 21 54 | OP_RATIOFINE2=22, // 22 55 | OP_RATIOFINE3=23, // 23 56 | OP_RATIOFINE4=24, // 24 57 | OP_RATIOFINE5=25, // 25 58 | OP_RATIOFINE6=26, // 26 59 | OP_FINE1=27, // 27 60 | OP_FINE2=28, // 28 61 | OP_FINE3=29, // 29 62 | OP_FINE4=30, // 30 63 | OP_FINE5=31, // 31 64 | OP_FINE6=32, // 32 65 | OP_LEVEL1=33, // 33 66 | OP_LEVEL2=34, // 34 67 | OP_LEVEL3=35, // 35 68 | OP_LEVEL4=36, // 36 69 | OP_LEVEL5=37, // 37 70 | OP_LEVEL6=38, // 38 71 | OP_VELO_SENS1=39, // 39 72 | OP_VELO_SENS2=40, // 40 73 | OP_VELO_SENS3=41, // 41 74 | OP_VELO_SENS4=42, // 42 75 | OP_VELO_SENS5=43, // 43 76 | OP_VELO_SENS6=44, // 44 77 | OP_KEY_BP1=45, // 45 78 | OP_KEY_BP2=46, // 46 79 | OP_KEY_BP3=47, // 47 80 | OP_KEY_BP4=48, // 48 81 | OP_KEY_BP5=49, // 49 82 | OP_KEY_BP6=50, // 50 83 | OP_KEY_LDEPTH1=51, // 51 84 | OP_KEY_LDEPTH2=52, // 52 85 | OP_KEY_LDEPTH3=53, // 53 86 | OP_KEY_LDEPTH4=54, // 54 87 | OP_KEY_LDEPTH5=55, // 55 88 | OP_KEY_LDEPTH6=56, // 56 89 | OP_KEY_RDEPTH1=57, // 57 90 | OP_KEY_RDEPTH2=58, // 58 91 | OP_KEY_RDEPTH3=59, // 59 92 | OP_KEY_RDEPTH4=60, // 60 93 | OP_KEY_RDEPTH5=61, // 61 94 | OP_KEY_RDEPTH6=62, // 62 95 | OP_KEY_LCURVE1=63, // 63 96 | OP_KEY_LCURVE2=64, // 64 97 | OP_KEY_LCURVE3=65, // 65 98 | OP_KEY_LCURVE4=66, // 66 99 | OP_KEY_LCURVE5=67, // 67 100 | OP_KEY_LCURVE6=68, // 68 101 | OP_KEY_RCURVE1=69, // 69 102 | OP_KEY_RCURVE2=70, // 70 103 | OP_KEY_RCURVE3=71, // 71 104 | OP_KEY_RCURVE4=72, // 72 105 | OP_KEY_RCURVE5=73, // 73 106 | OP_KEY_RCURVE6=74, // 74 107 | OP_LEVEL1_1=75, // 75 108 | OP_LEVEL1_2=76, // 76 109 | OP_LEVEL1_3=77, // 77 110 | OP_LEVEL1_4=78, // 78 111 | OP_LEVEL1_5=79, // 79 112 | OP_LEVEL1_6=80, // 80 113 | OP_LEVEL2_1=82, // 82 114 | OP_LEVEL2_2=83, // 83 115 | OP_LEVEL2_3=84, // 84 116 | OP_LEVEL2_4=85, // 85 117 | OP_LEVEL2_5=86, // 86 118 | OP_LEVEL2_6=87, // 87 119 | OP_LEVEL3_1=89, // 89 120 | OP_LEVEL3_2=90, // 90 121 | OP_LEVEL3_3=91, // 91 122 | OP_LEVEL3_4=92, // 92 123 | OP_LEVEL3_5=93, // 93 124 | OP_LEVEL3_6=94, // 94 125 | OP_LEVEL4_1=96, // 96 126 | OP_LEVEL4_2=97, // 97 127 | OP_LEVEL4_3=98, // 98 128 | OP_LEVEL4_4=99, // 99 129 | OP_LEVEL4_5=100, // 100 130 | OP_LEVEL4_6=101, // 101 131 | OP_RATE1_1=103, // 103 132 | OP_RATE1_2=104, // 104 133 | OP_RATE1_3=105, // 105 134 | OP_RATE1_4=106, // 106 135 | OP_RATE1_5=107, // 107 136 | OP_RATE1_6=108, // 108 137 | OP_RATE2_1=110, // 110 138 | OP_RATE2_2=111, // 111 139 | OP_RATE2_3=112, // 112 140 | OP_RATE2_4=113, // 113 141 | OP_RATE2_5=114, // 114 142 | OP_RATE2_6=115, // 115 143 | OP_RATE3_1=117, // 117 144 | OP_RATE3_2=118, // 118 145 | OP_RATE3_3=119, // 119 146 | OP_RATE3_4=120, // 120 147 | OP_RATE3_5=121, // 121 148 | OP_RATE3_6=122, // 122 149 | OP_RATE4_1=124, // 124 150 | OP_RATE4_2=125, // 125 151 | OP_RATE4_3=126, // 126 152 | OP_RATE4_4=127, // 127 153 | OP_RATE4_5=128, // 128 154 | OP_RATE4_6=129, // 129 155 | 156 | PITCH_EG_L1=130, // 130 157 | PITCH_EG_L2=131, // 131 158 | PITCH_EG_L3=132, // 132 159 | PITCH_EG_L4=133, // 133 160 | PITCH_EG_R1=134, // 134 161 | PITCH_EG_R2=135, // 135 162 | PITCH_EG_R3=136, // 136 163 | PITCH_EG_R4=137, // 137 164 | PITCH_EG_RANGE=138, // 138 165 | PITCH_EG_VELO=139, // 139 166 | 167 | OP_RATE_KEY1=140, // 140 168 | OP_RATE_KEY2=141, // 141 169 | OP_RATE_KEY3=142, // 142 170 | OP_RATE_KEY4=143, // 143 171 | OP_RATE_KEY5=144, // 144 172 | OP_RATE_KEY6=145, // 145 173 | 174 | PITCH_EG_RATE_KEY=146, // 146 175 | 176 | LFO_DEPTH_PITCH=149, // 149 177 | LFO_DEPTH_AMP=150, // 150 178 | LFO_SPEED=151, // 151 179 | LFO_SYNC=152, // 152 180 | LFO_WAVE=153, // 153 181 | LFO_FADE=154, // 154 182 | 183 | MOD_PITCH_LFO_WHEEL=155, // 155 184 | MOD_AMP_LFO_WHEEL=156, // 156 185 | MOD_PITCH_LFO_AFTER=157, // 157 186 | MOD_AMP_LFO_AFTER=158, // 158 187 | 188 | OP_AMS1=159, // 159 189 | OP_AMS2=160, // 160 190 | OP_AMS3=161, // 161 191 | OP_AMS4=162, // 162 192 | OP_AMS5=163, // 163 193 | OP_AMS6=164, // 164 194 | 195 | MASTER_PITCHBEND_UP=172, // 172 196 | MASTER_PITCHBEND_DOWN=173, // 173 197 | MASTER_TRANSPOSE=174, // 174 198 | MASTER_VOLUME=180, // 180 199 | 200 | OP_LEVEL0_1=181, // 181 201 | OP_LEVEL0_2=182, // 182 202 | OP_LEVEL0_3=183, // 183 203 | OP_LEVEL0_4=184, // 184 204 | OP_LEVEL0_5=185, // 185 205 | OP_LEVEL0_6=186, // 186 206 | OP_DELAY_1=187, // 187 207 | OP_DELAY_2=188, // 188 208 | OP_DELAY_3=189, // 189 209 | OP_DELAY_4=190, // 190 210 | OP_DELAY_5=191, // 191 211 | OP_DELAY_6=192, // 192 212 | OP_LEVEL5_1=193, // 193 213 | OP_LEVEL5_2=194, // 194 214 | OP_LEVEL5_3=195, // 195 215 | OP_LEVEL5_4=196, // 196 216 | OP_LEVEL5_5=197, // 197 217 | OP_LEVEL5_6=198, // 198 218 | OP_RATE5_1=199, // 199 219 | OP_RATE5_2=200, // 200 220 | OP_RATE5_3=201, // 201 221 | OP_RATE5_4=202, // 202 222 | OP_RATE5_5=203, // 203 223 | OP_RATE5_6=204, // 204 224 | 225 | PITCH_EG_L0=205, // 205 226 | PITCH_EG_DELAY=206, // 206 227 | PITCH_EG_L5=207, // 207 228 | PITCH_EG_R5=208, // 208 229 | 230 | MOD_PITCH_LFO_BREATH=209, // 209 231 | MOD_AMP_LFO_BREATH=210, // 210 232 | MOD_PITCH_LFO_FOOT=211, // 211 233 | MOD_AMP_LFO_FOOT=212, // 212 234 | 235 | MOD_EG_BIAS_AFTER=213, // 213 236 | MOD_EG_BIAS_WHEEL=214, // 214 237 | MOD_EG_BIAS_BREATH=215, // 215 238 | MOD_EG_BIAS_FOOT=216, // 216 239 | 240 | MOD_PITCH_AFTER=217, // 217 241 | MOD_PITCH_BREATH=218, // 218 242 | MOD_PITCH_FOOT=219, // 219 243 | MOD_PITCH_RANDOM=220, // 220 244 | 245 | MASTER_PAN=221, // 221 246 | 247 | OP_PMS_1=222, // 222 248 | OP_PMS_2=223, // 223 249 | OP_PMS_3=224, // 224 250 | OP_PMS_4=225, // 225 251 | OP_PMS_5=226, // 226 252 | OP_PMS_6=227, // 227 253 | 254 | MASTER_LEGATO=228, // 228 255 | MASTER_PORTAMENTO_MODE=229, // 229 256 | MASTER_PORTAMENTO_TIME=230, // 230 257 | 258 | OP_WAVE1_1=236, // 236 259 | OP_WAVE1_2=237, // 237 260 | OP_WAVE1_3=238, // 238 261 | OP_WAVE1_4=239, // 239 262 | OP_WAVE1_5=240, // 240 263 | OP_WAVE1_6=241, // 241 264 | 265 | MASTER_VELOCITY_OFFSET=242, // 242 266 | 267 | OP_EG_LOOP=244, // 244 268 | OP_EG_LOOP_SEG=245, // 245 269 | 270 | MASTER_EG_RESTART=246, // 246 271 | 272 | MASTER_TUNING=251, // 251 273 | 274 | OP_LEVEL_LEFT1=256, // 256 275 | OP_LEVEL_RIGHT1=257, // 257 276 | OP_LEVEL_LEFT2=258, // 258 277 | OP_LEVEL_RIGHT2=259, // 259 278 | OP_LEVEL_LEFT3=260, // 260 279 | OP_LEVEL_RIGHT3=261, // 261 280 | OP_LEVEL_LEFT4=262, // 262 281 | OP_LEVEL_RIGHT4=263, // 263 282 | OP_LEVEL_LEFT5=264, // 264 283 | OP_LEVEL_RIGHT5=265, // 265 284 | OP_LEVEL_LEFT6=266, // 266 285 | OP_LEVEL_RIGHT6=267, // 267 286 | 287 | OP_WAVE2_1=268, // 268 288 | OP_WAVE2_2=269, // 269 289 | OP_WAVE2_3=270, // 270 290 | OP_WAVE2_4=271, // 271 291 | OP_WAVE2_5=272, // 272 292 | OP_WAVE2_6=273, // 273 293 | 294 | OP_WMODE_1=274, // 274 295 | OP_WMODE_2=275, // 275 296 | OP_WMODE_3=276, // 276 297 | OP_WMODE_4=277, // 277 298 | OP_WMODE_5=278, // 278 299 | OP_WMODE_6=279, // 279 300 | OP_WRATIO_1=280, // 280 301 | OP_WRATIO_2=281, // 281 302 | OP_WRATIO_3=282, // 282 303 | OP_WRATIO_4=283, // 283 304 | OP_WRATIO_5=284, // 284 305 | OP_WRATIO_6=285, // 285 306 | OP_PHASE1=286, // 286 307 | OP_PHASE2=287, // 287 308 | OP_PHASE3=288, // 288 309 | OP_PHASE4=289, // 289 310 | OP_PHASE5=290, // 290 311 | OP_PHASE6=291, // 291 312 | 313 | FX_DELAY_DRY=300, // 300 314 | FX_DELAY_WET=301, // 301 315 | FX_DELAY_MODE=302, // 302 316 | FX_DELAY_TIME=303, // 303 317 | FX_DELAY_FEEDBACK=304, // 304 318 | FX_DELAY_LO=305, // 305 319 | FX_DELAY_HI=306, // 306 320 | FX_DELAY_TEMPO=307, // 307 321 | FX_DELAY_MUL=308, // 308 322 | FX_DELAY_DIV=309, // 309 323 | 324 | FX_PHASER_DRY=310, // 310 325 | FX_PHASER_WET=311, // 311 326 | FX_PHASER_MODE=312, // 312 327 | FX_PHASER_DEPTH=313, // 313 328 | FX_PHASER_SPEED=314, // 314 329 | FX_PHASER_FEEDBACK=315, // 315 330 | FX_PHASER_OFFSET=316, // 316 331 | FX_PHASER_STAGES=317, // 317 332 | FX_PHASER_LRPHASE=318, // 318 333 | 334 | FX_FILTER_LO=320, // 320 335 | FX_FILTER_HI=321, // 321 336 | 337 | FX_AM_SPEED=330, // 330 338 | FX_AM_RANGE=331, // 331 339 | FX_AM_DEPTH=332, // 332 340 | FX_AM_LRPHASE=333, // 333 341 | 342 | FX_CHORUS_DRY=360, // 360 343 | FX_CHORUS_WET=361, // 361 344 | FX_CHORUS_MODE=362, // 362 345 | FX_CHORUS_SPEED=363, // 363 346 | FX_CHORUS_DEPTH=364, // 364 347 | FX_CHORUS_FEEDBACK=365, // 365 348 | FX_CHORUS_LRPHASE=366, // 366 349 | 350 | FX_DECIMATOR_DEPTH=370, // 370 351 | FX_BITCRUSHER_DEPTH=380, // 380 352 | 353 | FX_REVERB_DRY=390, // 390 354 | FX_REVERB_WET=391, // 391 355 | FX_REVERB_MODE=392, // 392 356 | FX_REVERB_DECAY=393, // 393 357 | FX_REVERB_DAMP=394, // 394 358 | 359 | FX_ROUTING=410, // 410 360 | MASTER_OUTPUT=411, // 411 361 | 362 | PERFORMANCE_CTRL1_HI=420, // 420 363 | PERFORMANCE_CTRL1_LO=421, // 421 364 | PERFORMANCE_CTRL2_HI=422, // 422 365 | PERFORMANCE_CTRL2_LO=423, // 423 366 | PERFORMANCE_CTRL3_HI=424, // 424 367 | PERFORMANCE_CTRL3_LO=425, // 425 368 | PERFORMANCE_CTRL4_HI=426, // 426 369 | PERFORMANCE_CTRL4_LO=427, // 427 370 | 371 | ARPEGGIATOR_MODE=450, // 450 372 | ARPEGGIATOR_TEMPO=451, // 451 373 | ARPEGGIATOR_RESERVED=452, // 452 374 | ARPEGGIATOR_MUL=453, // 453 375 | ARPEGGIATOR_OCTAVES=454 // 454 376 | }; 377 | 378 | #endif // XFM2_H 379 | -------------------------------------------------------------------------------- /src/EffectsPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | import QtQuick.Controls.Styles 1.4 29 | 30 | Item { 31 | id: effectsPage 32 | width: 800 33 | height: 357 34 | 35 | 36 | Rectangle { 37 | anchors.fill: parent 38 | color: "#505050" 39 | } 40 | 41 | Connections { 42 | target: synthModel 43 | onPatchNumberChanged: { 44 | updatePage(); 45 | } 46 | } 47 | 48 | function updatePage() 49 | { 50 | dialChorusDry.value=synthModel.fxChorusDry; 51 | dialChorusWet.value=synthModel.fxChorusWet; 52 | dialChorusRate.value=synthModel.fxChorusSpeed; 53 | dialChorusDepth.value=synthModel.fxChorusDepth; 54 | dialChorusFeedback.value=synthModel.fxChorusFeedback; 55 | dialChorusPhase.value=synthModel.fxChorusPhase; 56 | spinChorusMode.value=synthModel.fxChorusMode; 57 | 58 | dialPhaserDry.value=synthModel.fxPhaserDry; 59 | dialPhaserWet.value=synthModel.fxPhaserWet; 60 | dialPhaserRate.value=synthModel.fxPhaserSpeed; 61 | dialPhaserDepth.value=synthModel.fxPhaserDepth; 62 | dialPhaserOffset.value=synthModel.fxPhaserOffset; 63 | dialPhaserFeedback.value=synthModel.fxPhaserFeedback; 64 | dialPhaserPhase.value=synthModel.fxPhaserPhase; 65 | spinPhaserStages.value=synthModel.fxPhaserStages; 66 | spinPhaserMode.value=synthModel.fxPhaserMode; 67 | 68 | dialDelayDry.value=synthModel.fxDelayDry; 69 | dialDelayWet.value=synthModel.fxDelayWet; 70 | dialDelayTime.value=synthModel.fxDelayTime; 71 | dialDelayFeedback.value=synthModel.fxDelayFeedback; 72 | dialDelayTempo.value=synthModel.fxDelayTempo; 73 | dialDelayMultiplier.value=synthModel.fxDelayMultiplier; 74 | dialDelayDivider.value=synthModel.fxDelayDivider; 75 | dialDelayLoPass.value=synthModel.fxDelayLowPass; 76 | dialDelayHiPass.value=synthModel.fxDelayHighPass; 77 | spinDelayMode.value=synthModel.fxDelayMode; 78 | } 79 | 80 | 81 | SpinBox { 82 | id: spinPhaserMode 83 | x: 676 84 | y: 182 85 | width: 115 86 | height: 40 87 | to: 2 88 | font.pointSize: 14 89 | onValueChanged: { 90 | synthModel.fxPhaserMode=spinPhaserMode.value; 91 | } 92 | textFromValue: function(value, locale) { 93 | var vals=["M", "S", "C"]; 94 | 95 | return vals[value]; 96 | } 97 | } 98 | 99 | SpinBox { 100 | id: spinPhaserStages 101 | x: 676 102 | y: 130 103 | width: 115 104 | height: 40 105 | to: 12 106 | from: 0 107 | font.pointSize: 14 108 | onValueChanged: { 109 | synthModel.fxPhaserStages=spinPhaserStages.value; 110 | } 111 | } 112 | 113 | Rectangle { 114 | id: rectangle1 115 | x: 60 116 | y: 119 117 | width: 731 118 | height: 2 119 | color: "#ffffff" 120 | border.color: "#ffb060" 121 | } 122 | 123 | Rectangle { 124 | id: rectangle 125 | x: 61 126 | y: 9 127 | width: 731 128 | height: 2 129 | color: "#ffffff" 130 | border.color: "#ffb060" 131 | } 132 | 133 | CustomDial { 134 | id: dialChorusDry 135 | x: 14 136 | y: 20 137 | width: 64 138 | height: 64 139 | maximumValue: 255 140 | onValueChanged: { 141 | synthModel.fxChorusDry=dialChorusDry.value; 142 | } 143 | } 144 | 145 | Label { 146 | id: label4 147 | x: 33 148 | y: 90 149 | color: "#ffffff" 150 | text: qsTr("Dry") 151 | font.pixelSize: 16 152 | } 153 | 154 | CustomDial { 155 | id: dialChorusWet 156 | x: 104 157 | y: 20 158 | width: 64 159 | height: 64 160 | maximumValue: 255 161 | onValueChanged: { 162 | synthModel.fxChorusWet=dialChorusWet.value; 163 | } 164 | } 165 | 166 | Label { 167 | id: label5 168 | x: 122 169 | y: 90 170 | color: "#ffffff" 171 | text: qsTr("Wet") 172 | font.pixelSize: 16 173 | } 174 | 175 | CustomDial { 176 | id: dialChorusRate 177 | x: 194 178 | y: 20 179 | width: 64 180 | height: 64 181 | maximumValue: 255 182 | onValueChanged: { 183 | synthModel.fxChorusSpeed=dialChorusRate.value; 184 | } 185 | } 186 | 187 | Label { 188 | id: label6 189 | x: 210 190 | y: 90 191 | color: "#ffffff" 192 | text: qsTr("Rate") 193 | font.pixelSize: 16 194 | } 195 | 196 | CustomDial { 197 | id: dialChorusDepth 198 | x: 284 199 | y: 20 200 | width: 64 201 | height: 64 202 | maximumValue: 255 203 | onValueChanged: { 204 | synthModel.fxChorusDepth=dialChorusDepth.value; 205 | } 206 | } 207 | 208 | Label { 209 | id: label7 210 | x: 295 211 | y: 90 212 | color: "#ffffff" 213 | text: qsTr("Depth") 214 | font.pixelSize: 16 215 | } 216 | 217 | CustomDial { 218 | id: dialChorusFeedback 219 | x: 374 220 | y: 20 221 | width: 64 222 | height: 64 223 | maximumValue: 255 224 | onValueChanged: { 225 | synthModel.fxChorusFeedback=dialChorusFeedback.value; 226 | } 227 | } 228 | 229 | Label { 230 | id: label8 231 | x: 374 232 | y: 90 233 | color: "#ffffff" 234 | text: qsTr("Feedback") 235 | font.pixelSize: 16 236 | } 237 | 238 | CustomDial { 239 | id: dialChorusPhase 240 | x: 464 241 | y: 20 242 | width: 64 243 | height: 64 244 | maximumValue: 255 245 | onValueChanged: { 246 | synthModel.fxChorusPhase=dialChorusPhase.value; 247 | } 248 | } 249 | 250 | Label { 251 | id: label9 252 | x: 462 253 | y: 90 254 | color: "#ffffff" 255 | text: qsTr("L/R Phase") 256 | font.pixelSize: 16 257 | } 258 | 259 | Label { 260 | id: label10 261 | x: 579 262 | y: 44 263 | color: "#ffffff" 264 | text: qsTr("Mode") 265 | font.pixelSize: 16 266 | } 267 | 268 | CustomDial { 269 | id: dialPhaserDry 270 | x: 14 271 | y: 137 272 | width: 64 273 | height: 64 274 | maximumValue: 255 275 | onValueChanged: { 276 | synthModel.fxPhaserDry=dialPhaserDry.value; 277 | } 278 | } 279 | 280 | Label { 281 | id: label15 282 | x: 33 283 | y: 207 284 | color: "#ffffff" 285 | text: qsTr("Dry") 286 | font.pixelSize: 16 287 | } 288 | 289 | CustomDial { 290 | id: dialPhaserWet 291 | x: 104 292 | y: 137 293 | width: 64 294 | height: 64 295 | maximumValue: 255 296 | onValueChanged: { 297 | synthModel.fxPhaserWet=dialPhaserWet.value; 298 | } 299 | } 300 | 301 | Label { 302 | id: label16 303 | x: 123 304 | y: 207 305 | color: "#ffffff" 306 | text: qsTr("Wet") 307 | font.pixelSize: 16 308 | } 309 | 310 | CustomDial { 311 | id: dialPhaserRate 312 | x: 194 313 | y: 137 314 | width: 64 315 | height: 64 316 | maximumValue: 255 317 | onValueChanged: { 318 | synthModel.fxPhaserSpeed=dialPhaserRate.value; 319 | } 320 | } 321 | 322 | Label { 323 | id: label17 324 | x: 210 325 | y: 207 326 | color: "#ffffff" 327 | text: qsTr("Rate") 328 | font.pixelSize: 16 329 | } 330 | 331 | CustomDial { 332 | id: dialPhaserDepth 333 | x: 284 334 | y: 137 335 | width: 64 336 | height: 64 337 | maximumValue: 255 338 | onValueChanged: { 339 | synthModel.fxPhaserDepth=dialPhaserDepth.value; 340 | } 341 | } 342 | 343 | Label { 344 | id: label18 345 | x: 295 346 | y: 207 347 | color: "#ffffff" 348 | text: qsTr("Depth") 349 | font.pixelSize: 16 350 | } 351 | 352 | CustomDial { 353 | id: dialPhaserFeedback 354 | x: 374 355 | y: 137 356 | width: 64 357 | height: 64 358 | maximumValue: 255 359 | onValueChanged: { 360 | synthModel.fxPhaserFeedback=dialPhaserFeedback.value; 361 | } 362 | } 363 | 364 | Label { 365 | id: label19 366 | x: 374 367 | y: 207 368 | color: "#ffffff" 369 | text: qsTr("Feedback") 370 | font.pixelSize: 16 371 | } 372 | 373 | CustomDial { 374 | id: dialPhaserPhase 375 | x: 464 376 | y: 137 377 | width: 64 378 | height: 64 379 | maximumValue: 255 380 | onValueChanged: { 381 | synthModel.fxPhaserPhase=dialPhaserPhase.value; 382 | } 383 | } 384 | 385 | Label { 386 | id: label20 387 | x: 464 388 | y: 207 389 | color: "#ffffff" 390 | text: qsTr("L/R Phase") 391 | font.pixelSize: 16 392 | } 393 | 394 | SpinBox { 395 | id: spinChorusMode 396 | x: 633 397 | y: 32 398 | width: 158 399 | height: 40 400 | to: 3 401 | onValueChanged: { 402 | synthModel.fxChorusMode=spinChorusMode.value; 403 | } 404 | textFromValue: function(value, locale) { 405 | var vals=["CHO (L)", "CHO (S)", "FLA (L)", "FLA (S)"]; 406 | 407 | return vals[value]; 408 | } 409 | } 410 | 411 | Label { 412 | id: label21 413 | x: 5 414 | y: 0 415 | color: "#ffb060" 416 | text: qsTr("Chorus") 417 | font.pixelSize: 16 418 | } 419 | 420 | Label { 421 | id: label22 422 | x: 5 423 | y: 111 424 | color: "#ffb060" 425 | text: qsTr("Phaser") 426 | font.pixelSize: 16 427 | } 428 | 429 | CustomDial { 430 | id: dialPhaserOffset 431 | x: 554 432 | y: 137 433 | width: 64 434 | height: 64 435 | maximumValue: 255 436 | onValueChanged: { 437 | synthModel.fxPhaserOffset=dialPhaserOffset.value; 438 | } 439 | } 440 | 441 | Label { 442 | id: label23 443 | x: 564 444 | y: 207 445 | color: "#ffffff" 446 | text: qsTr("Offset") 447 | font.pixelSize: 16 448 | horizontalAlignment: Text.AlignHCenter 449 | } 450 | 451 | Label { 452 | id: label11 453 | x: 624 454 | y: 142 455 | color: "#ffffff" 456 | text: qsTr("Stages") 457 | font.pixelSize: 16 458 | horizontalAlignment: Text.AlignRight 459 | } 460 | 461 | Label { 462 | id: label12 463 | x: 628 464 | y: 194 465 | color: "#ffffff" 466 | text: qsTr("Mode") 467 | font.pixelSize: 16 468 | horizontalAlignment: Text.AlignRight 469 | } 470 | 471 | Label { 472 | id: label24 473 | x: 5 474 | y: 230 475 | color: "#ffb060" 476 | text: qsTr("Delay") 477 | font.pixelSize: 16 478 | } 479 | 480 | Rectangle { 481 | id: rectangle2 482 | x: 61 483 | y: 238 484 | width: 731 485 | height: 2 486 | color: "#ffffff" 487 | border.color: "#ffb060" 488 | } 489 | 490 | CustomDial { 491 | id: dialDelayDry 492 | x: 14 493 | y: 260 494 | width: 64 495 | height: 64 496 | maximumValue: 255 497 | onValueChanged: { 498 | synthModel.fxDelayDry=dialDelayDry.value; 499 | } 500 | } 501 | 502 | Label { 503 | id: label25 504 | x: 33 505 | y: 325 506 | color: "#ffffff" 507 | text: qsTr("Dry") 508 | font.pixelSize: 16 509 | } 510 | 511 | CustomDial { 512 | id: dialDelayWet 513 | x: 104 514 | y: 260 515 | width: 64 516 | height: 64 517 | maximumValue: 255 518 | onValueChanged: { 519 | synthModel.fxDelayWet=dialDelayWet.value; 520 | } 521 | } 522 | 523 | Label { 524 | id: label26 525 | x: 122 526 | y: 325 527 | color: "#ffffff" 528 | text: qsTr("Wet") 529 | font.pixelSize: 16 530 | } 531 | 532 | CustomDial { 533 | id: dialDelayTime 534 | x: 194 535 | y: 260 536 | width: 64 537 | height: 64 538 | maximumValue: 255 539 | onValueChanged: { 540 | synthModel.fxDelayTime=dialDelayTime.value; 541 | } 542 | } 543 | 544 | Label { 545 | id: label27 546 | x: 209 547 | y: 325 548 | color: "#ffffff" 549 | text: qsTr("Time") 550 | font.pixelSize: 16 551 | } 552 | 553 | CustomDial { 554 | id: dialDelayFeedback 555 | x: 284 556 | y: 260 557 | width: 64 558 | height: 64 559 | maximumValue: 255 560 | onValueChanged: { 561 | synthModel.fxDelayFeedback=dialDelayFeedback.value; 562 | } 563 | } 564 | 565 | Label { 566 | id: label28 567 | x: 284 568 | y: 325 569 | color: "#ffffff" 570 | text: qsTr("Feedback") 571 | font.pixelSize: 16 572 | } 573 | 574 | CustomDial { 575 | id: dialDelayTempo 576 | x: 374 577 | y: 260 578 | width: 64 579 | height: 64 580 | maximumValue: 255 581 | onValueChanged: { 582 | synthModel.fxDelayTempo=dialDelayTempo.value; 583 | } 584 | } 585 | 586 | Label { 587 | id: label29 588 | x: 383 589 | y: 325 590 | color: "#ffffff" 591 | text: qsTr("Tempo") 592 | font.pixelSize: 16 593 | } 594 | 595 | CustomDial { 596 | id: dialDelayMultiplier 597 | x: 464 598 | y: 260 599 | width: 64 600 | height: 64 601 | maximumValue: 255 602 | onValueChanged: { 603 | synthModel.fxDelayMultiplier=dialDelayMultiplier.value; 604 | } 605 | } 606 | 607 | Label { 608 | id: label30 609 | x: 467 610 | y: 325 611 | color: "#ffffff" 612 | text: qsTr("Multiply") 613 | font.pixelSize: 16 614 | } 615 | 616 | CustomDial { 617 | id: dialDelayDivider 618 | x: 554 619 | y: 260 620 | width: 64 621 | height: 64 622 | maximumValue: 255 623 | onValueChanged: { 624 | synthModel.fxDelayDivider=dialDelayDivider.value; 625 | } 626 | } 627 | 628 | Label { 629 | id: label31 630 | x: 565 631 | y: 325 632 | color: "#ffffff" 633 | text: qsTr("Divide") 634 | font.pixelSize: 16 635 | } 636 | 637 | Label { 638 | id: label13 639 | x: 628 640 | y: 263 641 | color: "#ffffff" 642 | text: qsTr("Mode") 643 | font.pixelSize: 16 644 | horizontalAlignment: Text.AlignRight 645 | } 646 | 647 | SpinBox { 648 | id: spinDelayMode 649 | x: 676 650 | y: 251 651 | width: 115 652 | height: 40 653 | to: 2 654 | font.pointSize: 14 655 | onValueChanged: { 656 | synthModel.fxDelayMode=spinDelayMode.value; 657 | } 658 | textFromValue: function(value, locale) { 659 | var vals=["S", "C", "B"]; 660 | 661 | return vals[value]; 662 | } 663 | } 664 | 665 | CustomDial { 666 | id: dialDelayLoPass 667 | x: 674 668 | y: 301 669 | width: 48 670 | height: 48 671 | maximumValue: 255 672 | onValueChanged: { 673 | synthModel.fxDelayLowPass=dialDelayLoPass.value; 674 | } 675 | } 676 | 677 | CustomDial { 678 | id: dialDelayHiPass 679 | x: 747 680 | y: 301 681 | width: 48 682 | height: 48 683 | maximumValue: 255 684 | onValueChanged: { 685 | synthModel.fxDelayHighPass=dialDelayHiPass.value; 686 | } 687 | } 688 | 689 | Label { 690 | id: label14 691 | x: 651 692 | y: 317 693 | color: "#ffffff" 694 | text: qsTr("Lo") 695 | font.pixelSize: 16 696 | horizontalAlignment: Text.AlignRight 697 | } 698 | 699 | Label { 700 | id: label32 701 | x: 726 702 | y: 317 703 | width: 16 704 | height: 17 705 | color: "#ffffff" 706 | text: qsTr("Hi") 707 | font.pixelSize: 16 708 | horizontalAlignment: Text.AlignRight 709 | } 710 | 711 | Component.onCompleted: { 712 | updatePage(); 713 | } 714 | } 715 | -------------------------------------------------------------------------------- /src/AlgorithmPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | 29 | 30 | Item { 31 | id: algorithmPage 32 | width: 800 33 | height: 357 34 | 35 | Connections { 36 | target: synthModel 37 | onPatchNumberChanged: { 38 | updatePage(); 39 | } 40 | onOperatorHasChanged: { 41 | updatePage(); 42 | } 43 | } 44 | 45 | function updatePage() 46 | { 47 | checkCarrier1.checked=synthModel.isOperatorACarrier(0); 48 | checkCarrier2.checked=synthModel.isOperatorACarrier(1); 49 | checkCarrier3.checked=synthModel.isOperatorACarrier(2); 50 | checkCarrier4.checked=synthModel.isOperatorACarrier(3); 51 | checkCarrier5.checked=synthModel.isOperatorACarrier(4); 52 | checkCarrier6.checked=synthModel.isOperatorACarrier(5); 53 | 54 | checkModulate11.checked=synthModel.isOperatorModulating(0, 0); 55 | checkModulate21.checked=synthModel.isOperatorModulating(1, 0); 56 | checkModulate31.checked=synthModel.isOperatorModulating(2, 0); 57 | checkModulate41.checked=synthModel.isOperatorModulating(3, 0); 58 | checkModulate51.checked=synthModel.isOperatorModulating(4, 0); 59 | checkModulate61.checked=synthModel.isOperatorModulating(5, 0); 60 | 61 | checkModulate12.checked=synthModel.isOperatorModulating(0, 1); 62 | checkModulate22.checked=synthModel.isOperatorModulating(1, 1); 63 | checkModulate32.checked=synthModel.isOperatorModulating(2, 1); 64 | checkModulate42.checked=synthModel.isOperatorModulating(3, 1); 65 | checkModulate52.checked=synthModel.isOperatorModulating(4, 1); 66 | checkModulate62.checked=synthModel.isOperatorModulating(5, 1); 67 | 68 | checkModulate13.checked=synthModel.isOperatorModulating(0, 2); 69 | checkModulate23.checked=synthModel.isOperatorModulating(1, 2); 70 | checkModulate33.checked=synthModel.isOperatorModulating(2, 2); 71 | checkModulate43.checked=synthModel.isOperatorModulating(3, 2); 72 | checkModulate53.checked=synthModel.isOperatorModulating(4, 2); 73 | checkModulate63.checked=synthModel.isOperatorModulating(5, 2); 74 | 75 | checkModulate14.checked=synthModel.isOperatorModulating(0, 3); 76 | checkModulate24.checked=synthModel.isOperatorModulating(1, 3); 77 | checkModulate34.checked=synthModel.isOperatorModulating(2, 3); 78 | checkModulate44.checked=synthModel.isOperatorModulating(3, 3); 79 | checkModulate54.checked=synthModel.isOperatorModulating(4, 3); 80 | checkModulate64.checked=synthModel.isOperatorModulating(5, 3); 81 | 82 | checkModulate15.checked=synthModel.isOperatorModulating(0, 4); 83 | checkModulate25.checked=synthModel.isOperatorModulating(1, 4); 84 | checkModulate35.checked=synthModel.isOperatorModulating(2, 4); 85 | checkModulate45.checked=synthModel.isOperatorModulating(3, 4); 86 | checkModulate55.checked=synthModel.isOperatorModulating(4, 4); 87 | checkModulate65.checked=synthModel.isOperatorModulating(5, 4); 88 | 89 | checkModulate16.checked=synthModel.isOperatorModulating(0, 5); 90 | checkModulate26.checked=synthModel.isOperatorModulating(1, 5); 91 | checkModulate36.checked=synthModel.isOperatorModulating(2, 5); 92 | checkModulate46.checked=synthModel.isOperatorModulating(3, 5); 93 | checkModulate56.checked=synthModel.isOperatorModulating(4, 5); 94 | checkModulate66.checked=synthModel.isOperatorModulating(5, 5); 95 | } 96 | 97 | Rectangle { 98 | anchors.fill: parent 99 | color: "#505050" 100 | 101 | Grid { 102 | id: gridMatrix 103 | x: 14 104 | y: 14 105 | width: 383 106 | height: 335 107 | rows: 8 108 | columns: 7 109 | spacing: 0 110 | 111 | Label { 112 | id: heading1 113 | width: 100 114 | height: 40 115 | color: "#ffffff" 116 | text: qsTr("Operator") 117 | font.pixelSize: 16 118 | verticalAlignment: Text.AlignVCenter 119 | } 120 | Label { 121 | id: heading2 122 | width: 46 123 | height: 40 124 | color: "#ffffff" 125 | text: qsTr("1") 126 | font.pixelSize: 16 127 | verticalAlignment: Text.AlignVCenter 128 | horizontalAlignment: Text.AlignHCenter 129 | } 130 | Label { 131 | id: heading3 132 | width: 46 133 | height: 40 134 | color: "#ffffff" 135 | text: qsTr("2") 136 | font.pixelSize: 16 137 | verticalAlignment: Text.AlignVCenter 138 | horizontalAlignment: Text.AlignHCenter 139 | } 140 | Label { 141 | id: heading4 142 | width: 46 143 | height: 40 144 | color: "#ffffff" 145 | text: qsTr("3") 146 | font.pixelSize: 16 147 | verticalAlignment: Text.AlignVCenter 148 | horizontalAlignment: Text.AlignHCenter 149 | } 150 | Label { 151 | id: heading5 152 | width: 46 153 | height: 40 154 | color: "#ffffff" 155 | text: qsTr("4") 156 | font.pixelSize: 16 157 | verticalAlignment: Text.AlignVCenter 158 | horizontalAlignment: Text.AlignHCenter 159 | } 160 | Label { 161 | id: heading6 162 | width: 46 163 | height: 40 164 | color: "#ffffff" 165 | text: qsTr("5") 166 | font.pixelSize: 16 167 | verticalAlignment: Text.AlignVCenter 168 | horizontalAlignment: Text.AlignHCenter 169 | } 170 | Label { 171 | id: heading7 172 | width: 46 173 | height: 40 174 | color: "#ffffff" 175 | text: qsTr("6") 176 | font.pixelSize: 16 177 | verticalAlignment: Text.AlignVCenter 178 | horizontalAlignment: Text.AlignHCenter 179 | } 180 | Label { 181 | id: row1 182 | width: 100 183 | height: 40 184 | color: "#ffffff" 185 | text: qsTr("Carrier") 186 | font.pixelSize: 16 187 | verticalAlignment: Text.AlignVCenter 188 | } 189 | 190 | CheckBox { 191 | id: checkCarrier1 192 | text: qsTr("") 193 | display: AbstractButton.IconOnly 194 | onCheckedChanged: { 195 | synthModel.makeOperatorACarrier(0, checkCarrier1.checked); 196 | } 197 | } 198 | 199 | CheckBox { 200 | id: checkCarrier2 201 | text: qsTr("") 202 | display: AbstractButton.IconOnly 203 | onCheckedChanged: { 204 | synthModel.makeOperatorACarrier(1, checkCarrier2.checked); 205 | } 206 | } 207 | 208 | CheckBox { 209 | id: checkCarrier3 210 | text: qsTr("") 211 | display: AbstractButton.IconOnly 212 | onCheckedChanged: { 213 | synthModel.makeOperatorACarrier(2, checkCarrier3.checked); 214 | } 215 | } 216 | 217 | CheckBox { 218 | id: checkCarrier4 219 | text: qsTr("") 220 | display: AbstractButton.IconOnly 221 | onCheckedChanged: { 222 | synthModel.makeOperatorACarrier(3, checkCarrier4.checked); 223 | } 224 | } 225 | 226 | CheckBox { 227 | id: checkCarrier5 228 | text: qsTr("") 229 | display: AbstractButton.IconOnly 230 | onCheckedChanged: { 231 | synthModel.makeOperatorACarrier(4, checkCarrier5.checked); 232 | } 233 | } 234 | 235 | CheckBox { 236 | id: checkCarrier6 237 | text: qsTr("") 238 | display: AbstractButton.IconOnly 239 | onCheckedChanged: { 240 | synthModel.makeOperatorACarrier(5, checkCarrier6.checked); 241 | } 242 | } 243 | 244 | Label { 245 | id: row2 246 | width: 100 247 | height: 40 248 | color: "#ffffff" 249 | text: qsTr("Modulate 1") 250 | font.pixelSize: 16 251 | verticalAlignment: Text.AlignVCenter 252 | } 253 | 254 | CheckBox { 255 | id: checkModulate11 256 | text: qsTr("") 257 | display: AbstractButton.IconOnly 258 | onCheckedChanged: { 259 | synthModel.makeOperatorModulate(0, 0, checkModulate11.checked); 260 | } 261 | } 262 | 263 | CheckBox { 264 | id: checkModulate21 265 | text: qsTr("") 266 | display: AbstractButton.IconOnly 267 | onCheckedChanged: { 268 | synthModel.makeOperatorModulate(1, 0, checkModulate21.checked); 269 | } 270 | } 271 | 272 | CheckBox { 273 | id: checkModulate31 274 | text: qsTr("") 275 | display: AbstractButton.IconOnly 276 | onCheckedChanged: { 277 | synthModel.makeOperatorModulate(2, 0, checkModulate31.checked); 278 | } 279 | } 280 | 281 | CheckBox { 282 | id: checkModulate41 283 | text: qsTr("") 284 | display: AbstractButton.IconOnly 285 | onCheckedChanged: { 286 | synthModel.makeOperatorModulate(3, 0, checkModulate41.checked); 287 | } 288 | } 289 | 290 | CheckBox { 291 | id: checkModulate51 292 | text: qsTr("") 293 | display: AbstractButton.IconOnly 294 | onCheckedChanged: { 295 | synthModel.makeOperatorModulate(4, 0, checkModulate51.checked); 296 | } 297 | } 298 | 299 | CheckBox { 300 | id: checkModulate61 301 | text: qsTr("") 302 | display: AbstractButton.IconOnly 303 | onCheckedChanged: { 304 | synthModel.makeOperatorModulate(5, 0, checkModulate61.checked); 305 | } 306 | } 307 | 308 | Label { 309 | id: row3 310 | width: 100 311 | height: 40 312 | color: "#ffffff" 313 | text: qsTr("Modulate 2") 314 | font.pixelSize: 16 315 | verticalAlignment: Text.AlignVCenter 316 | } 317 | 318 | CheckBox { 319 | id: checkModulate12 320 | text: qsTr("") 321 | display: AbstractButton.IconOnly 322 | onCheckedChanged: { 323 | synthModel.makeOperatorModulate(0, 1, checkModulate12.checked); 324 | } 325 | } 326 | 327 | CheckBox { 328 | id: checkModulate22 329 | text: qsTr("") 330 | display: AbstractButton.IconOnly 331 | onCheckedChanged: { 332 | synthModel.makeOperatorModulate(1, 1, checkModulate22.checked); 333 | } 334 | } 335 | 336 | CheckBox { 337 | id: checkModulate32 338 | text: qsTr("") 339 | display: AbstractButton.IconOnly 340 | onCheckedChanged: { 341 | synthModel.makeOperatorModulate(2, 1, checkModulate32.checked); 342 | } 343 | } 344 | 345 | CheckBox { 346 | id: checkModulate42 347 | text: qsTr("") 348 | display: AbstractButton.IconOnly 349 | onCheckedChanged: { 350 | synthModel.makeOperatorModulate(3, 1, checkModulate42.checked); 351 | } 352 | } 353 | 354 | CheckBox { 355 | id: checkModulate52 356 | text: qsTr("") 357 | display: AbstractButton.IconOnly 358 | onCheckedChanged: { 359 | synthModel.makeOperatorModulate(4, 1, checkModulate52.checked); 360 | } 361 | } 362 | 363 | CheckBox { 364 | id: checkModulate62 365 | text: qsTr("") 366 | display: AbstractButton.IconOnly 367 | onCheckedChanged: { 368 | synthModel.makeOperatorModulate(5, 1, checkModulate62.checked); 369 | } 370 | } 371 | 372 | Label { 373 | id: row4 374 | width: 100 375 | height: 40 376 | color: "#ffffff" 377 | text: qsTr("Modulate 3") 378 | font.pixelSize: 16 379 | verticalAlignment: Text.AlignVCenter 380 | } 381 | 382 | CheckBox { 383 | id: checkModulate13 384 | text: qsTr("") 385 | display: AbstractButton.IconOnly 386 | onCheckedChanged: { 387 | synthModel.makeOperatorModulate(0, 2, checkModulate13.checked); 388 | } 389 | } 390 | 391 | CheckBox { 392 | id: checkModulate23 393 | text: qsTr("") 394 | display: AbstractButton.IconOnly 395 | onCheckedChanged: { 396 | synthModel.makeOperatorModulate(1, 2, checkModulate23.checked); 397 | } 398 | } 399 | 400 | CheckBox { 401 | id: checkModulate33 402 | text: qsTr("") 403 | display: AbstractButton.IconOnly 404 | onCheckedChanged: { 405 | synthModel.makeOperatorModulate(2, 2, checkModulate33.checked); 406 | } 407 | } 408 | 409 | CheckBox { 410 | id: checkModulate43 411 | text: qsTr("") 412 | display: AbstractButton.IconOnly 413 | onCheckedChanged: { 414 | synthModel.makeOperatorModulate(3, 2, checkModulate43.checked); 415 | } 416 | } 417 | 418 | CheckBox { 419 | id: checkModulate53 420 | text: qsTr("") 421 | display: AbstractButton.IconOnly 422 | onCheckedChanged: { 423 | synthModel.makeOperatorModulate(4, 2, checkModulate53.checked); 424 | } 425 | } 426 | 427 | CheckBox { 428 | id: checkModulate63 429 | text: qsTr("") 430 | display: AbstractButton.IconOnly 431 | onCheckedChanged: { 432 | synthModel.makeOperatorModulate(5, 2, checkModulate63.checked); 433 | } 434 | } 435 | 436 | Label { 437 | id: row5 438 | width: 100 439 | height: 40 440 | color: "#ffffff" 441 | text: qsTr("Modulate 4") 442 | font.pixelSize: 16 443 | verticalAlignment: Text.AlignVCenter 444 | } 445 | 446 | CheckBox { 447 | id: checkModulate14 448 | text: qsTr("") 449 | display: AbstractButton.IconOnly 450 | onCheckedChanged: { 451 | synthModel.makeOperatorModulate(0, 3, checkModulate14.checked); 452 | } 453 | } 454 | 455 | CheckBox { 456 | id: checkModulate24 457 | text: qsTr("") 458 | display: AbstractButton.IconOnly 459 | onCheckedChanged: { 460 | synthModel.makeOperatorModulate(1, 3, checkModulate24.checked); 461 | } 462 | } 463 | 464 | CheckBox { 465 | id: checkModulate34 466 | text: qsTr("") 467 | display: AbstractButton.IconOnly 468 | onCheckedChanged: { 469 | synthModel.makeOperatorModulate(2, 3, checkModulate34.checked); 470 | } 471 | } 472 | 473 | CheckBox { 474 | id: checkModulate44 475 | text: qsTr("") 476 | display: AbstractButton.IconOnly 477 | onCheckedChanged: { 478 | synthModel.makeOperatorModulate(3, 3, checkModulate44.checked); 479 | } 480 | } 481 | 482 | CheckBox { 483 | id: checkModulate54 484 | text: qsTr("") 485 | display: AbstractButton.IconOnly 486 | onCheckedChanged: { 487 | synthModel.makeOperatorModulate(4, 3, checkModulate54.checked); 488 | } 489 | } 490 | 491 | CheckBox { 492 | id: checkModulate64 493 | text: qsTr("") 494 | display: AbstractButton.IconOnly 495 | onCheckedChanged: { 496 | synthModel.makeOperatorModulate(5, 3, checkModulate64.checked); 497 | } 498 | } 499 | 500 | Label { 501 | id: row6 502 | width: 100 503 | height: 40 504 | color: "#ffffff" 505 | text: qsTr("Modulate 5") 506 | font.pixelSize: 16 507 | verticalAlignment: Text.AlignVCenter 508 | } 509 | 510 | CheckBox { 511 | id: checkModulate15 512 | text: qsTr("") 513 | display: AbstractButton.IconOnly 514 | onCheckedChanged: { 515 | synthModel.makeOperatorModulate(0, 4, checkModulate15.checked); 516 | } 517 | } 518 | 519 | CheckBox { 520 | id: checkModulate25 521 | text: qsTr("") 522 | display: AbstractButton.IconOnly 523 | onCheckedChanged: { 524 | synthModel.makeOperatorModulate(1, 4, checkModulate25.checked); 525 | } 526 | } 527 | 528 | CheckBox { 529 | id: checkModulate35 530 | text: qsTr("") 531 | display: AbstractButton.IconOnly 532 | onCheckedChanged: { 533 | synthModel.makeOperatorModulate(2, 4, checkModulate35.checked); 534 | } 535 | } 536 | 537 | CheckBox { 538 | id: checkModulate45 539 | text: qsTr("") 540 | display: AbstractButton.IconOnly 541 | onCheckedChanged: { 542 | synthModel.makeOperatorModulate(3, 4, checkModulate45.checked); 543 | } 544 | } 545 | 546 | CheckBox { 547 | id: checkModulate55 548 | text: qsTr("") 549 | display: AbstractButton.IconOnly 550 | onCheckedChanged: { 551 | synthModel.makeOperatorModulate(4, 4, checkModulate55.checked); 552 | } 553 | } 554 | 555 | CheckBox { 556 | id: checkModulate65 557 | text: qsTr("") 558 | display: AbstractButton.IconOnly 559 | onCheckedChanged: { 560 | synthModel.makeOperatorModulate(5, 4, checkModulate65.checked); 561 | } 562 | } 563 | 564 | Label { 565 | id: row7 566 | width: 100 567 | height: 40 568 | color: "#ffffff" 569 | text: qsTr("Modulate 6") 570 | font.pixelSize: 16 571 | verticalAlignment: Text.AlignVCenter 572 | } 573 | 574 | CheckBox { 575 | id: checkModulate16 576 | text: qsTr("") 577 | display: AbstractButton.IconOnly 578 | onCheckedChanged: { 579 | synthModel.makeOperatorModulate(0, 5, checkModulate16.checked); 580 | } 581 | } 582 | 583 | CheckBox { 584 | id: checkModulate26 585 | text: qsTr("") 586 | display: AbstractButton.IconOnly 587 | onCheckedChanged: { 588 | synthModel.makeOperatorModulate(1, 5, checkModulate26.checked); 589 | } 590 | } 591 | 592 | CheckBox { 593 | id: checkModulate36 594 | text: qsTr("") 595 | display: AbstractButton.IconOnly 596 | onCheckedChanged: { 597 | synthModel.makeOperatorModulate(2, 5, checkModulate36.checked); 598 | } 599 | } 600 | 601 | CheckBox { 602 | id: checkModulate46 603 | text: qsTr("") 604 | display: AbstractButton.IconOnly 605 | onCheckedChanged: { 606 | synthModel.makeOperatorModulate(3, 5, checkModulate46.checked); 607 | } 608 | } 609 | 610 | CheckBox { 611 | id: checkModulate56 612 | text: qsTr("") 613 | display: AbstractButton.IconOnly 614 | onCheckedChanged: { 615 | synthModel.makeOperatorModulate(4, 5, checkModulate56.checked); 616 | } 617 | } 618 | 619 | CheckBox { 620 | id: checkModulate66 621 | text: qsTr("") 622 | display: AbstractButton.IconOnly 623 | onCheckedChanged: { 624 | synthModel.makeOperatorModulate(5, 5, checkModulate66.checked); 625 | } 626 | } 627 | } 628 | } 629 | 630 | Button { 631 | id: buttonDX7Algorithm 632 | x: 728 633 | y: 19 634 | width: 59 635 | height: 40 636 | text: qsTr("SET") 637 | 638 | onClicked: { 639 | synthModel.makeDX7Algorithm(spinDX7Algorithm.value-1); 640 | } 641 | } 642 | 643 | SpinBox { 644 | id: spinDX7Algorithm 645 | x: 573 646 | y: 19 647 | to: 32 648 | from: 1 649 | value: 1 650 | } 651 | 652 | Label { 653 | id: label 654 | x: 416 655 | y: 26 656 | color: "#ffb060" 657 | text: qsTr("DX7 Algorithm") 658 | font.pixelSize: 22 659 | } 660 | 661 | Component.onCompleted: { 662 | updatePage(); 663 | } 664 | 665 | } 666 | 667 | 668 | /*##^## 669 | Designer { 670 | D{i:1;anchors_height:400;anchors_x:0;anchors_y:0}D{i:11;anchors_width:100}D{i:18;anchors_width:100} 671 | D{i:25;anchors_width:100}D{i:32;anchors_width:100}D{i:39;anchors_width:100}D{i:46;anchors_width:100} 672 | D{i:53;anchors_width:100} 673 | } 674 | ##^##*/ 675 | -------------------------------------------------------------------------------- /src/SynthModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | #ifndef SYNTHMODEL_H 25 | #define SYNTHMODEL_H 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "xfm2.h" 32 | #include "xfmoperator.h" 33 | #include 34 | #include 35 | 36 | /* 37 | * This is a model for controlling the synth. It works by using a memory buffer to hold 38 | * a copy of the synth parameters so they may be queried without having to access the serial 39 | * port. This reduces latency. 40 | * 41 | * It's not the most efficient interface. Ideally it would have made sense to create 42 | * smaller classes for some of the function blocks, e.g. I could have made a PitchEnvelope class 43 | * instead of a bunch of pitchEG properties. But in the end I went for simplicity over 44 | * a complicated class design, just to keep things easy to maintain should additional 45 | * parameters be added to the synth itself. I did use a separate class for the Operators 46 | * in order to reduce the number of duplicate properties and methods. 47 | * 48 | */ 49 | 50 | class SynthModel : public QObject { 51 | Q_OBJECT 52 | 53 | // Global info 54 | Q_PROPERTY(bool isConnected READ isConnected) 55 | Q_PROPERTY(int patchNumber READ patchNumber WRITE setPatchNumber NOTIFY patchNumberChanged) 56 | Q_PROPERTY(QString patchName READ patchName WRITE setPatchName NOTIFY patchNameChanged) 57 | 58 | // Common 59 | Q_PROPERTY(int masterPitchBendUp READ masterPitchBendUp WRITE setMasterPitchBendUp NOTIFY masterPitchBendUpChanged) 60 | Q_PROPERTY(int masterPitchBendDown READ masterPitchBendDown WRITE setMasterPitchBendDown NOTIFY masterPitchBendDownChanged) 61 | Q_PROPERTY(int masterTranspose READ masterTranspose WRITE setMasterTranspose NOTIFY masterTransposeChanged) 62 | Q_PROPERTY(int masterVolume READ masterVolume WRITE setMasterVolume NOTIFY masterVolumeChanged) 63 | Q_PROPERTY(int outputLevel READ outputLevel WRITE setOutputLevel NOTIFY outputLevelChanged) 64 | Q_PROPERTY(int masterPan READ masterPan WRITE setMasterPan NOTIFY masterPanChanged) 65 | Q_PROPERTY(int masterLegato READ masterLegato WRITE setMasterLegato NOTIFY masterLegatoChanged) 66 | Q_PROPERTY(int masterTuning READ masterTuning WRITE setMasterTuning NOTIFY masterTuningChanged) 67 | Q_PROPERTY(int masterVelocityOffset READ masterVelocityOffset WRITE setMasterVelocityOffset NOTIFY masterVelocityOffsetChanged) 68 | 69 | // Operators 70 | Q_PROPERTY(QList fmOperators READ fmOperators) 71 | Q_PROPERTY(int operatorSync READ operatorSync WRITE setOperatorSync NOTIFY operatorSyncChanged) 72 | Q_PROPERTY(int operatorMode READ operatorMode WRITE setOperatorMode NOTIFY operatorModeChanged) 73 | Q_PROPERTY(int envelopeLoop READ envelopeLoop WRITE setEnvelopeLoop NOTIFY envelopeLoopChanged) 74 | Q_PROPERTY(int envelopeLoopSegment READ envelopeLoopSegment WRITE setEnvelopeLoopSegment NOTIFY envelopeLoopSegmentChanged) 75 | Q_PROPERTY(int envelopeRestart READ envelopeRestart WRITE setEnvelopeRestart NOTIFY envelopeRestartChanged) 76 | 77 | // Pitch EG 78 | Q_PROPERTY(int pitchEG_L0 READ pitchEG_L0 WRITE setPitchEG_L0 NOTIFY pitchEG_L0Changed) 79 | Q_PROPERTY(int pitchEG_L1 READ pitchEG_L1 WRITE setPitchEG_L1 NOTIFY pitchEG_L1Changed) 80 | Q_PROPERTY(int pitchEG_L2 READ pitchEG_L2 WRITE setPitchEG_L2 NOTIFY pitchEG_L2Changed) 81 | Q_PROPERTY(int pitchEG_L3 READ pitchEG_L3 WRITE setPitchEG_L3 NOTIFY pitchEG_L3Changed) 82 | Q_PROPERTY(int pitchEG_L4 READ pitchEG_L4 WRITE setPitchEG_L4 NOTIFY pitchEG_L4Changed) 83 | Q_PROPERTY(int pitchEG_L5 READ pitchEG_L5 WRITE setPitchEG_L5 NOTIFY pitchEG_L5Changed) 84 | Q_PROPERTY(int pitchEG_R0 READ pitchEG_R0 WRITE setPitchEG_R0 NOTIFY pitchEG_R0Changed) 85 | Q_PROPERTY(int pitchEG_R1 READ pitchEG_R1 WRITE setPitchEG_R1 NOTIFY pitchEG_R1Changed) 86 | Q_PROPERTY(int pitchEG_R2 READ pitchEG_R2 WRITE setPitchEG_R2 NOTIFY pitchEG_R2Changed) 87 | Q_PROPERTY(int pitchEG_R3 READ pitchEG_R3 WRITE setPitchEG_R3 NOTIFY pitchEG_R3Changed) 88 | Q_PROPERTY(int pitchEG_R4 READ pitchEG_R4 WRITE setPitchEG_R4 NOTIFY pitchEG_R4Changed) 89 | Q_PROPERTY(int pitchEG_R5 READ pitchEG_R5 WRITE setPitchEG_R5 NOTIFY pitchEG_R5Changed) 90 | Q_PROPERTY(int pitchEG_Range READ pitchEG_Range WRITE setPitchEG_Range NOTIFY pitchEG_RangeChanged) 91 | Q_PROPERTY(int pitchEG_Velocity READ pitchEG_Velocity WRITE setPitchEG_Velocity NOTIFY pitchEG_VelocityChanged) 92 | Q_PROPERTY(int pitchEG_RateKey READ pitchEG_RateKey WRITE setPitchEG_RateKey NOTIFY pitchEG_RateKeyChanged) 93 | 94 | // LFO 95 | Q_PROPERTY(int lfoDepthPitch READ lfoDepthPitch WRITE setLFODepthPitch NOTIFY lfoDepthPitchChanged) 96 | Q_PROPERTY(int lfoDepthAmplitude READ lfoDepthAmplitude WRITE setLFODepthAmplitude NOTIFY lfoDepthAmplitudeChanged) 97 | Q_PROPERTY(int lfoSpeed READ lfoSpeed WRITE setLFOSpeed NOTIFY lfoSpeedChanged) 98 | Q_PROPERTY(int lfoSync READ lfoSync WRITE setLFOSync NOTIFY lfoSyncChanged) 99 | Q_PROPERTY(int lfoWave READ lfoWave WRITE setLFOWave NOTIFY lfoWaveChanged) 100 | Q_PROPERTY(int lfoFade READ lfoFade WRITE setLFOFade NOTIFY lfoFadeChanged) 101 | 102 | // Modulation 103 | Q_PROPERTY(int modPitchLFOWheel READ modPitchLFOWheel WRITE setModPitchLFOWheel NOTIFY modPitchLFOWheelChanged) 104 | Q_PROPERTY(int modPitchLFOFoot READ modPitchLFOFoot WRITE setModPitchLFOFoot NOTIFY modPitchLFOFootChanged) 105 | Q_PROPERTY(int modAmpLFOAftertouch READ modAmpLFOAftertouch WRITE setModAmpLFOAftertouch NOTIFY modAmpLFOAftertouchChanged) 106 | Q_PROPERTY(int modAmpLFOWheel READ modAmpLFOWheel WRITE setModAmpLFOWheel NOTIFY modAmpLFOWheelChanged) 107 | Q_PROPERTY(int modAmpLFOBreath READ modAmpLFOBreath WRITE setModAmpLFOBreath NOTIFY modAmpLFOBreathChanged) 108 | Q_PROPERTY(int modAmpLFOFoot READ modAmpLFOFoot WRITE setModAmpLFOFoot NOTIFY modAmpLFOFootChanged) 109 | Q_PROPERTY(int modPitchLFOAftertouch READ modPitchLFOAftertouch WRITE setModPitchLFOAftertouch NOTIFY modPitchLFOAftertouchChanged) 110 | Q_PROPERTY(int modPitchLFOBreath READ modPitchLFOBreath WRITE setModPitchLFOBreath NOTIFY modPitchLFOBreathChanged) 111 | Q_PROPERTY(int modEnvelopeBiasAftertouch READ modEnvelopeBiasAftertouch WRITE setModEnvelopeBiasAftertouch NOTIFY modEnvelopeBiasAftertouchChanged) 112 | Q_PROPERTY(int modEnvelopeBiasWheel READ modEnvelopeBiasWheel WRITE setModEnvelopeBiasWheel NOTIFY modEnvelopeBiasWheelChanged) 113 | Q_PROPERTY(int modEnvelopeBiasBreath READ modEnvelopeBiasBreath WRITE setModEnvelopeBiasBreath NOTIFY modEnvelopeBiasBreathChanged) 114 | Q_PROPERTY(int modEnvelopeBiasFoot READ modEnvelopeBiasFoot WRITE setModEnvelopeBiasFoot NOTIFY modEnvelopeBiasFootChanged) 115 | Q_PROPERTY(int modPitchAftertouch READ modPitchAftertouch WRITE setModPitchAftertouch NOTIFY modPitchAftertouchChanged) 116 | Q_PROPERTY(int modPitchBreath READ modPitchBreath WRITE setModPitchBreath NOTIFY modPitchBreathChanged) 117 | Q_PROPERTY(int modPitchFoot READ modPitchFoot WRITE setModPitchFoot NOTIFY modPitchFootChanged) 118 | Q_PROPERTY(int modPitchRandom READ modPitchRandom WRITE setModPitchRandom NOTIFY modPitchRandomChanged) 119 | 120 | // Portamento 121 | Q_PROPERTY(int portamentoMode READ portamentoMode WRITE setPortamentoMode NOTIFY portamentoModeChanged) 122 | Q_PROPERTY(int portamentoTime READ portamentoTime WRITE setPortamentoTime NOTIFY portamentoTimeChanged) 123 | 124 | // Arpeggiator 125 | Q_PROPERTY(int arpeggiatorMode READ arpeggiatorMode WRITE setArpeggiatorMode NOTIFY arpeggiatorModeChanged) 126 | Q_PROPERTY(int arpeggiatorTempo READ arpeggiatorTempo WRITE setArpeggiatorTempo NOTIFY arpeggiatorTempoChanged) 127 | Q_PROPERTY(int arpeggiatorTempoMultiplier READ arpeggiatorTempoMultiplier WRITE setArpeggiatorTempoMultiplier NOTIFY arpeggiatorTempoMultiplierChanged) 128 | Q_PROPERTY(int arpeggiatorOctaveRange READ arpeggiatorOctaveRange WRITE setArpeggiatorOctaveRange NOTIFY arpeggiatorOctaveRangeChanged) 129 | 130 | // Filter 131 | Q_PROPERTY(int filterLoCutoff READ filterLoCutoff WRITE setFilterLoCutoff NOTIFY filterLoCutoffChanged) 132 | Q_PROPERTY(int filterHiCutoff READ filterHiCutoff WRITE setFilterHiCutoff NOTIFY filterHiCutoffChanged) 133 | 134 | // FX: Bit crush and decimator 135 | Q_PROPERTY(int fxBitCrushDepth READ fxBitCrushDepth WRITE setFxBitCrushDepth NOTIFY fxBitCrushDepthChanged) 136 | Q_PROPERTY(int fxDecimator READ fxDecimator WRITE setFxDecimator NOTIFY fxDecimatorChanged) 137 | 138 | // FX: Chorus 139 | Q_PROPERTY(int fxChorusDry READ fxChorusDry WRITE setFxChorusDry NOTIFY fxChorusDryChanged) 140 | Q_PROPERTY(int fxChorusWet READ fxChorusWet WRITE setFxChorusWet NOTIFY fxChorusWetChanged) 141 | Q_PROPERTY(int fxChorusMode READ fxChorusMode WRITE setFxChorusMode NOTIFY fxChorusModeChanged) 142 | Q_PROPERTY(int fxChorusSpeed READ fxChorusSpeed WRITE setFxChorusSpeed NOTIFY fxChorusSpeedChanged) 143 | Q_PROPERTY(int fxChorusDepth READ fxChorusDepth WRITE setFxChorusDepth NOTIFY fxChorusDepthChanged) 144 | Q_PROPERTY(int fxChorusFeedback READ fxChorusFeedback WRITE setFxChorusFeedback NOTIFY fxChorusFeedbackChanged) 145 | Q_PROPERTY(int fxChorusPhase READ fxChorusPhase WRITE setFxChorusPhase NOTIFY fxChorusPhaseChanged) 146 | 147 | // FX: Phaser 148 | Q_PROPERTY(int fxPhaserDry READ fxPhaserDry WRITE setFxPhaserDry NOTIFY fxPhaserDryChanged) 149 | Q_PROPERTY(int fxPhaserWet READ fxPhaserWet WRITE setFxPhaserWet NOTIFY fxPhaserWetChanged) 150 | Q_PROPERTY(int fxPhaserMode READ fxPhaserMode WRITE setFxPhaserMode NOTIFY fxPhaserModeChanged) 151 | Q_PROPERTY(int fxPhaserSpeed READ fxPhaserSpeed WRITE setFxPhaserSpeed NOTIFY fxPhaserSpeedChanged) 152 | Q_PROPERTY(int fxPhaserDepth READ fxPhaserDepth WRITE setFxPhaserDepth NOTIFY fxPhaserDepthChanged) 153 | Q_PROPERTY(int fxPhaserFeedback READ fxPhaserFeedback WRITE setFxPhaserFeedback NOTIFY fxPhaserFeedbackChanged) 154 | Q_PROPERTY(int fxPhaserPhase READ fxPhaserPhase WRITE setFxPhaserPhase NOTIFY fxPhaserPhaseChanged) 155 | Q_PROPERTY(int fxPhaserOffset READ fxPhaserOffset WRITE setFxPhaserOffset NOTIFY fxPhaserOffsetChanged) 156 | Q_PROPERTY(int fxPhaserStages READ fxPhaserStages WRITE setFxPhaserStages NOTIFY fxPhaserStagesChanged) 157 | 158 | // FX: Delay 159 | Q_PROPERTY(int fxDelayDry READ fxDelayDry WRITE setFxDelayDry NOTIFY fxDelayDryChanged) 160 | Q_PROPERTY(int fxDelayWet READ fxDelayWet WRITE setFxDelayWet NOTIFY fxDelayWetChanged) 161 | Q_PROPERTY(int fxDelayMode READ fxDelayMode WRITE setFxDelayMode NOTIFY fxDelayModeChanged) 162 | Q_PROPERTY(int fxDelayTime READ fxDelayTime WRITE setFxDelayTime NOTIFY fxDelayTimeChanged) 163 | Q_PROPERTY(int fxDelayFeedback READ fxDelayFeedback WRITE setFxDelayFeedback NOTIFY fxDelayFeedbackChanged) 164 | Q_PROPERTY(int fxDelayLowPass READ fxDelayLowPass WRITE setFxDelayLowPass NOTIFY fxDelayLowPassChanged) 165 | Q_PROPERTY(int fxDelayHighPass READ fxDelayHighPass WRITE setFxDelayHighPass NOTIFY fxDelayHighPassChanged) 166 | Q_PROPERTY(int fxDelayTempo READ fxDelayTempo WRITE setFxDelayTempo NOTIFY fxDelayTempoChanged) 167 | Q_PROPERTY(int fxDelayMultiplier READ fxDelayMultiplier WRITE setFxDelayMultiplier NOTIFY fxDelayMultiplierChanged) 168 | Q_PROPERTY(int fxDelayDivider READ fxDelayDivider WRITE setFxDelayDivider NOTIFY fxDelayDividerChanged) 169 | 170 | // FX: Amplitude modulator 171 | Q_PROPERTY(int fxAMDepth READ fxAMDepth WRITE setFxAMDepth NOTIFY fxAMDepthChanged) 172 | Q_PROPERTY(int fxAMSpeed READ fxAMSpeed WRITE setFxAMSpeed NOTIFY fxAMSpeedChanged) 173 | Q_PROPERTY(int fxAMRange READ fxAMRange WRITE setFxAMRange NOTIFY fxAMRangeChanged) 174 | Q_PROPERTY(int fxAMPhase READ fxAMPhase WRITE setFxAMPhase NOTIFY fxAMPhaseChanged) 175 | 176 | // FX: Reverb 177 | Q_PROPERTY(int fxReverbDry READ fxReverbDry WRITE setFxReverbDry NOTIFY fxReverbDryChanged) 178 | Q_PROPERTY(int fxReverbWet READ fxReverbWet WRITE setFxReverbWet NOTIFY fxReverbWetChanged) 179 | Q_PROPERTY(int fxReverbDecay READ fxReverbDecay WRITE setFxReverbDecay NOTIFY fxReverbDecayChanged) 180 | Q_PROPERTY(int fxReverbDamp READ fxReverbDamp WRITE setFxReverbDamp NOTIFY fxReverbDampChanged) 181 | Q_PROPERTY(int fxReverbMode READ fxReverbMode WRITE setFxReverbMode NOTIFY fxReverbModeChanged) 182 | 183 | // FX Routing 184 | Q_PROPERTY(int fxRoute READ fxRoute WRITE setFxRoute NOTIFY fxRouteChanged) 185 | 186 | 187 | public: 188 | explicit SynthModel(QObject *parent = nullptr); 189 | 190 | // Helper functions 191 | 192 | // Read and write the patch buffer. When writing, an optional parameter allows the current 193 | // patch to be saved as a new patch 194 | Q_INVOKABLE bool readPatchBuffer(); 195 | Q_INVOKABLE bool writePatchBuffer(int toPatch=-1); 196 | 197 | // Initialise the patch buffer 198 | Q_INVOKABLE bool initPatchBuffer(); 199 | 200 | // Restore the current patch (revert to saved version) 201 | Q_INVOKABLE bool reloadPatch(); 202 | 203 | // Send operator changes to the synth and update the memory buffer 204 | Q_INVOKABLE bool updateOperator(XFMOperator *op, bool notify=false); 205 | 206 | // Operator algorithm design: 207 | // XFM2 allows any operator to be a carrier, and any operator can 208 | // also modulate any other operator 209 | Q_INVOKABLE bool isOperatorACarrier(int op); 210 | Q_INVOKABLE bool makeOperatorACarrier(int op, bool enabled); 211 | Q_INVOKABLE bool isOperatorModulating(int op, int modulatingop); 212 | Q_INVOKABLE bool makeOperatorModulate(int op, int modulatingop, bool enabled); 213 | 214 | // Similate a DX7 algorithm by configuring the operators 215 | // in the same way as the DX7 would, based on info at https://www.futur3soundz.com/da-blog/dx7-algorithms-in-xfm2 216 | Q_INVOKABLE bool makeDX7Algorithm(int a); 217 | 218 | 219 | signals: 220 | void patchNumberChanged(); 221 | void operatorSyncChanged(); 222 | void operatorModeChanged(); 223 | void pitchEG_L0Changed(); 224 | void pitchEG_L1Changed(); 225 | void pitchEG_L2Changed(); 226 | void pitchEG_L3Changed(); 227 | void pitchEG_L4Changed(); 228 | void pitchEG_L5Changed(); 229 | void pitchEG_R0Changed(); 230 | void pitchEG_R1Changed(); 231 | void pitchEG_R2Changed(); 232 | void pitchEG_R3Changed(); 233 | void pitchEG_R4Changed(); 234 | void pitchEG_R5Changed(); 235 | void pitchEG_RangeChanged(); 236 | void pitchEG_VelocityChanged(); 237 | void pitchEG_RateKeyChanged(); 238 | void lfoDepthPitchChanged(); 239 | void lfoDepthAmplitudeChanged(); 240 | void lfoSpeedChanged(); 241 | void lfoSyncChanged(); 242 | void lfoWaveChanged(); 243 | void lfoFadeChanged(); 244 | void modPitchLFOWheelChanged(); 245 | void modPitchLFOBreathChanged(); 246 | void modPitchLFOFootChanged(); 247 | void modAmpLFOAftertouchChanged(); 248 | void modAmpLFOWheelChanged(); 249 | void modAmpLFOBreathChanged(); 250 | void modAmpLFOFootChanged(); 251 | void modPitchLFOAftertouchChanged(); 252 | void masterPitchBendUpChanged(); 253 | void masterPitchBendDownChanged(); 254 | void masterTransposeChanged(); 255 | void masterVolumeChanged(); 256 | void masterPanChanged(); 257 | void masterLegatoChanged(); 258 | void modEnvelopeBiasAftertouchChanged(); 259 | void modEnvelopeBiasWheelChanged(); 260 | void modEnvelopeBiasBreathChanged(); 261 | void modEnvelopeBiasFootChanged(); 262 | void modPitchAftertouchChanged(); 263 | void modPitchBreathChanged(); 264 | void modPitchFootChanged(); 265 | void modPitchRandomChanged(); 266 | void portamentoModeChanged(); 267 | void portamentoTimeChanged(); 268 | void masterVelocityOffsetChanged(); 269 | void envelopeLoopChanged(); 270 | void envelopeLoopSegmentChanged(); 271 | void envelopeRestartChanged(); 272 | void masterTuningChanged(); 273 | void operatorHasChanged(); 274 | void outputLevelChanged(); 275 | void arpeggiatorModeChanged(); 276 | void arpeggiatorTempoChanged(); 277 | void arpeggiatorTempoMultiplierChanged(); 278 | void arpeggiatorOctaveRangeChanged(); 279 | void fxBitCrushDepthChanged(); 280 | void fxDecimatorChanged(); 281 | void filterLoCutoffChanged(); 282 | void filterHiCutoffChanged(); 283 | void fxChorusDryChanged(); 284 | void fxChorusWetChanged(); 285 | void fxChorusModeChanged(); 286 | void fxChorusSpeedChanged(); 287 | void fxChorusDepthChanged(); 288 | void fxChorusFeedbackChanged(); 289 | void fxChorusPhaseChanged(); 290 | void fxPhaserDryChanged(); 291 | void fxPhaserWetChanged(); 292 | void fxPhaserModeChanged(); 293 | void fxPhaserSpeedChanged(); 294 | void fxPhaserDepthChanged(); 295 | void fxPhaserOffsetChanged(); 296 | void fxPhaserStagesChanged(); 297 | void fxPhaserFeedbackChanged(); 298 | void fxPhaserPhaseChanged(); 299 | void fxDelayDryChanged(); 300 | void fxDelayWetChanged(); 301 | void fxDelayModeChanged(); 302 | void fxDelayTimeChanged(); 303 | void fxDelayFeedbackChanged(); 304 | void fxDelayLowPassChanged(); 305 | void fxDelayHighPassChanged(); 306 | void fxDelayTempoChanged(); 307 | void fxDelayMultiplierChanged(); 308 | void fxDelayDividerChanged(); 309 | void fxAMDepthChanged(); 310 | void fxAMSpeedChanged(); 311 | void fxAMRangeChanged(); 312 | void fxAMPhaseChanged(); 313 | void fxReverbDryChanged(); 314 | void fxReverbWetChanged(); 315 | void fxReverbDecayChanged(); 316 | void fxReverbDampChanged(); 317 | void fxReverbModeChanged(); 318 | void fxRouteChanged(); 319 | void patchNameChanged(); 320 | 321 | protected: 322 | bool isConnected() const; 323 | 324 | int patchNumber() const; 325 | void setPatchNumber(int p); 326 | 327 | int operatorSync(); 328 | void setOperatorSync(int v); 329 | 330 | int operatorMode(); 331 | void setOperatorMode(int v); 332 | 333 | int pitchEG_L0(); 334 | void setPitchEG_L0(int f); 335 | 336 | int pitchEG_L1(); 337 | void setPitchEG_L1(int f); 338 | 339 | int pitchEG_L2(); 340 | void setPitchEG_L2(int f); 341 | 342 | int pitchEG_L3(); 343 | void setPitchEG_L3(int f); 344 | 345 | int pitchEG_L4(); 346 | void setPitchEG_L4(int f); 347 | 348 | int pitchEG_L5(); 349 | void setPitchEG_L5(int f); 350 | 351 | int pitchEG_R0(); 352 | void setPitchEG_R0(int f); 353 | 354 | int pitchEG_R1(); 355 | void setPitchEG_R1(int f); 356 | 357 | int pitchEG_R2(); 358 | void setPitchEG_R2(int f); 359 | 360 | int pitchEG_R3(); 361 | void setPitchEG_R3(int f); 362 | 363 | int pitchEG_R4(); 364 | void setPitchEG_R4(int f); 365 | 366 | int pitchEG_R5(); 367 | void setPitchEG_R5(int f); 368 | 369 | int pitchEG_Range(); 370 | void setPitchEG_Range(int f); 371 | 372 | int pitchEG_Velocity(); 373 | void setPitchEG_Velocity(int f); 374 | 375 | int pitchEG_RateKey(); 376 | void setPitchEG_RateKey(int f); 377 | 378 | int lfoDepthPitch(); 379 | void setLFODepthPitch(int f); 380 | 381 | int lfoDepthAmplitude(); 382 | void setLFODepthAmplitude(int v); 383 | 384 | int lfoSpeed(); 385 | void setLFOSpeed(int v); 386 | 387 | int lfoSync(); 388 | void setLFOSync(int v); 389 | 390 | int lfoWave(); 391 | void setLFOWave(int v); 392 | 393 | int lfoFade(); 394 | void setLFOFade(int v); 395 | 396 | int modPitchLFOWheel(); 397 | void setModPitchLFOWheel(int v); 398 | 399 | int modAmpLFOAftertouch(); 400 | void setModAmpLFOAftertouch(int v); 401 | 402 | int modPitchLFOBreath(); 403 | void setModPitchLFOBreath(int v); 404 | 405 | int modPitchLFOFoot(); 406 | void setModPitchLFOFoot(int v); 407 | 408 | int modAmpLFOWheel(); 409 | void setModAmpLFOWheel(int v); 410 | 411 | int modAmpLFOBreath(); 412 | void setModAmpLFOBreath(int v); 413 | 414 | int modAmpLFOFoot(); 415 | void setModAmpLFOFoot(int v); 416 | 417 | int modPitchLFOAftertouch(); 418 | void setModPitchLFOAftertouch(int v); 419 | 420 | int masterPitchBendUp(); 421 | void setMasterPitchBendUp(int v); 422 | 423 | int masterPitchBendDown(); 424 | void setMasterPitchBendDown(int v); 425 | 426 | int masterTranspose(); 427 | void setMasterTranspose(int v); 428 | 429 | int masterVolume(); 430 | void setMasterVolume(int v); 431 | 432 | int masterPan(); 433 | void setMasterPan(int v); 434 | 435 | int masterLegato(); 436 | void setMasterLegato(int v); 437 | 438 | int masterVelocityOffset(); 439 | void setMasterVelocityOffset(int v); 440 | 441 | int masterTuning(); 442 | void setMasterTuning(int v); 443 | 444 | int modEnvelopeBiasAftertouch(); 445 | void setModEnvelopeBiasAftertouch(int v); 446 | 447 | int modEnvelopeBiasWheel(); 448 | void setModEnvelopeBiasWheel(int v); 449 | 450 | int modEnvelopeBiasBreath(); 451 | void setModEnvelopeBiasBreath(int v); 452 | 453 | int modEnvelopeBiasFoot(); 454 | void setModEnvelopeBiasFoot(int v); 455 | 456 | int modPitchAftertouch(); 457 | void setModPitchAftertouch(int v); 458 | 459 | int modPitchBreath(); 460 | void setModPitchBreath(int v); 461 | 462 | int modPitchFoot(); 463 | void setModPitchFoot(int v); 464 | 465 | int modPitchRandom(); 466 | void setModPitchRandom(int v); 467 | 468 | int portamentoMode(); 469 | void setPortamentoMode(int v); 470 | 471 | int portamentoTime(); 472 | void setPortamentoTime(int v); 473 | 474 | int envelopeLoop(); 475 | void setEnvelopeLoop(int v); 476 | 477 | int envelopeLoopSegment(); 478 | void setEnvelopeLoopSegment(int v); 479 | 480 | int envelopeRestart(); 481 | void setEnvelopeRestart(int v); 482 | 483 | int outputLevel(); 484 | void setOutputLevel(int v); 485 | 486 | int arpeggiatorMode(); 487 | void setArpeggiatorMode(int v); 488 | 489 | int arpeggiatorTempo(); 490 | void setArpeggiatorTempo(int v); 491 | 492 | int arpeggiatorTempoMultiplier(); 493 | void setArpeggiatorTempoMultiplier(int v); 494 | 495 | int arpeggiatorOctaveRange(); 496 | void setArpeggiatorOctaveRange(int v); 497 | 498 | int fxBitCrushDepth(); 499 | void setFxBitCrushDepth(int v); 500 | 501 | int fxDecimator(); 502 | void setFxDecimator(int v); 503 | 504 | int filterLoCutoff(); 505 | void setFilterLoCutoff(int v); 506 | 507 | int filterHiCutoff(); 508 | void setFilterHiCutoff(int v); 509 | 510 | int fxChorusDry(); 511 | void setFxChorusDry(int v); 512 | 513 | int fxChorusWet(); 514 | void setFxChorusWet(int v); 515 | 516 | int fxChorusMode(); 517 | void setFxChorusMode(int v); 518 | 519 | int fxChorusSpeed(); 520 | void setFxChorusSpeed(int v); 521 | 522 | int fxChorusDepth(); 523 | void setFxChorusDepth(int v); 524 | 525 | int fxChorusFeedback(); 526 | void setFxChorusFeedback(int v); 527 | 528 | int fxChorusPhase(); 529 | void setFxChorusPhase(int v); 530 | 531 | int fxPhaserDry(); 532 | void setFxPhaserDry(int v); 533 | 534 | int fxPhaserWet(); 535 | void setFxPhaserWet(int v); 536 | 537 | int fxPhaserMode(); 538 | void setFxPhaserMode(int v); 539 | 540 | int fxPhaserSpeed(); 541 | void setFxPhaserSpeed(int v); 542 | 543 | int fxPhaserDepth(); 544 | void setFxPhaserDepth(int v); 545 | 546 | int fxPhaserOffset(); 547 | void setFxPhaserOffset(int v); 548 | 549 | int fxPhaserStages(); 550 | void setFxPhaserStages(int v); 551 | 552 | int fxPhaserFeedback(); 553 | void setFxPhaserFeedback(int v); 554 | 555 | int fxPhaserPhase(); 556 | void setFxPhaserPhase(int v); 557 | 558 | int fxDelayDry(); 559 | void setFxDelayDry(int v); 560 | 561 | int fxDelayWet(); 562 | void setFxDelayWet(int v); 563 | 564 | int fxDelayMode(); 565 | void setFxDelayMode(int v); 566 | 567 | int fxDelayTime(); 568 | void setFxDelayTime(int v); 569 | 570 | int fxDelayFeedback(); 571 | void setFxDelayFeedback(int v); 572 | 573 | int fxDelayLowPass(); 574 | void setFxDelayLowPass(int v); 575 | 576 | int fxDelayHighPass(); 577 | void setFxDelayHighPass(int v); 578 | 579 | int fxDelayTempo(); 580 | void setFxDelayTempo(int v); 581 | 582 | int fxDelayMultiplier(); 583 | void setFxDelayMultiplier(int v); 584 | 585 | int fxDelayDivider(); 586 | void setFxDelayDivider(int v); 587 | 588 | int fxAMDepth(); 589 | void setFxAMDepth(int v); 590 | 591 | int fxAMSpeed(); 592 | void setFxAMSpeed(int v); 593 | 594 | int fxAMRange(); 595 | void setFxAMRange(int v); 596 | 597 | int fxAMPhase(); 598 | void setFxAMPhase(int v); 599 | 600 | int fxReverbDry(); 601 | void setFxReverbDry(int v); 602 | 603 | int fxReverbWet(); 604 | void setFxReverbWet(int v); 605 | 606 | int fxReverbDecay(); 607 | void setFxReverbDecay(int v); 608 | 609 | int fxReverbDamp(); 610 | void setFxReverbDamp(int v); 611 | 612 | int fxReverbMode(); 613 | void setFxReverbMode(int v); 614 | 615 | int fxRoute(); 616 | void setFxRoute(int v); 617 | 618 | void loadPatchNames(); 619 | void savePatchNames(); 620 | 621 | QString patchName(); 622 | void setPatchName(const QString &str); 623 | 624 | unsigned char readMemoryLocation(XFM2Parameter offset, bool useCache=true); 625 | bool writeMemoryLocation(XFM2Parameter offset, unsigned char data); 626 | 627 | QList fmOperators(); 628 | 629 | private: 630 | unsigned char m_xfm2[512]; // Memory buffer 631 | std::vector m_patchNames; // XFM2 hardware doesn't hold patch names, so we use the app to store them 632 | QSerialPort * m_port; // USB serial port connection 633 | int m_patchnumber; // Current patch number 634 | bool m_isconnected; // True if the hardware is connected 635 | bool m_initialised; // True if the model is initialised and the memory buffer has been read 636 | std::string m_patchNameBuffer; // The current patch name 637 | }; 638 | 639 | #endif // SYNTHMODEL_H 640 | -------------------------------------------------------------------------------- /src/CustomKeyboard.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.9 25 | import QtQuick.Window 2.3 26 | import QtQuick.Controls 2.2 27 | 28 | /* 29 | * This is a custom virtual keyboard because it is actually 30 | * rather difficult to get the standard Qt keyboard working on 31 | * a Raspberry Pi. I ran out of patience and built my own. 32 | * It suits my requirements - your mileage may vary ;-) 33 | */ 34 | 35 | Item { 36 | id: root 37 | width:800 38 | height:260 39 | 40 | signal keyPressed(var keyval) 41 | 42 | property var keyColor: "#D3D3D3" 43 | property var backgroundColor: "#202020" 44 | property bool punctuationMode: false 45 | 46 | function setPunctuationMode(enabled) 47 | { 48 | punctuationMode=enabled; 49 | 50 | if (enabled) { 51 | keyQ.text="!"; 52 | keyW.text="="; 53 | keyE.text="&"; 54 | keyR.text="\\"; 55 | keyT.text="?"; 56 | keyY.text="("; 57 | keyU.text="{"; 58 | keyI.text="\""; 59 | keyO.text="~"; 60 | keyP.text="_"; 61 | 62 | keyA.text="#"; 63 | keyS.text="$"; 64 | keyD.text="*"; 65 | keyF.text="/"; 66 | keyG.text=":"; 67 | keyH.text=")"; 68 | keyJ.text="}"; 69 | keyK.text="'"; 70 | keyL.text="`"; 71 | 72 | keyZ.text="%"; 73 | keyX.text="^"; 74 | keyC.text="-"; 75 | keyV.text="+"; 76 | keyB.text=";"; 77 | keyN.text=","; 78 | keyM.text="|"; 79 | } else { 80 | keyQ.text="Q"; 81 | keyW.text="W"; 82 | keyE.text="E"; 83 | keyR.text="R"; 84 | keyT.text="T"; 85 | keyY.text="Y"; 86 | keyU.text="U"; 87 | keyI.text="I"; 88 | keyO.text="O"; 89 | keyP.text="P"; 90 | 91 | keyA.text="A"; 92 | keyS.text="S"; 93 | keyD.text="D"; 94 | keyF.text="F"; 95 | keyG.text="G"; 96 | keyH.text="H"; 97 | keyJ.text="J"; 98 | keyK.text="K"; 99 | keyL.text="L"; 100 | 101 | keyZ.text="Z"; 102 | keyX.text="X"; 103 | keyC.text="C"; 104 | keyV.text="V"; 105 | keyB.text="B"; 106 | keyN.text="N"; 107 | keyM.text="M"; 108 | } 109 | } 110 | 111 | Rectangle { 112 | id: rectangleBackground 113 | x: 0 114 | y: 0 115 | width: parent.width 116 | height: 260 117 | color: backgroundColor 118 | 119 | Button { 120 | id: key1 121 | x: 13 122 | y: 8 123 | width: 56 124 | height: 56 125 | text: "1" 126 | font.bold: true 127 | flat: false 128 | background: Rectangle { 129 | color: keyColor 130 | radius: 8 131 | } 132 | font.pixelSize: 28 133 | onClicked: { 134 | root.keyPressed("1"); 135 | } 136 | } 137 | 138 | Button { 139 | id: key2 140 | x: 85 141 | y: 8 142 | width: 56 143 | height: 56 144 | text: "2" 145 | font.bold: true 146 | flat: false 147 | background: Rectangle { 148 | color: keyColor 149 | radius: 8 150 | } 151 | font.pixelSize: 28 152 | onClicked: { 153 | root.keyPressed("2"); 154 | } 155 | } 156 | 157 | Button { 158 | id: key3 159 | x: 157 160 | y: 8 161 | width: 56 162 | height: 56 163 | text: "3" 164 | font.bold: true 165 | flat: false 166 | background: Rectangle { 167 | color: keyColor 168 | radius: 8 169 | } 170 | font.pixelSize: 28 171 | onClicked: { 172 | root.keyPressed("3"); 173 | } 174 | } 175 | 176 | Button { 177 | id: key4 178 | x: 229 179 | y: 8 180 | width: 56 181 | height: 56 182 | text: "4" 183 | font.bold: true 184 | flat: false 185 | background: Rectangle { 186 | color: keyColor 187 | radius: 8 188 | } 189 | font.pixelSize: 28 190 | onClicked: { 191 | root.keyPressed("4"); 192 | } 193 | } 194 | 195 | Button { 196 | id: key5 197 | x: 301 198 | y: 8 199 | width: 56 200 | height: 56 201 | text: "5" 202 | font.bold: true 203 | flat: false 204 | background: Rectangle { 205 | color: keyColor 206 | radius: 8 207 | } 208 | font.pixelSize: 28 209 | onClicked: { 210 | root.keyPressed("5"); 211 | } 212 | } 213 | 214 | Button { 215 | id: key6 216 | x: 373 217 | y: 8 218 | width: 56 219 | height: 56 220 | text: "6" 221 | font.bold: true 222 | flat: false 223 | background: Rectangle { 224 | color: keyColor 225 | radius: 8 226 | } 227 | font.pixelSize: 28 228 | onClicked: { 229 | root.keyPressed("6"); 230 | } 231 | } 232 | 233 | Button { 234 | id: key7 235 | x: 445 236 | y: 8 237 | width: 56 238 | height: 56 239 | text: "7" 240 | font.bold: true 241 | flat: false 242 | background: Rectangle { 243 | color: keyColor 244 | radius: 8 245 | } 246 | font.pixelSize: 28 247 | onClicked: { 248 | root.keyPressed("7"); 249 | } 250 | } 251 | 252 | Button { 253 | id: key8 254 | x: 517 255 | y: 8 256 | width: 56 257 | height: 56 258 | text: "8" 259 | font.bold: true 260 | flat: false 261 | background: Rectangle { 262 | color: keyColor 263 | radius: 8 264 | } 265 | font.pixelSize: 28 266 | onClicked: { 267 | root.keyPressed("8"); 268 | } 269 | } 270 | 271 | Button { 272 | id: key9 273 | x: 589 274 | y: 8 275 | width: 56 276 | height: 56 277 | text: "9" 278 | font.bold: true 279 | flat: false 280 | background: Rectangle { 281 | color: keyColor 282 | radius: 8 283 | } 284 | font.pixelSize: 28 285 | onClicked: { 286 | root.keyPressed("9"); 287 | } 288 | } 289 | 290 | Button { 291 | id: key10 292 | x: 661 293 | y: 8 294 | width: 56 295 | height: 56 296 | text: "0" 297 | font.bold: true 298 | flat: false 299 | background: Rectangle { 300 | color: keyColor 301 | radius: 8 302 | } 303 | font.pixelSize: 28 304 | onClicked: { 305 | root.keyPressed("0"); 306 | } 307 | } 308 | 309 | Button { 310 | id: keyQ 311 | x: 14 312 | y: 69 313 | width: 56 314 | height: 56 315 | text: qsTr("Q") 316 | font.bold: true 317 | flat: false 318 | background: Rectangle { 319 | color: keyColor 320 | radius: 8 321 | } 322 | font.pixelSize: 28 323 | onClicked: { 324 | root.keyPressed(punctuationMode ? "!" : "Q"); 325 | } 326 | } 327 | 328 | Button { 329 | id: keyA 330 | x: 14 331 | y: 130 332 | width: 56 333 | height: 56 334 | text: qsTr("A") 335 | font.bold: true 336 | flat: false 337 | background: Rectangle { 338 | color: keyColor 339 | radius: 8 340 | } 341 | font.pixelSize: 28 342 | onClicked: { 343 | root.keyPressed(punctuationMode ? "#" : "A"); 344 | } 345 | } 346 | 347 | Button { 348 | id: keyZ 349 | x: 14 350 | y: 191 351 | width: 56 352 | height: 56 353 | text: qsTr("Z") 354 | font.bold: true 355 | flat: false 356 | background: Rectangle { 357 | color: keyColor 358 | radius: 8 359 | } 360 | font.pixelSize: 28 361 | onClicked: { 362 | root.keyPressed(punctuationMode ? "%" : "Z"); 363 | } 364 | } 365 | 366 | Button { 367 | id: keyW 368 | x: 86 369 | y: 69 370 | width: 56 371 | height: 56 372 | text: "W" 373 | font.bold: true 374 | flat: false 375 | background: Rectangle { 376 | color: keyColor 377 | radius: 8 378 | } 379 | font.pixelSize: 28 380 | onClicked: { 381 | root.keyPressed(punctuationMode ? "=" : "W"); 382 | } 383 | } 384 | 385 | Button { 386 | id: keyE 387 | x: 158 388 | y: 69 389 | width: 56 390 | height: 56 391 | text: "E" 392 | font.bold: true 393 | flat: false 394 | background: Rectangle { 395 | color: keyColor 396 | radius: 8 397 | } 398 | font.pixelSize: 28 399 | onClicked: { 400 | root.keyPressed(punctuationMode ? "&" : "E"); 401 | } 402 | } 403 | 404 | Button { 405 | id: keyR 406 | x: 230 407 | y: 69 408 | width: 56 409 | height: 56 410 | text: "R" 411 | font.bold: true 412 | flat: false 413 | background: Rectangle { 414 | color: keyColor 415 | radius: 8 416 | } 417 | font.pixelSize: 28 418 | onClicked: { 419 | root.keyPressed(punctuationMode ? "\\" : "R"); 420 | } 421 | } 422 | 423 | Button { 424 | id: keyT 425 | x: 302 426 | y: 69 427 | width: 56 428 | height: 56 429 | text: "T" 430 | font.bold: true 431 | flat: false 432 | background: Rectangle { 433 | color: keyColor 434 | radius: 8 435 | } 436 | font.pixelSize: 28 437 | onClicked: { 438 | root.keyPressed(punctuationMode ? "?" : "T"); 439 | } 440 | } 441 | 442 | Button { 443 | id: keyY 444 | x: 374 445 | y: 69 446 | width: 56 447 | height: 56 448 | text: "Y" 449 | font.bold: true 450 | flat: false 451 | background: Rectangle { 452 | color: keyColor 453 | radius: 8 454 | } 455 | font.pixelSize: 28 456 | onClicked: { 457 | root.keyPressed(punctuationMode ? "(" : "Y"); 458 | } 459 | } 460 | 461 | Button { 462 | id: keyU 463 | x: 446 464 | y: 69 465 | width: 56 466 | height: 56 467 | text: "U" 468 | font.bold: true 469 | flat: false 470 | background: Rectangle { 471 | color: keyColor 472 | radius: 8 473 | } 474 | font.pixelSize: 28 475 | onClicked: { 476 | root.keyPressed(punctuationMode ? "{" : "U"); 477 | } 478 | } 479 | 480 | Button { 481 | id: keyI 482 | x: 518 483 | y: 69 484 | width: 56 485 | height: 56 486 | text: "I" 487 | font.bold: true 488 | flat: false 489 | background: Rectangle { 490 | color: keyColor 491 | radius: 8 492 | } 493 | font.pixelSize: 28 494 | onClicked: { 495 | root.keyPressed(punctuationMode ? "\"" : "I"); 496 | } 497 | } 498 | 499 | Button { 500 | id: keyO 501 | x: 590 502 | y: 69 503 | width: 56 504 | height: 56 505 | text: "O" 506 | font.bold: true 507 | flat: false 508 | background: Rectangle { 509 | color: keyColor 510 | radius: 8 511 | } 512 | font.pixelSize: 28 513 | onClicked: { 514 | root.keyPressed(punctuationMode ? "~" : "O"); 515 | } 516 | } 517 | 518 | Button { 519 | id: keyP 520 | x: 662 521 | y: 69 522 | width: 56 523 | height: 56 524 | text: "P" 525 | font.bold: true 526 | flat: false 527 | background: Rectangle { 528 | color: keyColor 529 | radius: 8 530 | } 531 | font.pixelSize: 28 532 | onClicked: { 533 | root.keyPressed(punctuationMode ? "_" : "P"); 534 | } 535 | } 536 | 537 | Button { 538 | id: keyS 539 | x: 86 540 | y: 130 541 | width: 56 542 | height: 56 543 | text: "S" 544 | font.bold: true 545 | flat: false 546 | background: Rectangle { 547 | color: keyColor 548 | radius: 8 549 | } 550 | font.pixelSize: 28 551 | onClicked: { 552 | root.keyPressed(punctuationMode ? "$" : "S"); 553 | } 554 | } 555 | 556 | Button { 557 | id: keyD 558 | x: 158 559 | y: 130 560 | width: 56 561 | height: 56 562 | text: "D" 563 | font.bold: true 564 | flat: false 565 | background: Rectangle { 566 | color: keyColor 567 | radius: 8 568 | } 569 | font.pixelSize: 28 570 | onClicked: { 571 | root.keyPressed(punctuationMode ? "*" : "D"); 572 | } 573 | } 574 | 575 | Button { 576 | id: keyF 577 | x: 230 578 | y: 130 579 | width: 56 580 | height: 56 581 | text: "F" 582 | font.bold: true 583 | flat: false 584 | background: Rectangle { 585 | color: keyColor 586 | radius: 8 587 | } 588 | font.pixelSize: 28 589 | onClicked: { 590 | root.keyPressed(punctuationMode ? "/" : "F"); 591 | } 592 | } 593 | 594 | Button { 595 | id: keyG 596 | x: 302 597 | y: 130 598 | width: 56 599 | height: 56 600 | text: "G" 601 | font.bold: true 602 | flat: false 603 | background: Rectangle { 604 | color: keyColor 605 | radius: 8 606 | } 607 | font.pixelSize: 28 608 | onClicked: { 609 | root.keyPressed(punctuationMode ? ":" : "G"); 610 | } 611 | } 612 | 613 | Button { 614 | id: keyH 615 | x: 374 616 | y: 130 617 | width: 56 618 | height: 56 619 | text: "H" 620 | font.bold: true 621 | flat: false 622 | background: Rectangle { 623 | color: keyColor 624 | radius: 8 625 | } 626 | font.pixelSize: 28 627 | onClicked: { 628 | root.keyPressed(punctuationMode ? ")" : "H"); 629 | } 630 | } 631 | 632 | Button { 633 | id: keyJ 634 | x: 446 635 | y: 130 636 | width: 56 637 | height: 56 638 | text: "J" 639 | font.bold: true 640 | flat: false 641 | background: Rectangle { 642 | color: keyColor 643 | radius: 8 644 | } 645 | font.pixelSize: 28 646 | onClicked: { 647 | root.keyPressed(punctuationMode ? "}" : "J"); 648 | } 649 | } 650 | 651 | Button { 652 | id: keyK 653 | x: 518 654 | y: 130 655 | width: 56 656 | height: 56 657 | text: "K" 658 | font.bold: true 659 | flat: false 660 | background: Rectangle { 661 | color: keyColor 662 | radius: 8 663 | } 664 | font.pixelSize: 28 665 | onClicked: { 666 | root.keyPressed(punctuationMode ? "'" : "K"); 667 | } 668 | } 669 | 670 | Button { 671 | id: keyL 672 | x: 590 673 | y: 130 674 | width: 56 675 | height: 56 676 | text: "L" 677 | font.bold: true 678 | flat: false 679 | background: Rectangle { 680 | color: keyColor 681 | radius: 8 682 | } 683 | font.pixelSize: 28 684 | onClicked: { 685 | root.keyPressed(punctuationMode ? "`" : "L"); 686 | } 687 | } 688 | 689 | Button { 690 | id: keyX 691 | x: 86 692 | y: 191 693 | width: 56 694 | height: 56 695 | text: "X" 696 | font.bold: true 697 | flat: false 698 | background: Rectangle { 699 | color: keyColor 700 | radius: 8 701 | } 702 | font.pixelSize: 28 703 | onClicked: { 704 | root.keyPressed(punctuationMode ? "%" : "X"); 705 | } 706 | } 707 | 708 | Button { 709 | id: keyC 710 | x: 158 711 | y: 191 712 | width: 56 713 | height: 56 714 | text: "C" 715 | font.bold: true 716 | flat: false 717 | background: Rectangle { 718 | color: keyColor 719 | radius: 8 720 | } 721 | font.pixelSize: 28 722 | onClicked: { 723 | root.keyPressed(punctuationMode ? "-" : "C"); 724 | } 725 | } 726 | 727 | Button { 728 | id: keyV 729 | x: 230 730 | y: 191 731 | width: 56 732 | height: 56 733 | text: "V" 734 | font.bold: true 735 | flat: false 736 | background: Rectangle { 737 | color: keyColor 738 | radius: 8 739 | } 740 | font.pixelSize: 28 741 | onClicked: { 742 | root.keyPressed(punctuationMode ? "+" : "V"); 743 | } 744 | } 745 | 746 | Button { 747 | id: keyB 748 | x: 302 749 | y: 191 750 | width: 56 751 | height: 56 752 | text: "B" 753 | font.bold: true 754 | flat: false 755 | background: Rectangle { 756 | color: keyColor 757 | radius: 8 758 | } 759 | font.pixelSize: 28 760 | onClicked: { 761 | root.keyPressed(punctuationMode ? ";" : "B"); 762 | } 763 | } 764 | 765 | Button { 766 | id: keyN 767 | x: 374 768 | y: 191 769 | width: 56 770 | height: 56 771 | text: "N" 772 | font.bold: true 773 | flat: false 774 | background: Rectangle { 775 | color: keyColor 776 | radius: 8 777 | } 778 | font.pixelSize: 28 779 | onClicked: { 780 | root.keyPressed(punctuationMode ? "," : "N"); 781 | } 782 | } 783 | 784 | Button { 785 | id: keyM 786 | x: 446 787 | y: 191 788 | width: 56 789 | height: 56 790 | text: "M" 791 | font.bold: true 792 | flat: false 793 | background: Rectangle { 794 | color: keyColor 795 | radius: 8 796 | } 797 | font.pixelSize: 28 798 | onClicked: { 799 | root.keyPressed(punctuationMode ? "|" : "M"); 800 | } 801 | } 802 | 803 | Button { 804 | id: keySpace 805 | x: 590 806 | y: 191 807 | width: 56 808 | height: 56 809 | text: "" 810 | font.bold: true 811 | flat: false 812 | background: Rectangle { 813 | color: keyColor 814 | radius: 8 815 | } 816 | font.pixelSize: 28 817 | Image { 818 | id: keySpaceImage 819 | width: 38 820 | height: 38 821 | anchors.fill: parent 822 | source: "images/Key - Space.png" 823 | fillMode: Image.PreserveAspectFit 824 | } 825 | onClicked: { 826 | root.keyPressed(" "); 827 | } 828 | } 829 | 830 | Button { 831 | id: keyLeft 832 | x: 662 833 | y: 191 834 | width: 56 835 | height: 56 836 | text: "" 837 | font.bold: true 838 | flat: false 839 | background: Rectangle { 840 | color: keyColor 841 | radius: 8 842 | } 843 | font.pixelSize: 28 844 | Image { 845 | id: keyLeftImage 846 | width: 38 847 | height: 38 848 | anchors.centerIn: parent 849 | source: "images/Key - Left Arrow.png" 850 | fillMode: Image.PreserveAspectFit 851 | } 852 | onClicked: { 853 | root.keyPressed("LEFT"); 854 | } 855 | } 856 | 857 | Button { 858 | id: keyRight 859 | x: 733 860 | y: 191 861 | width: 56 862 | height: 56 863 | text: "" 864 | font.bold: true 865 | flat: false 866 | background: Rectangle { 867 | color: keyColor 868 | radius: 8 869 | } 870 | font.pixelSize: 28 871 | Image { 872 | id: keyRightImage 873 | width: 38 874 | height: 38 875 | anchors.centerIn: parent 876 | source: "images/Key - Right Arrow.png" 877 | fillMode: Image.PreserveAspectFit 878 | } 879 | onClicked: { 880 | root.keyPressed("RIGHT"); 881 | } 882 | } 883 | 884 | Button { 885 | id: keyBackspace 886 | x: 733 887 | y: 8 888 | width: 56 889 | height: 56 890 | text: "" 891 | font.bold: true 892 | flat: false 893 | background: Rectangle { 894 | color: keyColor 895 | radius: 8 896 | } 897 | font.pixelSize: 28 898 | Image { 899 | id: keyBSImage 900 | width: 42 901 | height: 42 902 | anchors.centerIn: parent 903 | source: "images/Key - Backspace.png" 904 | fillMode: Image.PreserveAspectFit 905 | } 906 | onClicked: { 907 | root.keyPressed("DEL"); 908 | } 909 | } 910 | 911 | Button { 912 | id: keyEnter 913 | x: 733 914 | y: 69 915 | width: 56 916 | height: 56 917 | text: "" 918 | font.family: "Futura PT" 919 | flat: false 920 | background: Rectangle { 921 | color: keyColor 922 | radius: 8 923 | } 924 | font.pixelSize: 28 925 | Image { 926 | id: keyEnterImage 927 | width: 42 928 | height: 42 929 | anchors.centerIn: parent 930 | source: "images/Key - Enter.png" 931 | fillMode: Image.PreserveAspectFit 932 | } 933 | onClicked: { 934 | root.keyPressed("ENTER"); 935 | } 936 | } 937 | 938 | Button { 939 | id: keyAt 940 | x: 662 941 | y: 130 942 | width: 56 943 | height: 56 944 | text: "@" 945 | flat: false 946 | background: Rectangle { 947 | color: keyColor 948 | radius: 8 949 | } 950 | font.pixelSize: 28 951 | font.bold: true 952 | onClicked: { 953 | root.keyPressed("@"); 954 | } 955 | } 956 | 957 | Button { 958 | id: keyPeriod 959 | x: 518 960 | y: 191 961 | width: 56 962 | height: 56 963 | text: "." 964 | font.bold: true 965 | flat: false 966 | background: Rectangle { 967 | color: keyColor 968 | radius: 8 969 | } 970 | font.pixelSize: 28 971 | onClicked: { 972 | root.keyPressed("."); 973 | } 974 | } 975 | 976 | Button { 977 | id: keyPunct 978 | x: 733 979 | y: 129 980 | width: 56 981 | height: 56 982 | text: "..." 983 | flat: false 984 | font.bold: true 985 | background: Rectangle { 986 | color: keyColor 987 | radius: 8 988 | } 989 | font.pixelSize: 28 990 | onClicked: { 991 | // Toggle punctuation mode 992 | // This relabels a bunch of key caps 993 | setPunctuationMode(!punctuationMode); 994 | } 995 | } 996 | } 997 | } 998 | -------------------------------------------------------------------------------- /src/EnvelopePage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * XFM2 Synth Controller 3 | * 4 | * This is a user-friendly controller for the excellent XFM2 synth hardware designed by Futur3soundz 5 | * https://www.futur3soundz.com/xfm2 6 | * 7 | * 8 | * This file is part of the XFM2Controller distribution (https://github.com/ataristdude/xfm2controller). 9 | * Copyright (c) 2020 Don Fletcher 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, version 3. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | import QtQuick 2.12 25 | import QtQuick.Controls 2.3 26 | import QtQuick.Extras 1.4 27 | import Xfm.Synth 1.0 28 | 29 | Item { 30 | id: envelopePage 31 | width: 800 32 | height: 357 33 | 34 | Connections { 35 | target: synthModel 36 | onPatchNumberChanged: { 37 | updatePage(); 38 | } 39 | } 40 | 41 | function updatePage() 42 | { 43 | dialRange.value=synthModel.pitchEG_Range; 44 | dialVelocity.value=synthModel.pitchEG_Velocity; 45 | dialRateKey.value=synthModel.pitchEG_RateKey; 46 | canvas.requestPaint(); 47 | } 48 | 49 | Rectangle { 50 | anchors.fill: parent 51 | color: "#505050" 52 | } 53 | 54 | Row { 55 | id: envLoopRow 56 | x: 444 57 | y: 151 58 | width: 334 59 | height: 56 60 | spacing: 2 61 | 62 | Label { 63 | id: labelLoop1 64 | width: 50 65 | text: qsTr("1") 66 | font.pixelSize: 24 67 | verticalAlignment: Text.AlignVCenter 68 | horizontalAlignment: Text.AlignHCenter 69 | anchors.bottom: parent.bottom 70 | anchors.bottomMargin: 0 71 | anchors.top: parent.top 72 | anchors.topMargin: 0 73 | color: synthModel.envelopeLoop & 0x01 ? "#ffffff" : "#000000" 74 | background: Rectangle { 75 | color: synthModel.envelopeLoop & 0x01 ? "#0000FF" : "#808080" 76 | radius: 5 77 | } 78 | 79 | MouseArea { 80 | anchors.fill: parent 81 | onClicked: { 82 | var i=synthModel.envelopeLoop; 83 | 84 | i=i ^ 0x01; 85 | synthModel.envelopeLoop=i; 86 | } 87 | } 88 | } 89 | Label { 90 | id: labelLoop2 91 | width: 50 92 | text: qsTr("2") 93 | font.pixelSize: 24 94 | verticalAlignment: Text.AlignVCenter 95 | horizontalAlignment: Text.AlignHCenter 96 | anchors.bottom: parent.bottom 97 | anchors.bottomMargin: 0 98 | anchors.top: parent.top 99 | anchors.topMargin: 0 100 | color: synthModel.envelopeLoop & 0x02 ? "#ffffff" : "#000000" 101 | background: Rectangle { 102 | color: synthModel.envelopeLoop & 0x02 ? "#0000FF" : "#808080" 103 | radius: 5 104 | } 105 | MouseArea { 106 | anchors.fill: parent 107 | onClicked: { 108 | var i=synthModel.envelopeLoop; 109 | 110 | i=i ^ 0x02; 111 | synthModel.envelopeLoop=i; 112 | } 113 | } 114 | } 115 | Label { 116 | id: labelLoop3 117 | width: 50 118 | text: qsTr("3") 119 | font.pixelSize: 24 120 | verticalAlignment: Text.AlignVCenter 121 | horizontalAlignment: Text.AlignHCenter 122 | anchors.bottom: parent.bottom 123 | anchors.bottomMargin: 0 124 | anchors.top: parent.top 125 | anchors.topMargin: 0 126 | color: synthModel.envelopeLoop & 0x04 ? "#ffffff" : "#000000" 127 | background: Rectangle { 128 | color: synthModel.envelopeLoop & 0x04 ? "#0000FF" : "#808080" 129 | radius: 5 130 | } 131 | MouseArea { 132 | anchors.fill: parent 133 | onClicked: { 134 | var i=synthModel.envelopeLoop; 135 | 136 | i=i ^ 0x04; 137 | synthModel.envelopeLoop=i; 138 | } 139 | } 140 | } 141 | Label { 142 | id: labelLoop4 143 | width: 50 144 | text: qsTr("4") 145 | font.pixelSize: 24 146 | verticalAlignment: Text.AlignVCenter 147 | horizontalAlignment: Text.AlignHCenter 148 | anchors.bottom: parent.bottom 149 | anchors.bottomMargin: 0 150 | anchors.top: parent.top 151 | anchors.topMargin: 0 152 | color: synthModel.envelopeLoop & 0x08 ? "#ffffff" : "#000000" 153 | background: Rectangle { 154 | color: synthModel.envelopeLoop & 0x08 ? "#0000FF" : "#808080" 155 | radius: 5 156 | } 157 | MouseArea { 158 | anchors.fill: parent 159 | onClicked: { 160 | var i=synthModel.envelopeLoop; 161 | 162 | i=i ^ 0x08; 163 | synthModel.envelopeLoop=i; 164 | } 165 | } 166 | } 167 | Label { 168 | id: labelLoop5 169 | width: 50 170 | text: qsTr("5") 171 | font.pixelSize: 24 172 | verticalAlignment: Text.AlignVCenter 173 | horizontalAlignment: Text.AlignHCenter 174 | anchors.bottom: parent.bottom 175 | anchors.bottomMargin: 0 176 | anchors.top: parent.top 177 | anchors.topMargin: 0 178 | color: synthModel.envelopeLoop & 0x10 ? "#ffffff" : "#000000" 179 | background: Rectangle { 180 | color: synthModel.envelopeLoop & 0x10 ? "#0000FF" : "#808080" 181 | radius: 5 182 | } 183 | MouseArea { 184 | anchors.fill: parent 185 | onClicked: { 186 | var i=synthModel.envelopeLoop; 187 | 188 | i=i ^ 0x10; 189 | synthModel.envelopeLoop=i; 190 | } 191 | } 192 | } 193 | Label { 194 | id: labelLoop6 195 | width: 50 196 | text: qsTr("6") 197 | font.pixelSize: 24 198 | verticalAlignment: Text.AlignVCenter 199 | horizontalAlignment: Text.AlignHCenter 200 | anchors.bottom: parent.bottom 201 | anchors.bottomMargin: 0 202 | anchors.top: parent.top 203 | anchors.topMargin: 0 204 | color: synthModel.envelopeLoop & 0x20 ? "#ffffff" : "#000000" 205 | background: Rectangle { 206 | color: synthModel.envelopeLoop & 0x20 ? "#0000FF" : "#808080" 207 | radius: 5 208 | } 209 | MouseArea { 210 | anchors.fill: parent 211 | onClicked: { 212 | var i=synthModel.envelopeLoop; 213 | 214 | i=i ^ 0x20; 215 | synthModel.envelopeLoop=i; 216 | } 217 | } 218 | } 219 | } 220 | 221 | 222 | Row { 223 | id: evSegRow 224 | x: 444 225 | y: 220 226 | width: 334 227 | height: 56 228 | spacing: 2 229 | 230 | Label { 231 | id: labelSeg1 232 | width: 50 233 | text: qsTr("1") 234 | font.pixelSize: 24 235 | verticalAlignment: Text.AlignVCenter 236 | horizontalAlignment: Text.AlignHCenter 237 | anchors.bottom: parent.bottom 238 | anchors.bottomMargin: 0 239 | anchors.top: parent.top 240 | anchors.topMargin: 0 241 | color: synthModel.envelopeLoopSegment & 0x01 ? "#ffffff" : "#000000" 242 | background: Rectangle { 243 | color: synthModel.envelopeLoopSegment & 0x01 ? "#0000FF" : "#808080" 244 | radius: 5 245 | } 246 | 247 | MouseArea { 248 | anchors.fill: parent 249 | onClicked: { 250 | var i=synthModel.envelopeLoopSegment; 251 | 252 | i=i ^ 0x01; 253 | synthModel.envelopeLoopSegment=i; 254 | } 255 | } 256 | } 257 | Label { 258 | id: labelSeg2 259 | width: 50 260 | text: qsTr("2") 261 | font.pixelSize: 24 262 | verticalAlignment: Text.AlignVCenter 263 | horizontalAlignment: Text.AlignHCenter 264 | anchors.bottom: parent.bottom 265 | anchors.bottomMargin: 0 266 | anchors.top: parent.top 267 | anchors.topMargin: 0 268 | color: synthModel.envelopeLoopSegment & 0x02 ? "#ffffff" : "#000000" 269 | background: Rectangle { 270 | color: synthModel.envelopeLoopSegment & 0x02 ? "#0000FF" : "#808080" 271 | radius: 5 272 | } 273 | MouseArea { 274 | anchors.fill: parent 275 | onClicked: { 276 | var i=synthModel.envelopeLoopSegment; 277 | 278 | i=i ^ 0x02; 279 | synthModel.envelopeLoopSegment=i; 280 | } 281 | } 282 | } 283 | Label { 284 | id: labelSeg3 285 | width: 50 286 | text: qsTr("3") 287 | font.pixelSize: 24 288 | verticalAlignment: Text.AlignVCenter 289 | horizontalAlignment: Text.AlignHCenter 290 | anchors.bottom: parent.bottom 291 | anchors.bottomMargin: 0 292 | anchors.top: parent.top 293 | anchors.topMargin: 0 294 | color: synthModel.envelopeLoopSegment & 0x04 ? "#ffffff" : "#000000" 295 | background: Rectangle { 296 | color: synthModel.envelopeLoopSegment & 0x04 ? "#0000FF" : "#808080" 297 | radius: 5 298 | } 299 | MouseArea { 300 | anchors.fill: parent 301 | onClicked: { 302 | var i=synthModel.envelopeLoopSegment; 303 | 304 | i=i ^ 0x04; 305 | synthModel.envelopeLoopSegment=i; 306 | } 307 | } 308 | } 309 | Label { 310 | id: labelSeg4 311 | width: 50 312 | text: qsTr("4") 313 | font.pixelSize: 24 314 | verticalAlignment: Text.AlignVCenter 315 | horizontalAlignment: Text.AlignHCenter 316 | anchors.bottom: parent.bottom 317 | anchors.bottomMargin: 0 318 | anchors.top: parent.top 319 | anchors.topMargin: 0 320 | color: synthModel.envelopeLoopSegment & 0x08 ? "#ffffff" : "#000000" 321 | background: Rectangle { 322 | color: synthModel.envelopeLoopSegment & 0x08 ? "#0000FF" : "#808080" 323 | radius: 5 324 | } 325 | MouseArea { 326 | anchors.fill: parent 327 | onClicked: { 328 | var i=synthModel.envelopeLoopSegment; 329 | 330 | i=i ^ 0x08; 331 | synthModel.envelopeLoopSegment=i; 332 | } 333 | } 334 | } 335 | Label { 336 | id: labelSeg5 337 | width: 50 338 | text: qsTr("5") 339 | font.pixelSize: 24 340 | verticalAlignment: Text.AlignVCenter 341 | horizontalAlignment: Text.AlignHCenter 342 | anchors.bottom: parent.bottom 343 | anchors.bottomMargin: 0 344 | anchors.top: parent.top 345 | anchors.topMargin: 0 346 | color: synthModel.envelopeLoopSegment & 0x10 ? "#ffffff" : "#000000" 347 | background: Rectangle { 348 | color: synthModel.envelopeLoopSegment & 0x10 ? "#0000FF" : "#808080" 349 | radius: 5 350 | } 351 | MouseArea { 352 | anchors.fill: parent 353 | onClicked: { 354 | var i=synthModel.envelopeLoopSegment; 355 | 356 | i=i ^ 0x10; 357 | synthModel.envelopeLoopSegment=i; 358 | } 359 | } 360 | } 361 | Label { 362 | id: labelSeg6 363 | width: 50 364 | text: qsTr("6") 365 | font.pixelSize: 24 366 | verticalAlignment: Text.AlignVCenter 367 | horizontalAlignment: Text.AlignHCenter 368 | anchors.bottom: parent.bottom 369 | anchors.bottomMargin: 0 370 | anchors.top: parent.top 371 | anchors.topMargin: 0 372 | color: synthModel.envelopeLoopSegment & 0x20 ? "#ffffff" : "#000000" 373 | background: Rectangle { 374 | color: synthModel.envelopeLoopSegment & 0x20 ? "#0000FF" : "#808080" 375 | radius: 5 376 | } 377 | MouseArea { 378 | anchors.fill: parent 379 | onClicked: { 380 | var i=synthModel.envelopeLoopSegment; 381 | 382 | i=i ^ 0x20; 383 | synthModel.envelopeLoopSegment=i; 384 | } 385 | } 386 | } 387 | } 388 | 389 | 390 | Rectangle { 391 | color: "#000000" 392 | x: 16 393 | width: 768 394 | height: 128 395 | anchors.top: parent.top 396 | anchors.topMargin: 0 397 | } 398 | 399 | Canvas { 400 | id: canvas; 401 | x: 16 402 | width: 768 403 | height: 128 404 | contextType: qsTr("2d") 405 | anchors.top: parent.top 406 | anchors.topMargin: 0 407 | property int grabRadius: 20 408 | 409 | onPaint: { 410 | var ctx = getContext("2d") 411 | 412 | ctx.fillStyle = "black" 413 | ctx.fillRect(0, 0, canvas.width, canvas.height) 414 | 415 | var egL0=synthModel.pitchEG_L0/2; 416 | var egL1=synthModel.pitchEG_L1/2; 417 | var egL2=synthModel.pitchEG_L2/2; 418 | var egL3=synthModel.pitchEG_L3/2; 419 | var egL4=synthModel.pitchEG_L4/2; 420 | var egL5=synthModel.pitchEG_L5/2; 421 | var egR0=synthModel.pitchEG_R0/2; 422 | var egR1=synthModel.pitchEG_R1/2; 423 | var egR2=synthModel.pitchEG_R2/2; 424 | var egR3=synthModel.pitchEG_R3/2; 425 | var egR4=synthModel.pitchEG_R4/2; 426 | var egR5=synthModel.pitchEG_R5/2; 427 | 428 | if (egL0 > 127) egL0=127; 429 | if (egL1 > 127) egL1=127; 430 | if (egL2 > 127) egL2=127; 431 | if (egL3 > 127) egL3=127; 432 | if (egL4 > 127) egL4=127; 433 | if (egL5 > 127) egL5=127; 434 | if (egR0 > 127) egR0=127; 435 | if (egR1 > 127) egR1=127; 436 | if (egR2 > 127) egR2=127; 437 | if (egR3 > 127) egR3=127; 438 | if (egR4 > 127) egR4=127; 439 | if (egR5 > 127) egR5=127; 440 | 441 | ctx.lineWidth=1; 442 | ctx.strokeStyle="lightgray"; 443 | ctx.beginPath(); 444 | ctx.moveTo(egR0, 0); 445 | ctx.lineTo(egR0, canvas.height-1); 446 | ctx.moveTo(egR0+egR1, 0); 447 | ctx.lineTo(egR0+egR1, canvas.height-1); 448 | ctx.moveTo(egR0+egR1+egR2, 0); 449 | ctx.lineTo(egR0+egR1+egR2, canvas.height-1); 450 | ctx.moveTo(egR0+egR1+egR2+egR3, 0); 451 | ctx.lineTo(egR0+egR1+egR2+egR3, canvas.height-1); 452 | ctx.moveTo(egR0+egR1+egR2+egR3+egR4, 0); 453 | ctx.lineTo(egR0+egR1+egR2+egR3+egR4, canvas.height-1); 454 | ctx.moveTo(egR0+egR1+egR2+egR3+egR4+egR5, 0); 455 | ctx.lineTo(egR0+egR1+egR2+egR3+egR4+egR5, canvas.height-1); 456 | ctx.stroke(); 457 | 458 | ctx.lineWidth = 5; 459 | ctx.strokeStyle = "yellow" 460 | ctx.beginPath() 461 | ctx.moveTo(0, canvas.height-1); 462 | ctx.lineTo(egR0, canvas.height-egL0-1); 463 | ctx.lineTo(egR0+egR1, canvas.height-egL1-1); 464 | ctx.lineTo(egR0+egR1+egR2, canvas.height-egL2-1); 465 | ctx.lineTo(egR0+egR1+egR2+egR3, canvas.height-egL3-1); 466 | ctx.lineTo(egR0+egR1+egR2+egR3+egR4, canvas.height-egL4-1); 467 | ctx.lineTo(egR0+egR1+egR2+egR3+egR4+egR5, canvas.height-egL5-1); 468 | ctx.lineTo(canvas.width-1, canvas.height-egL5-1); 469 | //ctx.closePath() 470 | ctx.stroke() 471 | 472 | ctx.fillStyle="yellow" 473 | ctx.fillRect(egR0-5, canvas.height-egL0-5-1, 10, 10); 474 | ctx.fillRect(egR0+egR1-5, canvas.height-egL1-5-1, 10, 10); 475 | ctx.fillRect(egR0+egR1+egR2-5, canvas.height-egL2-5-1, 10, 10); 476 | ctx.fillRect(egR0+egR1+egR2+egR3-5, canvas.height-egL3-5-1, 10, 10); 477 | ctx.fillRect(egR0+egR1+egR2+egR3+egR4-5, canvas.height-egL4-5-1, 10, 10); 478 | ctx.fillRect(egR0+egR1+egR2+egR3+egR4+egR5-5, canvas.height-egL5-5-1, 10, 10); 479 | 480 | ctx.fillStyle = "white"; 481 | ctx.font = "16px monospace"; 482 | ctx.fillText("L0: "+egL0.toFixed(0), canvas.width-160, 16); 483 | ctx.fillText("L1: "+egL1.toFixed(0), canvas.width-160, 32); 484 | ctx.fillText("L2: "+egL2.toFixed(0), canvas.width-160, 48); 485 | ctx.fillText("L3: "+egL3.toFixed(0), canvas.width-160, 64); 486 | ctx.fillText("L4: "+egL4.toFixed(0), canvas.width-160, 80); 487 | ctx.fillText("L5: "+egL5.toFixed(0), canvas.width-160, 96); 488 | 489 | ctx.fillText("R0: "+egR0.toFixed(0), canvas.width-80, 16); 490 | ctx.fillText("R1: "+egR1.toFixed(0), canvas.width-80, 32); 491 | ctx.fillText("R2: "+egR2.toFixed(0), canvas.width-80, 48); 492 | ctx.fillText("R3: "+egR3.toFixed(0), canvas.width-80, 64); 493 | ctx.fillText("R4: "+egR4.toFixed(0), canvas.width-80, 80); 494 | ctx.fillText("R5: "+egR5.toFixed(0), canvas.width-80, 96); 495 | } 496 | 497 | MouseArea { 498 | id: mouseArea; 499 | anchors.fill: parent 500 | property int updatingPoint: -1 501 | property int deltaX: 0 502 | property int deltaY: 0 503 | 504 | onPressed: { 505 | var x=mouseArea.mouseX 506 | var y=mouseArea.mouseY 507 | 508 | var egL0=synthModel.pitchEG_L0/2; 509 | var egL1=synthModel.pitchEG_L1/2; 510 | var egL2=synthModel.pitchEG_L2/2; 511 | var egL3=synthModel.pitchEG_L3/2; 512 | var egL4=synthModel.pitchEG_L4/2; 513 | var egL5=synthModel.pitchEG_L5/2; 514 | var egR0=synthModel.pitchEG_R0/2; 515 | var egR1=synthModel.pitchEG_R1/2; 516 | var egR2=synthModel.pitchEG_R2/2; 517 | var egR3=synthModel.pitchEG_R3/2; 518 | var egR4=synthModel.pitchEG_R4/2; 519 | var egR5=synthModel.pitchEG_R5/2; 520 | 521 | if (x >= egR0-canvas.grabRadius && x <= egR0+canvas.grabRadius && y >= canvas.height-egL0-canvas.grabRadius-1 && y <= canvas.height-egL0+canvas.grabRadius-1) { 522 | console.debug("R0,L0 clicked") 523 | updatingPoint=0; 524 | deltaX=x-(egR0-canvas.grabRadius); 525 | deltaY=y-(canvas.height-egL0-canvas.grabRadius-1); 526 | } else if (x >= egR0+egR1-canvas.grabRadius && x <= egR0+egR1+canvas.grabRadius && y >= canvas.height-egL1-canvas.grabRadius-1 && y <= canvas.height-egL1+canvas.grabRadius-1) { 527 | console.debug("R1,L1 clicked") 528 | updatingPoint=1; 529 | deltaX=x-(egR0+egR1-canvas.grabRadius); 530 | deltaY=y-(canvas.height-egL1-canvas.grabRadius-1); 531 | } else if (x >= egR0+egR1+egR2-canvas.grabRadius && x <= egR0+egR1+egR2+canvas.grabRadius && y >= canvas.height-egL2-canvas.grabRadius-1 && y <= canvas.height-egL2+canvas.grabRadius-1) { 532 | console.debug("R2,L2 clicked") 533 | updatingPoint=2; 534 | deltaX=x-(egR0+egR1+egR2-canvas.grabRadius); 535 | deltaY=y-(canvas.height-egL2-canvas.grabRadius-1); 536 | } else if (x >= egR0+egR1+egR2+egR3-canvas.grabRadius && x <= egR0+egR1+egR2+egR3+canvas.grabRadius && y >= canvas.height-egL3-canvas.grabRadius-1 && y <= canvas.height-egL3+canvas.grabRadius-1) { 537 | console.debug("R3,L3 clicked") 538 | updatingPoint=3; 539 | deltaX=x-(egR0+egR1+egR2+egR3-canvas.grabRadius); 540 | deltaY=y-(canvas.height-egL3-canvas.grabRadius-1); 541 | } else if (x >= egR0+egR1+egR2+egR3+egR4-canvas.grabRadius && x <= egR0+egR1+egR2+egR3+egR4+canvas.grabRadius && y >= canvas.height-egL4-canvas.grabRadius-1 && y <= canvas.height-egL4+canvas.grabRadius-1) { 542 | console.debug("R4,L4 clicked") 543 | updatingPoint=4; 544 | deltaX=x-(egR0+egR1+egR2+egR3+egR4-canvas.grabRadius); 545 | deltaY=y-(canvas.height-egL4-canvas.grabRadius-1); 546 | } else if (x >= egR0+egR1+egR2+egR3+egR4+egR5-canvas.grabRadius && x <= egR0+egR1+egR2+egR3+egR4+egR5+canvas.grabRadius && y >= canvas.height-egL5-canvas.grabRadius-1 && y <= canvas.height-egL5+canvas.grabRadius-1) { 547 | console.debug("R5,L5 clicked") 548 | updatingPoint=5; 549 | deltaX=x-(egR0+egR1+egR2+egR3+egR4+egR5-canvas.grabRadius); 550 | deltaY=y-(canvas.height-egL5-canvas.grabRadius-1); 551 | } 552 | } 553 | 554 | onPositionChanged: { 555 | var x=mouseArea.mouseX-deltaX 556 | var y=mouseArea.mouseY-deltaY 557 | var rate; 558 | var level; 559 | 560 | if (updatingPoint == 0) { 561 | rate=x; 562 | 563 | if (rate < 0) { 564 | rate=0; 565 | } else if (rate > 127) { 566 | rate=127; 567 | } 568 | 569 | level=canvas.height-y-1; 570 | 571 | if (level < 0) { 572 | level=0; 573 | } else if (level > 127) { 574 | level=127; 575 | } 576 | 577 | synthModel.pitchEG_L0=level*2; 578 | synthModel.pitchEG_R0=rate*2; 579 | canvas.requestPaint(); 580 | } else if (updatingPoint == 1) { 581 | rate=x-synthModel.pitchEG_R0/2; 582 | 583 | if (rate < 0) { 584 | rate=0; 585 | } else if (rate > 127) { 586 | rate=127; 587 | } 588 | 589 | level=canvas.height-y-1; 590 | 591 | if (level < 0) { 592 | level=0; 593 | } else if (level > 127) { 594 | level=127; 595 | } 596 | 597 | synthModel.pitchEG_L1=level*2; 598 | synthModel.pitchEG_R1=rate*2; 599 | canvas.requestPaint(); 600 | } else if (updatingPoint == 2) { 601 | rate=x-(synthModel.pitchEG_R0+synthModel.pitchEG_R1)/2; 602 | 603 | if (rate < 0) { 604 | rate=0; 605 | } else if (rate > 127) { 606 | rate=127; 607 | } 608 | 609 | level=canvas.height-y-1; 610 | 611 | if (level < 0) { 612 | level=0; 613 | } else if (level > 127) { 614 | level=127; 615 | } 616 | 617 | synthModel.pitchEG_L2=level*2; 618 | synthModel.pitchEG_R2=rate*2; 619 | canvas.requestPaint(); 620 | } else if (updatingPoint == 3) { 621 | rate=x-(synthModel.pitchEG_R0+synthModel.pitchEG_R1+synthModel.pitchEG_R2)/2; 622 | 623 | if (rate < 0) { 624 | rate=0; 625 | } else if (rate > 127) { 626 | rate=127; 627 | } 628 | 629 | level=canvas.height-y-1; 630 | 631 | if (level < 0) { 632 | level=0; 633 | } else if (level > 127) { 634 | level=127; 635 | } 636 | 637 | synthModel.pitchEG_L3=level*2; 638 | synthModel.pitchEG_R3=rate*2; 639 | canvas.requestPaint(); 640 | } else if (updatingPoint == 4) { 641 | rate=x-(synthModel.pitchEG_R0+synthModel.pitchEG_R1+synthModel.pitchEG_R2+synthModel.pitchEG_R3)/2; 642 | 643 | if (rate < 0) { 644 | rate=0; 645 | } else if (rate > 127) { 646 | rate=127; 647 | } 648 | 649 | level=canvas.height-y-1; 650 | 651 | if (level < 0) { 652 | level=0; 653 | } else if (level > 127) { 654 | level=127; 655 | } 656 | 657 | synthModel.pitchEG_L4=level*2; 658 | synthModel.pitchEG_R4=rate*2; 659 | canvas.requestPaint(); 660 | } else if (updatingPoint == 5) { 661 | rate=x-(synthModel.pitchEG_R0+synthModel.pitchEG_R1+synthModel.pitchEG_R2+synthModel.pitchEG_R3+synthModel.pitchEG_R4)/2; 662 | 663 | if (rate < 0) { 664 | rate=0; 665 | } else if (rate > 127) { 666 | rate=127; 667 | } 668 | 669 | level=canvas.height-y-1; 670 | 671 | if (level < 0) { 672 | level=0; 673 | } else if (level > 127) { 674 | level=127; 675 | } 676 | 677 | synthModel.pitchEG_L5=level*2; 678 | synthModel.pitchEG_R5=rate*2; 679 | canvas.requestPaint(); 680 | } 681 | } 682 | 683 | onReleased: { 684 | updatingPoint=-1; 685 | } 686 | 687 | } 688 | } 689 | 690 | CustomDial { 691 | id: dialRange 692 | x: 16 693 | y: 139 694 | width: 121 695 | height: 80 696 | maximumValue: 127 697 | stepSize: 1 698 | onValueChanged: { 699 | synthModel.pitchEG_Range=dialRange.value; 700 | } 701 | } 702 | 703 | Label { 704 | id: label 705 | x: 55 706 | y: 225 707 | color: "#ffffff" 708 | text: qsTr("Range") 709 | font.pixelSize: 16 710 | } 711 | 712 | CustomDial { 713 | id: dialVelocity 714 | x: 143 715 | y: 139 716 | width: 121 717 | height: 80 718 | maximumValue: 255 719 | stepSize: 1 720 | onValueChanged: { 721 | synthModel.pitchEG_Velocity=dialVelocity.value; 722 | } 723 | } 724 | 725 | Label { 726 | id: label1 727 | x: 176 728 | y: 225 729 | color: "#ffffff" 730 | text: qsTr("Velocity") 731 | font.pixelSize: 16 732 | } 733 | 734 | CustomDial { 735 | id: dialRateKey 736 | x: 16 737 | y: 248 738 | width: 121 739 | height: 80 740 | maximumValue: 255 741 | stepSize: 1 742 | onValueChanged: { 743 | synthModel.pitchEG_RateKey=dialRateKey.value; 744 | } 745 | } 746 | 747 | Label { 748 | id: label2 749 | x: 49 750 | y: 334 751 | color: "#ffffff" 752 | text: qsTr("Rate Key") 753 | font.pixelSize: 16 754 | } 755 | 756 | Label { 757 | id: label3 758 | x: 330 759 | y: 171 760 | color: "#ffffff" 761 | text: qsTr("Envelope Loop") 762 | font.pixelSize: 16 763 | } 764 | 765 | Label { 766 | id: label4 767 | x: 304 768 | y: 240 769 | color: "#ffffff" 770 | text: qsTr("Env Loop Segment") 771 | font.pixelSize: 16 772 | } 773 | 774 | Component.onCompleted: { 775 | updatePage(); 776 | } 777 | 778 | } 779 | 780 | /*##^## 781 | Designer { 782 | D{i:1;anchors_height:400;anchors_x:0;anchors_y:0} 783 | } 784 | ##^##*/ 785 | --------------------------------------------------------------------------------