1/*
2 * Copyright 2003-2009 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Marcus Overhagen
7 */
8
9
10#include "MixerSettings.h"
11
12#include <string.h>
13
14#include <File.h>
15#include <Locker.h>
16#include <MediaDefs.h>
17#include <OS.h>
18#include <Path.h>
19
20#include "MixerCore.h"
21#include "MixerDebug.h"
22#include "MixerInput.h"
23#include "MixerOutput.h"
24
25
26#define SAVE_DELAY		5000000		// delay saving of settings for 5s
27#define SAVE_RUNTIME	30000000	// stop save thread after 30s inactivity
28
29#define SETTINGS_VERSION ((int32)0x94251601)
30
31
32MixerSettings::MixerSettings()
33	:
34	fLocker(new BLocker("mixer settings lock")),
35 	fSettingsFile(0),
36	fSettingsDirty(false),
37	fSettingsLastChange(0),
38	fSaveThread(-1),
39	fSaveThreadWaitSem(-1),
40	fSaveThreadRunning(false)
41{
42	Load();
43}
44
45
46MixerSettings::~MixerSettings()
47{
48	StopDeferredSave();
49	if (fSettingsDirty)
50		Save();
51	delete fLocker;
52	delete fSettingsFile;
53}
54
55
56void
57MixerSettings::SetSettingsFile(const char *file)
58{
59	fLocker->Lock();
60	delete fSettingsFile;
61	fSettingsFile = new BPath(file);
62	fLocker->Unlock();
63	Load();
64}
65
66
67bool
68MixerSettings::AttenuateOutput()
69{
70	bool temp;
71	fLocker->Lock();
72	temp = fSettings.AttenuateOutput;
73	fLocker->Unlock();
74	return temp;
75}
76
77
78void
79MixerSettings::SetAttenuateOutput(bool yesno)
80{
81	fLocker->Lock();
82	fSettings.AttenuateOutput = yesno;
83	fLocker->Unlock();
84	StartDeferredSave();
85}
86
87
88bool
89MixerSettings::UseBalanceControl()
90{
91	bool temp;
92	fLocker->Lock();
93	temp = fSettings.UseBalanceControl;
94	fLocker->Unlock();
95	return temp;
96}
97
98
99void
100MixerSettings::SetUseBalanceControl(bool yesno)
101{
102	fLocker->Lock();
103	fSettings.UseBalanceControl = yesno;
104	fLocker->Unlock();
105	StartDeferredSave();
106}
107
108
109bool
110MixerSettings::AllowOutputChannelRemapping()
111{
112	bool temp;
113	fLocker->Lock();
114	temp = fSettings.AllowOutputChannelRemapping;
115	fLocker->Unlock();
116	return temp;
117}
118
119
120void
121MixerSettings::SetAllowOutputChannelRemapping(bool yesno)
122{
123	fLocker->Lock();
124	fSettings.AllowOutputChannelRemapping = yesno;
125	fLocker->Unlock();
126	StartDeferredSave();
127}
128
129
130bool
131MixerSettings::AllowInputChannelRemapping()
132{
133	bool temp;
134	fLocker->Lock();
135	temp = fSettings.AllowInputChannelRemapping;
136	fLocker->Unlock();
137	return temp;
138}
139
140
141void
142MixerSettings::SetAllowInputChannelRemapping(bool yesno)
143{
144	fLocker->Lock();
145	fSettings.AllowInputChannelRemapping = yesno;
146	fLocker->Unlock();
147	StartDeferredSave();
148}
149
150
151int
152MixerSettings::InputGainControls()
153{
154	int temp;
155	fLocker->Lock();
156	temp = fSettings.InputGainControls;
157	fLocker->Unlock();
158	return temp;
159}
160
161
162void
163MixerSettings::SetInputGainControls(int value)
164{
165	fLocker->Lock();
166	fSettings.InputGainControls = value;
167	fLocker->Unlock();
168	StartDeferredSave();
169}
170
171
172int
173MixerSettings::ResamplingAlgorithm()
174{
175	int temp;
176	fLocker->Lock();
177	temp = fSettings.ResamplingAlgorithm;
178	fLocker->Unlock();
179	return temp;
180}
181
182
183void
184MixerSettings::SetResamplingAlgorithm(int value)
185{
186	fLocker->Lock();
187	fSettings.ResamplingAlgorithm = value;
188	fLocker->Unlock();
189	StartDeferredSave();
190}
191
192
193bool
194MixerSettings::RefuseOutputFormatChange()
195{
196	bool temp;
197	fLocker->Lock();
198	temp = fSettings.RefuseOutputFormatChange;
199	fLocker->Unlock();
200	return temp;
201}
202
203
204void
205MixerSettings::SetRefuseOutputFormatChange(bool yesno)
206{
207	fLocker->Lock();
208	fSettings.RefuseOutputFormatChange = yesno;
209	fLocker->Unlock();
210	StartDeferredSave();
211}
212
213
214bool
215MixerSettings::RefuseInputFormatChange()
216{
217	bool temp;
218	fLocker->Lock();
219	temp = fSettings.RefuseInputFormatChange;
220	fLocker->Unlock();
221	return temp;
222}
223
224
225void
226MixerSettings::SetRefuseInputFormatChange(bool yesno)
227{
228	fLocker->Lock();
229	fSettings.RefuseInputFormatChange = yesno;
230	fLocker->Unlock();
231	StartDeferredSave();
232}
233
234
235void
236MixerSettings::SaveConnectionSettings(MixerInput *input)
237{
238	fLocker->Lock();
239	int index = -1;
240
241	// try to find matching name first
242	for (int i = 0; i < MAX_INPUT_SETTINGS; i++) {
243		if (fInputSetting[i].IsEmpty())
244			continue;
245		if (!strcmp(fInputSetting[i].FindString("name"), input->MediaInput().name)) {
246			index = i;
247			break;
248		}
249	}
250	if (index == -1) {
251		// try to find empty location
252		for (int i = 0; i < MAX_INPUT_SETTINGS; i++) {
253			if (fInputSetting[i].IsEmpty()) {
254				index = i;
255				break;
256			}
257		}
258	}
259	if (index == -1) {
260		// find lru location
261		index = 0;
262		for (int i = 0; i < MAX_INPUT_SETTINGS; i++) {
263			if (fInputSetting[i].FindInt64("lru") < fInputSetting[index].FindInt64("lru"))
264				index = i;
265		}
266	}
267
268	TRACE("SaveConnectionSettings: using entry %d\n", index);
269
270	fInputSetting[index].MakeEmpty();
271	fInputSetting[index].AddInt64("lru", system_time());
272	fInputSetting[index].AddString("name", input->MediaInput().name);
273
274	int count = input->GetInputChannelCount();
275	fInputSetting[index].AddInt32("InputChannelCount", count);
276	fInputSetting[index].AddBool("InputIsEnabled", input->IsEnabled());
277
278	for (int i = 0; i < count; i++)
279		fInputSetting[index].AddFloat("InputChannelGain", input->GetInputChannelGain(i));
280
281	// XXX should save channel destinations and mixer channels
282
283	fLocker->Unlock();
284	StartDeferredSave();
285}
286
287
288void
289MixerSettings::LoadConnectionSettings(MixerInput *input)
290{
291	fLocker->Lock();
292	int index;
293	for (index = 0; index < MAX_INPUT_SETTINGS; index++) {
294		if (fInputSetting[index].IsEmpty())
295			continue;
296		if (!strcmp(fInputSetting[index].FindString("name"), input->MediaInput().name))
297			break;
298	}
299	if (index == MAX_INPUT_SETTINGS) {
300		TRACE("LoadConnectionSettings: entry not found\n");
301		fLocker->Unlock();
302		return;
303	}
304
305	TRACE("LoadConnectionSettings: found entry %d\n", index);
306
307	int count = input->GetInputChannelCount();
308	if (fInputSetting[index].FindInt32("InputChannelCount") == count) {
309		for (int i = 0; i < count; i++)
310			input->SetInputChannelGain(i, fInputSetting[index].FindFloat("InputChannelGain", i));
311		input->SetEnabled(fInputSetting[index].FindBool("InputIsEnabled"));
312	}
313
314	// XXX should load channel destinations and mixer channels
315
316	fInputSetting[index].ReplaceInt64("lru", system_time());
317	fLocker->Unlock();
318	StartDeferredSave();
319}
320
321
322void
323MixerSettings::SaveConnectionSettings(MixerOutput *output)
324{
325	fLocker->Lock();
326
327	fOutputSetting.MakeEmpty();
328
329	int count = output->GetOutputChannelCount();
330	fOutputSetting.AddInt32("OutputChannelCount", count);
331	for (int i = 0; i < count; i++)
332		fOutputSetting.AddFloat("OutputChannelGain", output->GetOutputChannelGain(i));
333	fOutputSetting.AddBool("OutputIsMuted", output->IsMuted());
334
335	// XXX should save channel sources and source gains
336
337	fLocker->Unlock();
338	StartDeferredSave();
339}
340
341
342void
343MixerSettings::LoadConnectionSettings(MixerOutput *output)
344{
345	fLocker->Lock();
346
347	int count = output->GetOutputChannelCount();
348	if (fOutputSetting.FindInt32("OutputChannelCount") == count) {
349		for (int i = 0; i < count; i++)
350			output->SetOutputChannelGain(i, fOutputSetting.FindFloat("OutputChannelGain", i));
351		output->SetMuted(fOutputSetting.FindBool("OutputIsMuted"));
352	}
353
354	// XXX should load channel sources and source gains
355	fLocker->Unlock();
356}
357
358
359void
360MixerSettings::Save()
361{
362	fLocker->Lock();
363	// if we don't have a settings file, don't continue
364	if (!fSettingsFile) {
365		fLocker->Unlock();
366		return;
367	}
368	TRACE("MixerSettings: SAVE!\n");
369
370	BMessage msg;
371	msg.AddInt32("version", SETTINGS_VERSION);
372	msg.AddData("settings", B_RAW_TYPE, (void *)&fSettings, sizeof(fSettings));
373	msg.AddMessage("output", &fOutputSetting);
374	for (int i = 0; i < MAX_INPUT_SETTINGS; i++)
375		msg.AddMessage("input", &fInputSetting[i]);
376
377	char *buffer;
378    size_t length;
379
380    length = msg.FlattenedSize();
381    buffer = new char [length];
382    msg.Flatten(buffer, length);
383
384	BFile file(fSettingsFile->Path(), B_READ_WRITE | B_CREATE_FILE);
385	file.Write(buffer, length);
386
387   	delete [] buffer;
388
389	fSettingsDirty = false;
390	fLocker->Unlock();
391}
392
393
394void
395MixerSettings::Load()
396{
397	fLocker->Lock();
398	// setup defaults
399	fSettings.AttenuateOutput = true;
400	fSettings.UseBalanceControl = false;
401	fSettings.AllowOutputChannelRemapping = false;
402	fSettings.AllowInputChannelRemapping = false;
403	fSettings.InputGainControls = 0;
404	fSettings.ResamplingAlgorithm = 2;
405	fSettings.RefuseOutputFormatChange = false;
406	fSettings.RefuseInputFormatChange = true;
407
408	// if we don't have a settings file, don't continue
409	if (!fSettingsFile) {
410		fLocker->Unlock();
411		return;
412	}
413
414	BFile file(fSettingsFile->Path(), B_READ_WRITE);
415	off_t size = 0;
416	file.GetSize(&size);
417	if (size == 0) {
418		fLocker->Unlock();
419		TRACE("MixerSettings: no settings file\n");
420		return;
421	}
422
423	char * buffer = new char[size];
424	if (size != file.Read(buffer, size)) {
425		delete [] buffer;
426		fLocker->Unlock();
427		TRACE("MixerSettings: can't read settings file\n");
428		return;
429	}
430
431	BMessage msg;
432	if (B_OK != msg.Unflatten(buffer)) {
433		delete [] buffer;
434		fLocker->Unlock();
435		TRACE("MixerSettings: can't unflatten settings\n");
436		return;
437	}
438
439	delete [] buffer;
440
441	if (msg.FindInt32("version") != SETTINGS_VERSION) {
442		fLocker->Unlock();
443		TRACE("MixerSettings: settings have wrong version\n");
444		return;
445	}
446
447	const void *data;
448	ssize_t datasize = 0;
449
450	msg.FindData("settings", B_RAW_TYPE, &data, &datasize);
451	if (datasize != sizeof(fSettings)) {
452		fLocker->Unlock();
453		TRACE("MixerSettings: settings have wrong size\n");
454		return;
455	}
456	memcpy((void *)&fSettings, data, sizeof(fSettings));
457
458	msg.FindMessage("output", &fOutputSetting);
459	for (int i = 0; i < MAX_INPUT_SETTINGS; i++)
460		msg.FindMessage("input", i, &fInputSetting[i]);
461
462	fLocker->Unlock();
463}
464
465
466void
467MixerSettings::StartDeferredSave()
468{
469	fLocker->Lock();
470
471	// if we don't have a settings file, don't save the settings
472	if (!fSettingsFile) {
473		fLocker->Unlock();
474		return;
475	}
476
477	fSettingsDirty = true;
478	fSettingsLastChange = system_time();
479
480	if (fSaveThreadRunning) {
481		fLocker->Unlock();
482		return;
483	}
484
485	StopDeferredSave();
486
487	ASSERT(fSaveThreadWaitSem < 0);
488	fSaveThreadWaitSem = create_sem(0, "save thread wait");
489	if (fSaveThreadWaitSem < B_OK) {
490		ERROR("MixerSettings: can't create semaphore\n");
491		Save();
492		fLocker->Unlock();
493		return;
494	}
495	ASSERT(fSaveThread < 0);
496	fSaveThread = spawn_thread(_save_thread_, "Attack of the Killer Tomatoes", 7, this);
497	if (fSaveThread < B_OK) {
498		ERROR("MixerSettings: can't spawn thread\n");
499		delete_sem(fSaveThreadWaitSem);
500		fSaveThreadWaitSem = -1;
501		Save();
502		fLocker->Unlock();
503		return;
504	}
505	resume_thread(fSaveThread);
506
507	fSaveThreadRunning = true;
508	fLocker->Unlock();
509}
510
511
512void
513MixerSettings::StopDeferredSave()
514{
515	fLocker->Lock();
516
517	if (fSaveThread >= 0) {
518		ASSERT(fSaveThreadWaitSem > 0);
519
520		status_t unused;
521		delete_sem(fSaveThreadWaitSem);
522		wait_for_thread(fSaveThread, &unused);
523
524		fSaveThread = -1;
525		fSaveThreadWaitSem = -1;
526	}
527
528	fLocker->Unlock();
529}
530
531
532void
533MixerSettings::SaveThread()
534{
535	bigtime_t timeout;
536	status_t rv;
537
538	TRACE("MixerSettings: save thread started\n");
539
540	fLocker->Lock();
541	timeout = fSettingsLastChange + SAVE_DELAY;
542	fLocker->Unlock();
543
544	for (;;) {
545		rv = acquire_sem_etc(fSaveThreadWaitSem, 1, B_ABSOLUTE_TIMEOUT, timeout);
546		if (rv == B_INTERRUPTED)
547			continue;
548		if (rv != B_TIMED_OUT && rv < B_OK)
549			break;
550		if (B_OK != fLocker->LockWithTimeout(200000))
551			continue;
552
553		TRACE("MixerSettings: save thread running\n");
554
555		bigtime_t delta = system_time() - fSettingsLastChange;
556
557		if (fSettingsDirty && delta > SAVE_DELAY) {
558			Save();
559		}
560
561		if (delta > SAVE_RUNTIME) {
562			fSaveThreadRunning = false;
563			fLocker->Unlock();
564			break;
565		}
566
567		timeout = system_time() + SAVE_DELAY;
568		fLocker->Unlock();
569	}
570
571	TRACE("MixerSettings: save thread ended\n");
572}
573
574
575int32
576MixerSettings::_save_thread_(void *arg)
577{
578	static_cast<MixerSettings *>(arg)->SaveThread();
579	return 0;
580}
581