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