1 // qsynthOptions.cpp
2 //
3 /****************************************************************************
4 Copyright (C) 2003-2020, rncbc aka Rui Nuno Capela. All rights reserved.
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 along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 *****************************************************************************/
21
22 #include "qsynthAbout.h"
23 #include "qsynthOptions.h"
24
25 #include "qsynthEngine.h"
26
27 #include <QTextStream>
28 #include <QComboBox>
29
30 #include <QApplication>
31
32 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
33 #include <QDesktopWidget>
34 #endif
35
36
37 //-------------------------------------------------------------------------
38 // qsynthOptions - Prototype settings structure.
39 //
40
41 // Constructor.
qsynthOptions(void)42 qsynthOptions::qsynthOptions (void)
43 : m_settings(QSYNTH_DOMAIN, QSYNTH_TITLE)
44 {
45 // Create default setup descriptor.
46 m_pDefaultSetup = new qsynthSetup();
47 // Load previous/default fluidsynth settings...
48 loadSetup(m_pDefaultSetup, QString());
49
50 loadOptions();
51 }
52
53
54 // Default Destructor.
~qsynthOptions(void)55 qsynthOptions::~qsynthOptions (void)
56 {
57 saveOptions();
58
59 // Delete default setup descriptor.
60 delete m_pDefaultSetup;
61 m_pDefaultSetup = nullptr;
62 }
63
64
65
66
67 // Explicit load method.
loadOptions(void)68 void qsynthOptions::loadOptions (void)
69 {
70 // Load defaults...
71 m_settings.beginGroup("/Defaults");
72 sSoundFontDir = m_settings.value("/SoundFontDir").toString().isEmpty() ? "/usr/local/share/sounds/sf2" : m_settings.value("/SoundFontDir").toString();
73 bPresetPreview = m_settings.value("/PresetPreview", false).toBool();
74 m_settings.endGroup();
75
76 // Load display options...
77 m_settings.beginGroup("/Options");
78 sMessagesFont = m_settings.value("/MessagesFont").toString();
79 bMessagesLimit = m_settings.value("/MessagesLimit", true).toBool();
80 iMessagesLimitLines = m_settings.value("/MessagesLimitLines", 1000).toInt();
81 bMessagesLog = m_settings.value("/MessagesLog", false).toBool();
82 sMessagesLogPath = m_settings.value("/MessagesLogPath", "qsynth.log").toString();
83 bQueryClose = m_settings.value("/QueryClose", true).toBool();
84 bKeepOnTop = m_settings.value("/KeepOnTop", false).toBool();
85 bStdoutCapture = m_settings.value("/StdoutCapture", true).toBool();
86 bOutputMeters = m_settings.value("/OutputMeters", false).toBool();
87 bSystemTray = m_settings.value("/SystemTray", false).toBool();
88 bSystemTrayQueryClose = m_settings.value("/SystemTrayQueryClose", true).toBool();
89 bStartMinimized = m_settings.value("/StartMinimized", false).toBool();
90 iBaseFontSize = m_settings.value("/BaseFontSize", 0).toInt();
91 iKnobStyle = m_settings.value("/KnobStyle", 0).toInt();
92 iKnobMotion = m_settings.value("/KnobMotion", 1).toInt();
93 m_settings.endGroup();
94
95 // Load custom options...
96 m_settings.beginGroup("/Custom");
97 sCustomColorTheme = m_settings.value("/ColorTheme").toString();
98 sCustomStyleTheme = m_settings.value("/StyleTheme").toString();
99 m_settings.endGroup();
100
101 // Load custom additional engines.
102 m_settings.beginGroup("/Engines");
103 const QString sEnginePrefix = "/Engine%1";
104 int iEngine = 0;
105 for (;;) {
106 QString sItem = m_settings.value(sEnginePrefix.arg(++iEngine)).toString();
107 if (sItem.isEmpty())
108 break;
109 engines.append(sItem);
110 }
111 m_settings.endGroup();
112 }
113
114
115 // Explicit save method.
saveOptions(void)116 void qsynthOptions::saveOptions (void)
117 {
118 // Make program version available in the future.
119 m_settings.beginGroup("/Program");
120 m_settings.setValue("/Version", CONFIG_BUILD_VERSION);
121 m_settings.endGroup();
122
123 // Save defaults...
124 m_settings.beginGroup("/Defaults");
125 m_settings.setValue("/SoundFontDir", sSoundFontDir);
126 m_settings.setValue("/PresetPreview", bPresetPreview);
127 m_settings.endGroup();
128
129 // Save display options...
130 m_settings.beginGroup("/Options");
131 m_settings.setValue("/MessagesFont", sMessagesFont);
132 m_settings.setValue("/MessagesLimit", bMessagesLimit);
133 m_settings.setValue("/MessagesLimitLines", iMessagesLimitLines);
134 m_settings.setValue("/MessagesLog", bMessagesLog);
135 m_settings.setValue("/MessagesLogPath", sMessagesLogPath);
136 m_settings.setValue("/QueryClose", bQueryClose);
137 m_settings.setValue("/KeepOnTop", bKeepOnTop);
138 m_settings.setValue("/StdoutCapture", bStdoutCapture);
139 m_settings.setValue("/OutputMeters", bOutputMeters);
140 m_settings.setValue("/SystemTray", bSystemTray);
141 m_settings.setValue("/SystemTrayQueryClose", bSystemTrayQueryClose);
142 m_settings.setValue("/StartMinimized", bStartMinimized);
143 m_settings.setValue("/BaseFontSize", iBaseFontSize);
144 m_settings.setValue("/KnobStyle", iKnobStyle);
145 m_settings.setValue("/KnobMotion", iKnobMotion);
146 m_settings.endGroup();
147
148 // Save custom options...
149 m_settings.beginGroup("/Custom");
150 m_settings.setValue("/ColorTheme", sCustomColorTheme);
151 m_settings.setValue("/StyleTheme", sCustomStyleTheme);
152 m_settings.endGroup();
153
154 // Save engines list...
155 m_settings.beginGroup("/Engines");
156 // Save last preset list.
157 const QString sEnginePrefix = "/Engine%1";
158 int iEngine = 0;
159 QStringListIterator iter(engines);
160 while (iter.hasNext())
161 m_settings.setValue(sEnginePrefix.arg(++iEngine), iter.next());
162 // Cleanup old entries, if any...
163 while (!m_settings.value(sEnginePrefix.arg(++iEngine)).toString().isEmpty())
164 m_settings.remove(sEnginePrefix.arg(iEngine));
165 m_settings.endGroup();
166
167 // Save/commit to disk.
168 m_settings.sync();
169 }
170
171
172 // Default instance setup accessor.
defaultSetup(void)173 qsynthSetup *qsynthOptions::defaultSetup (void)
174 {
175 return m_pDefaultSetup;
176 }
177
178
179 //-------------------------------------------------------------------------
180 // Settings accessor.
181 //
182
settings(void)183 QSettings& qsynthOptions::settings (void)
184 {
185 return m_settings;
186 }
187
188
189 //-------------------------------------------------------------------------
190 // Command-line argument stuff. Mostly to mimic fluidsynth CLI.
191 //
192
193 // Help about command line options.
print_usage(const QString & arg0)194 void qsynthOptions::print_usage ( const QString& arg0 )
195 {
196 QTextStream out(stderr);
197 const QString sEot = "\n\t";
198 const QString sEol = "\n\n";
199
200 out << QObject::tr("Usage: %1"
201 " [options] [soundfonts] [midifiles]").arg(arg0) + sEol;
202 out << QSYNTH_TITLE " - " + QObject::tr(QSYNTH_SUBTITLE) + sEol;
203 out << QObject::tr("Options") + ":" + sEol;
204 out << " -n, --no-midi-in" + sEot +
205 QObject::tr("Don't create a midi driver to read MIDI input events [default = yes]") + sEol;
206 out << " -m, --midi-driver=[label]" + sEot +
207 QObject::tr("The name of the midi driver to use [jack,sndio,oss,...]") + sEol;
208 out << " -K, --midi-channels=[num]" + sEot +
209 QObject::tr("The number of midi channels [default = 16]") + sEol;
210 out << " -a, --audio-driver=[label]" + sEot +
211 QObject::tr("The audio driver [jack,sndio,oss,...]") + sEol;
212 out << " -j, --connect-jack-outputs" + sEot +
213 QObject::tr("Attempt to connect the jack outputs to the physical ports") + sEol;
214 out << " -L, --audio-channels=[num]" + sEot +
215 QObject::tr("The number of stereo audio channels [default = 1]") + sEol;
216 out << " -G, --audio-groups=[num]" + sEot +
217 QObject::tr("The number of audio groups [default = 1]") + sEol;
218 out << " -z, --audio-bufsize=[size]" + sEot +
219 QObject::tr("Size of each audio buffer") + sEol;
220 out << " -c, --audio-bufcount=[count]" + sEot +
221 QObject::tr("Number of audio buffers") + sEol;
222 out << " -r, --sample-rate=[rate]" + sEot +
223 QObject::tr("Set the sample rate") + sEol;
224 out << " -R, --reverb=[flag]" + sEot +
225 QObject::tr("Turn the reverb on or off [1|0|yes|no|on|off, default = on]") + sEol;
226 out << " -C, --chorus=[flag]" + sEot +
227 QObject::tr("Turn the chorus on or off [1|0|yes|no|on|off, default = on]") + sEol;
228 out << " -g, --gain=[gain]" + sEot +
229 QObject::tr("Set the master gain [0 < gain < 10, default = 0.2]") + sEol;
230 out << " -o, --option [name=value]" + sEot +
231 QObject::tr("Define a setting name=value") + sEol;
232 out << " -s, --server" + sEot +
233 QObject::tr("Create and start server [default = no]") + sEol;
234 out << " -i, --no-shell" + sEot +
235 QObject::tr("Don't read commands from the shell [ignored]") + sEol;
236 out << " -d, --dump" + sEot +
237 QObject::tr("Dump midi router events") + sEol;
238 out << " -V, --verbose" + sEot +
239 QObject::tr("Print out verbose messages about midi events") + sEol;
240 out << " -h, --help" + sEot +
241 QObject::tr("Show help about command line options") + sEol;
242 out << " -v, --version" + sEot +
243 QObject::tr("Show version information") + sEol;
244 }
245
246
247 // Parse command line arguments into fluidsynth settings.
parse_args(const QStringList & args)248 bool qsynthOptions::parse_args ( const QStringList& args )
249 {
250 QTextStream out(stderr);
251 const QString sEol = "\n\n";
252 const int argc = args.count();
253 int iSoundFontOverride = 0;
254
255 for (int i = 1; i < argc; ++i) {
256
257 QString sVal;
258 QString sArg = args.at(i);
259 const int iEqual = sArg.indexOf('=');
260 if (iEqual >= 0) {
261 sVal = sArg.right(sArg.length() - iEqual - 1);
262 sArg = sArg.left(iEqual);
263 }
264 else if (i < argc - 1) {
265 sVal = args.at(i + 1);
266 if (sVal[0] == '-')
267 sVal.clear();
268 }
269
270 if (sArg == "-n" || sArg == "--no-midi-in") {
271 m_pDefaultSetup->bMidiIn = false;
272 }
273 else if (sArg == "-m" || sArg == "--midi-driver") {
274 if (sVal.isEmpty()) {
275 out << QObject::tr("Option -m requires an argument (midi-driver).") + sEol;
276 return false;
277 }
278 m_pDefaultSetup->sMidiDriver = sVal;
279 if (iEqual < 0)
280 ++i;
281 }
282 else if (sArg == "-K" || sArg == "--midi-channels") {
283 if (sVal.isEmpty()) {
284 out << QObject::tr("Option -K requires an argument (midi-channels).") + sEol;
285 return false;
286 }
287 m_pDefaultSetup->iMidiChannels = sVal.toInt();
288 if (iEqual < 0)
289 ++i;
290 }
291 else if (sArg == "-a" || sArg == "--audio-driver") {
292 if (sVal.isEmpty()) {
293 out << QObject::tr("Option -a requires an argument (audio-driver).") + sEol;
294 return false;
295 }
296 m_pDefaultSetup->sAudioDriver = sVal;
297 if (iEqual < 0)
298 ++i;
299 }
300 else if (sArg == "-j" || sArg == "--connect-jack-outputs") {
301 m_pDefaultSetup->bJackAutoConnect = true;
302 }
303 else if (sArg == "-L" || sArg == "--audio-channels") {
304 if (sVal.isEmpty()) {
305 out << QObject::tr("Option -L requires an argument (audio-channels).") + sEol;
306 return false;
307 }
308 m_pDefaultSetup->iAudioChannels = sVal.toInt();
309 if (iEqual < 0)
310 ++i;
311 }
312 else if (sArg == "-G" || sArg == "--audio-groups") {
313 if (sVal.isEmpty()) {
314 out << QObject::tr("Option -G requires an argument (audio-groups).") + sEol;
315 return false;
316 }
317 m_pDefaultSetup->iAudioGroups = sVal.toInt();
318 if (iEqual < 0)
319 i++;
320 }
321 else if (sArg == "-z" || sArg == "--audio-bufsize") {
322 if (sVal.isEmpty()) {
323 out << QObject::tr("Option -z requires an argument (audio-bufsize).") + sEol;
324 return false;
325 }
326 m_pDefaultSetup->iAudioBufSize = sVal.toInt();
327 if (iEqual < 0)
328 ++i;
329 }
330 else if (sArg == "-c" || sArg == "--audio-bufcount") {
331 if (sVal.isEmpty()) {
332 out << QObject::tr("Option -c requires an argument (audio-bufcount).") + sEol;
333 return false;
334 }
335 m_pDefaultSetup->iAudioBufCount = sVal.toInt();
336 if (iEqual < 0)
337 ++i;
338 }
339 else if (sArg == "-r" || sArg == "--sample-rate") {
340 if (sVal.isEmpty()) {
341 out << QObject::tr("Option -r requires an argument (sample-rate).") + sEol;
342 return false;
343 }
344 m_pDefaultSetup->fSampleRate = sVal.toFloat();
345 if (iEqual < 0)
346 ++i;
347 }
348 else if (sArg == "-R" || sArg == "--reverb") {
349 if (sVal.isEmpty()) {
350 m_pDefaultSetup->bReverbActive = true;
351 } else {
352 m_pDefaultSetup->bReverbActive = !(sVal == "0" || sVal == "no" || sVal == "off");
353 if (iEqual < 0)
354 ++i;
355 }
356 }
357 else if (sArg == "-C" || sArg == "--chorus") {
358 if (sVal.isEmpty()) {
359 m_pDefaultSetup->bChorusActive = true;
360 } else {
361 m_pDefaultSetup->bChorusActive = !(sVal == "0" || sVal == "no" || sVal == "off");
362 if (iEqual < 0)
363 ++i;
364 }
365 }
366 else if (sArg == "-g" || sArg == "--gain") {
367 if (sVal.isEmpty()) {
368 out << QObject::tr("Option -g requires an argument (gain).") + sEol;
369 return false;
370 }
371 m_pDefaultSetup->fGain = sVal.toFloat();
372 if (iEqual < 0)
373 ++i;
374 }
375 else if (sArg == "-o" || sArg == "--option") {
376 if (++i >= argc) {
377 out << QObject::tr("Option -o requires an argument.") + sEol;
378 return false;
379 }
380 m_pDefaultSetup->options.append(args.at(i));
381 }
382 else if (sArg == "-s" || sArg == "--server") {
383 m_pDefaultSetup->bServer = true;
384 }
385 else if (sArg == "-i" || sArg == "--no-shell") {
386 // Just ignore this one...
387 }
388 else if (sArg == "-d" || sArg == "--dump") {
389 m_pDefaultSetup->bMidiDump = true;
390 }
391 else if (sArg == "-V" || sArg == "--verbose") {
392 m_pDefaultSetup->bVerbose = true;
393 }
394 else if (sArg == "-h" || sArg == "--help") {
395 print_usage(args.at(0));
396 return false;
397 }
398 else if (sArg == "-v" || sArg == "--version") {
399 out << QString("Qt: %1").arg(qVersion());
400 #if defined(QT_STATIC)
401 out << "-static";
402 #endif
403 out << '\n';
404 out << QString("FluidSynth: %1\n")
405 .arg(::fluid_version_str());
406 out << QString("%1: %2\n")
407 .arg(QSYNTH_TITLE)
408 .arg(CONFIG_BUILD_VERSION);
409 return false;
410 }
411 else {
412 const QByteArray tmp = args.at(i).toUtf8();
413 const char *name = tmp.constData();
414 if (::fluid_is_soundfont(name)) {
415 if (++iSoundFontOverride == 1) {
416 m_pDefaultSetup->soundfonts.clear();
417 m_pDefaultSetup->bankoffsets.clear();
418 }
419 m_pDefaultSetup->soundfonts.append(name);
420 m_pDefaultSetup->bankoffsets.append(QString());
421 }
422 else if (::fluid_is_midifile(name)) {
423 m_pDefaultSetup->midifiles.append(name);
424 }
425 else {
426 out << QObject::tr("Unknown option '%1'.").arg(name) + sEol;
427 print_usage(args.at(0));
428 return false;
429 }
430 }
431 }
432
433 // Alright with argument parsing.
434 return true;
435 }
436
437
438 //---------------------------------------------------------------------------
439 // Engine entry management methods.
440
newEngine(qsynthEngine * pEngine)441 void qsynthOptions::newEngine ( qsynthEngine *pEngine )
442 {
443 if (pEngine == nullptr)
444 return;
445 if (pEngine->isDefault())
446 return;
447
448 const QString& sName = pEngine->name();
449 if (!engines.contains(sName))
450 engines.append(sName);
451 }
452
453
renameEngine(qsynthEngine * pEngine)454 bool qsynthOptions::renameEngine ( qsynthEngine *pEngine )
455 {
456 if (pEngine == nullptr)
457 return false;
458
459 qsynthSetup *pSetup = pEngine->setup();
460 if (pSetup == nullptr)
461 return false;
462
463 const QString sOldName = pEngine->name();
464 const QString sNewName = pSetup->sDisplayName;
465 if (sOldName == sNewName)
466 return false;
467
468 pEngine->setName(sNewName);
469
470 if (!pEngine->isDefault()) {
471 engines = engines.replaceInStrings(sOldName, sNewName);
472 m_settings.remove("/Engine/" + sOldName);
473 }
474
475 return true;
476 }
477
478
deleteEngine(qsynthEngine * pEngine)479 void qsynthOptions::deleteEngine ( qsynthEngine *pEngine )
480 {
481 if (pEngine == nullptr)
482 return;
483 if (pEngine->isDefault())
484 return;
485
486 const QString& sName = pEngine->name();
487 int iEngine = engines.indexOf(sName);
488 if (iEngine >= 0)
489 engines.removeAt(iEngine);
490
491 m_settings.remove("/Engine/" + sName);
492 }
493
494
495 //---------------------------------------------------------------------------
496 // Setup registry methods.
497
498 // Load instance m_settings.
loadSetup(qsynthSetup * pSetup,const QString & sName)499 void qsynthOptions::loadSetup ( qsynthSetup *pSetup, const QString& sName )
500 {
501 if (pSetup == nullptr)
502 return;
503
504 // Begin at key group?
505 if (!sName.isEmpty())
506 m_settings.beginGroup("/Engine/" + sName);
507
508 // Shall we have a default display name.
509 QString sDisplayName = sName;
510 if (sDisplayName.isEmpty())
511 sDisplayName = QSYNTH_TITLE "1";
512
513 // Load previous/default fluidsynth m_settings...
514 m_settings.beginGroup("/Settings");
515 pSetup->sDisplayName = m_settings.value("/DisplayName", sDisplayName).toString();
516 pSetup->bMidiIn = m_settings.value("/MidiIn", true).toBool();
517 #if defined(__APPLE__)
518 pSetup->sMidiDriver = m_settings.value("/MidiDriver", "coremidi").toString();
519 pSetup->sAudioDriver = m_settings.value("/AudioDriver", "coreaudio").toString();
520 #elif defined(__WIN32__) || defined(_WIN32) || defined(WIN32)
521 pSetup->sMidiDriver = m_settings.value("/MidiDriver", "winmidi").toString();
522 pSetup->sAudioDriver = m_settings.value("/AudioDriver", "dsound").toString();
523 #elif defined(__OpenBSD__)
524 pSetup->sMidiDriver = m_settings.value("/MidiDriver", "sndio").toString();
525 pSetup->sAudioDriver = m_settings.value("/AudioDriver", "sndio").toString();
526 #else
527 pSetup->sMidiDriver = m_settings.value("/MidiDriver", "jack").toString();
528 pSetup->sAudioDriver = m_settings.value("/AudioDriver", "jack").toString();
529 #endif
530 #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32)
531 pSetup->iAudioBufSize = m_settings.value("/AudioBufSize", 512).toInt();
532 pSetup->iAudioBufCount = m_settings.value("/AudioBufCount", 8).toInt();
533 #else
534 pSetup->iAudioBufSize = m_settings.value("/AudioBufSize", 64).toInt();
535 pSetup->iAudioBufCount = m_settings.value("/AudioBufCount", 2).toInt();
536 #endif
537 pSetup->sMidiName = m_settings.value("/AlsaName", "pid").toString();
538 pSetup->sJackName = m_settings.value("/JackName", "qsynth").toString();
539 pSetup->bJackAutoConnect = m_settings.value("/JackAutoConnect", true).toBool();
540 pSetup->bJackMulti = m_settings.value("/JackMulti", false).toBool();
541 #if defined(__OpenBSD__)
542 pSetup->sMidiDevice = m_settings.value("/MidiDevice", "midithru/0").toString();
543 #else
544 pSetup->sMidiDevice = m_settings.value("/MidiDevice").toString();
545 #endif
546 pSetup->iMidiChannels = m_settings.value("/MidiChannels", 16).toInt();
547 pSetup->sMidiBankSelect = m_settings.value("/MidiBankSelect", "gm").toString();
548 pSetup->sAudioDevice = m_settings.value("/AudioDevice").toString();
549 pSetup->iAudioChannels = m_settings.value("/AudioChannels", 1).toInt();
550 pSetup->iAudioGroups = m_settings.value("/AudioGroups", 1).toInt();
551 pSetup->sSampleFormat = m_settings.value("/SampleFormat", "16bits").toString();
552 pSetup->fSampleRate = m_settings.value("/SampleRate", 44100.0).toDouble();
553 pSetup->iPolyphony = m_settings.value("/Polyphony", 256).toInt();
554 pSetup->bReverbActive = m_settings.value("/ReverbActive", true).toBool();
555 pSetup->fReverbRoom = m_settings.value("/ReverbRoom", QSYNTH_REVERB_DEFAULT_ROOMSIZE).toDouble();
556 pSetup->fReverbDamp = m_settings.value("/ReverbDamp", QSYNTH_REVERB_DEFAULT_DAMP).toDouble();
557 pSetup->fReverbWidth = m_settings.value("/ReverbWidth", QSYNTH_REVERB_DEFAULT_WIDTH).toDouble();
558 pSetup->fReverbLevel = m_settings.value("/ReverbLevel", QSYNTH_REVERB_DEFAULT_LEVEL).toDouble();
559 pSetup->bChorusActive = m_settings.value("/ChorusActive", true).toBool();
560 pSetup->iChorusNr = m_settings.value("/ChorusNr", QSYNTH_CHORUS_DEFAULT_N).toInt();
561 pSetup->fChorusLevel = m_settings.value("/ChorusLevel", QSYNTH_CHORUS_DEFAULT_LEVEL).toDouble();
562 pSetup->fChorusSpeed = m_settings.value("/ChorusSpeed", QSYNTH_CHORUS_DEFAULT_SPEED).toDouble();
563 pSetup->fChorusDepth = m_settings.value("/ChorusDepth", QSYNTH_CHORUS_DEFAULT_DEPTH).toDouble();
564 pSetup->iChorusType = m_settings.value("/ChorusType", QSYNTH_CHORUS_DEFAULT_TYPE).toInt();
565 pSetup->bLadspaActive = m_settings.value("/LadspaActive", false).toBool();
566 pSetup->fGain = m_settings.value("/Gain", QSYNTH_MASTER_DEFAULT_GAIN).toDouble();
567 pSetup->bServer = m_settings.value("/Server", false).toBool();
568 pSetup->bMidiDump = m_settings.value("/MidiDump", false).toBool();
569 pSetup->bVerbose = m_settings.value("/Verbose", false).toBool();
570 m_settings.endGroup();
571
572 // Load soundfont list...
573 m_settings.beginGroup("/SoundFonts");
574 const QString sSoundFontPrefix = "/SoundFont%1";
575 const QString sBankOffsetPrefix = "/BankOffset%1";
576 int i = 0;
577 for (;;) {
578 ++i;
579 QString sSoundFont = m_settings.value(sSoundFontPrefix.arg(i)).toString();
580 QString sBankOffset = m_settings.value(sBankOffsetPrefix.arg(i)).toString();
581 if (sSoundFont.isEmpty())
582 break;
583 pSetup->soundfonts.append(sSoundFont);
584 pSetup->bankoffsets.append(sBankOffset);
585 }
586 m_settings.endGroup();
587
588 // Load channel presets list.
589 m_settings.beginGroup("/Presets");
590 pSetup->sDefPreset = m_settings.value("/DefPreset", pSetup->sDefPresetName).toString();
591 const QString sPresetPrefix = "/Preset%1";
592 int iPreset = 0;
593 for (;;) {
594 QString sItem = m_settings.value(sPresetPrefix.arg(++iPreset)).toString();
595 if (sItem.isEmpty())
596 break;
597 pSetup->presets.append(sItem);
598 }
599 m_settings.endGroup();
600
601 // Done with the key group?
602 if (!sName.isEmpty())
603 m_settings.endGroup();
604 }
605
606
607 // Save instance m_settings.
saveSetup(qsynthSetup * pSetup,const QString & sName)608 void qsynthOptions::saveSetup ( qsynthSetup *pSetup, const QString& sName )
609 {
610 if (pSetup == nullptr)
611 return;
612
613 // Begin at key group?
614 if (!sName.isEmpty())
615 m_settings.beginGroup("/Engine/" + sName);
616
617 // Save presets list...
618 m_settings.beginGroup("/Presets");
619 m_settings.setValue("/DefPreset", pSetup->sDefPreset);
620 // Save last preset list.
621 const QString sPresetPrefix = "/Preset%1";
622 int iPreset = 0;
623 QStringListIterator iter(pSetup->presets);
624 while (iter.hasNext())
625 m_settings.setValue(sPresetPrefix.arg(++iPreset), iter.next());
626 // Cleanup old entries, if any...
627 while (!m_settings.value(sPresetPrefix.arg(++iPreset)).toString().isEmpty())
628 m_settings.remove(sPresetPrefix.arg(iPreset));
629 m_settings.endGroup();
630
631 // Save last soundfont list.
632 m_settings.beginGroup("/SoundFonts");
633 const QString sSoundFontPrefix = "/SoundFont%1";
634 const QString sBankOffsetPrefix = "/BankOffset%1";
635 int i = 0;
636 QStringListIterator sfiter(pSetup->soundfonts);
637 while (sfiter.hasNext()) {
638 m_settings.setValue(sSoundFontPrefix.arg(++i), sfiter.next());
639 m_settings.setValue(sBankOffsetPrefix.arg(i), pSetup->bankoffsets[i - 1]);
640 }
641 // Cleanup old entries, if any...
642 for (;;) {
643 if (m_settings.value(sSoundFontPrefix.arg(++i)).toString().isEmpty())
644 break;
645 m_settings.remove(sSoundFontPrefix.arg(i));
646 m_settings.remove(sBankOffsetPrefix.arg(i));
647 }
648 m_settings.endGroup();
649
650 // Save last fluidsynth m_settings.
651 m_settings.beginGroup("/Settings");
652 m_settings.setValue("/DisplayName", pSetup->sDisplayName);
653 m_settings.setValue("/MidiIn", pSetup->bMidiIn);
654 m_settings.setValue("/MidiDriver", pSetup->sMidiDriver);
655 m_settings.setValue("/MidiDevice", pSetup->sMidiDevice);
656 m_settings.setValue("/MidiChannels", pSetup->iMidiChannels);
657 m_settings.setValue("/AlsaName", pSetup->sMidiName);
658 m_settings.setValue("/MidiBankSelect", pSetup->sMidiBankSelect);
659 m_settings.setValue("/AudioDriver", pSetup->sAudioDriver);
660 m_settings.setValue("/AudioDevice", pSetup->sAudioDevice);
661 m_settings.setValue("/JackName", pSetup->sJackName);
662 m_settings.setValue("/JackAutoConnect", pSetup->bJackAutoConnect);
663 m_settings.setValue("/JackMulti", pSetup->bJackMulti);
664 m_settings.setValue("/AudioChannels", pSetup->iAudioChannels);
665 m_settings.setValue("/AudioGroups", pSetup->iAudioGroups);
666 m_settings.setValue("/AudioBufSize", pSetup->iAudioBufSize);
667 m_settings.setValue("/AudioBufCount", pSetup->iAudioBufCount);
668 m_settings.setValue("/SampleFormat", pSetup->sSampleFormat);
669 m_settings.setValue("/SampleRate", pSetup->fSampleRate);
670 m_settings.setValue("/Polyphony", pSetup->iPolyphony);
671 m_settings.setValue("/ReverbActive", pSetup->bReverbActive);
672 m_settings.setValue("/ReverbRoom", pSetup->fReverbRoom);
673 m_settings.setValue("/ReverbDamp", pSetup->fReverbDamp);
674 m_settings.setValue("/ReverbWidth", pSetup->fReverbWidth);
675 m_settings.setValue("/ReverbLevel", pSetup->fReverbLevel);
676 m_settings.setValue("/ChorusActive", pSetup->bChorusActive);
677 m_settings.setValue("/ChorusNr", pSetup->iChorusNr);
678 m_settings.setValue("/ChorusLevel", pSetup->fChorusLevel);
679 m_settings.setValue("/ChorusSpeed", pSetup->fChorusSpeed);
680 m_settings.setValue("/ChorusDepth", pSetup->fChorusDepth);
681 m_settings.setValue("/ChorusType", pSetup->iChorusType);
682 m_settings.setValue("/LadspaActive", pSetup->bLadspaActive);
683 m_settings.setValue("/Gain", pSetup->fGain);
684 m_settings.setValue("/Server", pSetup->bServer);
685 m_settings.setValue("/MidiDump", pSetup->bMidiDump);
686 m_settings.setValue("/Verbose", pSetup->bVerbose);
687 m_settings.endGroup();
688
689 // Done with the key group?
690 if (!sName.isEmpty())
691 m_settings.endGroup();
692 }
693
694
695 //---------------------------------------------------------------------------
696 // Preset management methods.
697
loadPreset(qsynthEngine * pEngine,const QString & sPreset)698 bool qsynthOptions::loadPreset ( qsynthEngine *pEngine, const QString& sPreset )
699 {
700 if (pEngine == nullptr || pEngine->pSynth == nullptr)
701 return false;
702
703 qsynthSetup *pSetup = pEngine->setup();
704 if (pSetup == nullptr)
705 return false;
706
707 QString sSuffix;
708 if (sPreset != pSetup->sDefPresetName && !sPreset.isEmpty()) {
709 sSuffix = '/' + sPreset;
710 // Check if on list.
711 if (!pSetup->presets.contains(sPreset))
712 return false;
713 }
714
715 // Begin at key group?
716 if (!pEngine->isDefault())
717 m_settings.beginGroup("/Engine/" + pEngine->name());
718
719 // Load as current presets.
720 #ifdef CONFIG_FLUID_UNSET_PROGRAM
721 int iChannelsSet = 0;
722 #endif
723 const QString sPrefix = "/Chan%1";
724 m_settings.beginGroup("/Preset" + sSuffix);
725 int iChannels = ::fluid_synth_count_midi_channels(pEngine->pSynth);
726 for (int iChan = 0; iChan < iChannels; iChan++) {
727 QString sEntry = m_settings.value(sPrefix.arg(iChan + 1)).toString();
728 if (!sEntry.isEmpty() && iChan == sEntry.section(':', 0, 0).toInt()) {
729 ::fluid_synth_bank_select(pEngine->pSynth, iChan,
730 sEntry.section(':', 1, 1).toInt());
731 ::fluid_synth_program_change(pEngine->pSynth, iChan,
732 sEntry.section(':', 2, 2).toInt());
733 #ifdef CONFIG_FLUID_UNSET_PROGRAM
734 ++iChannelsSet;
735 #endif
736 }
737 #ifdef CONFIG_FLUID_UNSET_PROGRAM
738 else ::fluid_synth_unset_program(pEngine->pSynth, iChan);
739 #endif
740 }
741 m_settings.endGroup();
742
743 // Done with the key group?
744 if (!pEngine->isDefault())
745 m_settings.endGroup();
746
747 #ifdef CONFIG_FLUID_UNSET_PROGRAM
748 // If there's none channels set (eg. empty/blank preset)
749 // then fallback to old default fill up all the channels
750 // according to available soundfont stack.
751 if (iChannelsSet < 1) {
752 int iProg = 0;
753 for (int iChan = 0; iChan < iChannels; iChan++) {
754 ::fluid_synth_bank_select(pEngine->pSynth, iChan, 0);
755 ::fluid_synth_program_change(pEngine->pSynth, iChan, iProg);
756 ++iProg;
757 }
758 }
759 #endif
760
761 // Recommended to post-stabilize things around.
762 ::fluid_synth_program_reset(pEngine->pSynth);
763
764 return true;
765 }
766
savePreset(qsynthEngine * pEngine,const QString & sPreset)767 bool qsynthOptions::savePreset ( qsynthEngine *pEngine, const QString& sPreset )
768 {
769 if (pEngine == nullptr || pEngine->pSynth == nullptr)
770 return false;
771
772 qsynthSetup *pSetup = pEngine->setup();
773 if (pSetup == nullptr)
774 return false;
775
776 QString sSuffix;
777 if (sPreset != pSetup->sDefPresetName && !sPreset.isEmpty()) {
778 sSuffix = '/' + sPreset;
779 // Append to list if not already.
780 if (!pSetup->presets.contains(sPreset))
781 pSetup->presets.prepend(sPreset);
782 }
783
784 // Begin at key group?
785 if (!pEngine->isDefault())
786 m_settings.beginGroup("/Engine/" + pEngine->name());
787
788 // Unload current presets.
789 const QString sPrefix = "/Chan%1";
790 m_settings.beginGroup("/Preset" + sSuffix);
791 int iChannels = ::fluid_synth_count_midi_channels(pEngine->pSynth);
792 int iChan = 0;
793 for ( ; iChan < iChannels; iChan++) {
794 #ifdef CONFIG_FLUID_CHANNEL_INFO
795 fluid_synth_channel_info_t info;
796 ::memset(&info, 0, sizeof(info));
797 ::fluid_synth_get_channel_info(pEngine->pSynth, iChan, &info);
798 if (info.assigned) {
799 #ifdef CONFIG_FLUID_BANK_OFFSET
800 info.bank += ::fluid_synth_get_bank_offset(pEngine->pSynth, info.sfont_id);
801 #endif
802 QString sEntry = QString::number(iChan);
803 sEntry += ':';
804 sEntry += QString::number(info.bank);
805 sEntry += ':';
806 sEntry += QString::number(info.program);
807 m_settings.setValue(sPrefix.arg(iChan + 1), sEntry);
808 }
809 #else
810 fluid_preset_t *pPreset = ::fluid_synth_get_channel_preset(pEngine->pSynth, iChan);
811 if (pPreset) {
812 #ifdef CONFIG_FLUID_PRESET_GET_BANKNUM
813 int iBank = ::fluid_preset_get_banknum(pPreset);
814 #else
815 int iBank = pPreset->get_banknum(pPreset);
816 #endif
817 #ifdef CONFIG_FLUID_BANK_OFFSET
818 int iSFID = 0;
819 #ifdef CONFIG_FLUID_PRESET_GET_SFONT
820 fluid_sfont_t *pSoundFont = ::fluid_preset_get_sfont(pPreset);
821 #else
822 fluid_sfont_t *pSoundFont = pPreset->sfont;
823 #endif
824 if (pSoundFont) {
825 #ifdef CONFIG_FLUID_SFONT_GET_ID
826 iSFID = ::fluid_sfont_get_id(pSoundFont);
827 #else
828 iSFID = pSoundFont->id;
829 #endif
830 }
831 iBank += ::fluid_synth_get_bank_offset(pEngine->pSynth, iSFID);
832 #endif
833 #ifdef CONFIG_FLUID_PRESET_GET_NUM
834 const int iProg = ::fluid_preset_get_num(pPreset);
835 #else
836 const int iProg = pPreset->get_num(pPreset);
837 #endif
838 QString sEntry = QString::number(iChan);
839 sEntry += ':';
840 sEntry += QString::number(iBank);
841 sEntry += ':';
842 sEntry += QString::number(iProg);
843 m_settings.setValue(sPrefix.arg(iChan + 1), sEntry);
844 }
845 #endif
846 else m_settings.remove(sPrefix.arg(iChan + 1));
847 }
848 // Cleanup old entries, if any...
849 while (!m_settings.value(sPrefix.arg(++iChan)).toString().isEmpty())
850 m_settings.remove(sPrefix.arg(iChan));
851 m_settings.endGroup();
852
853 // Done with the key group?
854 if (!pEngine->isDefault())
855 m_settings.endGroup();
856
857 return true;
858 }
859
deletePreset(qsynthEngine * pEngine,const QString & sPreset)860 bool qsynthOptions::deletePreset ( qsynthEngine *pEngine, const QString& sPreset )
861 {
862 if (pEngine == nullptr)
863 return false;
864
865 qsynthSetup *pSetup = pEngine->setup();
866 if (pSetup == nullptr)
867 return false;
868
869 QString sPrefix;
870 if (!pEngine->isDefault())
871 sPrefix = "/Engine/" + pEngine->name();
872 QString sSuffix;
873 if (sPreset != pSetup->sDefPresetName && !sPreset.isEmpty()) {
874 sSuffix = "/" + sPreset;
875 int iPreset = pSetup->presets.indexOf(sPreset);
876 if (iPreset < 0)
877 return false;
878 pSetup->presets.removeAt(iPreset);
879 m_settings.remove(sPrefix + "/Preset" + sSuffix);
880 }
881
882 return true;
883 }
884
885
886 //---------------------------------------------------------------------------
887 // Combo box history persistence helper implementation.
888
loadComboBoxHistory(QComboBox * pComboBox,int iLimit)889 void qsynthOptions::loadComboBoxHistory ( QComboBox *pComboBox, int iLimit )
890 {
891 const bool bBlockSignals = pComboBox->blockSignals(true);
892
893 // Load combobox list from configuration settings file...
894 m_settings.beginGroup("/History/" + pComboBox->objectName());
895
896 if (m_settings.childKeys().count() > 0) {
897 pComboBox->setUpdatesEnabled(false);
898 pComboBox->setDuplicatesEnabled(false);
899 pComboBox->clear();
900 for (int i = 0; i < iLimit; ++i) {
901 const QString& sText = m_settings.value(
902 "/Item" + QString::number(i + 1)).toString();
903 if (sText.isEmpty())
904 break;
905 pComboBox->addItem(sText);
906 }
907 pComboBox->setUpdatesEnabled(true);
908 }
909
910 m_settings.endGroup();
911
912 pComboBox->blockSignals(bBlockSignals);
913 }
914
915
saveComboBoxHistory(QComboBox * pComboBox,int iLimit)916 void qsynthOptions::saveComboBoxHistory ( QComboBox *pComboBox, int iLimit )
917 {
918 const bool bBlockSignals = pComboBox->blockSignals(true);
919
920 // Add current text as latest item...
921 const QString sCurrentText = pComboBox->currentText();
922 int iCount = pComboBox->count();
923 for (int i = 0; i < iCount; i++) {
924 const QString& sText = pComboBox->itemText(i);
925 if (sText == sCurrentText) {
926 pComboBox->removeItem(i);
927 --iCount;
928 break;
929 }
930 }
931 while (iCount >= iLimit)
932 pComboBox->removeItem(--iCount);
933 pComboBox->insertItem(0, sCurrentText);
934 pComboBox->setCurrentIndex(0);
935 ++iCount;
936
937 // Save combobox list to configuration settings file...
938 m_settings.beginGroup("/History/" + pComboBox->objectName());
939 for (int i = 0; i < iCount; ++i) {
940 const QString& sText = pComboBox->itemText(i);
941 if (sText.isEmpty())
942 break;
943 m_settings.setValue("/Item" + QString::number(i + 1), sText);
944 }
945 m_settings.endGroup();
946
947 pComboBox->blockSignals(bBlockSignals);
948 }
949
950
951 //---------------------------------------------------------------------------
952 // Widget geometry persistence helper methods.
953
loadWidgetGeometry(QWidget * pWidget,bool bVisible)954 void qsynthOptions::loadWidgetGeometry ( QWidget *pWidget, bool bVisible )
955 {
956 // Try to restore old form window positioning.
957 if (pWidget) {
958 // if (bVisible) pWidget->show(); -- force initial exposure?
959 m_settings.beginGroup("/Geometry/" + pWidget->objectName());
960 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
961 const QByteArray& geometry
962 = m_settings.value("/geometry").toByteArray();
963 if (geometry.isEmpty()) {
964 QWidget *pParent = pWidget->parentWidget();
965 if (pParent)
966 pParent = pParent->window();
967 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
968 if (pParent == nullptr)
969 pParent = QApplication::desktop();
970 #endif
971 if (pParent) {
972 QRect wrect(pWidget->geometry());
973 wrect.moveCenter(pParent->geometry().center());
974 pWidget->move(wrect.topLeft());
975 }
976 } else {
977 pWidget->restoreGeometry(geometry);
978 }
979 #else//--LOAD_OLD_GEOMETRY
980 QPoint wpos;
981 QSize wsize;
982 wpos.setX(m_settings.value("/x", -1).toInt());
983 wpos.setY(m_settings.value("/y", -1).toInt());
984 wsize.setWidth(m_settings.value("/width", -1).toInt());
985 wsize.setHeight(m_settings.value("/height", -1).toInt());
986 if (wpos.x() > 0 && wpos.y() > 0)
987 pWidget->move(wpos);
988 if (wsize.width() > 0 && wsize.height() > 0)
989 pWidget->resize(wsize);
990 #endif
991 // else
992 // pWidget->adjustSize();
993 if (!bVisible)
994 bVisible = m_settings.value("/visible", false).toBool();
995 if (bVisible && !bStartMinimized)
996 pWidget->show();
997 else
998 pWidget->hide();
999 m_settings.endGroup();
1000 }
1001 }
1002
1003
saveWidgetGeometry(QWidget * pWidget,bool bVisible)1004 void qsynthOptions::saveWidgetGeometry ( QWidget *pWidget, bool bVisible )
1005 {
1006 // Try to save form window position...
1007 // (due to X11 window managers ideossincrasies, we better
1008 // only save the form geometry while its up and visible)
1009 if (pWidget) {
1010 m_settings.beginGroup("/Geometry/" + pWidget->objectName());
1011 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
1012 m_settings.setValue("/geometry", pWidget->saveGeometry());
1013 #else//--SAVE_OLD_GEOMETRY
1014 const QPoint& wpos = pWidget->pos();
1015 const QSize& wsize = pWidget->size();
1016 m_settings.setValue("/x", wpos.x());
1017 m_settings.setValue("/y", wpos.y());
1018 m_settings.setValue("/width", wsize.width());
1019 m_settings.setValue("/height", wsize.height());
1020 #endif
1021 if (!bVisible) bVisible = pWidget->isVisible();
1022 m_settings.setValue("/visible", bVisible && !bStartMinimized);
1023 m_settings.endGroup();
1024 }
1025 }
1026
1027
1028 // end of qsynthOptions.cpp
1029
1030