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
MidiPlayerWindow()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
~MidiPlayerWindow()78 MidiPlayerWindow::~MidiPlayerWindow()
79 {
80 StopSynth();
81
82 //fSynthBridge->Unregister();
83 fSynthBridge->Release();
84 }
85
86
87 bool
QuitRequested()88 MidiPlayerWindow::QuitRequested()
89 {
90 be_app->PostMessage(B_QUIT_REQUESTED);
91 return true;
92 }
93
94
95 void
MessageReceived(BMessage * message)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
FrameMoved(BPoint origin)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
MenusBeginning()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
CreateInputMenu()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
CreateReverbMenu()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
CreateViews()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
InitControls()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
LoadSettings()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
SaveSettings()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
LoadFile(entry_ref * ref)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
StartSynth()402 MidiPlayerWindow::StartSynth()
403 {
404 fMidiSynthFile.Start();
405 fMidiSynthFile.SetFileHook(_StopHook, (int32)(addr_t)this);
406 fIsPlaying = true;
407 }
408
409
410 void
StopSynth()411 MidiPlayerWindow::StopSynth()
412 {
413 if (!fMidiSynthFile.IsFinished())
414 fMidiSynthFile.Fade();
415
416 fIsPlaying = false;
417 }
418
419
420 void
_StopHook(int32 arg)421 MidiPlayerWindow::_StopHook(int32 arg)
422 {
423 ((MidiPlayerWindow*)(addr_t)arg)->StopHook();
424 }
425
426
427 void
StopHook()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
OnPlayStop()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
OnShowScope()466 MidiPlayerWindow::OnShowScope()
467 {
468 fScopeEnabled = !fScopeEnabled;
469 fScopeView->SetEnabled(fScopeEnabled);
470 fScopeView->Invalidate();
471 SaveSettings();
472 }
473
474
475 void
OnInputChanged(BMessage * message)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
OnReverb(reverb_mode mode)516 MidiPlayerWindow::OnReverb(reverb_mode mode)
517 {
518 fReverbMode = mode;
519 be_synth->SetReverb(fReverbMode);
520 SaveSettings();
521 }
522
523
524 void
OnVolume()525 MidiPlayerWindow::OnVolume()
526 {
527 fVolume = fVolumeSlider->Value();
528 fMidiSynthFile.SetVolume(fVolume / 100.0f);
529 SaveSettings();
530 }
531
532
533 void
OnDrop(BMessage * message)534 MidiPlayerWindow::OnDrop(BMessage* message)
535 {
536 entry_ref ref;
537 if (message->FindRef("refs", &ref) == B_OK)
538 LoadFile(&ref);
539 }
540