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