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