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