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