2 * Copyright 2014, Dario Casalinuovo. All rights reserved.
3 * Copyright 2005, Jérôme Duval. All rights reserved.
4 * Distributed under the terms of the MIT License.
6 * Inspired by SoundCapture from Be newsletter (Media Kit Basics:
7 * Consumers and Producers)
10 #include <Application.h>
16 #include <TextControl.h>
17 #include <MenuField.h>
18 #include <PopUpMenu.h>
21 #include <ScrollView.h>
23 #include <StringView.h>
29 #include <FindDirectory.h>
30 #include <MediaAddOn.h>
32 #include <SoundPlayer.h>
42 #include <MediaRoster.h>
43 #include <TimeSource.h>
46 #include "RecorderWindow.h"
47 #include "FileUtils.h"
50 #define FPRINTF(args) fprintf args
56 #define CONNECT FPRINTF
57 #define WINDOW FPRINTF
59 #undef B_TRANSLATION_CONTEXT
60 #define B_TRANSLATION_CONTEXT "RecorderWindow"
63 // default window positioning
64 static const float MIN_WIDTH
= 400.0f
;
65 static const float MIN_HEIGHT
= 175.0f
;
66 static const float XPOS
= 100.0f
;
67 static const float YPOS
= 200.0f
;
69 #define FOURCC(a,b,c,d) ((((uint32)(d)) << 24) | (((uint32)(c)) << 16) \
70 | (((uint32)(b)) << 8) | ((uint32)(a)))
74 uint32 riff_id
; // 'RIFF'
76 uint32 wave_id
; // 'WAVE'
89 uint32 samples_per_sec
;
90 uint32 avg_bytes_per_sec
;
92 uint16 bits_per_sample
;
98 struct riff_struct riff
;
99 struct chunk_struct format_chunk
;
100 struct format_struct format
;
101 struct chunk_struct data_chunk
;
105 RecorderWindow::RecorderWindow()
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
),
120 fRecordButton
= NULL
;
128 fExternalConnection
= false;
130 fButtonState
= btnPaused
;
132 CalcSizes(MIN_WIDTH
, MIN_HEIGHT
);
134 fInitCheck
= InitWindow();
135 if (fInitCheck
!= B_OK
) {
136 if (fInitCheck
== B_NAME_NOT_FOUND
)
137 ErrorAlert(B_TRANSLATE("Cannot find default audio hardware"),
140 ErrorAlert(B_TRANSLATE("Cannot connect to media server"),
142 PostMessage(B_QUIT_REQUESTED
);
148 RecorderWindow::~RecorderWindow()
150 // The MediaRecorder have to be deleted, the dtor
151 // disconnect it from the media_kit.
156 if (fPlayTrack
&& fPlayFile
)
157 fPlayFile
->ReleaseTrack(fPlayTrack
);
164 // Clean up items in list view.
166 fSoundList
->DeselectAll();
167 for (int i
= 0; i
< fSoundList
->CountItems(); i
++) {
168 WINDOW((stderr
, "clean up item %d\n", i
+1));
169 SoundListItem
* item
= dynamic_cast<SoundListItem
*>(fSoundList
->ItemAt(i
));
172 item
->Entry().Remove(); // delete temp file
176 fSoundList
->MakeEmpty();
178 // Clean up currently recording file, if any.
187 RecorderWindow::InitCheck()
194 RecorderWindow::CalcSizes(float min_width
, float min_height
)
196 // Set up size limits based on new screen size
197 BScreen
screen(this);
198 BRect rect
= screen
.Frame();
199 float width
= rect
.Width() - 12;
200 SetSizeLimits(min_width
, width
, min_height
, rect
.Height() - 24);
202 // Don't zoom to cover all of screen; user can resize last bit if necessary.
203 // This leaves other windows visible.
205 width
= 640 + (width
- 640) / 2;
206 SetZoomLimits(width
, rect
.Height() - 24);
211 RecorderWindow::InitWindow()
213 BPopUpMenu
* popup
= 0;
217 // Find temp directory for recorded sounds.
219 if (!(error
= find_directory(B_SYSTEM_TEMP_DIRECTORY
, &path
)))
220 error
= fTempDir
.SetTo(path
.Path());
224 // Make sure the media roster is there (which means the server is there).
225 fRoster
= BMediaRoster::Roster(&error
);
229 error
= fRoster
->GetAudioInput(&fAudioInputNode
);
230 if (error
< B_OK
) // there's no input?
233 error
= fRoster
->GetAudioMixer(&fAudioMixerNode
);
234 if (error
< B_OK
) // there's no mixer?
237 fRecorder
= new BMediaRecorder("Sound Recorder",
240 if (fRecorder
->InitCheck() < B_OK
)
243 // Set the node to accept only audio data
244 media_format output_format
;
245 output_format
.type
= B_MEDIA_RAW_AUDIO
;
246 output_format
.u
.raw_audio
= media_raw_audio_format::wildcard
;
247 fRecorder
->SetAcceptedFormat(output_format
);
249 // Create the window header with controls
251 r
.bottom
= r
.top
+ 175;
252 BBox
*background
= new BBox(r
, "_background",
253 B_FOLLOW_LEFT_RIGHT
| B_FOLLOW_TOP
, B_WILL_DRAW
254 | B_FRAME_EVENTS
| B_NAVIGABLE_JUMP
, B_NO_BORDER
);
256 AddChild(background
);
258 r
= background
->Bounds();
260 r
.right
= r
.left
+ 38;
261 r
.bottom
= r
.top
+ 104;
262 fVUView
= new VUView(r
, B_FOLLOW_LEFT
|B_FOLLOW_TOP
);
263 background
->AddChild(fVUView
);
265 r
= background
->Bounds();
266 r
.left
= r
.left
+ 40;
267 r
.bottom
= r
.top
+ 104;
268 fScopeView
= new ScopeView(r
, B_FOLLOW_LEFT_RIGHT
|B_FOLLOW_TOP
);
269 background
->AddChild(fScopeView
);
271 r
= background
->Bounds();
275 r
.bottom
= r
.top
+ 30;
276 fTrackSlider
= new TrackSlider(r
, "trackSlider", new BMessage(POSITION_CHANGED
),
277 B_FOLLOW_LEFT_RIGHT
| B_FOLLOW_TOP
);
278 background
->AddChild(fTrackSlider
);
282 // Button for rewinding
283 buttonRect
= BRect(BPoint(0,0), kSkipButtonSize
);
284 buttonRect
.OffsetTo(background
->Bounds().LeftBottom() - BPoint(-7, 25));
285 fRewindButton
= new TransportButton(buttonRect
, B_TRANSLATE("Rewind"),
286 kSkipBackBitmapBits
, kPressedSkipBackBitmapBits
,
287 kDisabledSkipBackBitmapBits
, new BMessage(REWIND
));
288 background
->AddChild(fRewindButton
);
290 // Button for stopping recording or playback
291 buttonRect
= BRect(BPoint(0,0), kStopButtonSize
);
292 buttonRect
.OffsetTo(background
->Bounds().LeftBottom() - BPoint(-48, 25));
293 fStopButton
= new TransportButton(buttonRect
, B_TRANSLATE("Stop"),
294 kStopButtonBitmapBits
, kPressedStopButtonBitmapBits
,
295 kDisabledStopButtonBitmapBits
, new BMessage(STOP
));
296 background
->AddChild(fStopButton
);
298 // Button for starting playback of selected sound
299 BRect
playRect(BPoint(0,0), kPlayButtonSize
);
300 playRect
.OffsetTo(background
->Bounds().LeftBottom() - BPoint(-82, 25));
301 fPlayButton
= new PlayPauseButton(playRect
, B_TRANSLATE("Play"),
302 new BMessage(PLAY
), new BMessage(PLAY_PERIOD
), ' ', 0);
303 background
->AddChild(fPlayButton
);
305 // Button for forwarding
306 buttonRect
= BRect(BPoint(0,0), kSkipButtonSize
);
307 buttonRect
.OffsetTo(background
->Bounds().LeftBottom() - BPoint(-133, 25));
308 fForwardButton
= new TransportButton(buttonRect
, B_TRANSLATE("Forward"),
309 kSkipForwardBitmapBits
, kPressedSkipForwardBitmapBits
,
310 kDisabledSkipForwardBitmapBits
, new BMessage(FORWARD
));
311 background
->AddChild(fForwardButton
);
313 // Button to start recording (or waiting for sound)
314 buttonRect
= BRect(BPoint(0,0), kRecordButtonSize
);
315 buttonRect
.OffsetTo(background
->Bounds().LeftBottom() - BPoint(-174, 25));
316 fRecordButton
= new RecordButton(buttonRect
, B_TRANSLATE("Record"),
317 new BMessage(RECORD
), new BMessage(RECORD_PERIOD
));
318 background
->AddChild(fRecordButton
);
320 // Button for saving selected sound
321 buttonRect
= BRect(BPoint(0,0), kDiskButtonSize
);
322 buttonRect
.OffsetTo(background
->Bounds().LeftBottom() - BPoint(-250, 21));
323 fSaveButton
= new TransportButton(buttonRect
, B_TRANSLATE("Save"),
324 kDiskButtonBitmapsBits
, kPressedDiskButtonBitmapsBits
,
325 kDisabledDiskButtonBitmapsBits
, new BMessage(SAVE
));
326 fSaveButton
->SetResizingMode(B_FOLLOW_RIGHT
| B_FOLLOW_TOP
);
327 background
->AddChild(fSaveButton
);
330 buttonRect
= BRect(BPoint(0,0), kArrowSize
);
331 buttonRect
.OffsetTo(background
->Bounds().RightBottom() - BPoint(23, 48));
332 fLoopButton
= new DrawButton(buttonRect
, B_TRANSLATE("Loop"),
333 kLoopArrowBits
, kArrowBits
, new BMessage(LOOP
));
334 fLoopButton
->SetResizingMode(B_FOLLOW_RIGHT
| B_FOLLOW_TOP
);
335 fLoopButton
->SetTarget(this);
336 background
->AddChild(fLoopButton
);
338 buttonRect
= BRect(BPoint(0,0), kSpeakerIconBitmapSize
);
339 buttonRect
.OffsetTo(background
->Bounds().RightBottom() - BPoint(121, 17));
340 SpeakerView
*speakerView
= new SpeakerView(buttonRect
,
341 B_FOLLOW_LEFT
| B_FOLLOW_TOP
);
342 speakerView
->SetResizingMode(B_FOLLOW_RIGHT
| B_FOLLOW_TOP
);
343 background
->AddChild(speakerView
);
345 buttonRect
= BRect(BPoint(0,0), BPoint(84, 19));
346 buttonRect
.OffsetTo(background
->Bounds().RightBottom() - BPoint(107, 20));
347 fVolumeSlider
= new VolumeSlider(buttonRect
, "volumeSlider",
348 B_FOLLOW_LEFT
| B_FOLLOW_TOP
);
349 fVolumeSlider
->SetResizingMode(B_FOLLOW_RIGHT
| B_FOLLOW_TOP
);
350 background
->AddChild(fVolumeSlider
);
352 // Button to mask/see sounds list
353 buttonRect
= BRect(BPoint(0,0), kUpDownButtonSize
);
354 buttonRect
.OffsetTo(background
->Bounds().RightBottom() - BPoint(21, 25));
355 fUpDownButton
= new UpDownButton(buttonRect
, new BMessage(VIEW_LIST
));
356 fUpDownButton
->SetResizingMode(B_FOLLOW_RIGHT
| B_FOLLOW_TOP
);
357 background
->AddChild(fUpDownButton
);
360 r
.top
= background
->Bounds().bottom
+ 1;
361 fBottomBox
= new BBox(r
, "bottomBox", B_FOLLOW_ALL
);
362 fBottomBox
->SetBorder(B_NO_BORDER
);
363 AddChild(fBottomBox
);
365 // The actual list of recorded sounds (initially empty) sits
366 // below the header with the controls.
367 r
= fBottomBox
->Bounds();
372 r
.right
-= B_V_SCROLL_BAR_WIDTH
;
374 fSoundList
= new SoundListView(r
, B_TRANSLATE("Sound List"),
376 fSoundList
->SetSelectionMessage(new BMessage(SOUND_SELECTED
));
377 fSoundList
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
378 BScrollView
*scroller
= new BScrollView("scroller", fSoundList
,
379 B_FOLLOW_ALL
, 0, false, true, B_FANCY_BORDER
);
380 fBottomBox
->AddChild(scroller
);
382 r
= fBottomBox
->Bounds();
383 r
.right
= r
.left
+ 190;
387 fFileInfoBox
= new BBox(r
, "fileinfo", B_FOLLOW_LEFT
);
388 fFileInfoBox
->SetLabel(B_TRANSLATE("File info"));
390 fFileInfoBox
->SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
392 BFont font
= be_plain_font
;
393 font
.SetSize(font
.Size() * 0.92f
);
395 font
.GetHeight(&height
);
396 float fontHeight
= height
.ascent
+ height
.leading
+ height
.descent
;
398 r
= fFileInfoBox
->Bounds();
400 r
.top
= fontHeight
+ 6;
401 r
.bottom
= r
.top
+ fontHeight
+ 3;
403 fFilename
= new BStringView(r
, "filename", B_TRANSLATE("File name:"));
404 fFileInfoBox
->AddChild(fFilename
);
405 fFilename
->SetFont(&font
, B_FONT_SIZE
);
406 fFilename
->SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
409 r
.bottom
= r
.top
+ fontHeight
+ 3;
410 fFormat
= new BStringView(r
, "format", B_TRANSLATE("Format:"));
411 fFileInfoBox
->AddChild(fFormat
);
412 fFormat
->SetFont(&font
, B_FONT_SIZE
);
413 fFormat
->SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
416 r
.bottom
= r
.top
+ fontHeight
+ 3;
417 fCompression
= new BStringView(r
, "compression",
418 B_TRANSLATE("Compression:"));
419 fFileInfoBox
->AddChild(fCompression
);
420 fCompression
->SetFont(&font
, B_FONT_SIZE
);
421 fCompression
->SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
424 r
.bottom
= r
.top
+ fontHeight
+ 3;
425 fChannels
= new BStringView(r
, "channels", B_TRANSLATE("Channels:"));
426 fFileInfoBox
->AddChild(fChannels
);
427 fChannels
->SetFont(&font
, B_FONT_SIZE
);
428 fChannels
->SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
431 r
.bottom
= r
.top
+ fontHeight
+ 3;
432 fSampleSize
= new BStringView(r
, "samplesize",
433 B_TRANSLATE("Sample size:"));
434 fFileInfoBox
->AddChild(fSampleSize
);
435 fSampleSize
->SetFont(&font
, B_FONT_SIZE
);
436 fSampleSize
->SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
439 r
.bottom
= r
.top
+ fontHeight
+ 3;
440 fSampleRate
= new BStringView(r
, "samplerate",
441 B_TRANSLATE("Sample rate:"));
442 fFileInfoBox
->AddChild(fSampleRate
);
443 fSampleRate
->SetFont(&font
, B_FONT_SIZE
);
444 fSampleRate
->SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
447 r
.bottom
= r
.top
+ fontHeight
+ 3;
448 fDuration
= new BStringView(r
, "duration", B_TRANSLATE("Duration:"));
449 fFileInfoBox
->AddChild(fDuration
);
450 fDuration
->SetFont(&font
, B_FONT_SIZE
);
451 fDuration
->SetHighColor(ui_color(B_PANEL_TEXT_COLOR
));
453 fFileInfoBox
->ResizeTo(fFileInfoBox
->Frame().Width(),
454 r
.bottom
+ fontHeight
/ 2.0f
);
455 fDeployedHeight
= MIN_HEIGHT
+ fFileInfoBox
->Bounds().Height() + 40.0f
;
457 // Input selection lists all available physical inputs that produce
458 // buffers with B_MEDIA_RAW_AUDIO format data.
459 popup
= new BPopUpMenu(B_TRANSLATE("Input"));
460 const int maxInputCount
= 64;
461 dormant_node_info dni
[maxInputCount
];
463 int32 real_count
= maxInputCount
;
465 error
= fRoster
->GetDormantNodes(dni
, &real_count
, 0, &output_format
,
466 0, B_BUFFER_PRODUCER
| B_PHYSICAL_INPUT
);
467 if (real_count
> maxInputCount
) {
468 WINDOW((stderr
, "dropped %" B_PRId32
" inputs\n", real_count
- maxInputCount
));
469 real_count
= maxInputCount
;
471 char selected_name
[B_MEDIA_NAME_LENGTH
] = "Default input";
474 for (int i
= 0; i
< real_count
; i
++) {
475 msg
= new BMessage(INPUT_SELECTED
);
476 msg
->AddData("node", B_RAW_TYPE
, &dni
[i
], sizeof(dni
[i
]));
477 item
= new BMenuItem(dni
[i
].name
, msg
);
478 popup
->AddItem(item
);
479 media_node_id ni
[12];
481 error
= fRoster
->GetInstancesFor(dni
[i
].addon
, dni
[i
].flavor_id
,
484 for (int j
= 0; j
< ni_count
; j
++) {
485 if (ni
[j
] == fAudioInputNode
.node
) {
486 strcpy(selected_name
, dni
[i
].name
);
493 // Create the actual widget
494 r
= fFileInfoBox
->Bounds();
495 r
.top
= r
.bottom
+ 2;
496 r
.bottom
= r
.top
+ 18;
498 fInputField
= new BMenuField(r
, "Input", B_TRANSLATE("Input:"), popup
);
499 fInputField
->SetDivider(fInputField
->StringWidth(B_TRANSLATE("Input:"))
501 fBottomBox
->AddChild(fInputField
);
503 fBottomBox
->AddChild(fFileInfoBox
);
506 CalcSizes(MIN_WIDTH
, MIN_HEIGHT
);
507 ResizeTo(Frame().Width(), MIN_HEIGHT
);
509 popup
->Superitem()->SetLabel(selected_name
);
511 // Make sure the save panel is happy.
512 fSavePanel
= new BFilePanel(B_SAVE_PANEL
);
513 fSavePanel
->SetTarget(this);
536 RecorderWindow::QuitRequested() // this means Close pressed
540 be_app
->PostMessage(B_QUIT_REQUESTED
);
546 RecorderWindow::MessageReceived(BMessage
* message
)
548 // Your average generic message dispatching switch() statement.
549 switch (message
->what
) {
564 if (fPlayer
->HasData())
565 fPlayButton
->SetPlaying();
567 fPlayButton
->SetPaused();
571 fRecordButton
->SetRecording();
585 case B_SAVE_REQUESTED
:
589 if (fUpDownButton
->Value() == B_CONTROL_ON
) {
591 CalcSizes(MIN_WIDTH
, fDeployedHeight
);
592 ResizeTo(Frame().Width(), fDeployedHeight
);
595 CalcSizes(MIN_WIDTH
, MIN_HEIGHT
);
596 ResizeTo(Frame().Width(), MIN_HEIGHT
);
600 case UPDATE_TRACKSLIDER
:
602 bigtime_t timestamp
= fPlayTrack
->CurrentTime();
603 fTrackSlider
->SetMainTime(timestamp
, false);
604 fScopeView
->SetMainTime(timestamp
);
607 case POSITION_CHANGED
:
609 bigtime_t right
, left
, main
;
610 if (message
->FindInt64("main", &main
) == B_OK
) {
612 fPlayTrack
->SeekToTime(fTrackSlider
->MainTime());
613 fPlayFrame
= fPlayTrack
->CurrentFrame();
615 fScopeView
->SetMainTime(main
);
617 if (message
->FindInt64("right", &right
) == B_OK
) {
619 fPlayLimit
= MIN(fPlayFrames
,
620 (off_t
)(right
* fPlayFormat
.u
.raw_audio
.frame_rate
623 fScopeView
->SetRightTime(right
);
625 if (message
->FindInt64("left", &left
) == B_OK
)
626 fScopeView
->SetLeftTime(left
);
630 fLooping
= fLoopButton
->ButtonState();
633 case B_REFS_RECEIVED
:
635 RefsReceived(message
);
642 BWindow::MessageReceived(message
);
649 RecorderWindow::Record(BMessage
* message
)
651 // User pressed Record button
653 if (fButtonState
!= btnPaused
) {
655 return; // user is too fast on the mouse
657 SetButtonState(btnRecording
);
658 fRecordButton
->SetRecording();
661 // Create a file with a temporary name
662 status_t err
= NewTempName(name
);
664 ErrorAlert(B_TRANSLATE("Cannot find an unused name to use for the "
665 "new recording"), err
);
668 // Find the file so we can refer to it later
669 err
= fTempDir
.FindEntry(name
, &fRecEntry
);
671 ErrorAlert(B_TRANSLATE("Cannot find the temporary file created to "
672 "hold the new recording"), err
);
675 err
= fRecFile
.SetTo(&fTempDir
, name
, O_RDWR
);
677 ErrorAlert(B_TRANSLATE("Cannot open the temporary file created to "
678 "hold the new recording"), err
);
682 // Reserve space on disk (creates fewer fragments)
683 err
= fRecFile
.SetSize(4 * fRecordFormat
.u
.raw_audio
.channel_count
684 * fRecordFormat
.u
.raw_audio
.frame_rate
685 * (fRecordFormat
.u
.raw_audio
.format
686 & media_raw_audio_format::B_AUDIO_SIZE_MASK
));
688 ErrorAlert(B_TRANSLATE("Cannot record a sound that long"), err
);
695 fRecFile
.Seek(sizeof(struct wave_struct
), SEEK_SET
);
698 err
= MakeRecordConnection(fAudioInputNode
);
700 ErrorAlert(B_TRANSLATE("Cannot connect to the selected sound input"),
711 RecorderWindow::Play(BMessage
* message
)
714 // User pressed Play button and playing
715 if (fPlayer
->HasData())
716 fPlayButton
->SetPaused();
718 fPlayButton
->SetPlaying();
719 fPlayer
->SetHasData(!fPlayer
->HasData());
723 SetButtonState(btnPlaying
);
724 fPlayButton
->SetPlaying();
727 ErrorAlert(B_TRANSLATE("Cannot get the file to play"), B_ERROR
);
731 fPlayLimit
= MIN(fPlayFrames
, (off_t
)(fTrackSlider
->RightTime()
732 * fPlayFormat
.u
.raw_audio
.frame_rate
/ 1000000LL));
733 fPlayTrack
->SeekToTime(fTrackSlider
->MainTime());
734 fPlayFrame
= fPlayTrack
->CurrentFrame();
736 // Create our internal Node which plays sound, and register it.
737 fPlayer
= new BSoundPlayer(fAudioMixerNode
, &fPlayFormat
.u
.raw_audio
,
739 status_t err
= fPlayer
->InitCheck();
743 fVolumeSlider
->SetSoundPlayer(fPlayer
);
744 fPlayer
->SetCallbacks(PlayFile
, NotifyPlayFile
, this);
746 // And get it going...
748 fPlayer
->SetHasData(true);
753 RecorderWindow::Stop(BMessage
* message
)
755 // User pressed Stop button.
764 RecorderWindow::Save(BMessage
* message
)
766 // User pressed Save button.
767 // Find the item to save.
768 int32 index
= fSoundList
->CurrentSelection();
769 SoundListItem
* pItem
= dynamic_cast<SoundListItem
*>(fSoundList
->ItemAt(index
));
770 if ((! pItem
) || (pItem
->Entry().InitCheck() != B_OK
))
773 // Update the save panel and show it.
774 char filename
[B_FILE_NAME_LENGTH
];
775 pItem
->Entry().GetName(filename
);
776 BMessage
saveMsg(B_SAVE_REQUESTED
);
778 pItem
->Entry().GetRef(&ref
);
780 if (saveMsg
.AddPointer("sound list item", pItem
) != B_OK
)
781 fprintf(stderr
, "failed to add pItem\n");
782 fSavePanel
->SetSaveText(filename
);
783 fSavePanel
->SetMessage(&saveMsg
);
789 RecorderWindow::DoSave(BMessage
* message
)
791 // User picked a place to put the file.
792 // Find the location of the old (e.g.
793 // temporary file), and the name of the
795 entry_ref old_ref
, new_dir_ref
;
796 const char* new_name
;
797 SoundListItem
* pItem
;
799 if ((message
->FindPointer("sound list item", (void**) &pItem
) == B_OK
)
800 && (message
->FindRef("directory", &new_dir_ref
) == B_OK
)
801 && (message
->FindString("name", &new_name
) == B_OK
)) {
802 BEntry
& oldEntry
= pItem
->Entry();
803 BFile
oldFile(&oldEntry
, B_READ_WRITE
);
804 if (oldFile
.InitCheck() != B_OK
)
807 BDirectory
newDir(&new_dir_ref
);
808 if (newDir
.InitCheck() != B_OK
)
812 newDir
.CreateFile(new_name
, &newFile
);
814 if (newFile
.InitCheck() != B_OK
)
817 status_t err
= CopyFile(newFile
, oldFile
);
820 // clean up the sound list and item
822 oldEntry
.Remove(); // blows away temp file!
823 oldEntry
.SetTo(&newDir
, new_name
);
824 pItem
->SetTemp(false); // don't blow the new entry away when we exit!
825 fSoundList
->Invalidate();
828 WINDOW((stderr
, "Couldn't save file.\n"));
834 RecorderWindow::Input(BMessage
* message
)
836 // User selected input from pop-up
837 const dormant_node_info
* dni
= 0;
839 if (message
->FindData("node", B_RAW_TYPE
, (const void **)&dni
, &size
))
840 return; // bad input selection message
842 media_node_id node_id
;
843 status_t error
= fRoster
->GetInstancesFor(dni
->addon
, dni
->flavor_id
, &node_id
);
845 fRoster
->InstantiateDormantNode(*dni
, &fAudioInputNode
);
847 fRoster
->GetNodeFor(node_id
, &fAudioInputNode
);
852 RecorderWindow::Selected(BMessage
* message
)
854 // User selected a sound in list view
855 int32 selIdx
= fSoundList
->CurrentSelection();
856 SoundListItem
* pItem
= dynamic_cast<SoundListItem
*>(fSoundList
->ItemAt(selIdx
));
859 status_t err
= UpdatePlayFile(pItem
, true);
861 ErrorAlert(B_TRANSLATE("Cannot recognize this file as a media file"),
862 err
== B_MEDIA_NO_HANDLER
? B_OK
: err
);
863 RemoveCurrentSoundItem();
870 RecorderWindow::MakeRecordConnection(const media_node
& input
)
872 CONNECT((stderr
, "RecorderWindow::MakeRecordConnection()\n"));
875 media_output audioOutput
;
877 if (!fRecorder
->IsConnected()) {
878 // Find an available output for the given input node.
880 err
= fRoster
->GetFreeOutputsFor(input
, &audioOutput
, 1,
881 &count
, B_MEDIA_RAW_AUDIO
);
884 CONNECT((stderr
, "RecorderWindow::MakeRecordConnection():"
885 " couldn't get free outputs from audio input node\n"));
890 CONNECT((stderr
, "RecorderWindow::MakeRecordConnection():"
891 " no free outputs from audio input node\n"));
895 // Get a format, any format.
896 fRecordFormat
.u
.raw_audio
= audioOutput
.format
.u
.raw_audio
;
897 fExternalConnection
= false;
899 fRecordFormat
.u
.raw_audio
= fRecorder
->AcceptedFormat().u
.raw_audio
;
900 fExternalConnection
= true;
903 fRecordFormat
.type
= B_MEDIA_RAW_AUDIO
;
905 // Tell the consumer where we want data to go.
906 err
= fRecorder
->SetHooks(RecordFile
, NotifyRecordFile
, this);
909 CONNECT((stderr
, "RecorderWindow::MakeRecordConnection():"
910 " couldn't set the sound recorder's hook functions\n"));
914 if (!fRecorder
->IsConnected()) {
916 err
= fRecorder
->Connect(input
, &audioOutput
, &fRecordFormat
);
919 CONNECT((stderr
, "RecorderWindow::MakeRecordConnection():"
920 " failed to connect sound recorder to audio input node.\n"));
922 fRecorder
->SetHooks(NULL
, NULL
, NULL
);
932 RecorderWindow::BreakRecordConnection()
934 return fRecorder
->Disconnect();
939 RecorderWindow::StopRecording()
946 err
= fRecorder
->Stop(true);
950 // We maintain the connection active
951 // if the user connected us from Cortex.
952 if (!fExternalConnection
) {
953 BreakRecordConnection();
956 fRecorder
->SetHooks(NULL
, NULL
, NULL
);
961 header
.riff
.riff_id
= FOURCC('R','I','F','F');
962 header
.riff
.len
= fRecSize
+ sizeof(header
) - 8;
963 header
.riff
.wave_id
= FOURCC('W','A','V','E');
964 header
.format_chunk
.fourcc
= FOURCC('f','m','t',' ');
965 header
.format_chunk
.len
= sizeof(header
.format
);
966 header
.format
.format_tag
= 1;
967 header
.format
.channels
= fRecordFormat
.u
.raw_audio
.channel_count
;
968 header
.format
.samples_per_sec
= (uint32
)fRecordFormat
.u
.raw_audio
.frame_rate
;
969 header
.format
.avg_bytes_per_sec
= (uint32
)(fRecordFormat
.u
.raw_audio
.frame_rate
970 * fRecordFormat
.u
.raw_audio
.channel_count
971 * (fRecordFormat
.u
.raw_audio
.format
& 0xf));
972 header
.format
.bits_per_sample
= (fRecordFormat
.u
.raw_audio
.format
& 0xf) * 8;
973 header
.format
.block_align
= (fRecordFormat
.u
.raw_audio
.format
& 0xf)
974 * fRecordFormat
.u
.raw_audio
.channel_count
;
975 header
.data_chunk
.fourcc
= FOURCC('d','a','t','a');
976 header
.data_chunk
.len
= fRecSize
;
977 fRecFile
.Seek(0, SEEK_SET
);
978 fRecFile
.Write(&header
, sizeof(header
));
980 fRecFile
.SetSize(fRecSize
+ sizeof(header
));
981 // We reserve space; make sure we cut off any excess at the end.
982 AddSoundItem(fRecEntry
, true);
986 // We're done for this time.
990 // No more recording going on.
992 SetButtonState(btnPaused
);
993 fRecordButton
->SetStopped();
1000 RecorderWindow::StopPlaying()
1004 fPlayer
->SetCallbacks(0, 0, 0);
1005 fVolumeSlider
->SetSoundPlayer(NULL
);
1009 SetButtonState(btnPaused
);
1010 fPlayButton
->SetStopped();
1011 fTrackSlider
->ResetMainTime();
1012 fScopeView
->SetMainTime(*fTrackSlider
->MainTime());
1018 RecorderWindow::SetButtonState(BtnState state
)
1020 fButtonState
= state
;
1026 RecorderWindow::UpdateButtons()
1028 bool hasSelection
= (fSoundList
->CurrentSelection() >= 0);
1029 fRecordButton
->SetEnabled(fButtonState
!= btnPlaying
);
1030 fPlayButton
->SetEnabled((fButtonState
!= btnRecording
) && hasSelection
);
1031 fRewindButton
->SetEnabled((fButtonState
!= btnRecording
) && hasSelection
);
1032 fForwardButton
->SetEnabled((fButtonState
!= btnRecording
) && hasSelection
);
1033 fStopButton
->SetEnabled(fButtonState
!= btnPaused
);
1034 fSaveButton
->SetEnabled(hasSelection
&& (fButtonState
!= btnRecording
));
1035 fInputField
->SetEnabled(fButtonState
!= btnRecording
);
1039 extern "C" status_t
DecodedFormat__11BMediaTrackP12media_format(
1040 BMediaTrack
*self
, media_format
*inout_format
);
1045 RecorderWindow::UpdatePlayFile(SoundListItem
* item
, bool updateDisplay
)
1047 fScopeView
->CancelRendering();
1051 if (fPlayTrack
&& fPlayFile
) {
1052 fPlayFile
->ReleaseTrack(fPlayTrack
);
1061 BEntry
& entry
= item
->Entry();
1064 fPlayFile
= new BMediaFile(&ref
); //, B_MEDIA_FILE_UNBUFFERED);
1065 if ((err
= fPlayFile
->InitCheck()) < B_OK
) {
1071 for (int ix
=0; ix
< fPlayFile
->CountTracks(); ix
++) {
1072 BMediaTrack
* track
= fPlayFile
->TrackAt(ix
);
1073 fPlayFormat
.type
= B_MEDIA_RAW_AUDIO
;
1075 if ((track
->DecodedFormat(&fPlayFormat
) == B_OK
)
1077 if ((DecodedFormat__11BMediaTrackP12media_format(track
, &fPlayFormat
) == B_OK
)
1079 && (fPlayFormat
.type
== B_MEDIA_RAW_AUDIO
)) {
1084 fPlayFile
->ReleaseTrack(track
);
1090 return B_STREAM_NOT_FOUND
;
1096 BString filename
= B_TRANSLATE("File name: ");
1097 filename
<< ref
.name
;
1098 fFilename
->SetText(filename
.String());
1100 BString format
= B_TRANSLATE("Format: ");
1101 media_file_format file_format
;
1102 if (fPlayFile
->GetFileFormatInfo(&file_format
) == B_OK
)
1103 format
<< file_format
.short_name
;
1104 BString compression
= B_TRANSLATE("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
<< B_TRANSLATE("None");
1110 compression
<< codec_info
.short_name
;
1112 BString channels
= B_TRANSLATE("Channels: ");
1113 channels
<< fPlayFormat
.u
.raw_audio
.channel_count
;
1114 BString samplesize
= B_TRANSLATE("Sample size: ");
1115 samplesize
<< 8 * (fPlayFormat
.u
.raw_audio
.format
& 0xf)
1116 << B_TRANSLATE(" bits");
1117 BString samplerate
= B_TRANSLATE("Sample rate: ");
1118 samplerate
<< (int)fPlayFormat
.u
.raw_audio
.frame_rate
;
1119 BString durationString
= B_TRANSLATE("Duration: ");
1120 bigtime_t duration
= fPlayTrack
->Duration();
1121 durationString
<< (float)(duration
/ 1000000.0) << B_TRANSLATE(" seconds");
1123 fFormat
->SetText(format
.String());
1124 fCompression
->SetText(compression
.String());
1125 fChannels
->SetText(channels
.String());
1126 fSampleSize
->SetText(samplesize
.String());
1127 fSampleRate
->SetText(samplerate
.String());
1128 fDuration
->SetText(durationString
.String());
1130 fTrackSlider
->SetTotalTime(duration
, true);
1131 fScopeView
->SetTotalTime(duration
, true);
1132 fScopeView
->RenderTrack(fPlayTrack
, fPlayFormat
);
1134 fPlayFrames
= fPlayTrack
->CountFrames();
1140 RecorderWindow::ErrorAlert(const char * action
, status_t err
)
1144 sprintf(msg
, "%s: %s. [%" B_PRIx32
"]", action
, strerror(err
), (int32
) err
);
1146 sprintf(msg
, "%s.", action
);
1147 BAlert
* alert
= new BAlert("", msg
, B_TRANSLATE("Stop"));
1148 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1154 RecorderWindow::NewTempName(char * name
)
1156 int init_count
= fTempCount
;
1158 if (fTempCount
-init_count
> 25) {
1164 sprintf(name
, "Audio Clip");
1166 sprintf(name
, "Audio Clip %d", fTempCount
);
1170 if ((err
= fTempDir
.GetEntry(&tempEnt
)) < B_OK
) {
1173 if ((err
= tempEnt
.GetPath(&path
)) < B_OK
) {
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) {
1189 RecorderWindow::AddSoundItem(const BEntry
& entry
, bool temp
)
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
));
1200 RecorderWindow::RemoveCurrentSoundItem() {
1201 int32 index
= fSoundList
->CurrentSelection();
1202 BListItem
*item
= fSoundList
->RemoveItem(index
);
1204 if (index
>= fSoundList
->CountItems())
1205 index
= fSoundList
->CountItems() - 1;
1206 fSoundList
->Select(index
);
1211 RecorderWindow::RecordFile(void* cookie
, bigtime_t timestamp
,
1212 void* data
, size_t size
, const media_format
&format
)
1214 // Callback called from the SoundConsumer when receiving buffers.
1215 RecorderWindow
* window
= (RecorderWindow
*)cookie
;
1217 if (window
->fRecording
) {
1218 // Write the data to file (we don't buffer or guard file access
1220 window
->fRecFile
.WriteAt(window
->fRecSize
, data
, size
);
1221 window
->fVUView
->ComputeLevels(data
, size
, format
.u
.raw_audio
.format
);
1222 window
->fRecSize
+= size
;
1228 RecorderWindow::NotifyRecordFile(void * cookie
,
1229 BMediaRecorder::notification code
, ...)
1231 if (code
== BMediaRecorder::B_WILL_STOP
) {
1232 RecorderWindow
* window
= (RecorderWindow
*)cookie
;
1233 // Tell the window we've stopped, if it doesn't
1235 window
->PostMessage(STOP_RECORDING
);
1241 RecorderWindow::PlayFile(void * cookie
, void * data
, size_t size
,
1242 const media_raw_audio_format
& format
)
1244 // Callback called from the SoundProducer when producing buffers.
1245 RecorderWindow
* window
= (RecorderWindow
*)cookie
;
1246 int32 frame_size
= (window
->fPlayFormat
.u
.raw_audio
.format
& 0xf) *
1247 window
->fPlayFormat
.u
.raw_audio
.channel_count
;
1249 if ((window
->fPlayFrame
< window
->fPlayLimit
) || window
->fLooping
) {
1250 if (window
->fPlayFrame
>= window
->fPlayLimit
) {
1251 bigtime_t left
= window
->fTrackSlider
->LeftTime();
1252 window
->fPlayTrack
->SeekToTime(&left
);
1253 window
->fPlayFrame
= window
->fPlayTrack
->CurrentFrame();
1256 window
->fPlayTrack
->ReadFrames(data
, &frames
);
1257 window
->fVUView
->ComputeLevels(data
, size
/ frame_size
, format
.format
);
1258 window
->fPlayFrame
+= size
/frame_size
;
1259 window
->PostMessage(UPDATE_TRACKSLIDER
);
1262 window
->PostMessage(STOP_PLAYING
);
1268 RecorderWindow::NotifyPlayFile(void * cookie
,
1269 BSoundPlayer::sound_player_notification code
, ...)
1271 if ((code
== BSoundPlayer::B_STOPPED
) || (code
== BSoundPlayer::B_SOUND_DONE
)) {
1272 RecorderWindow
* window
= (RecorderWindow
*)cookie
;
1273 // tell the window we've stopped, if it doesn't
1275 window
->PostMessage(STOP_PLAYING
);
1281 RecorderWindow::RefsReceived(BMessage
*msg
)
1285 int32 countGood
= 0;
1288 while (msg
->FindRef("refs", i
++, &ref
) == B_OK
) {
1290 BEntry
entry(&ref
, true);
1294 if (node
.IsFile()) {
1295 SoundListItem
* listItem
= new SoundListItem(entry
, false);
1296 if (UpdatePlayFile(listItem
) == B_OK
) {
1297 fSoundList
->AddItem(listItem
);
1302 } else if(node
.IsDirectory()) {
1308 if (countBad
== 1 && countGood
== 0) {
1309 BAlert
* alert
= new BAlert(B_TRANSLATE("Nothing to play"),
1310 B_TRANSLATE("The file doesn't appear to be an audio file."),
1311 B_TRANSLATE("OK"), NULL
, NULL
, B_WIDTH_AS_USUAL
, B_STOP_ALERT
);
1312 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1314 } else if (countBad
> 0 && countGood
== 0) {
1315 BAlert
* alert
= new BAlert(B_TRANSLATE("Nothing to play"),
1316 B_TRANSLATE("None of the files appear to be audio files."),
1317 B_TRANSLATE("OK"), NULL
, NULL
, B_WIDTH_AS_USUAL
, B_STOP_ALERT
);
1318 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1320 } else if (countGood
> 0) {
1322 BAlert
* alert
= new BAlert(B_TRANSLATE("Invalid audio files"),
1323 B_TRANSLATE("Some of the files don't appear to be audio files."),
1324 B_TRANSLATE("OK"), NULL
, NULL
, B_WIDTH_AS_USUAL
,
1326 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
1329 fSoundList
->Select(fSoundList
->CountItems() - 1);
1335 RecorderWindow::CopyTarget(BMessage
*msg
)
1337 const char *type
= NULL
;
1338 if (msg
->FindString("be:types", &type
) == B_OK
) {
1339 if (!strcasecmp(type
, B_FILE_MIME_TYPE
)) {
1342 if (msg
->FindString("be:filetypes") != NULL
1343 && msg
->FindString("name", &name
) == B_OK
1344 && msg
->FindRef("directory", &dir
) == B_OK
) {
1345 BDirectory
directory(&dir
);
1346 BFile
file(&directory
, name
, O_RDWR
| O_TRUNC
);
1349 bigtime_t start
= fTrackSlider
->LeftTime();
1352 bigtime_t diffTime
= fTrackSlider
->RightTime()
1353 - fTrackSlider
->LeftTime();
1354 int64 framesToWrite
= (int64
) (diffTime
1355 * fPlayFormat
.u
.raw_audio
.frame_rate
/ 1000000LL);
1356 int32 frameSize
= (fPlayFormat
.u
.raw_audio
.format
& 0xf)
1357 * fPlayFormat
.u
.raw_audio
.channel_count
;
1360 header
.riff
.riff_id
= FOURCC('R','I','F','F');
1362 = (frameSize
* framesToWrite
) + sizeof(header
) - 8;
1363 header
.riff
.wave_id
= FOURCC('W','A','V','E');
1364 header
.format_chunk
.fourcc
= FOURCC('f','m','t',' ');
1365 header
.format_chunk
.len
= sizeof(header
.format
);
1366 header
.format
.format_tag
= 1;
1367 header
.format
.channels
= fPlayFormat
.u
.raw_audio
.channel_count
;
1368 header
.format
.samples_per_sec
1369 = (uint32
)fPlayFormat
.u
.raw_audio
.frame_rate
;
1370 header
.format
.avg_bytes_per_sec
1371 = (uint32
)(fPlayFormat
.u
.raw_audio
.frame_rate
1372 * fPlayFormat
.u
.raw_audio
.channel_count
1373 * (fPlayFormat
.u
.raw_audio
.format
& 0xf));
1374 header
.format
.bits_per_sample
1375 = (fPlayFormat
.u
.raw_audio
.format
& 0xf) * 8;
1376 header
.format
.block_align
= frameSize
;
1377 header
.data_chunk
.fourcc
= FOURCC('d','a','t','a');
1378 header
.data_chunk
.len
= frameSize
* framesToWrite
;
1379 file
.Seek(0, SEEK_SET
);
1380 file
.Write(&header
, sizeof(header
));
1382 char *data
= (char *)malloc(fPlayFormat
.u
.raw_audio
.buffer_size
);
1384 fPlayTrack
->SeekToTime(&start
);
1385 fPlayFrame
= fPlayTrack
->CurrentFrame();
1386 while (framesToWrite
> 0) {
1388 status_t err
= fPlayTrack
->ReadFrames(data
, &frames
);
1389 if (frames
<= 0 || err
!= B_OK
) {
1391 fprintf(stderr
, "CopyTarget: ReadFrames failed\n");
1394 file
.Write(data
, frames
* frameSize
);
1395 framesToWrite
-= frames
;
1400 BNodeInfo
nodeInfo(&file
);