1 /* 2 * Copyright 2014, Dario Casalinuovo. All rights reserved. 3 * Copyright 2005, Jérôme Duval. All rights reserved. 4 * Distributed under the terms of the MIT License. 5 * 6 * Inspired by SoundCapture from Be newsletter (Media Kit Basics: 7 * Consumers and Producers) 8 */ 9 10 #include <Application.h> 11 #include <Alert.h> 12 #include <Debug.h> 13 #include <Screen.h> 14 #include <Button.h> 15 #include <CheckBox.h> 16 #include <TextControl.h> 17 #include <MenuField.h> 18 #include <PopUpMenu.h> 19 #include <MenuItem.h> 20 #include <Box.h> 21 #include <ScrollView.h> 22 #include <Beep.h> 23 #include <StringView.h> 24 #include <String.h> 25 #include <Slider.h> 26 #include <Message.h> 27 28 #include <Path.h> 29 #include <FindDirectory.h> 30 #include <MediaAddOn.h> 31 32 #include <SoundPlayer.h> 33 34 #include <assert.h> 35 #include <stdio.h> 36 #include <strings.h> 37 #include <stdlib.h> 38 #include <ctype.h> 39 #include <unistd.h> 40 #include <fcntl.h> 41 42 #include <MediaRoster.h> 43 #include <TimeSource.h> 44 #include <NodeInfo.h> 45 46 #include "RecorderWindow.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 fRecorder = NULL; 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 MediaRecorder have to be deleted, the dtor 150 // disconnect it from the media_kit. 151 delete fRecorder; 152 153 delete fPlayer; 154 155 if (fPlayTrack && fPlayFile) 156 fPlayFile->ReleaseTrack(fPlayTrack); 157 158 if (fPlayFile) 159 delete fPlayFile; 160 fPlayTrack = NULL; 161 fPlayFile = NULL; 162 163 // Clean up items in list view. 164 if (fSoundList) { 165 fSoundList->DeselectAll(); 166 for (int i = 0; i < fSoundList->CountItems(); i++) { 167 WINDOW((stderr, "clean up item %d\n", i+1)); 168 SoundListItem* item = dynamic_cast<SoundListItem *>(fSoundList->ItemAt(i)); 169 if (item) { 170 if (item->IsTemp()) 171 item->Entry().Remove(); // delete temp file 172 delete item; 173 } 174 } 175 fSoundList->MakeEmpty(); 176 } 177 // Clean up currently recording file, if any. 178 fRecEntry.Remove(); 179 fRecEntry.Unset(); 180 181 delete fSavePanel; 182 } 183 184 185 status_t 186 RecorderWindow::InitCheck() 187 { 188 return fInitCheck; 189 } 190 191 192 void 193 RecorderWindow::CalcSizes(float min_width, float min_height) 194 { 195 // Set up size limits based on new screen size 196 BScreen screen(this); 197 BRect rect = screen.Frame(); 198 float width = rect.Width() - 12; 199 SetSizeLimits(min_width, width, min_height, rect.Height() - 24); 200 201 // Don't zoom to cover all of screen; user can resize last bit if necessary. 202 // This leaves other windows visible. 203 if (width > 640) 204 width = 640 + (width - 640) / 2; 205 SetZoomLimits(width, rect.Height() - 24); 206 } 207 208 209 status_t 210 RecorderWindow::InitWindow() 211 { 212 BPopUpMenu * popup = 0; 213 status_t error; 214 215 try { 216 // Find temp directory for recorded sounds. 217 BPath path; 218 if (!(error = find_directory(B_SYSTEM_TEMP_DIRECTORY, &path))) 219 error = fTempDir.SetTo(path.Path()); 220 if (error < 0) 221 goto bad_mojo; 222 223 // Make sure the media roster is there (which means the server is there). 224 fRoster = BMediaRoster::Roster(&error); 225 if (!fRoster) 226 goto bad_mojo; 227 228 error = fRoster->GetAudioInput(&fAudioInputNode); 229 if (error < B_OK) // there's no input? 230 goto bad_mojo; 231 232 error = fRoster->GetAudioMixer(&fAudioMixerNode); 233 if (error < B_OK) // there's no mixer? 234 goto bad_mojo; 235 236 fRecorder = new BMediaRecorder("Sound Recorder", 237 B_MEDIA_RAW_AUDIO); 238 239 if (fRecorder->InitCheck() < B_OK) 240 goto bad_mojo; 241 242 // Set the node to accept only audio data 243 media_format output_format; 244 output_format.type = B_MEDIA_RAW_AUDIO; 245 output_format.u.raw_audio = media_raw_audio_format::wildcard; 246 fRecorder->SetAcceptedFormat(output_format); 247 248 // Create the window header with controls 249 BRect r(Bounds()); 250 r.bottom = r.top + 175; 251 BBox *background = new BBox(r, "_background", 252 B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW 253 | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER); 254 255 AddChild(background); 256 257 r = background->Bounds(); 258 r.left = 0; 259 r.right = r.left + 38; 260 r.bottom = r.top + 104; 261 fVUView = new VUView(r, B_FOLLOW_LEFT|B_FOLLOW_TOP); 262 background->AddChild(fVUView); 263 264 r = background->Bounds(); 265 r.left = r.left + 40; 266 r.bottom = r.top + 104; 267 fScopeView = new ScopeView(r, B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP); 268 background->AddChild(fScopeView); 269 270 r = background->Bounds(); 271 r.left = 2; 272 r.right -= 26; 273 r.top = 115; 274 r.bottom = r.top + 30; 275 fTrackSlider = new TrackSlider(r, "trackSlider", new BMessage(POSITION_CHANGED), 276 B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP); 277 background->AddChild(fTrackSlider); 278 279 BRect buttonRect; 280 281 // Button for rewinding 282 buttonRect = BRect(BPoint(0,0), kSkipButtonSize); 283 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-7, 25)); 284 fRewindButton = new TransportButton(buttonRect, B_TRANSLATE("Rewind"), 285 kSkipBackBitmapBits, kPressedSkipBackBitmapBits, 286 kDisabledSkipBackBitmapBits, new BMessage(REWIND)); 287 background->AddChild(fRewindButton); 288 289 // Button for stopping recording or playback 290 buttonRect = BRect(BPoint(0,0), kStopButtonSize); 291 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-48, 25)); 292 fStopButton = new TransportButton(buttonRect, B_TRANSLATE("Stop"), 293 kStopButtonBitmapBits, kPressedStopButtonBitmapBits, 294 kDisabledStopButtonBitmapBits, new BMessage(STOP)); 295 background->AddChild(fStopButton); 296 297 // Button for starting playback of selected sound 298 BRect playRect(BPoint(0,0), kPlayButtonSize); 299 playRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-82, 25)); 300 fPlayButton = new PlayPauseButton(playRect, B_TRANSLATE("Play"), 301 new BMessage(PLAY), new BMessage(PLAY_PERIOD), ' ', 0); 302 background->AddChild(fPlayButton); 303 304 // Button for forwarding 305 buttonRect = BRect(BPoint(0,0), kSkipButtonSize); 306 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-133, 25)); 307 fForwardButton = new TransportButton(buttonRect, B_TRANSLATE("Forward"), 308 kSkipForwardBitmapBits, kPressedSkipForwardBitmapBits, 309 kDisabledSkipForwardBitmapBits, new BMessage(FORWARD)); 310 background->AddChild(fForwardButton); 311 312 // Button to start recording (or waiting for sound) 313 buttonRect = BRect(BPoint(0,0), kRecordButtonSize); 314 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-174, 25)); 315 fRecordButton = new RecordButton(buttonRect, B_TRANSLATE("Record"), 316 new BMessage(RECORD), new BMessage(RECORD_PERIOD)); 317 background->AddChild(fRecordButton); 318 319 // Button for saving selected sound 320 buttonRect = BRect(BPoint(0,0), kDiskButtonSize); 321 buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-250, 21)); 322 fSaveButton = new TransportButton(buttonRect, B_TRANSLATE("Save"), 323 kDiskButtonBitmapsBits, kPressedDiskButtonBitmapsBits, 324 kDisabledDiskButtonBitmapsBits, new BMessage(SAVE)); 325 fSaveButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 326 background->AddChild(fSaveButton); 327 328 // Button Loop 329 buttonRect = BRect(BPoint(0,0), kArrowSize); 330 buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(23, 48)); 331 fLoopButton = new DrawButton(buttonRect, B_TRANSLATE("Loop"), 332 kLoopArrowBits, kArrowBits, new BMessage(LOOP)); 333 fLoopButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 334 fLoopButton->SetTarget(this); 335 background->AddChild(fLoopButton); 336 337 buttonRect = BRect(BPoint(0,0), kSpeakerIconBitmapSize); 338 buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(121, 17)); 339 SpeakerView *speakerView = new SpeakerView(buttonRect, 340 B_FOLLOW_LEFT | B_FOLLOW_TOP); 341 speakerView->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 342 background->AddChild(speakerView); 343 344 buttonRect = BRect(BPoint(0,0), BPoint(84, 19)); 345 buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(107, 20)); 346 fVolumeSlider = new VolumeSlider(buttonRect, "volumeSlider", 347 B_FOLLOW_LEFT | B_FOLLOW_TOP); 348 fVolumeSlider->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 349 background->AddChild(fVolumeSlider); 350 351 // Button to mask/see sounds list 352 buttonRect = BRect(BPoint(0,0), kUpDownButtonSize); 353 buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(21, 25)); 354 fUpDownButton = new UpDownButton(buttonRect, new BMessage(VIEW_LIST)); 355 fUpDownButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP); 356 background->AddChild(fUpDownButton); 357 358 r = Bounds(); 359 r.top = background->Bounds().bottom + 1; 360 fBottomBox = new BBox(r, "bottomBox", B_FOLLOW_ALL); 361 fBottomBox->SetBorder(B_NO_BORDER); 362 AddChild(fBottomBox); 363 364 // The actual list of recorded sounds (initially empty) sits 365 // below the header with the controls. 366 r = fBottomBox->Bounds(); 367 r.left += 190; 368 r.InsetBy(10, 10); 369 r.left -= 10; 370 r.top += 4; 371 r.right -= B_V_SCROLL_BAR_WIDTH; 372 r.bottom -= 25; 373 fSoundList = new SoundListView(r, B_TRANSLATE("Sound List"), 374 B_FOLLOW_ALL); 375 fSoundList->SetSelectionMessage(new BMessage(SOUND_SELECTED)); 376 fSoundList->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 377 BScrollView *scroller = new BScrollView("scroller", fSoundList, 378 B_FOLLOW_ALL, 0, false, true, B_FANCY_BORDER); 379 fBottomBox->AddChild(scroller); 380 381 r = fBottomBox->Bounds(); 382 r.right = r.left + 190; 383 r.bottom -= 25; 384 r.InsetBy(10, 8); 385 r.top -= 1; 386 fFileInfoBox = new BBox(r, "fileinfo", B_FOLLOW_LEFT); 387 fFileInfoBox->SetLabel(B_TRANSLATE("File info")); 388 389 fFileInfoBox->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 390 391 BFont font = be_plain_font; 392 font.SetSize(font.Size() * 0.92f); 393 font_height height; 394 font.GetHeight(&height); 395 float fontHeight = height.ascent + height.leading + height.descent; 396 397 r = fFileInfoBox->Bounds(); 398 r.left = 8; 399 r.top = fontHeight + 6; 400 r.bottom = r.top + fontHeight + 3; 401 r.right -= 10; 402 fFilename = new BStringView(r, "filename", B_TRANSLATE("File name:")); 403 fFileInfoBox->AddChild(fFilename); 404 fFilename->SetFont(&font, B_FONT_SIZE); 405 fFilename->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 406 407 r.top += fontHeight; 408 r.bottom = r.top + fontHeight + 3; 409 fFormat = new BStringView(r, "format", B_TRANSLATE("Format:")); 410 fFileInfoBox->AddChild(fFormat); 411 fFormat->SetFont(&font, B_FONT_SIZE); 412 fFormat->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 413 414 r.top += fontHeight; 415 r.bottom = r.top + fontHeight + 3; 416 fCompression = new BStringView(r, "compression", 417 B_TRANSLATE("Compression:")); 418 fFileInfoBox->AddChild(fCompression); 419 fCompression->SetFont(&font, B_FONT_SIZE); 420 fCompression->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 421 422 r.top += fontHeight; 423 r.bottom = r.top + fontHeight + 3; 424 fChannels = new BStringView(r, "channels", B_TRANSLATE("Channels:")); 425 fFileInfoBox->AddChild(fChannels); 426 fChannels->SetFont(&font, B_FONT_SIZE); 427 fChannels->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 428 429 r.top += fontHeight; 430 r.bottom = r.top + fontHeight + 3; 431 fSampleSize = new BStringView(r, "samplesize", 432 B_TRANSLATE("Sample size:")); 433 fFileInfoBox->AddChild(fSampleSize); 434 fSampleSize->SetFont(&font, B_FONT_SIZE); 435 fSampleSize->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 436 437 r.top += fontHeight; 438 r.bottom = r.top + fontHeight + 3; 439 fSampleRate = new BStringView(r, "samplerate", 440 B_TRANSLATE("Sample rate:")); 441 fFileInfoBox->AddChild(fSampleRate); 442 fSampleRate->SetFont(&font, B_FONT_SIZE); 443 fSampleRate->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 444 445 r.top += fontHeight; 446 r.bottom = r.top + fontHeight + 3; 447 fDuration = new BStringView(r, "duration", B_TRANSLATE("Duration:")); 448 fFileInfoBox->AddChild(fDuration); 449 fDuration->SetFont(&font, B_FONT_SIZE); 450 fDuration->SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 451 452 fFileInfoBox->ResizeTo(fFileInfoBox->Frame().Width(), 453 r.bottom + fontHeight / 2.0f); 454 fDeployedHeight = MIN_HEIGHT + fFileInfoBox->Bounds().Height() + 40.0f; 455 456 // Input selection lists all available physical inputs that produce 457 // buffers with B_MEDIA_RAW_AUDIO format data. 458 popup = new BPopUpMenu(B_TRANSLATE("Input")); 459 const int maxInputCount = 64; 460 dormant_node_info dni[maxInputCount]; 461 462 int32 real_count = maxInputCount; 463 464 error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format, 465 0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT); 466 if (real_count > maxInputCount) { 467 WINDOW((stderr, "dropped %" B_PRId32 " inputs\n", real_count - maxInputCount)); 468 real_count = maxInputCount; 469 } 470 char selected_name[B_MEDIA_NAME_LENGTH] = "Default input"; 471 BMessage * msg; 472 BMenuItem * item; 473 for (int i = 0; i < real_count; i++) { 474 msg = new BMessage(INPUT_SELECTED); 475 msg->AddData("node", B_RAW_TYPE, &dni[i], sizeof(dni[i])); 476 item = new BMenuItem(dni[i].name, msg); 477 popup->AddItem(item); 478 media_node_id ni[12]; 479 int32 ni_count = 12; 480 error = fRoster->GetInstancesFor(dni[i].addon, dni[i].flavor_id, 481 ni, &ni_count); 482 if (error == B_OK) { 483 for (int j = 0; j < ni_count; j++) { 484 if (ni[j] == fAudioInputNode.node) { 485 strcpy(selected_name, dni[i].name); 486 break; 487 } 488 } 489 } 490 } 491 492 // Create the actual widget 493 r = fFileInfoBox->Bounds(); 494 r.top = r.bottom + 2; 495 r.bottom = r.top + 18; 496 r.InsetBy(10, 10); 497 fInputField = new BMenuField(r, "Input", B_TRANSLATE("Input:"), popup); 498 fInputField->SetDivider(fInputField->StringWidth(B_TRANSLATE("Input:")) 499 + 4.0f); 500 fBottomBox->AddChild(fInputField); 501 502 fBottomBox->AddChild(fFileInfoBox); 503 504 fBottomBox->Hide(); 505 CalcSizes(MIN_WIDTH, MIN_HEIGHT); 506 ResizeTo(Frame().Width(), MIN_HEIGHT); 507 508 popup->Superitem()->SetLabel(selected_name); 509 510 // Make sure the save panel is happy. 511 fSavePanel = new BFilePanel(B_SAVE_PANEL); 512 fSavePanel->SetTarget(this); 513 } 514 catch (...) { 515 goto bad_mojo; 516 } 517 UpdateButtons(); 518 return B_OK; 519 520 // Error handling. 521 bad_mojo: 522 if (error >= 0) 523 error = B_ERROR; 524 if (fRecorder) 525 delete fRecorder; 526 527 delete fPlayer; 528 if (!fInputField) 529 delete popup; 530 return error; 531 } 532 533 534 bool 535 RecorderWindow::QuitRequested() // this means Close pressed 536 { 537 StopRecording(); 538 StopPlaying(); 539 be_app->PostMessage(B_QUIT_REQUESTED); 540 return true; 541 } 542 543 544 void 545 RecorderWindow::MessageReceived(BMessage * message) 546 { 547 // Your average generic message dispatching switch() statement. 548 switch (message->what) { 549 case INPUT_SELECTED: 550 Input(message); 551 break; 552 case SOUND_SELECTED: 553 Selected(message); 554 break; 555 case STOP_PLAYING: 556 StopPlaying(); 557 break; 558 case STOP_RECORDING: 559 StopRecording(); 560 break; 561 case PLAY_PERIOD: 562 if (fPlayer) { 563 if (fPlayer->HasData()) 564 fPlayButton->SetPlaying(); 565 else 566 fPlayButton->SetPaused(); 567 } 568 break; 569 case RECORD_PERIOD: 570 fRecordButton->SetRecording(); 571 break; 572 case RECORD: 573 Record(message); 574 break; 575 case STOP: 576 Stop(message); 577 break; 578 case PLAY: 579 Play(message); 580 break; 581 case SAVE: 582 Save(message); 583 break; 584 case B_SAVE_REQUESTED: 585 DoSave(message); 586 break; 587 case VIEW_LIST: 588 if (fUpDownButton->Value() == B_CONTROL_ON) { 589 fBottomBox->Show(); 590 CalcSizes(MIN_WIDTH, fDeployedHeight); 591 ResizeTo(Frame().Width(), fDeployedHeight); 592 } else { 593 fBottomBox->Hide(); 594 CalcSizes(MIN_WIDTH, MIN_HEIGHT); 595 ResizeTo(Frame().Width(), MIN_HEIGHT); 596 597 } 598 break; 599 case UPDATE_TRACKSLIDER: 600 { 601 bigtime_t timestamp = fPlayTrack->CurrentTime(); 602 fTrackSlider->SetMainTime(timestamp, false); 603 fScopeView->SetMainTime(timestamp); 604 } 605 break; 606 case POSITION_CHANGED: 607 { 608 bigtime_t right, left, main; 609 if (message->FindInt64("main", &main) == B_OK) { 610 if (fPlayTrack) { 611 fPlayTrack->SeekToTime(fTrackSlider->MainTime()); 612 fPlayFrame = fPlayTrack->CurrentFrame(); 613 } 614 fScopeView->SetMainTime(main); 615 } 616 if (message->FindInt64("right", &right) == B_OK) { 617 if (fPlayTrack) { 618 fPlayLimit = MIN(fPlayFrames, 619 (off_t)(right * fPlayFormat.u.raw_audio.frame_rate 620 / 1000000LL)); 621 } 622 fScopeView->SetRightTime(right); 623 } 624 if (message->FindInt64("left", &left) == B_OK) 625 fScopeView->SetLeftTime(left); 626 break; 627 } 628 case LOOP: 629 fLooping = fLoopButton->ButtonState(); 630 break; 631 case B_SIMPLE_DATA: 632 case B_REFS_RECEIVED: 633 { 634 RefsReceived(message); 635 break; 636 } 637 case B_COPY_TARGET: 638 CopyTarget(message); 639 break; 640 default: 641 BWindow::MessageReceived(message); 642 break; 643 } 644 } 645 646 647 void 648 RecorderWindow::Record(BMessage * message) 649 { 650 // User pressed Record button 651 fRecording = true; 652 if (fButtonState != btnPaused) { 653 StopRecording(); 654 return; // user is too fast on the mouse 655 } 656 SetButtonState(btnRecording); 657 fRecordButton->SetRecording(); 658 659 char name[256]; 660 // Create a file with a temporary name 661 status_t err = NewTempName(name); 662 if (err < B_OK) { 663 ErrorAlert(B_TRANSLATE("Cannot find an unused name to use for the " 664 "new recording"), err); 665 return; 666 } 667 // Find the file so we can refer to it later 668 err = fTempDir.FindEntry(name, &fRecEntry); 669 if (err < B_OK) { 670 ErrorAlert(B_TRANSLATE("Cannot find the temporary file created to " 671 "hold the new recording"), err); 672 return; 673 } 674 err = fRecFile.SetTo(&fTempDir, name, O_RDWR); 675 if (err < B_OK) { 676 ErrorAlert(B_TRANSLATE("Cannot open the temporary file created to " 677 "hold the new recording"), err); 678 fRecEntry.Unset(); 679 return; 680 } 681 // Reserve space on disk (creates fewer fragments) 682 err = fRecFile.SetSize(4 * fRecordFormat.u.raw_audio.channel_count 683 * fRecordFormat.u.raw_audio.frame_rate 684 * (fRecordFormat.u.raw_audio.format 685 & media_raw_audio_format::B_AUDIO_SIZE_MASK)); 686 if (err < B_OK) { 687 ErrorAlert(B_TRANSLATE("Cannot record a sound that long"), err); 688 fRecEntry.Remove(); 689 fRecEntry.Unset(); 690 return; 691 } 692 fRecSize = 0; 693 694 fRecFile.Seek(sizeof(struct wave_struct), SEEK_SET); 695 696 // Hook up input 697 err = MakeRecordConnection(fAudioInputNode); 698 if (err < B_OK) { 699 ErrorAlert(B_TRANSLATE("Cannot connect to the selected sound input"), 700 err); 701 fRecEntry.Remove(); 702 fRecEntry.Unset(); 703 return; 704 } 705 706 fRecorder->Start(); 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 status_t err = B_OK; 875 media_output audioOutput; 876 877 if (!fRecorder->IsConnected()) { 878 // Find an available output for the given input node. 879 int32 count = 0; 880 err = fRoster->GetFreeOutputsFor(input, &audioOutput, 1, 881 &count, B_MEDIA_RAW_AUDIO); 882 883 if (err < B_OK) { 884 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 885 " couldn't get free outputs from audio input node\n")); 886 return err; 887 } 888 889 if (count < 1) { 890 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 891 " no free outputs from audio input node\n")); 892 return B_BUSY; 893 } 894 895 } else { 896 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 897 " audio input node already connected\n")); 898 899 return B_BUSY; 900 } 901 902 // Get a format, any format. 903 fRecordFormat.u.raw_audio = audioOutput.format.u.raw_audio; 904 fRecordFormat.type = B_MEDIA_RAW_AUDIO; 905 906 // Tell the consumer where we want data to go. 907 err = fRecorder->SetHooks(RecordFile, NotifyRecordFile, this); 908 909 if (err < B_OK) { 910 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 911 " couldn't set the sound recorder's hook functions\n")); 912 return err; 913 } 914 915 if (!fRecorder->IsConnected()) { 916 917 err = fRecorder->Connect(input, &audioOutput, &fRecordFormat); 918 919 if (err < B_OK) { 920 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 921 " failed to connect sound recorder to audio input node.\n")); 922 923 fRecorder->SetHooks(NULL, NULL, NULL); 924 return err; 925 } 926 } 927 928 return B_OK; 929 } 930 931 932 status_t 933 RecorderWindow::BreakRecordConnection() 934 { 935 status_t err = B_OK; 936 937 err = fRecorder->Stop(true); 938 if (err < B_OK) 939 return err; 940 941 return fRecorder->Disconnect(); 942 } 943 944 945 status_t 946 RecorderWindow::StopRecording() 947 { 948 if (!fRecording) 949 return B_OK; 950 fRecording = false; 951 952 BreakRecordConnection(); 953 954 fRecorder->SetHooks(NULL, NULL, NULL); 955 956 if (fRecSize > 0) { 957 958 wave_struct header; 959 header.riff.riff_id = FOURCC('R','I','F','F'); 960 header.riff.len = fRecSize + sizeof(header) - 8; 961 header.riff.wave_id = FOURCC('W','A','V','E'); 962 header.format_chunk.fourcc = FOURCC('f','m','t',' '); 963 header.format_chunk.len = sizeof(header.format); 964 header.format.format_tag = 1; 965 header.format.channels = fRecordFormat.u.raw_audio.channel_count; 966 header.format.samples_per_sec = (uint32)fRecordFormat.u.raw_audio.frame_rate; 967 header.format.avg_bytes_per_sec = (uint32)(fRecordFormat.u.raw_audio.frame_rate 968 * fRecordFormat.u.raw_audio.channel_count 969 * (fRecordFormat.u.raw_audio.format & 0xf)); 970 header.format.bits_per_sample = (fRecordFormat.u.raw_audio.format & 0xf) * 8; 971 header.format.block_align = (fRecordFormat.u.raw_audio.format & 0xf) 972 * fRecordFormat.u.raw_audio.channel_count; 973 header.data_chunk.fourcc = FOURCC('d','a','t','a'); 974 header.data_chunk.len = fRecSize; 975 fRecFile.Seek(0, SEEK_SET); 976 fRecFile.Write(&header, sizeof(header)); 977 978 fRecFile.SetSize(fRecSize + sizeof(header)); 979 // We reserve space; make sure we cut off any excess at the end. 980 AddSoundItem(fRecEntry, true); 981 } else 982 fRecEntry.Remove(); 983 984 // We're done for this time. 985 fRecEntry.Unset(); 986 // Close the file. 987 fRecFile.Unset(); 988 // No more recording going on. 989 fRecSize = 0; 990 SetButtonState(btnPaused); 991 fRecordButton->SetStopped(); 992 993 return B_OK; 994 } 995 996 997 status_t 998 RecorderWindow::StopPlaying() 999 { 1000 if (fPlayer) { 1001 fPlayer->Stop(); 1002 fPlayer->SetCallbacks(0, 0, 0); 1003 fVolumeSlider->SetSoundPlayer(NULL); 1004 delete fPlayer; 1005 fPlayer = NULL; 1006 } 1007 SetButtonState(btnPaused); 1008 fPlayButton->SetStopped(); 1009 fTrackSlider->ResetMainTime(); 1010 fScopeView->SetMainTime(*fTrackSlider->MainTime()); 1011 return B_OK; 1012 } 1013 1014 1015 void 1016 RecorderWindow::SetButtonState(BtnState state) 1017 { 1018 fButtonState = state; 1019 UpdateButtons(); 1020 } 1021 1022 1023 void 1024 RecorderWindow::UpdateButtons() 1025 { 1026 bool hasSelection = (fSoundList->CurrentSelection() >= 0); 1027 fRecordButton->SetEnabled(fButtonState != btnPlaying); 1028 fPlayButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1029 fRewindButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1030 fForwardButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1031 fStopButton->SetEnabled(fButtonState != btnPaused); 1032 fSaveButton->SetEnabled(hasSelection && (fButtonState != btnRecording)); 1033 fInputField->SetEnabled(fButtonState != btnRecording); 1034 } 1035 1036 #ifndef __HAIKU__ 1037 extern "C" status_t DecodedFormat__11BMediaTrackP12media_format( 1038 BMediaTrack *self, media_format *inout_format); 1039 #endif 1040 1041 1042 status_t 1043 RecorderWindow::UpdatePlayFile(SoundListItem* item, bool updateDisplay) 1044 { 1045 fScopeView->CancelRendering(); 1046 StopPlaying(); 1047 StopRecording(); 1048 1049 if (fPlayTrack && fPlayFile) { 1050 fPlayFile->ReleaseTrack(fPlayTrack); 1051 fPlayTrack = NULL; 1052 } 1053 if (fPlayFile) { 1054 delete fPlayFile; 1055 fPlayFile = NULL; 1056 } 1057 1058 status_t err; 1059 BEntry& entry = item->Entry(); 1060 entry_ref ref; 1061 entry.GetRef(&ref); 1062 fPlayFile = new BMediaFile(&ref); //, B_MEDIA_FILE_UNBUFFERED); 1063 if ((err = fPlayFile->InitCheck()) < B_OK) { 1064 delete fPlayFile; 1065 fPlayFile = NULL; 1066 return err; 1067 } 1068 1069 for (int ix=0; ix < fPlayFile->CountTracks(); ix++) { 1070 BMediaTrack * track = fPlayFile->TrackAt(ix); 1071 fPlayFormat.type = B_MEDIA_RAW_AUDIO; 1072 #ifdef __HAIKU__ 1073 if ((track->DecodedFormat(&fPlayFormat) == B_OK) 1074 #else 1075 if ((DecodedFormat__11BMediaTrackP12media_format(track, &fPlayFormat) == B_OK) 1076 #endif 1077 && (fPlayFormat.type == B_MEDIA_RAW_AUDIO)) { 1078 fPlayTrack = track; 1079 break; 1080 } 1081 if (track) 1082 fPlayFile->ReleaseTrack(track); 1083 } 1084 1085 if (!fPlayTrack) { 1086 delete fPlayFile; 1087 fPlayFile = NULL; 1088 return B_STREAM_NOT_FOUND; 1089 } 1090 1091 if (!updateDisplay) 1092 return B_OK; 1093 1094 BString filename = B_TRANSLATE("File name: "); 1095 filename << ref.name; 1096 fFilename->SetText(filename.String()); 1097 1098 BString format = B_TRANSLATE("Format: "); 1099 media_file_format file_format; 1100 if (fPlayFile->GetFileFormatInfo(&file_format) == B_OK) 1101 format << file_format.short_name; 1102 BString compression = B_TRANSLATE("Compression: "); 1103 media_codec_info codec_info; 1104 if (fPlayTrack->GetCodecInfo(&codec_info) == B_OK) { 1105 if (strcmp(codec_info.short_name, "raw")==0) 1106 compression << B_TRANSLATE("None"); 1107 else 1108 compression << codec_info.short_name; 1109 } 1110 BString channels = B_TRANSLATE("Channels: "); 1111 channels << fPlayFormat.u.raw_audio.channel_count; 1112 BString samplesize = B_TRANSLATE("Sample size: "); 1113 samplesize << 8 * (fPlayFormat.u.raw_audio.format & 0xf) 1114 << B_TRANSLATE(" bits"); 1115 BString samplerate = B_TRANSLATE("Sample rate: "); 1116 samplerate << (int)fPlayFormat.u.raw_audio.frame_rate; 1117 BString durationString = B_TRANSLATE("Duration: "); 1118 bigtime_t duration = fPlayTrack->Duration(); 1119 durationString << (float)(duration / 1000000.0) << B_TRANSLATE(" seconds"); 1120 1121 fFormat->SetText(format.String()); 1122 fCompression->SetText(compression.String()); 1123 fChannels->SetText(channels.String()); 1124 fSampleSize->SetText(samplesize.String()); 1125 fSampleRate->SetText(samplerate.String()); 1126 fDuration->SetText(durationString.String()); 1127 1128 fTrackSlider->SetTotalTime(duration, true); 1129 fScopeView->SetTotalTime(duration, true); 1130 fScopeView->RenderTrack(fPlayTrack, fPlayFormat); 1131 1132 fPlayFrames = fPlayTrack->CountFrames(); 1133 return B_OK; 1134 } 1135 1136 1137 void 1138 RecorderWindow::ErrorAlert(const char * action, status_t err) 1139 { 1140 char msg[300]; 1141 if (err != B_OK) 1142 sprintf(msg, "%s: %s. [%" B_PRIx32 "]", action, strerror(err), (int32) err); 1143 else 1144 sprintf(msg, "%s.", action); 1145 BAlert* alert = new BAlert("", msg, B_TRANSLATE("Stop")); 1146 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1147 alert->Go(); 1148 } 1149 1150 1151 status_t 1152 RecorderWindow::NewTempName(char * name) 1153 { 1154 int init_count = fTempCount; 1155 again: 1156 if (fTempCount-init_count > 25) { 1157 return B_ERROR; 1158 } 1159 else { 1160 fTempCount++; 1161 if (fTempCount==0) 1162 sprintf(name, "Audio Clip"); 1163 else 1164 sprintf(name, "Audio Clip %d", fTempCount); 1165 BPath path; 1166 status_t err; 1167 BEntry tempEnt; 1168 if ((err = fTempDir.GetEntry(&tempEnt)) < B_OK) { 1169 return err; 1170 } 1171 if ((err = tempEnt.GetPath(&path)) < B_OK) { 1172 return err; 1173 } 1174 path.Append(name); 1175 int fd; 1176 // Use O_EXCL so we know we created the file (sync with other instances) 1177 if ((fd = open(path.Path(), O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) { 1178 goto again; 1179 } 1180 close(fd); 1181 } 1182 return B_OK; 1183 } 1184 1185 1186 void 1187 RecorderWindow::AddSoundItem(const BEntry& entry, bool temp) 1188 { 1189 // Create list item to display. 1190 SoundListItem * listItem = new SoundListItem(entry, temp); 1191 fSoundList->AddItem(listItem); 1192 fSoundList->Invalidate(); 1193 fSoundList->Select(fSoundList->IndexOf(listItem)); 1194 } 1195 1196 1197 void 1198 RecorderWindow::RemoveCurrentSoundItem() { 1199 int32 index = fSoundList->CurrentSelection(); 1200 BListItem *item = fSoundList->RemoveItem(index); 1201 delete item; 1202 if (index >= fSoundList->CountItems()) 1203 index = fSoundList->CountItems() - 1; 1204 fSoundList->Select(index); 1205 } 1206 1207 1208 void 1209 RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp, 1210 void* data, size_t size, const media_format &format) 1211 { 1212 // Callback called from the SoundConsumer when receiving buffers. 1213 RecorderWindow * window = (RecorderWindow *)cookie; 1214 1215 if (window->fRecording) { 1216 // Write the data to file (we don't buffer or guard file access 1217 // or anything) 1218 window->fRecFile.WriteAt(window->fRecSize, data, size); 1219 window->fVUView->ComputeLevels(data, size, format.u.raw_audio.format); 1220 window->fRecSize += size; 1221 } 1222 } 1223 1224 1225 void 1226 RecorderWindow::NotifyRecordFile(void * cookie, 1227 BMediaRecorder::notification code, ...) 1228 { 1229 if (code == BMediaRecorder::B_WILL_STOP) { 1230 RecorderWindow * window = (RecorderWindow *)cookie; 1231 // Tell the window we've stopped, if it doesn't 1232 // already know. 1233 window->PostMessage(STOP_RECORDING); 1234 } 1235 } 1236 1237 1238 void 1239 RecorderWindow::PlayFile(void * cookie, void * data, size_t size, 1240 const media_raw_audio_format & format) 1241 { 1242 // Callback called from the SoundProducer when producing buffers. 1243 RecorderWindow * window = (RecorderWindow *)cookie; 1244 int32 frame_size = (window->fPlayFormat.u.raw_audio.format & 0xf) * 1245 window->fPlayFormat.u.raw_audio.channel_count; 1246 1247 if ((window->fPlayFrame < window->fPlayLimit) || window->fLooping) { 1248 if (window->fPlayFrame >= window->fPlayLimit) { 1249 bigtime_t left = window->fTrackSlider->LeftTime(); 1250 window->fPlayTrack->SeekToTime(&left); 1251 window->fPlayFrame = window->fPlayTrack->CurrentFrame(); 1252 } 1253 int64 frames = 0; 1254 window->fPlayTrack->ReadFrames(data, &frames); 1255 window->fVUView->ComputeLevels(data, size / frame_size, format.format); 1256 window->fPlayFrame += size/frame_size; 1257 window->PostMessage(UPDATE_TRACKSLIDER); 1258 } else { 1259 // we're done! 1260 window->PostMessage(STOP_PLAYING); 1261 } 1262 } 1263 1264 1265 void 1266 RecorderWindow::NotifyPlayFile(void * cookie, 1267 BSoundPlayer::sound_player_notification code, ...) 1268 { 1269 if ((code == BSoundPlayer::B_STOPPED) || (code == BSoundPlayer::B_SOUND_DONE)) { 1270 RecorderWindow * window = (RecorderWindow *)cookie; 1271 // tell the window we've stopped, if it doesn't 1272 // already know. 1273 window->PostMessage(STOP_PLAYING); 1274 } 1275 } 1276 1277 1278 void 1279 RecorderWindow::RefsReceived(BMessage *msg) 1280 { 1281 entry_ref ref; 1282 int32 i = 0; 1283 int32 countGood = 0; 1284 int32 countBad = 0; 1285 1286 while (msg->FindRef("refs", i++, &ref) == B_OK) { 1287 1288 BEntry entry(&ref, true); 1289 BPath path(&entry); 1290 BNode node(&entry); 1291 1292 if (node.IsFile()) { 1293 SoundListItem * listItem = new SoundListItem(entry, false); 1294 if (UpdatePlayFile(listItem) == B_OK) { 1295 fSoundList->AddItem(listItem); 1296 countGood++; 1297 continue; 1298 } 1299 delete listItem; 1300 } else if(node.IsDirectory()) { 1301 1302 } 1303 countBad++; 1304 } 1305 1306 if (countBad == 1 && countGood == 0) { 1307 BAlert* alert = new BAlert(B_TRANSLATE("Nothing to play"), 1308 B_TRANSLATE("The file doesn't appear to be an audio file."), 1309 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1310 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1311 alert->Go(); 1312 } else if (countBad > 0 && countGood == 0) { 1313 BAlert* alert = new BAlert(B_TRANSLATE("Nothing to play"), 1314 B_TRANSLATE("None of the files appear to be audio files."), 1315 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 1316 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1317 alert->Go(); 1318 } else if (countGood > 0) { 1319 if (countBad > 0) { 1320 BAlert* alert = new BAlert(B_TRANSLATE("Invalid audio files"), 1321 B_TRANSLATE("Some of the files don't appear to be audio files."), 1322 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1323 B_WARNING_ALERT); 1324 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1325 alert->Go(); 1326 } 1327 fSoundList->Select(fSoundList->CountItems() - 1); 1328 } 1329 } 1330 1331 1332 void 1333 RecorderWindow::CopyTarget(BMessage *msg) 1334 { 1335 const char *type = NULL; 1336 if (msg->FindString("be:types", &type) == B_OK) { 1337 if (!strcasecmp(type, B_FILE_MIME_TYPE)) { 1338 const char *name; 1339 entry_ref dir; 1340 if (msg->FindString("be:filetypes") == B_OK 1341 && msg->FindString("name", &name) == B_OK 1342 && msg->FindRef("directory", &dir) == B_OK) { 1343 BDirectory directory(&dir); 1344 BFile file(&directory, name, O_RDWR | O_TRUNC); 1345 1346 // seek time 1347 bigtime_t start = fTrackSlider->LeftTime(); 1348 1349 // write data 1350 bigtime_t diffTime = fTrackSlider->RightTime() 1351 - fTrackSlider->LeftTime(); 1352 int64 framesToWrite = (int64) (diffTime 1353 * fPlayFormat.u.raw_audio.frame_rate / 1000000LL); 1354 int32 frameSize = (fPlayFormat.u.raw_audio.format & 0xf) 1355 * fPlayFormat.u.raw_audio.channel_count; 1356 1357 wave_struct header; 1358 header.riff.riff_id = FOURCC('R','I','F','F'); 1359 header.riff.len 1360 = (frameSize * framesToWrite) + sizeof(header) - 8; 1361 header.riff.wave_id = FOURCC('W','A','V','E'); 1362 header.format_chunk.fourcc = FOURCC('f','m','t',' '); 1363 header.format_chunk.len = sizeof(header.format); 1364 header.format.format_tag = 1; 1365 header.format.channels = fPlayFormat.u.raw_audio.channel_count; 1366 header.format.samples_per_sec 1367 = (uint32)fPlayFormat.u.raw_audio.frame_rate; 1368 header.format.avg_bytes_per_sec 1369 = (uint32)(fPlayFormat.u.raw_audio.frame_rate 1370 * fPlayFormat.u.raw_audio.channel_count 1371 * (fPlayFormat.u.raw_audio.format & 0xf)); 1372 header.format.bits_per_sample 1373 = (fPlayFormat.u.raw_audio.format & 0xf) * 8; 1374 header.format.block_align = frameSize; 1375 header.data_chunk.fourcc = FOURCC('d','a','t','a'); 1376 header.data_chunk.len = frameSize * framesToWrite; 1377 file.Seek(0, SEEK_SET); 1378 file.Write(&header, sizeof(header)); 1379 1380 char *data = (char *)malloc(fPlayFormat.u.raw_audio.buffer_size); 1381 1382 fPlayTrack->SeekToTime(&start); 1383 fPlayFrame = fPlayTrack->CurrentFrame(); 1384 while (framesToWrite > 0) { 1385 int64 frames = 0; 1386 status_t err = fPlayTrack->ReadFrames(data, &frames); 1387 if (frames <= 0 || err != B_OK) { 1388 if (err != B_OK) 1389 fprintf(stderr, "CopyTarget: ReadFrames failed\n"); 1390 break; 1391 } 1392 file.Write(data, frames * frameSize); 1393 framesToWrite -= frames; 1394 } 1395 1396 file.Sync(); 1397 free(data); 1398 BNodeInfo nodeInfo(&file); 1399 // set type 1400 } 1401 } else { 1402 1403 } 1404 } 1405 } 1406