1 /* 2 * Copyright 2005, Jérôme Duval. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Inspired by SoundCapture from Be newsletter (Media Kit Basics: 6 * Consumers and Producers) 7 */ 8 9 #include <Application.h> 10 #include <Alert.h> 11 #include <Debug.h> 12 #include <Screen.h> 13 #include <Button.h> 14 #include <CheckBox.h> 15 #include <TextControl.h> 16 #include <MenuField.h> 17 #include <PopUpMenu.h> 18 #include <MenuItem.h> 19 #include <Box.h> 20 #include <ScrollView.h> 21 #include <Beep.h> 22 #include <StringView.h> 23 #include <String.h> 24 #include <Slider.h> 25 #include <Message.h> 26 27 #include <Path.h> 28 #include <FindDirectory.h> 29 #include <MediaAddOn.h> 30 31 #include <SoundPlayer.h> 32 33 #include <assert.h> 34 #include <stdio.h> 35 #include <string.h> 36 #include <stdlib.h> 37 #include <ctype.h> 38 #include <unistd.h> 39 #include <fcntl.h> 40 41 #include <MediaRoster.h> 42 #include <TimeSource.h> 43 #include <NodeInfo.h> 44 45 #include "RecorderWindow.h" 46 #include "SoundConsumer.h" 47 #include "FileUtils.h" 48 49 #if ! NDEBUG 50 #define FPRINTF(args) fprintf args 51 #else 52 #define FPRINTF(args) 53 #endif 54 55 #define DEATH FPRINTF 56 #define CONNECT FPRINTF 57 #define WINDOW FPRINTF 58 59 #undef B_TRANSLATION_CONTEXT 60 #define B_TRANSLATION_CONTEXT "RecorderWindow" 61 62 63 // default window positioning 64 static const float MIN_WIDTH = 400.0f; 65 static const float MIN_HEIGHT = 175.0f; 66 static const float XPOS = 100.0f; 67 static const float YPOS = 200.0f; 68 69 #define FOURCC(a,b,c,d) ((((uint32)(d)) << 24) | (((uint32)(c)) << 16) \ 70 | (((uint32)(b)) << 8) | ((uint32)(a))) 71 72 struct riff_struct 73 { 74 uint32 riff_id; // 'RIFF' 75 uint32 len; 76 uint32 wave_id; // 'WAVE' 77 }; 78 79 struct chunk_struct 80 { 81 uint32 fourcc; 82 uint32 len; 83 }; 84 85 struct format_struct 86 { 87 uint16 format_tag; 88 uint16 channels; 89 uint32 samples_per_sec; 90 uint32 avg_bytes_per_sec; 91 uint16 block_align; 92 uint16 bits_per_sample; 93 }; 94 95 96 struct wave_struct 97 { 98 struct riff_struct riff; 99 struct chunk_struct format_chunk; 100 struct format_struct format; 101 struct chunk_struct data_chunk; 102 }; 103 104 105 RecorderWindow::RecorderWindow() 106 : 107 BWindow(BRect(XPOS, YPOS, XPOS + MIN_WIDTH, YPOS + MIN_HEIGHT), 108 B_TRANSLATE_SYSTEM_NAME("SoundRecorder"), B_TITLED_WINDOW, 109 B_ASYNCHRONOUS_CONTROLS | B_NOT_V_RESIZABLE | B_NOT_ZOOMABLE), 110 fPlayer(NULL), 111 fSoundList(NULL), 112 fPlayFile(NULL), 113 fPlayTrack(NULL), 114 fPlayFrames(0), 115 fLooping(false), 116 fSavePanel(NULL), 117 fInitCheck(B_OK) 118 { 119 fRoster = NULL; 120 fRecordButton = NULL; 121 fPlayButton = NULL; 122 fStopButton = NULL; 123 fSaveButton = NULL; 124 fLoopButton = NULL; 125 fInputField = NULL; 126 fRecordNode = 0; 127 fRecording = false; 128 fTempCount = -1; 129 fButtonState = btnPaused; 130 131 CalcSizes(MIN_WIDTH, MIN_HEIGHT); 132 133 fInitCheck = InitWindow(); 134 if (fInitCheck != B_OK) { 135 if (fInitCheck == B_NAME_NOT_FOUND) 136 ErrorAlert(B_TRANSLATE("Cannot find default audio hardware"), 137 fInitCheck); 138 else 139 ErrorAlert(B_TRANSLATE("Cannot connect to media server"), 140 fInitCheck); 141 PostMessage(B_QUIT_REQUESTED); 142 } else 143 Show(); 144 } 145 146 147 RecorderWindow::~RecorderWindow() 148 { 149 // The sound consumer and producer are Nodes; it has to be released and the Roster 150 // will reap it when it's done. 151 if (fRecordNode) 152 fRecordNode->Release(); 153 delete fPlayer; 154 155 if (fPlayTrack && fPlayFile) 156 fPlayFile->ReleaseTrack(fPlayTrack); 157 if (fPlayFile) 158 delete fPlayFile; 159 fPlayTrack = NULL; 160 fPlayFile = NULL; 161 162 // Clean up items in list view. 163 if (fSoundList) { 164 fSoundList->DeselectAll(); 165 for (int i = 0; i < fSoundList->CountItems(); i++) { 166 WINDOW((stderr, "clean up item %d\n", i+1)); 167 SoundListItem* item = dynamic_cast<SoundListItem *>(fSoundList->ItemAt(i)); 168 if (item) { 169 if (item->IsTemp()) 170 item->Entry().Remove(); // delete temp file 171 delete item; 172 } 173 } 174 fSoundList->MakeEmpty(); 175 } 176 // Clean up currently recording file, if any. 177 fRecEntry.Remove(); 178 fRecEntry.Unset(); 179 180 delete fSavePanel; 181 } 182 183 184 status_t 185 RecorderWindow::InitCheck() 186 { 187 return fInitCheck; 188 } 189 190 191 void 192 RecorderWindow::CalcSizes(float min_width, float min_height) 193 { 194 // Set up size limits based on new screen size 195 BScreen screen(this); 196 BRect rect = screen.Frame(); 197 float width = rect.Width() - 12; 198 SetSizeLimits(min_width, width, min_height, rect.Height() - 24); 199 200 // Don't zoom to cover all of screen; user can resize last bit if necessary. 201 // This leaves other windows visible. 202 if (width > 640) 203 width = 640 + (width - 640) / 2; 204 SetZoomLimits(width, rect.Height() - 24); 205 } 206 207 208 status_t 209 RecorderWindow::InitWindow() 210 { 211 BPopUpMenu * popup = 0; 212 status_t error; 213 214 try { 215 // Find temp directory for recorded sounds. 216 BPath path; 217 if (!(error = find_directory(B_COMMON_TEMP_DIRECTORY, &path))) 218 error = fTempDir.SetTo(path.Path()); 219 if (error < 0) 220 goto bad_mojo; 221 222 // Make sure the media roster is there (which means the server is there). 223 fRoster = BMediaRoster::Roster(&error); 224 if (!fRoster) 225 goto bad_mojo; 226 227 error = fRoster->GetAudioInput(&fAudioInputNode); 228 if (error < B_OK) // there's no input? 229 goto bad_mojo; 230 231 error = fRoster->GetAudioMixer(&fAudioMixerNode); 232 if (error < B_OK) // there's no mixer? 233 goto bad_mojo; 234 235 // Create our internal Node which records sound, and register it. 236 fRecordNode = new SoundConsumer("Sound Recorder"); 237 error = fRoster->RegisterNode(fRecordNode); 238 if (error < B_OK) 239 goto bad_mojo; 240 241 // Create the window header with controls 242 BRect r(Bounds()); 243 r.bottom = r.top + 175; 244 BBox *background = new BBox(r, "_background", B_FOLLOW_LEFT_RIGHT 245 | B_FOLLOW_TOP, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER); 246 AddChild(background); 247 248 r = background->Bounds(); 249 r.left = 0; 250 r.right = r.left + 38; 251 r.bottom = r.top + 104; 252 fVUView = new VUView(r, B_FOLLOW_LEFT|B_FOLLOW_TOP); 253 background->AddChild(fVUView); 254 255 r = background->Bounds(); 256 r.left = r.left + 40; 257 r.bottom = r.top + 104; 258 fScopeView = new ScopeView(r, B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP); 259 background->AddChild(fScopeView); 260 261 r = background->Bounds(); 262 r.left = 2; 263 r.right -= 26; 264 r.top = 115; 265 r.bottom = r.top + 30; 266 fTrackSlider = new TrackSlider(r, "trackSlider", new BMessage(POSITION_CHANGED), 267 B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP); 268 background->AddChild(fTrackSlider); 269 270 BRect buttonRect; 271 272 // Button for rewinding 273 buttonRect = BRect(BPoint(0,0), kSkipButtonSize); 274 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-7, 25)); 275 fRewindButton = new TransportButton(buttonRect, B_TRANSLATE("Rewind"), 276 kSkipBackBitmapBits, kPressedSkipBackBitmapBits, 277 kDisabledSkipBackBitmapBits, new BMessage(REWIND)); 278 background->AddChild(fRewindButton); 279 280 // Button for stopping recording or playback 281 buttonRect = BRect(BPoint(0,0), kStopButtonSize); 282 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-48, 25)); 283 fStopButton = new TransportButton(buttonRect, B_TRANSLATE("Stop"), 284 kStopButtonBitmapBits, kPressedStopButtonBitmapBits, 285 kDisabledStopButtonBitmapBits, new BMessage(STOP)); 286 background->AddChild(fStopButton); 287 288 // Button for starting playback of selected sound 289 BRect playRect(BPoint(0,0), kPlayButtonSize); 290 playRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-82, 25)); 291 fPlayButton = new PlayPauseButton(playRect, B_TRANSLATE("Play"), 292 new BMessage(PLAY), new BMessage(PLAY_PERIOD), ' ', 0); 293 background->AddChild(fPlayButton); 294 295 // Button for forwarding 296 buttonRect = BRect(BPoint(0,0), kSkipButtonSize); 297 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-133, 25)); 298 fForwardButton = new TransportButton(buttonRect, B_TRANSLATE("Forward"), 299 kSkipForwardBitmapBits, kPressedSkipForwardBitmapBits, 300 kDisabledSkipForwardBitmapBits, new BMessage(FORWARD)); 301 background->AddChild(fForwardButton); 302 303 // Button to start recording (or waiting for sound) 304 buttonRect = BRect(BPoint(0,0), kRecordButtonSize); 305 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-174, 25)); 306 fRecordButton = new RecordButton(buttonRect, B_TRANSLATE("Record"), 307 new BMessage(RECORD), new BMessage(RECORD_PERIOD)); 308 background->AddChild(fRecordButton); 309 310 // Button for saving selected sound 311 buttonRect = BRect(BPoint(0,0), kDiskButtonSize); 312 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-250, 21)); 313 fSaveButton = new TransportButton(buttonRect, B_TRANSLATE("Save"), 314 kDiskButtonBitmapsBits, kPressedDiskButtonBitmapsBits, 315 kDisabledDiskButtonBitmapsBits, new BMessage(SAVE)); 316 fSaveButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 317 background->AddChild(fSaveButton); 318 319 // Button Loop 320 buttonRect = BRect(BPoint(0,0), kArrowSize); 321 buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(23, 48)); 322 fLoopButton = new DrawButton(buttonRect, B_TRANSLATE("Loop"), 323 kLoopArrowBits, kArrowBits, new BMessage(LOOP)); 324 fLoopButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 325 fLoopButton->SetTarget(this); 326 background->AddChild(fLoopButton); 327 328 buttonRect = BRect(BPoint(0,0), kSpeakerIconBitmapSize); 329 buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(121, 17)); 330 SpeakerView *speakerView = new SpeakerView(buttonRect, 331 B_FOLLOW_LEFT | B_FOLLOW_TOP); 332 speakerView->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 333 background->AddChild(speakerView); 334 335 buttonRect = BRect(BPoint(0,0), BPoint(84, 19)); 336 buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(107, 20)); 337 fVolumeSlider = new VolumeSlider(buttonRect, "volumeSlider", 338 B_FOLLOW_LEFT | B_FOLLOW_TOP); 339 fVolumeSlider->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 340 background->AddChild(fVolumeSlider); 341 342 // Button to mask/see sounds list 343 buttonRect = BRect(BPoint(0,0), kUpDownButtonSize); 344 buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(21, 25)); 345 fUpDownButton = new UpDownButton(buttonRect, new BMessage(VIEW_LIST)); 346 fUpDownButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 347 background->AddChild(fUpDownButton); 348 349 r = Bounds(); 350 r.top = background->Bounds().bottom + 1; 351 fBottomBox = new BBox(r, "bottomBox", B_FOLLOW_ALL); 352 fBottomBox->SetBorder(B_NO_BORDER); 353 AddChild(fBottomBox); 354 355 // The actual list of recorded sounds (initially empty) sits 356 // below the header with the controls. 357 r = fBottomBox->Bounds(); 358 r.left += 190; 359 r.InsetBy(10, 10); 360 r.left -= 10; 361 r.top += 4; 362 r.right -= B_V_SCROLL_BAR_WIDTH; 363 r.bottom -= 25; 364 fSoundList = new SoundListView(r, B_TRANSLATE("Sound List"), 365 B_FOLLOW_ALL); 366 fSoundList->SetSelectionMessage(new BMessage(SOUND_SELECTED)); 367 fSoundList->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 368 BScrollView *scroller = new BScrollView("scroller", fSoundList, 369 B_FOLLOW_ALL, 0, false, true, B_FANCY_BORDER); 370 fBottomBox->AddChild(scroller); 371 372 r = fBottomBox->Bounds(); 373 r.right = r.left + 190; 374 r.bottom -= 25; 375 r.InsetBy(10, 8); 376 r.top -= 1; 377 fFileInfoBox = new BBox(r, "fileinfo", B_FOLLOW_LEFT); 378 fFileInfoBox->SetLabel(B_TRANSLATE("File info")); 379 380 fFileInfoBox->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 381 382 BFont font = be_plain_font; 383 font.SetSize(font.Size() * 0.92f); 384 font_height height; 385 font.GetHeight(&height); 386 float fontHeight = height.ascent + height.leading + height.descent; 387 388 r = fFileInfoBox->Bounds(); 389 r.left = 8; 390 r.top = fontHeight + 6; 391 r.bottom = r.top + fontHeight + 3; 392 r.right -= 10; 393 fFilename = new BStringView(r, "filename", B_TRANSLATE("File name:")); 394 fFileInfoBox->AddChild(fFilename); 395 fFilename->SetFont(&font, B_FONT_SIZE); 396 fFilename->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 397 398 r.top += fontHeight; 399 r.bottom = r.top + fontHeight + 3; 400 fFormat = new BStringView(r, "format", B_TRANSLATE("Format:")); 401 fFileInfoBox->AddChild(fFormat); 402 fFormat->SetFont(&font, B_FONT_SIZE); 403 fFormat->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 404 405 r.top += fontHeight; 406 r.bottom = r.top + fontHeight + 3; 407 fCompression = new BStringView(r, "compression", 408 B_TRANSLATE("Compression:")); 409 fFileInfoBox->AddChild(fCompression); 410 fCompression->SetFont(&font, B_FONT_SIZE); 411 fCompression->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 412 413 r.top += fontHeight; 414 r.bottom = r.top + fontHeight + 3; 415 fChannels = new BStringView(r, "channels", B_TRANSLATE("Channels:")); 416 fFileInfoBox->AddChild(fChannels); 417 fChannels->SetFont(&font, B_FONT_SIZE); 418 fChannels->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 419 420 r.top += fontHeight; 421 r.bottom = r.top + fontHeight + 3; 422 fSampleSize = new BStringView(r, "samplesize", 423 B_TRANSLATE("Sample size:")); 424 fFileInfoBox->AddChild(fSampleSize); 425 fSampleSize->SetFont(&font, B_FONT_SIZE); 426 fSampleSize->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 427 428 r.top += fontHeight; 429 r.bottom = r.top + fontHeight + 3; 430 fSampleRate = new BStringView(r, "samplerate", 431 B_TRANSLATE("Sample rate:")); 432 fFileInfoBox->AddChild(fSampleRate); 433 fSampleRate->SetFont(&font, B_FONT_SIZE); 434 fSampleRate->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 435 436 r.top += fontHeight; 437 r.bottom = r.top + fontHeight + 3; 438 fDuration = new BStringView(r, "duration", B_TRANSLATE("Duration:")); 439 fFileInfoBox->AddChild(fDuration); 440 fDuration->SetFont(&font, B_FONT_SIZE); 441 fDuration->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 442 443 fFileInfoBox->ResizeTo(fFileInfoBox->Frame().Width(), 444 r.bottom + fontHeight / 2.0f); 445 fDeployedHeight = MIN_HEIGHT + fFileInfoBox->Bounds().Height() + 40.0f; 446 447 // Input selection lists all available physical inputs that produce 448 // buffers with B_MEDIA_RAW_AUDIO format data. 449 popup = new BPopUpMenu(B_TRANSLATE("Input")); 450 const int maxInputCount = 64; 451 dormant_node_info dni[maxInputCount]; 452 453 int32 real_count = maxInputCount; 454 media_format output_format; 455 output_format.type = B_MEDIA_RAW_AUDIO; 456 output_format.u.raw_audio = media_raw_audio_format::wildcard; 457 error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format, 458 0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT); 459 if (real_count > maxInputCount) { 460 WINDOW((stderr, "dropped %" B_PRId32 " inputs\n", real_count - maxInputCount)); 461 real_count = maxInputCount; 462 } 463 char selected_name[B_MEDIA_NAME_LENGTH] = "Default input"; 464 BMessage * msg; 465 BMenuItem * item; 466 for (int i = 0; i < real_count; i++) { 467 msg = new BMessage(INPUT_SELECTED); 468 msg->AddData("node", B_RAW_TYPE, &dni[i], sizeof(dni[i])); 469 item = new BMenuItem(dni[i].name, msg); 470 popup->AddItem(item); 471 media_node_id ni[12]; 472 int32 ni_count = 12; 473 error = fRoster->GetInstancesFor(dni[i].addon, dni[i].flavor_id, 474 ni, &ni_count); 475 if (error == B_OK) { 476 for (int j = 0; j < ni_count; j++) { 477 if (ni[j] == fAudioInputNode.node) { 478 strcpy(selected_name, dni[i].name); 479 break; 480 } 481 } 482 } 483 } 484 485 // Create the actual widget 486 r = fFileInfoBox->Bounds(); 487 r.top = r.bottom + 2; 488 r.bottom = r.top + 18; 489 r.InsetBy(10, 10); 490 fInputField = new BMenuField(r, "Input", B_TRANSLATE("Input:"), popup); 491 fInputField->SetDivider(fInputField->StringWidth(B_TRANSLATE("Input:")) 492 + 4.0f); 493 fBottomBox->AddChild(fInputField); 494 495 fBottomBox->AddChild(fFileInfoBox); 496 497 fBottomBox->Hide(); 498 CalcSizes(MIN_WIDTH, MIN_HEIGHT); 499 ResizeTo(Frame().Width(), MIN_HEIGHT); 500 501 popup->Superitem()->SetLabel(selected_name); 502 503 // Make sure the save panel is happy. 504 fSavePanel = new BFilePanel(B_SAVE_PANEL); 505 fSavePanel->SetTarget(this); 506 } 507 catch (...) { 508 goto bad_mojo; 509 } 510 UpdateButtons(); 511 return B_OK; 512 513 // Error handling. 514 bad_mojo: 515 if (error >= 0) 516 error = B_ERROR; 517 if (fRecordNode) 518 fRecordNode->Release(); 519 520 delete fPlayer; 521 if (!fInputField) 522 delete popup; 523 return error; 524 } 525 526 527 bool 528 RecorderWindow::QuitRequested() // this means Close pressed 529 { 530 StopRecording(); 531 StopPlaying(); 532 be_app->PostMessage(B_QUIT_REQUESTED); 533 return true; 534 } 535 536 537 void 538 RecorderWindow::MessageReceived(BMessage * message) 539 { 540 // Your average generic message dispatching switch() statement. 541 switch (message->what) { 542 case INPUT_SELECTED: 543 Input(message); 544 break; 545 case SOUND_SELECTED: 546 Selected(message); 547 break; 548 case STOP_PLAYING: 549 StopPlaying(); 550 break; 551 case STOP_RECORDING: 552 StopRecording(); 553 break; 554 case PLAY_PERIOD: 555 if (fPlayer) { 556 if (fPlayer->HasData()) 557 fPlayButton->SetPlaying(); 558 else 559 fPlayButton->SetPaused(); 560 } 561 break; 562 case RECORD_PERIOD: 563 fRecordButton->SetRecording(); 564 break; 565 case RECORD: 566 Record(message); 567 break; 568 case STOP: 569 Stop(message); 570 break; 571 case PLAY: 572 Play(message); 573 break; 574 case SAVE: 575 Save(message); 576 break; 577 case B_SAVE_REQUESTED: 578 DoSave(message); 579 break; 580 case VIEW_LIST: 581 if (fUpDownButton->Value() == B_CONTROL_ON) { 582 fBottomBox->Show(); 583 CalcSizes(MIN_WIDTH, fDeployedHeight); 584 ResizeTo(Frame().Width(), fDeployedHeight); 585 } else { 586 fBottomBox->Hide(); 587 CalcSizes(MIN_WIDTH, MIN_HEIGHT); 588 ResizeTo(Frame().Width(), MIN_HEIGHT); 589 590 } 591 break; 592 case UPDATE_TRACKSLIDER: 593 { 594 bigtime_t timestamp = fPlayTrack->CurrentTime(); 595 fTrackSlider->SetMainTime(timestamp, false); 596 fScopeView->SetMainTime(timestamp); 597 } 598 break; 599 case POSITION_CHANGED: 600 { 601 bigtime_t right, left, main; 602 if (message->FindInt64("main", &main) == B_OK) { 603 if (fPlayTrack) { 604 fPlayTrack->SeekToTime(fTrackSlider->MainTime()); 605 fPlayFrame = fPlayTrack->CurrentFrame(); 606 } 607 fScopeView->SetMainTime(main); 608 } 609 if (message->FindInt64("right", &right) == B_OK) { 610 if (fPlayTrack) { 611 fPlayLimit = MIN(fPlayFrames, 612 (off_t)(right * fPlayFormat.u.raw_audio.frame_rate 613 / 1000000LL)); 614 } 615 fScopeView->SetRightTime(right); 616 } 617 if (message->FindInt64("left", &left) == B_OK) 618 fScopeView->SetLeftTime(left); 619 break; 620 } 621 case LOOP: 622 fLooping = fLoopButton->ButtonState(); 623 break; 624 case B_SIMPLE_DATA: 625 case B_REFS_RECEIVED: 626 { 627 RefsReceived(message); 628 break; 629 } 630 case B_COPY_TARGET: 631 CopyTarget(message); 632 break; 633 default: 634 BWindow::MessageReceived(message); 635 break; 636 } 637 } 638 639 640 void 641 RecorderWindow::Record(BMessage * message) 642 { 643 // User pressed Record button 644 fRecording = true; 645 if (fButtonState != btnPaused) { 646 StopRecording(); 647 return; // user is too fast on the mouse 648 } 649 SetButtonState(btnRecording); 650 fRecordButton->SetRecording(); 651 652 char name[256]; 653 // Create a file with a temporary name 654 status_t err = NewTempName(name); 655 if (err < B_OK) { 656 ErrorAlert(B_TRANSLATE("Cannot find an unused name to use for the " 657 "new recording"), err); 658 return; 659 } 660 // Find the file so we can refer to it later 661 err = fTempDir.FindEntry(name, &fRecEntry); 662 if (err < B_OK) { 663 ErrorAlert(B_TRANSLATE("Cannot find the temporary file created to " 664 "hold the new recording"), err); 665 return; 666 } 667 err = fRecFile.SetTo(&fTempDir, name, O_RDWR); 668 if (err < B_OK) { 669 ErrorAlert(B_TRANSLATE("Cannot open the temporary file created to " 670 "hold the new recording"), err); 671 fRecEntry.Unset(); 672 return; 673 } 674 // Reserve space on disk (creates fewer fragments) 675 err = fRecFile.SetSize(4 * fRecordFormat.u.raw_audio.channel_count 676 * fRecordFormat.u.raw_audio.frame_rate 677 * (fRecordFormat.u.raw_audio.format 678 & media_raw_audio_format::B_AUDIO_SIZE_MASK)); 679 if (err < B_OK) { 680 ErrorAlert(B_TRANSLATE("Cannot record a sound that long"), err); 681 fRecEntry.Remove(); 682 fRecEntry.Unset(); 683 return; 684 } 685 fRecSize = 0; 686 687 fRecFile.Seek(sizeof(struct wave_struct), SEEK_SET); 688 689 // Hook up input 690 err = MakeRecordConnection(fAudioInputNode); 691 if (err < B_OK) { 692 ErrorAlert(B_TRANSLATE("Cannot connect to the selected sound input"), 693 err); 694 fRecEntry.Remove(); 695 fRecEntry.Unset(); 696 return; 697 } 698 699 // And get it going... 700 bigtime_t then = fRecordNode->TimeSource()->Now() + 50000LL; 701 fRoster->StartNode(fRecordNode->Node(), then); 702 if (fAudioInputNode.kind & B_TIME_SOURCE) { 703 fRoster->StartNode(fAudioInputNode, 704 fRecordNode->TimeSource()->RealTimeFor(then, 0)); 705 } else 706 fRoster->StartNode(fAudioInputNode, then); 707 } 708 709 710 void 711 RecorderWindow::Play(BMessage * message) 712 { 713 if (fPlayer) { 714 // User pressed Play button and playing 715 if (fPlayer->HasData()) 716 fPlayButton->SetPaused(); 717 else 718 fPlayButton->SetPlaying(); 719 fPlayer->SetHasData(!fPlayer->HasData()); 720 return; 721 } 722 723 SetButtonState(btnPlaying); 724 fPlayButton->SetPlaying(); 725 726 if (!fPlayTrack) { 727 ErrorAlert(B_TRANSLATE("Cannot get the file to play"), B_ERROR); 728 return; 729 } 730 731 fPlayLimit = MIN(fPlayFrames, (off_t)(fTrackSlider->RightTime() 732 * fPlayFormat.u.raw_audio.frame_rate / 1000000LL)); 733 fPlayTrack->SeekToTime(fTrackSlider->MainTime()); 734 fPlayFrame = fPlayTrack->CurrentFrame(); 735 736 // Create our internal Node which plays sound, and register it. 737 fPlayer = new BSoundPlayer(fAudioMixerNode, &fPlayFormat.u.raw_audio, 738 "Sound Player"); 739 status_t err = fPlayer->InitCheck(); 740 if (err < B_OK) 741 return; 742 743 fVolumeSlider->SetSoundPlayer(fPlayer); 744 fPlayer->SetCallbacks(PlayFile, NotifyPlayFile, this); 745 746 // And get it going... 747 fPlayer->Start(); 748 fPlayer->SetHasData(true); 749 } 750 751 752 void 753 RecorderWindow::Stop(BMessage * message) 754 { 755 // User pressed Stop button. 756 // Stop recorder. 757 StopRecording(); 758 // Stop player. 759 StopPlaying(); 760 } 761 762 763 void 764 RecorderWindow::Save(BMessage * message) 765 { 766 // User pressed Save button. 767 // Find the item to save. 768 int32 index = fSoundList->CurrentSelection(); 769 SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(index)); 770 if ((! pItem) || (pItem->Entry().InitCheck() != B_OK)) 771 return; 772 773 // Update the save panel and show it. 774 char filename[B_FILE_NAME_LENGTH]; 775 pItem->Entry().GetName(filename); 776 BMessage saveMsg(B_SAVE_REQUESTED); 777 entry_ref ref; 778 pItem->Entry().GetRef(&ref); 779 780 if (saveMsg.AddPointer("sound list item", pItem) != B_OK) 781 fprintf(stderr, "failed to add pItem\n"); 782 fSavePanel->SetSaveText(filename); 783 fSavePanel->SetMessage(&saveMsg); 784 fSavePanel->Show(); 785 } 786 787 788 void 789 RecorderWindow::DoSave(BMessage * message) 790 { 791 // User picked a place to put the file. 792 // Find the location of the old (e.g. 793 // temporary file), and the name of the 794 // new file to save. 795 entry_ref old_ref, new_dir_ref; 796 const char* new_name; 797 SoundListItem* pItem; 798 799 if ((message->FindPointer("sound list item", (void**) &pItem) == B_OK) 800 && (message->FindRef("directory", &new_dir_ref) == B_OK) 801 && (message->FindString("name", &new_name) == B_OK)) { 802 BEntry& oldEntry = pItem->Entry(); 803 BFile oldFile(&oldEntry, B_READ_WRITE); 804 if (oldFile.InitCheck() != B_OK) 805 return; 806 807 BDirectory newDir(&new_dir_ref); 808 if (newDir.InitCheck() != B_OK) 809 return; 810 811 BFile newFile; 812 newDir.CreateFile(new_name, &newFile); 813 814 if (newFile.InitCheck() != B_OK) 815 return; 816 817 status_t err = CopyFile(newFile, oldFile); 818 819 if (err == B_OK) { 820 // clean up the sound list and item 821 if (pItem->IsTemp()) 822 oldEntry.Remove(); // blows away temp file! 823 oldEntry.SetTo(&newDir, new_name); 824 pItem->SetTemp(false); // don't blow the new entry away when we exit! 825 fSoundList->Invalidate(); 826 } 827 } else { 828 WINDOW((stderr, "Couldn't save file.\n")); 829 } 830 } 831 832 833 void 834 RecorderWindow::Input(BMessage * message) 835 { 836 // User selected input from pop-up 837 const dormant_node_info * dni = 0; 838 ssize_t size = 0; 839 if (message->FindData("node", B_RAW_TYPE, (const void **)&dni, &size)) 840 return; // bad input selection message 841 842 media_node_id node_id; 843 status_t error = fRoster->GetInstancesFor(dni->addon, dni->flavor_id, &node_id); 844 if (error != B_OK) 845 fRoster->InstantiateDormantNode(*dni, &fAudioInputNode); 846 else 847 fRoster->GetNodeFor(node_id, &fAudioInputNode); 848 } 849 850 851 void 852 RecorderWindow::Selected(BMessage * message) 853 { 854 // User selected a sound in list view 855 int32 selIdx = fSoundList->CurrentSelection(); 856 SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(selIdx)); 857 if (!pItem) 858 return; 859 status_t err = UpdatePlayFile(pItem, true); 860 if (err != B_OK) { 861 ErrorAlert(B_TRANSLATE("Cannot recognize this file as a media file"), 862 err == B_MEDIA_NO_HANDLER ? B_OK : err); 863 RemoveCurrentSoundItem(); 864 } 865 UpdateButtons(); 866 } 867 868 869 status_t 870 RecorderWindow::MakeRecordConnection(const media_node & input) 871 { 872 CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n")); 873 874 // Find an available output for the given input node. 875 int32 count = 0; 876 status_t err = fRoster->GetFreeOutputsFor(input, &fAudioOutput, 1, &count, B_MEDIA_RAW_AUDIO); 877 if (err < B_OK) { 878 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 879 " couldn't get free outputs from audio input node\n")); 880 return err; 881 } 882 if (count < 1) { 883 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 884 " no free outputs from audio input node\n")); 885 return B_BUSY; 886 } 887 888 // Find an available input for our own Node. Note that we go through the 889 // MediaRoster; calling Media Kit methods directly on Nodes in our app is 890 // not OK (because synchronization happens in the service thread, not in 891 // the calling thread). 892 // TODO: explain this 893 err = fRoster->GetFreeInputsFor(fRecordNode->Node(), &fRecInput, 1, &count, B_MEDIA_RAW_AUDIO); 894 if (err < B_OK) { 895 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 896 " couldn't get free inputs for sound recorder\n")); 897 return err; 898 } 899 if (count < 1) { 900 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 901 " no free inputs for sound recorder\n")); 902 return B_BUSY; 903 } 904 905 // Find out what the time source of the input is. 906 // For most nodes, we just use the preferred time source (the DAC) for synchronization. 907 // However, nodes that record from an input need to synchronize to the audio input node 908 // instead for best results. 909 // MakeTimeSourceFor gives us a "clone" of the time source node that we can manipulate 910 // to our heart's content. When we're done with it, though, we need to call Release() 911 // on the time source node, so that it keeps an accurate reference count and can delete 912 // itself when it's no longer needed. 913 // TODO: what about filters connected to audio input? 914 media_node use_time_source; 915 BTimeSource * tsobj = fRoster->MakeTimeSourceFor(input); 916 if (! tsobj) { 917 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 918 " couldn't clone time source from audio input node\n")); 919 return B_MEDIA_BAD_NODE; 920 } 921 922 // Apply the time source in effect to our own Node. 923 err = fRoster->SetTimeSourceFor(fRecordNode->Node().node, tsobj->Node().node); 924 if (err < B_OK) { 925 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 926 " couldn't set the sound recorder's time source\n")); 927 tsobj->Release(); 928 return err; 929 } 930 931 // Get a format, any format. 932 fRecordFormat.u.raw_audio = fAudioOutput.format.u.raw_audio; 933 fRecordFormat.type = B_MEDIA_RAW_AUDIO; 934 935 // Tell the consumer where we want data to go. 936 err = fRecordNode->SetHooks(RecordFile, NotifyRecordFile, this); 937 if (err < B_OK) { 938 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 939 " couldn't set the sound recorder's hook functions\n")); 940 tsobj->Release(); 941 return err; 942 } 943 944 // Using the same structs for input and output is OK in 945 // BMediaRoster::Connect(). 946 err = fRoster->Connect(fAudioOutput.source, fRecInput.destination, 947 &fRecordFormat, &fAudioOutput, &fRecInput); 948 if (err < B_OK) { 949 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 950 " failed to connect sound recorder to audio input node.\n")); 951 tsobj->Release(); 952 fRecordNode->SetHooks(0, 0, 0); 953 return err; 954 } 955 956 // Start the time source if it's not running. 957 if ((tsobj->Node() != input) && !tsobj->IsRunning()) 958 fRoster->StartNode(tsobj->Node(), BTimeSource::RealTime()); 959 960 tsobj->Release(); // we're done with this time source instance! 961 return B_OK; 962 } 963 964 965 status_t 966 RecorderWindow::BreakRecordConnection() 967 { 968 status_t err; 969 970 // If we are the last connection, the Node will stop automatically since it 971 // has nowhere to send data to. 972 err = fRoster->StopNode(fRecInput.node, 0); 973 err = fRoster->Disconnect(fAudioOutput.node.node, fAudioOutput.source, 974 fRecInput.node.node, fRecInput.destination); 975 fAudioOutput.source = media_source::null; 976 fRecInput.destination = media_destination::null; 977 return err; 978 } 979 980 981 status_t 982 RecorderWindow::StopRecording() 983 { 984 if (!fRecording) 985 return B_OK; 986 fRecording = false; 987 BreakRecordConnection(); 988 fRecordNode->SetHooks(NULL,NULL,NULL); 989 if (fRecSize > 0) { 990 991 wave_struct header; 992 header.riff.riff_id = FOURCC('R','I','F','F'); 993 header.riff.len = fRecSize + sizeof(header) - 8; 994 header.riff.wave_id = FOURCC('W','A','V','E'); 995 header.format_chunk.fourcc = FOURCC('f','m','t',' '); 996 header.format_chunk.len = sizeof(header.format); 997 header.format.format_tag = 1; 998 header.format.channels = fRecordFormat.u.raw_audio.channel_count; 999 header.format.samples_per_sec = (uint32)fRecordFormat.u.raw_audio.frame_rate; 1000 header.format.avg_bytes_per_sec = (uint32)(fRecordFormat.u.raw_audio.frame_rate 1001 * fRecordFormat.u.raw_audio.channel_count 1002 * (fRecordFormat.u.raw_audio.format & 0xf)); 1003 header.format.bits_per_sample = (fRecordFormat.u.raw_audio.format & 0xf) * 8; 1004 header.format.block_align = (fRecordFormat.u.raw_audio.format & 0xf) 1005 * fRecordFormat.u.raw_audio.channel_count; 1006 header.data_chunk.fourcc = FOURCC('d','a','t','a'); 1007 header.data_chunk.len = fRecSize; 1008 fRecFile.Seek(0, SEEK_SET); 1009 fRecFile.Write(&header, sizeof(header)); 1010 1011 fRecFile.SetSize(fRecSize + sizeof(header)); 1012 // We reserve space; make sure we cut off any excess at the end. 1013 AddSoundItem(fRecEntry, true); 1014 } else 1015 fRecEntry.Remove(); 1016 1017 // We're done for this time. 1018 fRecEntry.Unset(); 1019 // Close the file. 1020 fRecFile.Unset(); 1021 // No more recording going on. 1022 fRecSize = 0; 1023 SetButtonState(btnPaused); 1024 fRecordButton->SetStopped(); 1025 1026 return B_OK; 1027 } 1028 1029 1030 status_t 1031 RecorderWindow::StopPlaying() 1032 { 1033 if (fPlayer) { 1034 fPlayer->Stop(); 1035 fPlayer->SetCallbacks(0, 0, 0); 1036 fVolumeSlider->SetSoundPlayer(NULL); 1037 delete fPlayer; 1038 fPlayer = NULL; 1039 } 1040 SetButtonState(btnPaused); 1041 fPlayButton->SetStopped(); 1042 fTrackSlider->ResetMainTime(); 1043 fScopeView->SetMainTime(*fTrackSlider->MainTime()); 1044 return B_OK; 1045 } 1046 1047 1048 void 1049 RecorderWindow::SetButtonState(BtnState state) 1050 { 1051 fButtonState = state; 1052 UpdateButtons(); 1053 } 1054 1055 1056 void 1057 RecorderWindow::UpdateButtons() 1058 { 1059 bool hasSelection = (fSoundList->CurrentSelection() >= 0); 1060 fRecordButton->SetEnabled(fButtonState != btnPlaying); 1061 fPlayButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1062 fRewindButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1063 fForwardButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1064 fStopButton->SetEnabled(fButtonState != btnPaused); 1065 fSaveButton->SetEnabled(hasSelection && (fButtonState != btnRecording)); 1066 fInputField->SetEnabled(fButtonState != btnRecording); 1067 } 1068 1069 #ifndef __HAIKU__ 1070 extern "C" status_t DecodedFormat__11BMediaTrackP12media_format( 1071 BMediaTrack *self, media_format *inout_format); 1072 #endif 1073 1074 1075 status_t 1076 RecorderWindow::UpdatePlayFile(SoundListItem* item, bool updateDisplay) 1077 { 1078 fScopeView->CancelRendering(); 1079 StopPlaying(); 1080 StopRecording(); 1081 1082 if (fPlayTrack && fPlayFile) { 1083 fPlayFile->ReleaseTrack(fPlayTrack); 1084 fPlayTrack = NULL; 1085 } 1086 if (fPlayFile) { 1087 delete fPlayFile; 1088 fPlayFile = NULL; 1089 } 1090 1091 status_t err; 1092 BEntry& entry = item->Entry(); 1093 entry_ref ref; 1094 entry.GetRef(&ref); 1095 fPlayFile = new BMediaFile(&ref); //, B_MEDIA_FILE_UNBUFFERED); 1096 if ((err = fPlayFile->InitCheck()) < B_OK) { 1097 delete fPlayFile; 1098 fPlayFile = NULL; 1099 return err; 1100 } 1101 1102 for (int ix=0; ix < fPlayFile->CountTracks(); ix++) { 1103 BMediaTrack * track = fPlayFile->TrackAt(ix); 1104 fPlayFormat.type = B_MEDIA_RAW_AUDIO; 1105 #ifdef __HAIKU__ 1106 if ((track->DecodedFormat(&fPlayFormat) == B_OK) 1107 #else 1108 if ((DecodedFormat__11BMediaTrackP12media_format(track, &fPlayFormat) == B_OK) 1109 #endif 1110 && (fPlayFormat.type == B_MEDIA_RAW_AUDIO)) { 1111 fPlayTrack = track; 1112 break; 1113 } 1114 if (track) 1115 fPlayFile->ReleaseTrack(track); 1116 } 1117 1118 if (!fPlayTrack) { 1119 delete fPlayFile; 1120 fPlayFile = NULL; 1121 return B_STREAM_NOT_FOUND; 1122 } 1123 1124 if (!updateDisplay) 1125 return B_OK; 1126 1127 BString filename = B_TRANSLATE("File name: "); 1128 filename << ref.name; 1129 fFilename->SetText(filename.String()); 1130 1131 BString format = B_TRANSLATE("Format: "); 1132 media_file_format file_format; 1133 if (fPlayFile->GetFileFormatInfo(&file_format) == B_OK) 1134 format << file_format.short_name; 1135 BString compression = B_TRANSLATE("Compression: "); 1136 media_codec_info codec_info; 1137 if (fPlayTrack->GetCodecInfo(&codec_info) == B_OK) { 1138 if (strcmp(codec_info.short_name, "raw")==0) 1139 compression << B_TRANSLATE("None"); 1140 else 1141 compression << codec_info.short_name; 1142 } 1143 BString channels = B_TRANSLATE("Channels: "); 1144 channels << fPlayFormat.u.raw_audio.channel_count; 1145 BString samplesize = B_TRANSLATE("Sample size: "); 1146 samplesize << 8 * (fPlayFormat.u.raw_audio.format & 0xf) 1147 << B_TRANSLATE(" bits"); 1148 BString samplerate = B_TRANSLATE("Sample rate: "); 1149 samplerate << (int)fPlayFormat.u.raw_audio.frame_rate; 1150 BString durationString = B_TRANSLATE("Duration: "); 1151 bigtime_t duration = fPlayTrack->Duration(); 1152 durationString << (float)(duration / 1000000.0) << B_TRANSLATE(" seconds"); 1153 1154 fFormat->SetText(format.String()); 1155 fCompression->SetText(compression.String()); 1156 fChannels->SetText(channels.String()); 1157 fSampleSize->SetText(samplesize.String()); 1158 fSampleRate->SetText(samplerate.String()); 1159 fDuration->SetText(durationString.String()); 1160 1161 fTrackSlider->SetTotalTime(duration, true); 1162 fScopeView->SetTotalTime(duration, true); 1163 fScopeView->RenderTrack(fPlayTrack, fPlayFormat); 1164 1165 fPlayFrames = fPlayTrack->CountFrames(); 1166 return B_OK; 1167 } 1168 1169 1170 void 1171 RecorderWindow::ErrorAlert(const char * action, status_t err) 1172 { 1173 char msg[300]; 1174 if (err != B_OK) 1175 sprintf(msg, "%s: %s. [%" B_PRIx32 "]", action, strerror(err), (int32) err); 1176 else 1177 sprintf(msg, "%s.", action); 1178 BAlert* alert = new BAlert("", msg, B_TRANSLATE("Stop")); 1179 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1180 alert->Go(); 1181 } 1182 1183 1184 status_t 1185 RecorderWindow::NewTempName(char * name) 1186 { 1187 int init_count = fTempCount; 1188 again: 1189 if (fTempCount-init_count > 25) { 1190 return B_ERROR; 1191 } 1192 else { 1193 fTempCount++; 1194 if (fTempCount==0) 1195 sprintf(name, "Audio Clip"); 1196 else 1197 sprintf(name, "Audio Clip %d", fTempCount); 1198 BPath path; 1199 status_t err; 1200 BEntry tempEnt; 1201 if ((err = fTempDir.GetEntry(&tempEnt)) < B_OK) { 1202 return err; 1203 } 1204 if ((err = tempEnt.GetPath(&path)) < B_OK) { 1205 return err; 1206 } 1207 path.Append(name); 1208 int fd; 1209 // Use O_EXCL so we know we created the file (sync with other instances) 1210 if ((fd = open(path.Path(), O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) { 1211 goto again; 1212 } 1213 close(fd); 1214 } 1215 return B_OK; 1216 } 1217 1218 1219 void 1220 RecorderWindow::AddSoundItem(const BEntry& entry, bool temp) 1221 { 1222 // Create list item to display. 1223 SoundListItem * listItem = new SoundListItem(entry, temp); 1224 fSoundList->AddItem(listItem); 1225 fSoundList->Invalidate(); 1226 fSoundList->Select(fSoundList->IndexOf(listItem)); 1227 } 1228 1229 1230 void 1231 RecorderWindow::RemoveCurrentSoundItem() { 1232 int32 index = fSoundList->CurrentSelection(); 1233 BListItem *item = fSoundList->RemoveItem(index); 1234 delete item; 1235 if (index >= fSoundList->CountItems()) 1236 index = fSoundList->CountItems() - 1; 1237 fSoundList->Select(index); 1238 } 1239 1240 1241 void 1242 RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp, 1243 void* data, size_t size, const media_raw_audio_format &format) 1244 { 1245 // Callback called from the SoundConsumer when receiving buffers. 1246 RecorderWindow * window = (RecorderWindow *)cookie; 1247 1248 if (window->fRecording) { 1249 // Write the data to file (we don't buffer or guard file access 1250 // or anything) 1251 window->fRecFile.WriteAt(window->fRecSize, data, size); 1252 window->fVUView->ComputeLevels(data, size, format.format); 1253 window->fRecSize += size; 1254 } 1255 } 1256 1257 1258 void 1259 RecorderWindow::NotifyRecordFile(void * cookie, int32 code, ...) 1260 { 1261 if ((code == B_WILL_STOP) || (code == B_NODE_DIES)) { 1262 RecorderWindow * window = (RecorderWindow *)cookie; 1263 // Tell the window we've stopped, if it doesn't 1264 // already know. 1265 window->PostMessage(STOP_RECORDING); 1266 } 1267 } 1268 1269 1270 void 1271 RecorderWindow::PlayFile(void * cookie, void * data, size_t size, 1272 const media_raw_audio_format & format) 1273 { 1274 // Callback called from the SoundProducer when producing buffers. 1275 RecorderWindow * window = (RecorderWindow *)cookie; 1276 int32 frame_size = (window->fPlayFormat.u.raw_audio.format & 0xf) * 1277 window->fPlayFormat.u.raw_audio.channel_count; 1278 1279 if ((window->fPlayFrame < window->fPlayLimit) || window->fLooping) { 1280 if (window->fPlayFrame >= window->fPlayLimit) { 1281 bigtime_t left = window->fTrackSlider->LeftTime(); 1282 window->fPlayTrack->SeekToTime(&left); 1283 window->fPlayFrame = window->fPlayTrack->CurrentFrame(); 1284 } 1285 int64 frames = 0; 1286 window->fPlayTrack->ReadFrames(data, &frames); 1287 window->fVUView->ComputeLevels(data, size / frame_size, format.format); 1288 window->fPlayFrame += size/frame_size; 1289 window->PostMessage(UPDATE_TRACKSLIDER); 1290 } else { 1291 // we're done! 1292 window->PostMessage(STOP_PLAYING); 1293 } 1294 } 1295 1296 1297 void 1298 RecorderWindow::NotifyPlayFile(void * cookie, 1299 BSoundPlayer::sound_player_notification code, ...) 1300 { 1301 if ((code == BSoundPlayer::B_STOPPED) || (code == BSoundPlayer::B_SOUND_DONE)) { 1302 RecorderWindow * window = (RecorderWindow *)cookie; 1303 // tell the window we've stopped, if it doesn't 1304 // already know. 1305 window->PostMessage(STOP_PLAYING); 1306 } 1307 } 1308 1309 1310 void 1311 RecorderWindow::RefsReceived(BMessage *msg) 1312 { 1313 entry_ref ref; 1314 int32 i = 0; 1315 int32 countGood = 0; 1316 int32 countBad = 0; 1317 1318 while (msg->FindRef("refs", i++, &ref) == B_OK) { 1319 1320 BEntry entry(&ref, true); 1321 BPath path(&entry); 1322 BNode node(&entry); 1323 1324 if (node.IsFile()) { 1325 SoundListItem * listItem = new SoundListItem(entry, false); 1326 if (UpdatePlayFile(listItem) == B_OK) { 1327 fSoundList->AddItem(listItem); 1328 countGood++; 1329 continue; 1330 } 1331 delete listItem; 1332 } else if(node.IsDirectory()) { 1333 1334 } 1335 countBad++; 1336 } 1337 1338 if (countBad > 0 && countGood == 0) { 1339 BAlert* alert = new BAlert(B_TRANSLATE("Nothing to play"), 1340 B_TRANSLATE("None of the files appear to be audio files"), 1341 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1342 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1343 alert->Go(); 1344 } else if (countGood > 0) { 1345 if (countBad > 0) { 1346 BAlert* alert = new BAlert(B_TRANSLATE("Invalid audio files"), 1347 B_TRANSLATE("Some of the files don't appear to be audio files"), 1348 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1349 B_WARNING_ALERT); 1350 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1351 alert->Go(); 1352 } 1353 fSoundList->Select(fSoundList->CountItems() - 1); 1354 } 1355 } 1356 1357 1358 void 1359 RecorderWindow::CopyTarget(BMessage *msg) 1360 { 1361 const char *type = NULL; 1362 if (msg->FindString("be:types", &type) == B_OK) { 1363 if (!strcasecmp(type, B_FILE_MIME_TYPE)) { 1364 const char *name; 1365 entry_ref dir; 1366 if (msg->FindString("be:filetypes") == B_OK 1367 && msg->FindString("name", &name) == B_OK 1368 && msg->FindRef("directory", &dir) == B_OK) { 1369 BDirectory directory(&dir); 1370 BFile file(&directory, name, O_RDWR | O_TRUNC); 1371 1372 // seek time 1373 bigtime_t start = fTrackSlider->LeftTime(); 1374 1375 // write data 1376 bigtime_t diffTime = fTrackSlider->RightTime() 1377 - fTrackSlider->LeftTime(); 1378 int64 framesToWrite = (int64) (diffTime 1379 * fPlayFormat.u.raw_audio.frame_rate / 1000000LL); 1380 int32 frameSize = (fPlayFormat.u.raw_audio.format & 0xf) 1381 * fPlayFormat.u.raw_audio.channel_count; 1382 1383 wave_struct header; 1384 header.riff.riff_id = FOURCC('R','I','F','F'); 1385 header.riff.len 1386 = (frameSize * framesToWrite) + sizeof(header) - 8; 1387 header.riff.wave_id = FOURCC('W','A','V','E'); 1388 header.format_chunk.fourcc = FOURCC('f','m','t',' '); 1389 header.format_chunk.len = sizeof(header.format); 1390 header.format.format_tag = 1; 1391 header.format.channels = fPlayFormat.u.raw_audio.channel_count; 1392 header.format.samples_per_sec 1393 = (uint32)fPlayFormat.u.raw_audio.frame_rate; 1394 header.format.avg_bytes_per_sec 1395 = (uint32)(fPlayFormat.u.raw_audio.frame_rate 1396 * fPlayFormat.u.raw_audio.channel_count 1397 * (fPlayFormat.u.raw_audio.format & 0xf)); 1398 header.format.bits_per_sample 1399 = (fPlayFormat.u.raw_audio.format & 0xf) * 8; 1400 header.format.block_align = frameSize; 1401 header.data_chunk.fourcc = FOURCC('d','a','t','a'); 1402 header.data_chunk.len = frameSize * framesToWrite; 1403 file.Seek(0, SEEK_SET); 1404 file.Write(&header, sizeof(header)); 1405 1406 char *data = (char *)malloc(fPlayFormat.u.raw_audio.buffer_size); 1407 1408 fPlayTrack->SeekToTime(&start); 1409 fPlayFrame = fPlayTrack->CurrentFrame(); 1410 while (framesToWrite > 0) { 1411 int64 frames = 0; 1412 status_t err = fPlayTrack->ReadFrames(data, &frames); 1413 if (frames <= 0 || err != B_OK) { 1414 if (err != B_OK) 1415 fprintf(stderr, "CopyTarget: ReadFrames failed\n"); 1416 break; 1417 } 1418 file.Write(data, frames * frameSize); 1419 framesToWrite -= frames; 1420 } 1421 1422 file.Sync(); 1423 free(data); 1424 BNodeInfo nodeInfo(&file); 1425 // set type 1426 } 1427 } else { 1428 1429 } 1430 } 1431 } 1432