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_TRANSLATE_CONTEXT 60 #define B_TRANSLATE_CONTEXT "RecorderWindow" 61 62 63 // default window positioning 64 static const float MIN_WIDTH = 400.0f; 65 static const float MIN_HEIGHT = 336.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(216, 216, 216); 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 | B_FOLLOW_BOTTOM); 378 fFileInfoBox->SetLabel(B_TRANSLATE("File info")); 379 380 r = fFileInfoBox->Bounds(); 381 r.left = 8; 382 r.top = 13; 383 r.bottom = r.top + 15; 384 r.right -= 10; 385 fFilename = new BStringView(r, "filename", B_TRANSLATE("File name:")); 386 fFileInfoBox->AddChild(fFilename); 387 r.top += 13; 388 r.bottom = r.top + 15; 389 fFormat = new BStringView(r, "format", B_TRANSLATE("Format:")); 390 fFileInfoBox->AddChild(fFormat); 391 r.top += 13; 392 r.bottom = r.top + 15; 393 fCompression = new BStringView(r, "compression", 394 B_TRANSLATE("Compression:")); 395 fFileInfoBox->AddChild(fCompression); 396 r.top += 13; 397 r.bottom = r.top + 15; 398 fChannels = new BStringView(r, "channels", B_TRANSLATE("Channels:")); 399 fFileInfoBox->AddChild(fChannels); 400 r.top += 13; 401 r.bottom = r.top + 15; 402 fSampleSize = new BStringView(r, "samplesize", 403 B_TRANSLATE("Sample size:")); 404 fFileInfoBox->AddChild(fSampleSize); 405 r.top += 13; 406 r.bottom = r.top + 15; 407 fSampleRate = new BStringView(r, "samplerate", 408 B_TRANSLATE("Sample rate:")); 409 fFileInfoBox->AddChild(fSampleRate); 410 r.top += 13; 411 r.bottom = r.top + 15; 412 fDuration = new BStringView(r, "duration", B_TRANSLATE("Duration:")); 413 fFileInfoBox->AddChild(fDuration); 414 415 // Input selection lists all available physical inputs that produce 416 // buffers with B_MEDIA_RAW_AUDIO format data. 417 popup = new BPopUpMenu(B_TRANSLATE("Input")); 418 const int maxInputCount = 64; 419 dormant_node_info dni[maxInputCount]; 420 421 int32 real_count = maxInputCount; 422 media_format output_format; 423 output_format.type = B_MEDIA_RAW_AUDIO; 424 output_format.u.raw_audio = media_raw_audio_format::wildcard; 425 error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format, 426 0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT); 427 if (real_count > maxInputCount) { 428 WINDOW((stderr, "dropped %ld inputs\n", real_count - maxInputCount)); 429 real_count = maxInputCount; 430 } 431 char selected_name[B_MEDIA_NAME_LENGTH] = "Default input"; 432 BMessage * msg; 433 BMenuItem * item; 434 for (int i = 0; i < real_count; i++) { 435 msg = new BMessage(INPUT_SELECTED); 436 msg->AddData("node", B_RAW_TYPE, &dni[i], sizeof(dni[i])); 437 item = new BMenuItem(dni[i].name, msg); 438 popup->AddItem(item); 439 media_node_id ni[12]; 440 int32 ni_count = 12; 441 error = fRoster->GetInstancesFor(dni[i].addon, dni[i].flavor_id, 442 ni, &ni_count); 443 if (error == B_OK) { 444 for (int j = 0; j < ni_count; j++) { 445 if (ni[j] == fAudioInputNode.node) { 446 strcpy(selected_name, dni[i].name); 447 break; 448 } 449 } 450 } 451 } 452 453 // Create the actual widget 454 BRect frame(fBottomBox->Bounds()); 455 r = frame; 456 r.left = 42; 457 r.right = (r.left + r.right) / 2; 458 r.InsetBy(10,10); 459 r.top = r.bottom - 18; 460 fInputField = new BMenuField(r, "Input", B_TRANSLATE("Input:"), popup); 461 fInputField->SetDivider(fInputField->StringWidth(B_TRANSLATE("Input:")) 462 + 4.0f); 463 fBottomBox->AddChild(fInputField); 464 465 fBottomBox->AddChild(fFileInfoBox); 466 467 fBottomBox->Hide(); 468 CalcSizes(Frame().Width(), MIN_HEIGHT-161); 469 ResizeTo(Frame().Width(), MIN_HEIGHT-161); 470 471 472 popup->Superitem()->SetLabel(selected_name); 473 474 // Make sure the save panel is happy. 475 fSavePanel = new BFilePanel(B_SAVE_PANEL); 476 fSavePanel->SetTarget(this); 477 } 478 catch (...) { 479 goto bad_mojo; 480 } 481 UpdateButtons(); 482 return B_OK; 483 484 // Error handling. 485 bad_mojo: 486 if (error >= 0) 487 error = B_ERROR; 488 if (fRecordNode) 489 fRecordNode->Release(); 490 491 delete fPlayer; 492 if (!fInputField) 493 delete popup; 494 return error; 495 } 496 497 498 bool 499 RecorderWindow::QuitRequested() // this means Close pressed 500 { 501 StopRecording(); 502 StopPlaying(); 503 be_app->PostMessage(B_QUIT_REQUESTED); 504 return true; 505 } 506 507 508 void 509 RecorderWindow::MessageReceived(BMessage * message) 510 { 511 // Your average generic message dispatching switch() statement. 512 switch (message->what) { 513 case INPUT_SELECTED: 514 Input(message); 515 break; 516 case SOUND_SELECTED: 517 Selected(message); 518 break; 519 case STOP_PLAYING: 520 StopPlaying(); 521 break; 522 case STOP_RECORDING: 523 StopRecording(); 524 break; 525 case PLAY_PERIOD: 526 if (fPlayer) { 527 if (fPlayer->HasData()) 528 fPlayButton->SetPlaying(); 529 else 530 fPlayButton->SetPaused(); 531 } 532 break; 533 case RECORD_PERIOD: 534 fRecordButton->SetRecording(); 535 break; 536 case RECORD: 537 Record(message); 538 break; 539 case STOP: 540 Stop(message); 541 break; 542 case PLAY: 543 Play(message); 544 break; 545 case SAVE: 546 Save(message); 547 break; 548 case B_SAVE_REQUESTED: 549 DoSave(message); 550 break; 551 case VIEW_LIST: 552 if (fUpDownButton->Value() == B_CONTROL_ON) { 553 fBottomBox->Show(); 554 CalcSizes(Frame().Width(), MIN_HEIGHT); 555 ResizeTo(Frame().Width(), MIN_HEIGHT); 556 } else { 557 fBottomBox->Hide(); 558 CalcSizes(Frame().Width(), MIN_HEIGHT-161); 559 ResizeTo(Frame().Width(), MIN_HEIGHT-161); 560 561 } 562 break; 563 case UPDATE_TRACKSLIDER: 564 { 565 bigtime_t timestamp = fPlayTrack->CurrentTime(); 566 fTrackSlider->SetMainTime(timestamp, false); 567 fScopeView->SetMainTime(timestamp); 568 } 569 break; 570 case POSITION_CHANGED: 571 { 572 bigtime_t right, left, main; 573 if (message->FindInt64("main", &main) == B_OK) { 574 if (fPlayTrack) { 575 fPlayTrack->SeekToTime(fTrackSlider->MainTime()); 576 fPlayFrame = fPlayTrack->CurrentFrame(); 577 } 578 fScopeView->SetMainTime(main); 579 } 580 if (message->FindInt64("right", &right) == B_OK) { 581 if (fPlayTrack) { 582 fPlayLimit = MIN(fPlayFrames, 583 (off_t)(right * fPlayFormat.u.raw_audio.frame_rate 584 / 1000000LL)); 585 } 586 fScopeView->SetRightTime(right); 587 } 588 if (message->FindInt64("left", &left) == B_OK) 589 fScopeView->SetLeftTime(left); 590 break; 591 } 592 case LOOP: 593 fLooping = fLoopButton->ButtonState(); 594 break; 595 case B_SIMPLE_DATA: 596 case B_REFS_RECEIVED: 597 { 598 RefsReceived(message); 599 break; 600 } 601 case B_COPY_TARGET: 602 CopyTarget(message); 603 break; 604 default: 605 BWindow::MessageReceived(message); 606 break; 607 } 608 } 609 610 611 void 612 RecorderWindow::Record(BMessage * message) 613 { 614 // User pressed Record button 615 fRecording = true; 616 if (fButtonState != btnPaused) { 617 StopRecording(); 618 return; // user is too fast on the mouse 619 } 620 SetButtonState(btnRecording); 621 fRecordButton->SetRecording(); 622 623 char name[256]; 624 // Create a file with a temporary name 625 status_t err = NewTempName(name); 626 if (err < B_OK) { 627 ErrorAlert(B_TRANSLATE("Cannot find an unused name to use for the " 628 "new recording"), err); 629 return; 630 } 631 // Find the file so we can refer to it later 632 err = fTempDir.FindEntry(name, &fRecEntry); 633 if (err < B_OK) { 634 ErrorAlert(B_TRANSLATE("Cannot find the temporary file created to " 635 "hold the new recording"), err); 636 return; 637 } 638 err = fRecFile.SetTo(&fTempDir, name, O_RDWR); 639 if (err < B_OK) { 640 ErrorAlert(B_TRANSLATE("Cannot open the temporary file created to " 641 "hold the new recording"), err); 642 fRecEntry.Unset(); 643 return; 644 } 645 // Reserve space on disk (creates fewer fragments) 646 err = fRecFile.SetSize(4 * fRecordFormat.u.raw_audio.channel_count 647 * fRecordFormat.u.raw_audio.frame_rate 648 * (fRecordFormat.u.raw_audio.format 649 & media_raw_audio_format::B_AUDIO_SIZE_MASK)); 650 if (err < B_OK) { 651 ErrorAlert(B_TRANSLATE("Cannot record a sound that long"), err); 652 fRecEntry.Remove(); 653 fRecEntry.Unset(); 654 return; 655 } 656 fRecSize = 0; 657 658 fRecFile.Seek(sizeof(struct wave_struct), SEEK_SET); 659 660 // Hook up input 661 err = MakeRecordConnection(fAudioInputNode); 662 if (err < B_OK) { 663 ErrorAlert(B_TRANSLATE("Cannot connect to the selected sound input"), 664 err); 665 fRecEntry.Remove(); 666 fRecEntry.Unset(); 667 return; 668 } 669 670 // And get it going... 671 bigtime_t then = fRecordNode->TimeSource()->Now() + 50000LL; 672 fRoster->StartNode(fRecordNode->Node(), then); 673 if (fAudioInputNode.kind & B_TIME_SOURCE) { 674 fRoster->StartNode(fAudioInputNode, 675 fRecordNode->TimeSource()->RealTimeFor(then, 0)); 676 } else 677 fRoster->StartNode(fAudioInputNode, then); 678 } 679 680 681 void 682 RecorderWindow::Play(BMessage * message) 683 { 684 if (fPlayer) { 685 // User pressed Play button and playing 686 if (fPlayer->HasData()) 687 fPlayButton->SetPaused(); 688 else 689 fPlayButton->SetPlaying(); 690 fPlayer->SetHasData(!fPlayer->HasData()); 691 return; 692 } 693 694 SetButtonState(btnPlaying); 695 fPlayButton->SetPlaying(); 696 697 if (!fPlayTrack) { 698 ErrorAlert(B_TRANSLATE("Cannot get the file to play"), B_ERROR); 699 return; 700 } 701 702 fPlayLimit = MIN(fPlayFrames, (off_t)(fTrackSlider->RightTime() 703 * fPlayFormat.u.raw_audio.frame_rate / 1000000LL)); 704 fPlayTrack->SeekToTime(fTrackSlider->MainTime()); 705 fPlayFrame = fPlayTrack->CurrentFrame(); 706 707 // Create our internal Node which plays sound, and register it. 708 fPlayer = new BSoundPlayer(fAudioMixerNode, &fPlayFormat.u.raw_audio, 709 "Sound Player"); 710 status_t err = fPlayer->InitCheck(); 711 if (err < B_OK) 712 return; 713 714 fVolumeSlider->SetSoundPlayer(fPlayer); 715 fPlayer->SetCallbacks(PlayFile, NotifyPlayFile, this); 716 717 // And get it going... 718 fPlayer->Start(); 719 fPlayer->SetHasData(true); 720 } 721 722 723 void 724 RecorderWindow::Stop(BMessage * message) 725 { 726 // User pressed Stop button. 727 // Stop recorder. 728 StopRecording(); 729 // Stop player. 730 StopPlaying(); 731 } 732 733 734 void 735 RecorderWindow::Save(BMessage * message) 736 { 737 // User pressed Save button. 738 // Find the item to save. 739 int32 index = fSoundList->CurrentSelection(); 740 SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(index)); 741 if ((! pItem) || (pItem->Entry().InitCheck() != B_OK)) 742 return; 743 744 // Update the save panel and show it. 745 char filename[B_FILE_NAME_LENGTH]; 746 pItem->Entry().GetName(filename); 747 BMessage saveMsg(B_SAVE_REQUESTED); 748 entry_ref ref; 749 pItem->Entry().GetRef(&ref); 750 751 if (saveMsg.AddPointer("sound list item", pItem) != B_OK) 752 fprintf(stderr, "failed to add pItem\n"); 753 fSavePanel->SetSaveText(filename); 754 fSavePanel->SetMessage(&saveMsg); 755 fSavePanel->Show(); 756 } 757 758 759 void 760 RecorderWindow::DoSave(BMessage * message) 761 { 762 // User picked a place to put the file. 763 // Find the location of the old (e.g. 764 // temporary file), and the name of the 765 // new file to save. 766 entry_ref old_ref, new_dir_ref; 767 const char* new_name; 768 SoundListItem* pItem; 769 770 if ((message->FindPointer("sound list item", (void**) &pItem) == B_OK) 771 && (message->FindRef("directory", &new_dir_ref) == B_OK) 772 && (message->FindString("name", &new_name) == B_OK)) { 773 BEntry& oldEntry = pItem->Entry(); 774 BFile oldFile(&oldEntry, B_READ_WRITE); 775 if (oldFile.InitCheck() != B_OK) 776 return; 777 778 BDirectory newDir(&new_dir_ref); 779 if (newDir.InitCheck() != B_OK) 780 return; 781 782 BFile newFile; 783 newDir.CreateFile(new_name, &newFile); 784 785 if (newFile.InitCheck() != B_OK) 786 return; 787 788 status_t err = CopyFile(newFile, oldFile); 789 790 if (err == B_OK) { 791 // clean up the sound list and item 792 if (pItem->IsTemp()) 793 oldEntry.Remove(); // blows away temp file! 794 oldEntry.SetTo(&newDir, new_name); 795 pItem->SetTemp(false); // don't blow the new entry away when we exit! 796 fSoundList->Invalidate(); 797 } 798 } else { 799 WINDOW((stderr, "Couldn't save file.\n")); 800 } 801 } 802 803 804 void 805 RecorderWindow::Input(BMessage * message) 806 { 807 // User selected input from pop-up 808 const dormant_node_info * dni = 0; 809 ssize_t size = 0; 810 if (message->FindData("node", B_RAW_TYPE, (const void **)&dni, &size)) 811 return; // bad input selection message 812 813 media_node_id node_id; 814 status_t error = fRoster->GetInstancesFor(dni->addon, dni->flavor_id, &node_id); 815 if (error != B_OK) 816 fRoster->InstantiateDormantNode(*dni, &fAudioInputNode); 817 else 818 fRoster->GetNodeFor(node_id, &fAudioInputNode); 819 } 820 821 822 void 823 RecorderWindow::Selected(BMessage * message) 824 { 825 // User selected a sound in list view 826 int32 selIdx = fSoundList->CurrentSelection(); 827 SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(selIdx)); 828 if (!pItem) 829 return; 830 status_t err = UpdatePlayFile(pItem, true); 831 if (err != B_OK) { 832 ErrorAlert(B_TRANSLATE("Cannot recognize this file as a media file"), 833 err == B_MEDIA_NO_HANDLER ? B_OK : err); 834 RemoveCurrentSoundItem(); 835 } 836 UpdateButtons(); 837 } 838 839 840 status_t 841 RecorderWindow::MakeRecordConnection(const media_node & input) 842 { 843 CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n")); 844 845 // Find an available output for the given input node. 846 int32 count = 0; 847 status_t err = fRoster->GetFreeOutputsFor(input, &fAudioOutput, 1, &count, B_MEDIA_RAW_AUDIO); 848 if (err < B_OK) { 849 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 850 " couldn't get free outputs from audio input node\n")); 851 return err; 852 } 853 if (count < 1) { 854 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 855 " no free outputs from audio input node\n")); 856 return B_BUSY; 857 } 858 859 // Find an available input for our own Node. Note that we go through the 860 // MediaRoster; calling Media Kit methods directly on Nodes in our app is 861 // not OK (because synchronization happens in the service thread, not in 862 // the calling thread). 863 // TODO: explain this 864 err = fRoster->GetFreeInputsFor(fRecordNode->Node(), &fRecInput, 1, &count, B_MEDIA_RAW_AUDIO); 865 if (err < B_OK) { 866 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 867 " couldn't get free inputs for sound recorder\n")); 868 return err; 869 } 870 if (count < 1) { 871 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 872 " no free inputs for sound recorder\n")); 873 return B_BUSY; 874 } 875 876 // Find out what the time source of the input is. 877 // For most nodes, we just use the preferred time source (the DAC) for synchronization. 878 // However, nodes that record from an input need to synchronize to the audio input node 879 // instead for best results. 880 // MakeTimeSourceFor gives us a "clone" of the time source node that we can manipulate 881 // to our heart's content. When we're done with it, though, we need to call Release() 882 // on the time source node, so that it keeps an accurate reference count and can delete 883 // itself when it's no longer needed. 884 // TODO: what about filters connected to audio input? 885 media_node use_time_source; 886 BTimeSource * tsobj = fRoster->MakeTimeSourceFor(input); 887 if (! tsobj) { 888 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 889 " couldn't clone time source from audio input node\n")); 890 return B_MEDIA_BAD_NODE; 891 } 892 893 // Apply the time source in effect to our own Node. 894 err = fRoster->SetTimeSourceFor(fRecordNode->Node().node, tsobj->Node().node); 895 if (err < B_OK) { 896 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 897 " couldn't set the sound recorder's time source\n")); 898 tsobj->Release(); 899 return err; 900 } 901 902 // Get a format, any format. 903 fRecordFormat.u.raw_audio = fAudioOutput.format.u.raw_audio; 904 fRecordFormat.type = B_MEDIA_RAW_AUDIO; 905 906 // Tell the consumer where we want data to go. 907 err = fRecordNode->SetHooks(RecordFile, NotifyRecordFile, this); 908 if (err < B_OK) { 909 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 910 " couldn't set the sound recorder's hook functions\n")); 911 tsobj->Release(); 912 return err; 913 } 914 915 // Using the same structs for input and output is OK in 916 // BMediaRoster::Connect(). 917 err = fRoster->Connect(fAudioOutput.source, fRecInput.destination, 918 &fRecordFormat, &fAudioOutput, &fRecInput); 919 if (err < B_OK) { 920 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 921 " failed to connect sound recorder to audio input node.\n")); 922 tsobj->Release(); 923 fRecordNode->SetHooks(0, 0, 0); 924 return err; 925 } 926 927 // Start the time source if it's not running. 928 if ((tsobj->Node() != input) && !tsobj->IsRunning()) 929 fRoster->StartNode(tsobj->Node(), BTimeSource::RealTime()); 930 931 tsobj->Release(); // we're done with this time source instance! 932 return B_OK; 933 } 934 935 936 status_t 937 RecorderWindow::BreakRecordConnection() 938 { 939 status_t err; 940 941 // If we are the last connection, the Node will stop automatically since it 942 // has nowhere to send data to. 943 err = fRoster->StopNode(fRecInput.node, 0); 944 err = fRoster->Disconnect(fAudioOutput.node.node, fAudioOutput.source, 945 fRecInput.node.node, fRecInput.destination); 946 fAudioOutput.source = media_source::null; 947 fRecInput.destination = media_destination::null; 948 return err; 949 } 950 951 952 status_t 953 RecorderWindow::StopRecording() 954 { 955 if (!fRecording) 956 return B_OK; 957 fRecording = false; 958 BreakRecordConnection(); 959 fRecordNode->SetHooks(NULL,NULL,NULL); 960 if (fRecSize > 0) { 961 962 wave_struct header; 963 header.riff.riff_id = FOURCC('R','I','F','F'); 964 header.riff.len = fRecSize + sizeof(header) - 8; 965 header.riff.wave_id = FOURCC('W','A','V','E'); 966 header.format_chunk.fourcc = FOURCC('f','m','t',' '); 967 header.format_chunk.len = sizeof(header.format); 968 header.format.format_tag = 1; 969 header.format.channels = fRecordFormat.u.raw_audio.channel_count; 970 header.format.samples_per_sec = (uint32)fRecordFormat.u.raw_audio.frame_rate; 971 header.format.avg_bytes_per_sec = (uint32)(fRecordFormat.u.raw_audio.frame_rate 972 * fRecordFormat.u.raw_audio.channel_count 973 * (fRecordFormat.u.raw_audio.format & 0xf)); 974 header.format.bits_per_sample = (fRecordFormat.u.raw_audio.format & 0xf) * 8; 975 header.format.block_align = (fRecordFormat.u.raw_audio.format & 0xf) 976 * fRecordFormat.u.raw_audio.channel_count; 977 header.data_chunk.fourcc = FOURCC('d','a','t','a'); 978 header.data_chunk.len = fRecSize; 979 fRecFile.Seek(0, SEEK_SET); 980 fRecFile.Write(&header, sizeof(header)); 981 982 fRecFile.SetSize(fRecSize + sizeof(header)); 983 // We reserve space; make sure we cut off any excess at the end. 984 AddSoundItem(fRecEntry, true); 985 } else 986 fRecEntry.Remove(); 987 988 // We're done for this time. 989 fRecEntry.Unset(); 990 // Close the file. 991 fRecFile.Unset(); 992 // No more recording going on. 993 fRecSize = 0; 994 SetButtonState(btnPaused); 995 fRecordButton->SetStopped(); 996 997 return B_OK; 998 } 999 1000 1001 status_t 1002 RecorderWindow::StopPlaying() 1003 { 1004 if (fPlayer) { 1005 fPlayer->Stop(); 1006 fPlayer->SetCallbacks(0, 0, 0); 1007 fVolumeSlider->SetSoundPlayer(NULL); 1008 delete fPlayer; 1009 fPlayer = NULL; 1010 } 1011 SetButtonState(btnPaused); 1012 fPlayButton->SetStopped(); 1013 fTrackSlider->ResetMainTime(); 1014 fScopeView->SetMainTime(*fTrackSlider->MainTime()); 1015 return B_OK; 1016 } 1017 1018 1019 void 1020 RecorderWindow::SetButtonState(BtnState state) 1021 { 1022 fButtonState = state; 1023 UpdateButtons(); 1024 } 1025 1026 1027 void 1028 RecorderWindow::UpdateButtons() 1029 { 1030 bool hasSelection = (fSoundList->CurrentSelection() >= 0); 1031 fRecordButton->SetEnabled(fButtonState != btnPlaying); 1032 fPlayButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1033 fRewindButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1034 fForwardButton->SetEnabled((fButtonState != btnRecording) && hasSelection); 1035 fStopButton->SetEnabled(fButtonState != btnPaused); 1036 fSaveButton->SetEnabled(hasSelection && (fButtonState != btnRecording)); 1037 fInputField->SetEnabled(fButtonState != btnRecording); 1038 } 1039 1040 #ifndef __HAIKU__ 1041 extern "C" status_t DecodedFormat__11BMediaTrackP12media_format( 1042 BMediaTrack *self, media_format *inout_format); 1043 #endif 1044 1045 1046 status_t 1047 RecorderWindow::UpdatePlayFile(SoundListItem* item, bool updateDisplay) 1048 { 1049 fScopeView->CancelRendering(); 1050 StopPlaying(); 1051 StopRecording(); 1052 1053 if (fPlayTrack && fPlayFile) { 1054 fPlayFile->ReleaseTrack(fPlayTrack); 1055 fPlayTrack = NULL; 1056 } 1057 if (fPlayFile) { 1058 delete fPlayFile; 1059 fPlayFile = NULL; 1060 } 1061 1062 status_t err; 1063 BEntry& entry = item->Entry(); 1064 entry_ref ref; 1065 entry.GetRef(&ref); 1066 fPlayFile = new BMediaFile(&ref); //, B_MEDIA_FILE_UNBUFFERED); 1067 if ((err = fPlayFile->InitCheck()) < B_OK) { 1068 delete fPlayFile; 1069 fPlayFile = NULL; 1070 return err; 1071 } 1072 1073 for (int ix=0; ix < fPlayFile->CountTracks(); ix++) { 1074 BMediaTrack * track = fPlayFile->TrackAt(ix); 1075 fPlayFormat.type = B_MEDIA_RAW_AUDIO; 1076 #ifdef __HAIKU__ 1077 if ((track->DecodedFormat(&fPlayFormat) == B_OK) 1078 #else 1079 if ((DecodedFormat__11BMediaTrackP12media_format(track, &fPlayFormat) == B_OK) 1080 #endif 1081 && (fPlayFormat.type == B_MEDIA_RAW_AUDIO)) { 1082 fPlayTrack = track; 1083 break; 1084 } 1085 if (track) 1086 fPlayFile->ReleaseTrack(track); 1087 } 1088 1089 if (!fPlayTrack) { 1090 delete fPlayFile; 1091 fPlayFile = NULL; 1092 return B_STREAM_NOT_FOUND; 1093 } 1094 1095 if (!updateDisplay) 1096 return B_OK; 1097 1098 BString filename = B_TRANSLATE("File name: "); 1099 filename << ref.name; 1100 fFilename->SetText(filename.String()); 1101 1102 BString format = B_TRANSLATE("Format: "); 1103 media_file_format file_format; 1104 if (fPlayFile->GetFileFormatInfo(&file_format) == B_OK) 1105 format << file_format.short_name; 1106 BString compression = B_TRANSLATE("Compression: "); 1107 media_codec_info codec_info; 1108 if (fPlayTrack->GetCodecInfo(&codec_info) == B_OK) { 1109 if (strcmp(codec_info.short_name, "raw")==0) 1110 compression << B_TRANSLATE("None"); 1111 else 1112 compression << codec_info.short_name; 1113 } 1114 BString channels = B_TRANSLATE("Channels: "); 1115 channels << fPlayFormat.u.raw_audio.channel_count; 1116 BString samplesize = B_TRANSLATE("Sample size: "); 1117 samplesize << 8 * (fPlayFormat.u.raw_audio.format & 0xf) 1118 << B_TRANSLATE(" bits"); 1119 BString samplerate = B_TRANSLATE("Sample rate: "); 1120 samplerate << (int)fPlayFormat.u.raw_audio.frame_rate; 1121 BString durationString = B_TRANSLATE("Duration: "); 1122 bigtime_t duration = fPlayTrack->Duration(); 1123 durationString << (float)(duration / 1000000.0) << B_TRANSLATE(" seconds"); 1124 1125 fFormat->SetText(format.String()); 1126 fCompression->SetText(compression.String()); 1127 fChannels->SetText(channels.String()); 1128 fSampleSize->SetText(samplesize.String()); 1129 fSampleRate->SetText(samplerate.String()); 1130 fDuration->SetText(durationString.String()); 1131 1132 fTrackSlider->SetTotalTime(duration, true); 1133 fScopeView->SetTotalTime(duration, true); 1134 fScopeView->RenderTrack(fPlayTrack, fPlayFormat); 1135 1136 fPlayFrames = fPlayTrack->CountFrames(); 1137 return B_OK; 1138 } 1139 1140 1141 void 1142 RecorderWindow::ErrorAlert(const char * action, status_t err) 1143 { 1144 char msg[300]; 1145 if (err != B_OK) 1146 sprintf(msg, "%s: %s. [%lx]", action, strerror(err), (int32) err); 1147 else 1148 sprintf(msg, "%s.", action); 1149 (new BAlert("", msg, B_TRANSLATE("Stop")))->Go(); 1150 } 1151 1152 1153 status_t 1154 RecorderWindow::NewTempName(char * name) 1155 { 1156 int init_count = fTempCount; 1157 again: 1158 if (fTempCount-init_count > 25) { 1159 return B_ERROR; 1160 } 1161 else { 1162 fTempCount++; 1163 if (fTempCount==0) 1164 sprintf(name, "Audio Clip"); 1165 else 1166 sprintf(name, "Audio Clip %d", fTempCount); 1167 BPath path; 1168 status_t err; 1169 BEntry tempEnt; 1170 if ((err = fTempDir.GetEntry(&tempEnt)) < B_OK) { 1171 return err; 1172 } 1173 if ((err = tempEnt.GetPath(&path)) < B_OK) { 1174 return err; 1175 } 1176 path.Append(name); 1177 int fd; 1178 // Use O_EXCL so we know we created the file (sync with other instances) 1179 if ((fd = open(path.Path(), O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) { 1180 goto again; 1181 } 1182 close(fd); 1183 } 1184 return B_OK; 1185 } 1186 1187 1188 void 1189 RecorderWindow::AddSoundItem(const BEntry& entry, bool temp) 1190 { 1191 // Create list item to display. 1192 SoundListItem * listItem = new SoundListItem(entry, temp); 1193 fSoundList->AddItem(listItem); 1194 fSoundList->Invalidate(); 1195 fSoundList->Select(fSoundList->IndexOf(listItem)); 1196 } 1197 1198 1199 void 1200 RecorderWindow::RemoveCurrentSoundItem() { 1201 int32 index = fSoundList->CurrentSelection(); 1202 BListItem *item = fSoundList->RemoveItem(index); 1203 delete item; 1204 if (index >= fSoundList->CountItems()) 1205 index = fSoundList->CountItems() - 1; 1206 fSoundList->Select(index); 1207 } 1208 1209 1210 void 1211 RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp, 1212 void* data, size_t size, const media_raw_audio_format &format) 1213 { 1214 // Callback called from the SoundConsumer when receiving buffers. 1215 RecorderWindow * window = (RecorderWindow *)cookie; 1216 1217 if (window->fRecording) { 1218 // Write the data to file (we don't buffer or guard file access 1219 // or anything) 1220 window->fRecFile.WriteAt(window->fRecSize, data, size); 1221 window->fVUView->ComputeLevels(data, size, format.format); 1222 window->fRecSize += size; 1223 } 1224 } 1225 1226 1227 void 1228 RecorderWindow::NotifyRecordFile(void * cookie, int32 code, ...) 1229 { 1230 if ((code == B_WILL_STOP) || (code == B_NODE_DIES)) { 1231 RecorderWindow * window = (RecorderWindow *)cookie; 1232 // Tell the window we've stopped, if it doesn't 1233 // already know. 1234 window->PostMessage(STOP_RECORDING); 1235 } 1236 } 1237 1238 1239 void 1240 RecorderWindow::PlayFile(void * cookie, void * data, size_t size, 1241 const media_raw_audio_format & format) 1242 { 1243 // Callback called from the SoundProducer when producing buffers. 1244 RecorderWindow * window = (RecorderWindow *)cookie; 1245 int32 frame_size = (window->fPlayFormat.u.raw_audio.format & 0xf) * 1246 window->fPlayFormat.u.raw_audio.channel_count; 1247 1248 if ((window->fPlayFrame < window->fPlayLimit) || window->fLooping) { 1249 if (window->fPlayFrame >= window->fPlayLimit) { 1250 bigtime_t left = window->fTrackSlider->LeftTime(); 1251 window->fPlayTrack->SeekToTime(&left); 1252 window->fPlayFrame = window->fPlayTrack->CurrentFrame(); 1253 } 1254 int64 frames = 0; 1255 window->fPlayTrack->ReadFrames(data, &frames); 1256 window->fVUView->ComputeLevels(data, size / frame_size, format.format); 1257 window->fPlayFrame += size/frame_size; 1258 window->PostMessage(UPDATE_TRACKSLIDER); 1259 } else { 1260 // we're done! 1261 window->PostMessage(STOP_PLAYING); 1262 } 1263 } 1264 1265 1266 void 1267 RecorderWindow::NotifyPlayFile(void * cookie, 1268 BSoundPlayer::sound_player_notification code, ...) 1269 { 1270 if ((code == BSoundPlayer::B_STOPPED) || (code == BSoundPlayer::B_SOUND_DONE)) { 1271 RecorderWindow * window = (RecorderWindow *)cookie; 1272 // tell the window we've stopped, if it doesn't 1273 // already know. 1274 window->PostMessage(STOP_PLAYING); 1275 } 1276 } 1277 1278 1279 void 1280 RecorderWindow::RefsReceived(BMessage *msg) 1281 { 1282 entry_ref ref; 1283 int32 i = 0; 1284 int32 countGood = 0; 1285 int32 countBad = 0; 1286 1287 while (msg->FindRef("refs", i++, &ref) == B_OK) { 1288 1289 BEntry entry(&ref, true); 1290 BPath path(&entry); 1291 BNode node(&entry); 1292 1293 if (node.IsFile()) { 1294 SoundListItem * listItem = new SoundListItem(entry, false); 1295 if (UpdatePlayFile(listItem) == B_OK) { 1296 fSoundList->AddItem(listItem); 1297 countGood++; 1298 continue; 1299 } 1300 delete listItem; 1301 } else if(node.IsDirectory()) { 1302 1303 } 1304 countBad++; 1305 } 1306 1307 if (countBad > 0 && countGood == 0) { 1308 (new BAlert(B_TRANSLATE("Nothing to play"), B_TRANSLATE("None of the " 1309 "files appear to be audio files"), B_TRANSLATE("OK"), NULL, NULL, 1310 B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(); 1311 } else if (countGood > 0) { 1312 if (countBad > 0) 1313 (new BAlert(B_TRANSLATE("Invalid audio files"), B_TRANSLATE("Some " 1314 "of the files don't appear to be audio files"), 1315 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1316 B_WARNING_ALERT))->Go(); 1317 fSoundList->Select(fSoundList->CountItems() - 1); 1318 } 1319 } 1320 1321 1322 void 1323 RecorderWindow::CopyTarget(BMessage *msg) 1324 { 1325 const char *type = NULL; 1326 if (msg->FindString("be:types", &type) == B_OK) { 1327 if (!strcasecmp(type, B_FILE_MIME_TYPE)) { 1328 const char *name; 1329 entry_ref dir; 1330 if (msg->FindString("be:filetypes") == B_OK 1331 && msg->FindString("name", &name) == B_OK 1332 && msg->FindRef("directory", &dir) == B_OK) { 1333 BDirectory directory(&dir); 1334 BFile file(&directory, name, O_RDWR | O_TRUNC); 1335 1336 // seek time 1337 bigtime_t start = fTrackSlider->LeftTime(); 1338 1339 // write data 1340 bigtime_t diffTime = fTrackSlider->RightTime() 1341 - fTrackSlider->LeftTime(); 1342 int64 framesToWrite = (int64) (diffTime 1343 * fPlayFormat.u.raw_audio.frame_rate / 1000000LL); 1344 int32 frameSize = (fPlayFormat.u.raw_audio.format & 0xf) 1345 * fPlayFormat.u.raw_audio.channel_count; 1346 1347 wave_struct header; 1348 header.riff.riff_id = FOURCC('R','I','F','F'); 1349 header.riff.len 1350 = (frameSize * framesToWrite) + sizeof(header) - 8; 1351 header.riff.wave_id = FOURCC('W','A','V','E'); 1352 header.format_chunk.fourcc = FOURCC('f','m','t',' '); 1353 header.format_chunk.len = sizeof(header.format); 1354 header.format.format_tag = 1; 1355 header.format.channels = fPlayFormat.u.raw_audio.channel_count; 1356 header.format.samples_per_sec 1357 = (uint32)fPlayFormat.u.raw_audio.frame_rate; 1358 header.format.avg_bytes_per_sec 1359 = (uint32)(fPlayFormat.u.raw_audio.frame_rate 1360 * fPlayFormat.u.raw_audio.channel_count 1361 * (fPlayFormat.u.raw_audio.format & 0xf)); 1362 header.format.bits_per_sample 1363 = (fPlayFormat.u.raw_audio.format & 0xf) * 8; 1364 header.format.block_align = frameSize; 1365 header.data_chunk.fourcc = FOURCC('d','a','t','a'); 1366 header.data_chunk.len = frameSize * framesToWrite; 1367 file.Seek(0, SEEK_SET); 1368 file.Write(&header, sizeof(header)); 1369 1370 char *data = (char *)malloc(fPlayFormat.u.raw_audio.buffer_size); 1371 1372 fPlayTrack->SeekToTime(&start); 1373 fPlayFrame = fPlayTrack->CurrentFrame(); 1374 while (framesToWrite > 0) { 1375 int64 frames = 0; 1376 status_t err = fPlayTrack->ReadFrames(data, &frames); 1377 if (frames <= 0 || err != B_OK) { 1378 if (err != B_OK) 1379 fprintf(stderr, "CopyTarget: ReadFrames failed\n"); 1380 break; 1381 } 1382 file.Write(data, frames * frameSize); 1383 framesToWrite -= frames; 1384 } 1385 1386 file.Sync(); 1387 free(data); 1388 BNodeInfo nodeInfo(&file); 1389 // set type 1390 } 1391 } else { 1392 1393 } 1394 } 1395 } 1396