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