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