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