xref: /haiku/src/apps/midiplayer/MidiPlayerWindow.cpp (revision c237c4ce593ee823d9867fd997e51e4c447f5623)
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 
52 MidiPlayerWindow::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 
78 MidiPlayerWindow::~MidiPlayerWindow()
79 {
80 	StopSynth();
81 
82 	//fSynthBridge->Unregister();
83 	fSynthBridge->Release();
84 }
85 
86 
87 bool
88 MidiPlayerWindow::QuitRequested()
89 {
90 	be_app->PostMessage(B_QUIT_REQUESTED);
91 	return true;
92 }
93 
94 
95 void
96 MidiPlayerWindow::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 
150 void
151 MidiPlayerWindow::FrameMoved(BPoint origin)
152 {
153 	super::FrameMoved(origin);
154 	fWindowX = Frame().left;
155 	fWindowY = Frame().top;
156 	SaveSettings();
157 }
158 
159 
160 void
161 MidiPlayerWindow::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 
187 void
188 MidiPlayerWindow::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 
203 void
204 MidiPlayerWindow::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 
231 void
232 MidiPlayerWindow::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 
295 void
296 MidiPlayerWindow::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 
324 void
325 MidiPlayerWindow::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 
341 void
342 MidiPlayerWindow::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 
359 void
360 MidiPlayerWindow::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 
401 void
402 MidiPlayerWindow::StartSynth()
403 {
404 	fMidiSynthFile.Start();
405 	fMidiSynthFile.SetFileHook(_StopHook, (int32)(addr_t)this);
406 	fIsPlaying = true;
407 }
408 
409 
410 void
411 MidiPlayerWindow::StopSynth()
412 {
413 	if (!fMidiSynthFile.IsFinished())
414 		fMidiSynthFile.Fade();
415 
416 	fIsPlaying = false;
417 }
418 
419 
420 void
421 MidiPlayerWindow::_StopHook(int32 arg)
422 {
423 	((MidiPlayerWindow*)(addr_t)arg)->StopHook();
424 }
425 
426 
427 void
428 MidiPlayerWindow::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 
445 void
446 MidiPlayerWindow::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 
465 void
466 MidiPlayerWindow::OnShowScope()
467 {
468 	fScopeEnabled = !fScopeEnabled;
469 	fScopeView->SetEnabled(fScopeEnabled);
470 	fScopeView->Invalidate();
471 	SaveSettings();
472 }
473 
474 
475 void
476 MidiPlayerWindow::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 
515 void
516 MidiPlayerWindow::OnReverb(reverb_mode mode)
517 {
518 	fReverbMode = mode;
519 	be_synth->SetReverb(fReverbMode);
520 	SaveSettings();
521 }
522 
523 
524 void
525 MidiPlayerWindow::OnVolume()
526 {
527 	fVolume = fVolumeSlider->Value();
528 	fMidiSynthFile.SetVolume(fVolume / 100.0f);
529 	SaveSettings();
530 }
531 
532 
533 void
534 MidiPlayerWindow::OnDrop(BMessage* message)
535 {
536 	entry_ref ref;
537 	if (message->FindRef("refs", &ref) == B_OK)
538 		LoadFile(&ref);
539 }
540