1 |
schoenebeck |
1667 |
// qsamplerChannelFxForm.cpp |
2 |
|
|
// |
3 |
|
|
/**************************************************************************** |
4 |
|
|
Copyright (C) 2008, Christian Schoenebeck |
5 |
|
|
|
6 |
|
|
This program is free software; you can redistribute it and/or |
7 |
|
|
modify it under the terms of the GNU General Public License |
8 |
|
|
as published by the Free Software Foundation; either version 2 |
9 |
|
|
of the License, or (at your option) any later version. |
10 |
|
|
|
11 |
|
|
This program is distributed in the hope that it will be useful, |
12 |
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 |
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 |
|
|
GNU General Public License for more details. |
15 |
|
|
|
16 |
|
|
You should have received a copy of the GNU General Public License |
17 |
|
|
along with this program; if not, write to the Free Software |
18 |
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
19 |
|
|
|
20 |
|
|
*****************************************************************************/ |
21 |
|
|
|
22 |
|
|
#include "qsamplerAbout.h" |
23 |
|
|
#include "qsamplerChannelFxForm.h" |
24 |
|
|
#include "qsamplerFxSendsModel.h" |
25 |
|
|
|
26 |
schoenebeck |
1668 |
// let's not reinvent the wheel for audio routing |
27 |
|
|
#include "qsamplerChannel.h" |
28 |
|
|
|
29 |
|
|
#include <math.h> |
30 |
|
|
|
31 |
schoenebeck |
1667 |
#include <QAbstractButton> |
32 |
schoenebeck |
1668 |
#include <QLineEdit> |
33 |
|
|
#include <QHeaderView> |
34 |
|
|
#include <QMap> |
35 |
schoenebeck |
1667 |
|
36 |
schoenebeck |
1668 |
namespace { // private namespace |
37 |
|
|
|
38 |
|
|
static const char* _midiControllerName(int iMidiCtrl) { |
39 |
|
|
switch (iMidiCtrl) { |
40 |
|
|
case 0: return "Bank select MSB"; |
41 |
|
|
case 1: return "Modulation MSB"; |
42 |
|
|
case 2: return "Breath Controller"; |
43 |
|
|
case 4: return "Foot Controller MSB"; |
44 |
|
|
case 5: return "Portamento Time MSB"; |
45 |
|
|
case 6: return "(N)RPN Data Byte"; |
46 |
|
|
case 7: return "Main Volume"; |
47 |
|
|
case 8: return "Balance"; |
48 |
|
|
case 10: return "Panorama"; |
49 |
|
|
case 11: return "Expression"; |
50 |
|
|
case 12: return "Effect Control 1"; |
51 |
|
|
case 13: return "Effect Control 2"; |
52 |
|
|
case 16: return "General Purpose Controller 1"; |
53 |
|
|
case 17: return "General Purpose Controller 2"; |
54 |
|
|
case 18: return "General Purpose Controller 3"; |
55 |
|
|
case 19: return "General Purpose Controller 4"; |
56 |
|
|
case 32: return "Bank select LSB"; |
57 |
|
|
case 63: return "LSB for Controllers 0?31"; |
58 |
|
|
case 64: return "Hold 1"; |
59 |
|
|
case 65: return "Portamento"; |
60 |
|
|
case 66: return "Sostenuto"; |
61 |
|
|
case 67: return "Soft Pedal"; |
62 |
|
|
case 68: return "Legato Footswitch"; |
63 |
|
|
case 69: return "Hold 2"; |
64 |
|
|
case 70: return "Sound Controller 1 (Sound Variation)"; |
65 |
|
|
case 71: return "Sound Controller 2 (Harmonic Content)"; |
66 |
|
|
case 72: return "Sound Controller 3 (Release Time)"; |
67 |
|
|
case 73: return "Sound Controller 4 (Attack Time)"; |
68 |
|
|
case 74: return "Sound Controller 5 (Brightness)"; |
69 |
|
|
case 75: return "Sound Controller 6"; |
70 |
|
|
case 76: return "Sound Controller 7"; |
71 |
|
|
case 77: return "Sound Controller 8"; |
72 |
|
|
case 78: return "Sound Controller 9"; |
73 |
|
|
case 79: return "Sound Controller 10"; |
74 |
|
|
case 80: return "General Purpose 5"; |
75 |
|
|
case 81: return "General Purpose 6"; |
76 |
|
|
case 82: return "General Purpose 7"; |
77 |
|
|
case 83: return "General Purpose 8"; |
78 |
|
|
case 84: return "Portamento Control"; |
79 |
|
|
case 91: return "Effects 1 Depth"; |
80 |
|
|
case 92: return "Effects 2 Depth"; |
81 |
|
|
case 93: return "Effects 3 Depth"; |
82 |
|
|
case 94: return "Effects 4 Depth"; |
83 |
|
|
case 95: return "Effects 5 Depth"; |
84 |
|
|
case 96: return "Data Increment (N)RPN"; |
85 |
|
|
case 97: return "Data Decrement (N)RPN"; |
86 |
|
|
case 98: return "NRPN LSB"; |
87 |
|
|
case 99: return "NRPN MSB"; |
88 |
|
|
case 100: return "RPN LSB"; |
89 |
|
|
case 101: return "RPN MSB"; |
90 |
|
|
case 120: return "All Sounds Off"; |
91 |
|
|
case 121: return "Controller Reset"; |
92 |
|
|
case 122: return "Local Control on/off"; |
93 |
|
|
case 123: return "All Notes Off"; |
94 |
|
|
case 124: return "Omni Off"; |
95 |
|
|
case 125: return "Omni On"; |
96 |
|
|
case 126: return "Mono On / Poly Off"; |
97 |
|
|
case 127: return "Poly On / Mono Off"; |
98 |
|
|
default: return ""; |
99 |
|
|
} |
100 |
|
|
} |
101 |
|
|
|
102 |
|
|
} // private namespace |
103 |
|
|
|
104 |
|
|
|
105 |
schoenebeck |
1667 |
namespace QSampler { |
106 |
|
|
|
107 |
|
|
ChannelFxForm::ChannelFxForm ( |
108 |
schoenebeck |
1668 |
Channel* pSamplerChannel, QWidget* pParent, Qt::WindowFlags wflags ) |
109 |
schoenebeck |
1667 |
: QDialog(pParent, wflags) |
110 |
|
|
{ |
111 |
|
|
m_ui.setupUi(this); |
112 |
|
|
|
113 |
schoenebeck |
1668 |
m_pSamplerChannel = pSamplerChannel; |
114 |
schoenebeck |
1667 |
|
115 |
schoenebeck |
1668 |
m_pAudioDevice = NULL; |
116 |
|
|
|
117 |
schoenebeck |
1667 |
FxSendsModel* pModel = |
118 |
schoenebeck |
1668 |
new FxSendsModel(m_pSamplerChannel->channelID(), m_ui.SendsListView); |
119 |
schoenebeck |
1667 |
m_ui.SendsListView->setModel(pModel); |
120 |
|
|
|
121 |
schoenebeck |
1668 |
const int iRowHeight = m_ui.audioRoutingTable->fontMetrics().height() + 4; |
122 |
|
|
m_ui.audioRoutingTable->verticalHeader()->setDefaultSectionSize(iRowHeight); |
123 |
|
|
m_ui.audioRoutingTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); |
124 |
|
|
ChannelRoutingModel* pRoutingModel = |
125 |
|
|
new ChannelRoutingModel(m_ui.audioRoutingTable); |
126 |
|
|
m_ui.audioRoutingTable->setModel(pRoutingModel); |
127 |
|
|
ChannelRoutingDelegate* pRoutingDelegate = |
128 |
|
|
new ChannelRoutingDelegate(m_ui.audioRoutingTable); |
129 |
|
|
m_ui.audioRoutingTable->setItemDelegate(pRoutingDelegate); |
130 |
|
|
m_ui.audioRoutingTable->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch); |
131 |
|
|
// m_ui.audioRoutingTable->verticalHeader()->hide(); |
132 |
|
|
|
133 |
schoenebeck |
1667 |
QAbstractButton* pApplyButton = |
134 |
|
|
m_ui.buttonBox->button(QDialogButtonBox::Apply); |
135 |
|
|
pApplyButton->setEnabled(false); |
136 |
|
|
pApplyButton->setIcon(QIcon(":/icons/formEdit.png")); |
137 |
|
|
|
138 |
|
|
QAbstractButton* pCancelButton = |
139 |
|
|
m_ui.buttonBox->button(QDialogButtonBox::Cancel); |
140 |
|
|
pCancelButton->setIcon(QIcon(":/icons/formRemove.png")); |
141 |
|
|
|
142 |
|
|
QAbstractButton* pOkButton = |
143 |
|
|
m_ui.buttonBox->button(QDialogButtonBox::Ok); |
144 |
|
|
pOkButton->setIcon(QIcon(":/icons/formAccept.png")); |
145 |
|
|
|
146 |
|
|
QAbstractButton* pResetButton = |
147 |
|
|
m_ui.buttonBox->button(QDialogButtonBox::Reset); |
148 |
|
|
pResetButton->setEnabled(false); |
149 |
schoenebeck |
1668 |
pResetButton->setToolTip("Revert all changes."); |
150 |
schoenebeck |
1667 |
|
151 |
|
|
m_ui.destroyPushButton->setEnabled(false); |
152 |
|
|
|
153 |
schoenebeck |
1668 |
m_ui.mainParametersGroupBox->setEnabled(false); |
154 |
|
|
m_ui.audioRoutingGroupBox->setEnabled(false); |
155 |
|
|
|
156 |
|
|
for (int i = 0; i < 128; ++i) { |
157 |
|
|
m_ui.depthCtrlComboBox->addItem( |
158 |
|
|
QString("[") + QString::number(i) + "] " + _midiControllerName(i) |
159 |
|
|
); |
160 |
|
|
} |
161 |
|
|
|
162 |
schoenebeck |
1667 |
connect( |
163 |
|
|
m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), |
164 |
|
|
this, SLOT(onButtonClicked(QAbstractButton*)) |
165 |
|
|
); |
166 |
|
|
connect( |
167 |
|
|
m_ui.createPushButton, SIGNAL(clicked()), |
168 |
|
|
this, SLOT(onCreateFxSend()) |
169 |
|
|
); |
170 |
|
|
connect( |
171 |
|
|
m_ui.destroyPushButton, SIGNAL(clicked()), |
172 |
|
|
this, SLOT(onDestroyFxSend()) |
173 |
|
|
); |
174 |
|
|
connect( |
175 |
|
|
pModel, SIGNAL(fxSendsDirtyChanged(bool)), |
176 |
|
|
pApplyButton, SLOT(setEnabled(bool)) |
177 |
|
|
); |
178 |
|
|
connect( |
179 |
|
|
pModel, SIGNAL(fxSendsDirtyChanged(bool)), |
180 |
|
|
pResetButton, SLOT(setEnabled(bool)) |
181 |
|
|
); |
182 |
|
|
connect( |
183 |
|
|
m_ui.SendsListView, SIGNAL(clicked(const QModelIndex&)), |
184 |
|
|
this, SLOT(onFxSendSelection(const QModelIndex&)) |
185 |
|
|
); |
186 |
schoenebeck |
1668 |
connect( |
187 |
|
|
m_ui.depthCtrlComboBox, SIGNAL(activated(int)), |
188 |
|
|
this, SLOT(onDepthCtrlChanged(int)) |
189 |
|
|
); |
190 |
|
|
connect( |
191 |
|
|
m_ui.depthSpinBox, SIGNAL(valueChanged(int)), |
192 |
|
|
this, SLOT(onCurrentSendDepthChanged(int)) |
193 |
|
|
); |
194 |
|
|
connect( |
195 |
|
|
pRoutingModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), |
196 |
|
|
this, SLOT(updateTableCellRenderers(const QModelIndex&, const QModelIndex&)) |
197 |
|
|
); |
198 |
|
|
connect( |
199 |
|
|
pRoutingModel, SIGNAL(modelReset()), |
200 |
|
|
this, SLOT(updateTableCellRenderers()) |
201 |
|
|
); |
202 |
|
|
connect( |
203 |
|
|
pRoutingModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), |
204 |
|
|
this, SLOT(onRoutingTableChanged()) |
205 |
|
|
); |
206 |
schoenebeck |
1667 |
} |
207 |
|
|
|
208 |
|
|
ChannelFxForm::~ChannelFxForm() { |
209 |
schoenebeck |
1668 |
if (m_pAudioDevice) delete m_pAudioDevice; |
210 |
schoenebeck |
1667 |
} |
211 |
|
|
|
212 |
|
|
void ChannelFxForm::onButtonClicked(QAbstractButton* button) { |
213 |
|
|
FxSendsModel* pModel = (FxSendsModel*) m_ui.SendsListView->model(); |
214 |
|
|
switch (m_ui.buttonBox->buttonRole(button)) { |
215 |
|
|
case QDialogButtonBox::AcceptRole: |
216 |
|
|
case QDialogButtonBox::ApplyRole: |
217 |
|
|
pModel->applyToSampler(); |
218 |
schoenebeck |
1668 |
// force a refresh of the parameter control elements |
219 |
|
|
onFxSendSelection(m_ui.SendsListView->currentIndex()); |
220 |
schoenebeck |
1667 |
break; |
221 |
|
|
case QDialogButtonBox::ResetRole: |
222 |
|
|
pModel->cleanRefresh(); |
223 |
schoenebeck |
1668 |
// force a refresh of the parameter control elements |
224 |
|
|
onFxSendSelection(m_ui.SendsListView->currentIndex()); |
225 |
schoenebeck |
1667 |
break; |
226 |
|
|
default: // to avoid gcc warnings |
227 |
|
|
break; |
228 |
|
|
} |
229 |
|
|
} |
230 |
|
|
|
231 |
|
|
void ChannelFxForm::onFxSendSelection(const QModelIndex& index) { |
232 |
schoenebeck |
1668 |
const bool bValid = index.isValid(); |
233 |
|
|
m_ui.destroyPushButton->setEnabled(bValid); |
234 |
|
|
m_ui.mainParametersGroupBox->setEnabled(bValid); |
235 |
|
|
m_ui.audioRoutingGroupBox->setEnabled(bValid); |
236 |
|
|
|
237 |
|
|
FxSendsModel* pModel = (FxSendsModel*) m_ui.SendsListView->model(); |
238 |
|
|
FxSend* pFxSend = pModel->fxSend(index); |
239 |
|
|
|
240 |
|
|
// clear routing model |
241 |
|
|
ChannelRoutingModel* pRoutingModel = |
242 |
|
|
(ChannelRoutingModel*) m_ui.audioRoutingTable->model(); |
243 |
|
|
pRoutingModel->refresh(NULL, ChannelRoutingMap()); |
244 |
|
|
pRoutingModel->routingMap().clear(); // Reset routing change map. |
245 |
|
|
if (m_pAudioDevice) { |
246 |
|
|
delete m_pAudioDevice; |
247 |
|
|
m_pAudioDevice = NULL; |
248 |
|
|
} |
249 |
|
|
|
250 |
|
|
if (!pFxSend) return; |
251 |
|
|
|
252 |
|
|
// update routing model |
253 |
|
|
if (m_pSamplerChannel->audioDevice() >= 0) { |
254 |
|
|
m_pAudioDevice = |
255 |
|
|
new Device(Device::Audio, m_pSamplerChannel->audioDevice()); |
256 |
|
|
pRoutingModel->refresh(m_pAudioDevice, pFxSend->audioRouting()); |
257 |
|
|
} |
258 |
|
|
|
259 |
|
|
m_ui.depthCtrlComboBox->setCurrentIndex(pFxSend->sendDepthMidiCtrl()); |
260 |
|
|
m_ui.depthSpinBox->setValue( |
261 |
|
|
::round(pFxSend->currentDepth() * 100.0) |
262 |
|
|
); |
263 |
schoenebeck |
1667 |
} |
264 |
|
|
|
265 |
|
|
void ChannelFxForm::onCreateFxSend() { |
266 |
|
|
FxSendsModel* pModel = (FxSendsModel*) m_ui.SendsListView->model(); |
267 |
|
|
pModel->addFxSend(); |
268 |
|
|
} |
269 |
|
|
|
270 |
|
|
void ChannelFxForm::onDestroyFxSend() { |
271 |
|
|
FxSendsModel* pModel = (FxSendsModel*) m_ui.SendsListView->model(); |
272 |
|
|
pModel->removeFxSend(m_ui.SendsListView->currentIndex()); |
273 |
|
|
} |
274 |
|
|
|
275 |
schoenebeck |
1668 |
void ChannelFxForm::onDepthCtrlChanged(int iMidiCtrl) { |
276 |
|
|
FxSendsModel* pModel = (FxSendsModel*) m_ui.SendsListView->model(); |
277 |
|
|
const QModelIndex index = m_ui.SendsListView->currentIndex(); |
278 |
|
|
FxSend* pFxSend = pModel->fxSend(index); |
279 |
|
|
if (!pFxSend) return; |
280 |
|
|
|
281 |
|
|
pFxSend->setSendDepthMidiCtrl(iMidiCtrl); |
282 |
|
|
pModel->onExternalModifiication(index); |
283 |
|
|
} |
284 |
|
|
|
285 |
|
|
void ChannelFxForm::onCurrentSendDepthChanged(int depthPercent) { |
286 |
|
|
FxSendsModel* pModel = (FxSendsModel*) m_ui.SendsListView->model(); |
287 |
|
|
const QModelIndex index = m_ui.SendsListView->currentIndex(); |
288 |
|
|
FxSend* pFxSend = pModel->fxSend(index); |
289 |
|
|
if (!pFxSend) return; |
290 |
|
|
|
291 |
|
|
if (depthPercent == int( ::round(pFxSend->currentDepth() * 100.0) )) |
292 |
|
|
return; // nothing changed actually |
293 |
|
|
|
294 |
|
|
pFxSend->setCurrentDepth(double(depthPercent) / 100.0); |
295 |
|
|
pModel->onExternalModifiication(index); |
296 |
|
|
} |
297 |
|
|
|
298 |
|
|
void ChannelFxForm::onRoutingTableChanged() { |
299 |
|
|
ChannelRoutingModel* pRoutingModel = |
300 |
|
|
(ChannelRoutingModel*) m_ui.audioRoutingTable->model(); |
301 |
|
|
if (pRoutingModel->routingMap().size() <= 0) |
302 |
|
|
return; // no changes |
303 |
|
|
|
304 |
|
|
FxSendsModel* pModel = (FxSendsModel*) m_ui.SendsListView->model(); |
305 |
|
|
const QModelIndex index = m_ui.SendsListView->currentIndex(); |
306 |
|
|
FxSend* pFxSend = pModel->fxSend(index); |
307 |
|
|
if (!pFxSend) { |
308 |
|
|
pRoutingModel->routingMap().clear(); // reset routing change map |
309 |
|
|
return; |
310 |
|
|
} |
311 |
|
|
|
312 |
|
|
ChannelRoutingMap routingMap = pRoutingModel->routingMap(); |
313 |
|
|
for ( |
314 |
|
|
ChannelRoutingMap::iterator iter = routingMap.begin(); |
315 |
|
|
iter != routingMap.end(); ++iter |
316 |
|
|
) pFxSend->setAudioChannel(iter.key(), iter.value()); |
317 |
|
|
|
318 |
|
|
pRoutingModel->routingMap().clear(); // reset routing change map |
319 |
|
|
|
320 |
|
|
pModel->onExternalModifiication(index); |
321 |
|
|
} |
322 |
|
|
|
323 |
|
|
void ChannelFxForm::updateTableCellRenderers() { |
324 |
|
|
ChannelRoutingModel* pRoutingModel = |
325 |
|
|
(ChannelRoutingModel*) m_ui.audioRoutingTable->model(); |
326 |
|
|
const int rows = pRoutingModel->rowCount(); |
327 |
|
|
const int cols = pRoutingModel->columnCount(); |
328 |
|
|
updateTableCellRenderers( |
329 |
|
|
pRoutingModel->index(0, 0), |
330 |
|
|
pRoutingModel->index(rows - 1, cols - 1) |
331 |
|
|
); |
332 |
|
|
} |
333 |
|
|
|
334 |
|
|
void ChannelFxForm::updateTableCellRenderers ( |
335 |
|
|
const QModelIndex& topLeft, const QModelIndex& bottomRight ) |
336 |
|
|
{ |
337 |
|
|
ChannelRoutingModel* pRoutingModel = |
338 |
|
|
(ChannelRoutingModel*) m_ui.audioRoutingTable->model(); |
339 |
|
|
for (int r = topLeft.row(); r <= bottomRight.row(); r++) { |
340 |
|
|
for (int c = topLeft.column(); c <= bottomRight.column(); c++) { |
341 |
|
|
const QModelIndex index = pRoutingModel->index(r, c); |
342 |
|
|
m_ui.audioRoutingTable->openPersistentEditor(index); |
343 |
|
|
} |
344 |
|
|
} |
345 |
|
|
} |
346 |
|
|
|
347 |
schoenebeck |
1667 |
} // namespace QSampler |
348 |
|
|
|
349 |
|
|
// end of qsamplerChannelFxForm.cpp |