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