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