1/*
2 * Copyright (c) 2004 Matthijs Hollemans
3 * Copyright (c) 2008-2014 Haiku, Inc. All rights reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24
25#include "MidiPlayerWindow.h"
26
27#include <Catalog.h>
28#include <LayoutBuilder.h>
29#include <Locale.h>
30#include <MidiProducer.h>
31#include <MidiRoster.h>
32#include <SeparatorView.h>
33#include <StorageKit.h>
34#include <SpaceLayoutItem.h>
35
36#include "MidiPlayerApp.h"
37#include "ScopeView.h"
38#include "SynthBridge.h"
39
40
41#define _W(a) (a->Frame().Width())
42#define _H(a) (a->Frame().Height())
43
44
45#undef B_TRANSLATION_CONTEXT
46#define B_TRANSLATION_CONTEXT "Main Window"
47
48
49//	#pragma mark - MidiPlayerWindow
50
51
52MidiPlayerWindow::MidiPlayerWindow()
53	:
54	BWindow(BRect(0, 0, 1, 1), B_TRANSLATE_SYSTEM_NAME("MidiPlayer"),
55		B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_NOT_RESIZABLE
56			| B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS)
57{
58	fIsPlaying = false;
59	fScopeEnabled = true;
60	fReverbMode = B_REVERB_BALLROOM;
61	fVolume = 75;
62	fWindowX = -1;
63	fWindowY = -1;
64	fInputId = -1;
65	fInstrumentLoaded = false;
66
67	be_synth->SetSamplingRate(44100);
68
69	fSynthBridge = new SynthBridge;
70	//fSynthBridge->Register();
71
72	CreateViews();
73	LoadSettings();
74	InitControls();
75}
76
77
78MidiPlayerWindow::~MidiPlayerWindow()
79{
80	StopSynth();
81
82	//fSynthBridge->Unregister();
83	fSynthBridge->Release();
84}
85
86
87bool
88MidiPlayerWindow::QuitRequested()
89{
90	be_app->PostMessage(B_QUIT_REQUESTED);
91	return true;
92}
93
94
95void
96MidiPlayerWindow::MessageReceived(BMessage* message)
97{
98	switch (message->what) {
99		case MSG_PLAY_STOP:
100			OnPlayStop();
101			break;
102
103		case MSG_SHOW_SCOPE:
104			OnShowScope();
105			break;
106
107		case MSG_INPUT_CHANGED:
108			OnInputChanged(message);
109			break;
110
111		case MSG_REVERB_NONE:
112			OnReverb(B_REVERB_NONE);
113			break;
114
115		case MSG_REVERB_CLOSET:
116			OnReverb(B_REVERB_CLOSET);
117			break;
118
119		case MSG_REVERB_GARAGE:
120			OnReverb(B_REVERB_GARAGE);
121			break;
122
123		case MSG_REVERB_IGOR:
124			OnReverb(B_REVERB_BALLROOM);
125			break;
126
127		case MSG_REVERB_CAVERN:
128			OnReverb(B_REVERB_CAVERN);
129			break;
130
131		case MSG_REVERB_DUNGEON:
132			OnReverb(B_REVERB_DUNGEON);
133			break;
134
135		case MSG_VOLUME:
136			OnVolume();
137			break;
138
139		case B_SIMPLE_DATA:
140			OnDrop(message);
141			break;
142
143		default:
144			super::MessageReceived(message);
145			break;
146	}
147}
148
149
150void
151MidiPlayerWindow::FrameMoved(BPoint origin)
152{
153	super::FrameMoved(origin);
154	fWindowX = Frame().left;
155	fWindowY = Frame().top;
156	SaveSettings();
157}
158
159
160void
161MidiPlayerWindow::MenusBeginning()
162{
163	for (int32 t = fInputPopUpMenu->CountItems() - 1; t > 0; --t)
164		delete fInputPopUpMenu->RemoveItem(t);
165
166	// Note: if the selected endpoint no longer exists, then no endpoint is
167	// marked. However, we won't disconnect it until you choose another one.
168
169	fInputOffMenuItem->SetMarked(fInputId == -1);
170
171	int32 id = 0;
172	while (BMidiEndpoint* endpoint = BMidiRoster::NextEndpoint(&id)) {
173		if (endpoint->IsProducer()) {
174			BMessage* message = new BMessage(MSG_INPUT_CHANGED);
175			message->AddInt32("id", id);
176
177			BMenuItem* item = new BMenuItem(endpoint->Name(), message);
178			fInputPopUpMenu->AddItem(item);
179			item->SetMarked(fInputId == id);
180		}
181
182		endpoint->Release();
183	}
184}
185
186
187void
188MidiPlayerWindow::CreateInputMenu()
189{
190	fInputPopUpMenu = new BPopUpMenu("inputPopUp");
191
192	BMessage* message = new BMessage(MSG_INPUT_CHANGED);
193	message->AddInt32("id", -1);
194
195	fInputOffMenuItem = new BMenuItem(B_TRANSLATE("Off"), message);
196	fInputPopUpMenu->AddItem(fInputOffMenuItem);
197
198	fInputMenuField = new BMenuField(B_TRANSLATE("Live input:"),
199		fInputPopUpMenu);
200}
201
202
203void
204MidiPlayerWindow::CreateReverbMenu()
205{
206	BPopUpMenu* reverbPopUpMenu = new BPopUpMenu("reverbPopUp");
207	fReverbNoneMenuItem = new BMenuItem(B_TRANSLATE("None"),
208		new BMessage(MSG_REVERB_NONE));
209	fReverbClosetMenuItem = new BMenuItem(B_TRANSLATE("Closet"),
210		new BMessage(MSG_REVERB_CLOSET));
211	fReverbGarageMenuItem = new BMenuItem(B_TRANSLATE("Garage"),
212		new BMessage(MSG_REVERB_GARAGE));
213	fReverbIgorMenuItem = new BMenuItem(B_TRANSLATE("Igor's lab"),
214		new BMessage(MSG_REVERB_IGOR));
215	fReverbCavern = new BMenuItem(B_TRANSLATE("Cavern"),
216		new BMessage(MSG_REVERB_CAVERN));
217	fReverbDungeon = new BMenuItem(B_TRANSLATE("Dungeon"),
218		new BMessage(MSG_REVERB_DUNGEON));
219
220	reverbPopUpMenu->AddItem(fReverbNoneMenuItem);
221	reverbPopUpMenu->AddItem(fReverbClosetMenuItem);
222	reverbPopUpMenu->AddItem(fReverbGarageMenuItem);
223	reverbPopUpMenu->AddItem(fReverbIgorMenuItem);
224	reverbPopUpMenu->AddItem(fReverbCavern);
225	reverbPopUpMenu->AddItem(fReverbDungeon);
226
227	fReverbMenuField = new BMenuField(B_TRANSLATE("Reverb:"), reverbPopUpMenu);
228}
229
230
231void
232MidiPlayerWindow::CreateViews()
233{
234	// Set up needed views
235	fScopeView = new ScopeView();
236
237	fShowScopeCheckBox = new BCheckBox("showScope", B_TRANSLATE("Scope"),
238		new BMessage(MSG_SHOW_SCOPE));
239	fShowScopeCheckBox->SetValue(B_CONTROL_ON);
240
241	CreateInputMenu();
242	CreateReverbMenu();
243
244	fVolumeSlider = new BSlider("volumeSlider", NULL, NULL, 0, 100,
245		B_HORIZONTAL);
246	rgb_color color = (rgb_color){ 152, 152, 255 };
247	fVolumeSlider->UseFillColor(true, &color);
248	fVolumeSlider->SetModificationMessage(new BMessage(MSG_VOLUME));
249	fVolumeSlider->SetExplicitMinSize(
250		BSize(fScopeView->Frame().Width(), B_SIZE_UNSET));
251
252	fPlayButton = new BButton("playButton", B_TRANSLATE("Play"),
253		new BMessage(MSG_PLAY_STOP));
254	fPlayButton->SetEnabled(false);
255
256	BStringView* volumeLabel = new BStringView(NULL, B_TRANSLATE("Volume:"));
257	volumeLabel->SetAlignment(B_ALIGN_LEFT);
258
259	// Build the layout
260	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
261		.Add(fScopeView)
262		.AddGroup(B_VERTICAL, 0)
263			.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
264				.Add(fShowScopeCheckBox, 1, 0)
265
266				.Add(fReverbMenuField->CreateLabelLayoutItem(), 0, 1)
267				.AddGroup(B_HORIZONTAL, 0.0f, 1, 1)
268					.Add(fReverbMenuField->CreateMenuBarLayoutItem())
269					.AddGlue()
270					.End()
271
272				.Add(fInputMenuField->CreateLabelLayoutItem(), 0, 2)
273				.AddGroup(B_HORIZONTAL, 0.0f, 1, 2)
274					.Add(fInputMenuField->CreateMenuBarLayoutItem())
275					.AddGlue()
276					.End()
277
278				.Add(volumeLabel, 0, 3)
279				.Add(fVolumeSlider, 0, 4, 2, 1)
280				.End()
281			.AddGlue()
282			.SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING,
283				B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
284
285			.End()
286		.Add(new BSeparatorView(B_HORIZONTAL))
287		.AddGroup(B_VERTICAL, 0)
288			.Add(fPlayButton)
289			.SetInsets(0, B_USE_DEFAULT_SPACING, 0, B_USE_WINDOW_SPACING)
290			.End()
291		.End();
292}
293
294
295void
296MidiPlayerWindow::InitControls()
297{
298	Lock();
299
300	fShowScopeCheckBox->SetValue(fScopeEnabled ? B_CONTROL_ON : B_CONTROL_OFF);
301	fScopeView->SetEnabled(fScopeEnabled);
302
303	fInputOffMenuItem->SetMarked(true);
304
305	fReverbNoneMenuItem->SetMarked(fReverbMode == B_REVERB_NONE);
306	fReverbClosetMenuItem->SetMarked(fReverbMode == B_REVERB_CLOSET);
307	fReverbGarageMenuItem->SetMarked(fReverbMode == B_REVERB_GARAGE);
308	fReverbIgorMenuItem->SetMarked(fReverbMode == B_REVERB_BALLROOM);
309	fReverbCavern->SetMarked(fReverbMode == B_REVERB_CAVERN);
310	fReverbDungeon->SetMarked(fReverbMode == B_REVERB_DUNGEON);
311	be_synth->SetReverb(fReverbMode);
312
313	fVolumeSlider->SetValue(fVolume);
314
315	if (fWindowX != -1 && fWindowY != -1)
316		MoveTo(fWindowX, fWindowY);
317	else
318		CenterOnScreen();
319
320	Unlock();
321}
322
323
324void
325MidiPlayerWindow::LoadSettings()
326{
327	BFile file(SETTINGS_FILE, B_READ_ONLY);
328	if (file.InitCheck() != B_OK || file.Lock() != B_OK)
329		return;
330
331	file.ReadAttr("Scope", B_BOOL_TYPE, 0, &fScopeEnabled, sizeof(bool));
332	file.ReadAttr("Reverb", B_INT32_TYPE, 0, &fReverbMode, sizeof(int32));
333	file.ReadAttr("Volume", B_INT32_TYPE, 0, &fWindowX, sizeof(int32));
334	file.ReadAttr("WindowX", B_FLOAT_TYPE, 0, &fWindowX, sizeof(float));
335	file.ReadAttr("WindowY", B_FLOAT_TYPE, 0, &fWindowY, sizeof(float));
336
337	file.Unlock();
338}
339
340
341void
342MidiPlayerWindow::SaveSettings()
343{
344	BFile file(SETTINGS_FILE, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
345	if (file.InitCheck() != B_OK || file.Lock() != B_OK)
346		return;
347
348	file.WriteAttr("Scope", B_BOOL_TYPE, 0, &fScopeEnabled, sizeof(bool));
349	file.WriteAttr("Reverb", B_INT32_TYPE, 0, &fReverbMode, sizeof(int32));
350	file.WriteAttr("Volume", B_INT32_TYPE, 0, &fVolume, sizeof(int32));
351	file.WriteAttr("WindowX", B_FLOAT_TYPE, 0, &fWindowX, sizeof(float));
352	file.WriteAttr("WindowY", B_FLOAT_TYPE, 0, &fWindowY, sizeof(float));
353
354	file.Sync();
355	file.Unlock();
356}
357
358
359void
360MidiPlayerWindow::LoadFile(entry_ref* ref)
361{
362	if (fIsPlaying) {
363		fScopeView->SetPlaying(false);
364		fScopeView->Invalidate();
365		UpdateIfNeeded();
366
367		StopSynth();
368	}
369
370	fMidiSynthFile.UnloadFile();
371
372	if (fMidiSynthFile.LoadFile(ref) == B_OK) {
373		// Ideally, we would call SetVolume() in InitControls(),
374		// but for some reason that doesn't work: BMidiSynthFile
375		// will use the default volume instead. So we do it here.
376		fMidiSynthFile.SetVolume(fVolume / 100.0f);
377
378		fPlayButton->SetEnabled(true);
379		fPlayButton->SetLabel(B_TRANSLATE("Stop"));
380		fScopeView->SetHaveFile(true);
381		fScopeView->SetPlaying(true);
382		fScopeView->Invalidate();
383
384		StartSynth();
385	} else {
386		fPlayButton->SetEnabled(false);
387		fPlayButton->SetLabel(B_TRANSLATE("Play"));
388		fScopeView->SetHaveFile(false);
389		fScopeView->SetPlaying(false);
390		fScopeView->Invalidate();
391
392		BAlert* alert = new BAlert(NULL, B_TRANSLATE("Could not load song"),
393			B_TRANSLATE("OK"), NULL, NULL,
394			B_WIDTH_AS_USUAL, B_STOP_ALERT);
395		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
396		alert->Go();
397	}
398}
399
400
401void
402MidiPlayerWindow::StartSynth()
403{
404	fMidiSynthFile.Start();
405	fMidiSynthFile.SetFileHook(_StopHook, (int32)(addr_t)this);
406	fIsPlaying = true;
407}
408
409
410void
411MidiPlayerWindow::StopSynth()
412{
413	if (!fMidiSynthFile.IsFinished())
414		fMidiSynthFile.Fade();
415
416	fIsPlaying = false;
417}
418
419
420void
421MidiPlayerWindow::_StopHook(int32 arg)
422{
423	((MidiPlayerWindow*)(addr_t)arg)->StopHook();
424}
425
426
427void
428MidiPlayerWindow::StopHook()
429{
430	if (Lock()) {
431		// we may be called from the synth's thread
432
433		fIsPlaying = false;
434
435		fScopeView->SetPlaying(false);
436		fScopeView->Invalidate();
437		fPlayButton->SetEnabled(true);
438		fPlayButton->SetLabel(B_TRANSLATE("Play"));
439
440		Unlock();
441	}
442}
443
444
445void
446MidiPlayerWindow::OnPlayStop()
447{
448	if (fIsPlaying) {
449		fPlayButton->SetEnabled(false);
450		fScopeView->SetPlaying(false);
451		fScopeView->Invalidate();
452		UpdateIfNeeded();
453
454		StopSynth();
455	} else {
456		fPlayButton->SetLabel(B_TRANSLATE("Stop"));
457		fScopeView->SetPlaying(true);
458		fScopeView->Invalidate();
459
460		StartSynth();
461	}
462}
463
464
465void
466MidiPlayerWindow::OnShowScope()
467{
468	fScopeEnabled = !fScopeEnabled;
469	fScopeView->SetEnabled(fScopeEnabled);
470	fScopeView->Invalidate();
471	SaveSettings();
472}
473
474
475void
476MidiPlayerWindow::OnInputChanged(BMessage* message)
477{
478	int32 newId;
479	if (message->FindInt32("id", &newId) == B_OK) {
480		BMidiProducer* endpoint = BMidiRoster::FindProducer(fInputId);
481		if (endpoint != NULL) {
482			endpoint->Disconnect(fSynthBridge);
483			endpoint->Release();
484		}
485
486		fInputId = newId;
487
488		endpoint = BMidiRoster::FindProducer(fInputId);
489		if (endpoint != NULL) {
490			if (!fInstrumentLoaded) {
491				fScopeView->SetLoading(true);
492				fScopeView->Invalidate();
493				UpdateIfNeeded();
494
495				fSynthBridge->Init(B_BIG_SYNTH);
496				fInstrumentLoaded = true;
497
498				fScopeView->SetLoading(false);
499				fScopeView->Invalidate();
500			}
501
502			endpoint->Connect(fSynthBridge);
503			endpoint->Release();
504
505			fScopeView->SetLiveInput(true);
506			fScopeView->Invalidate();
507		} else {
508			fScopeView->SetLiveInput(false);
509			fScopeView->Invalidate();
510		}
511	}
512}
513
514
515void
516MidiPlayerWindow::OnReverb(reverb_mode mode)
517{
518	fReverbMode = mode;
519	be_synth->SetReverb(fReverbMode);
520	SaveSettings();
521}
522
523
524void
525MidiPlayerWindow::OnVolume()
526{
527	fVolume = fVolumeSlider->Value();
528	fMidiSynthFile.SetVolume(fVolume / 100.0f);
529	SaveSettings();
530}
531
532
533void
534MidiPlayerWindow::OnDrop(BMessage* message)
535{
536	entry_ref ref;
537	if (message->FindRef("refs", &ref) == B_OK)
538		LoadFile(&ref);
539}
540