1/*
2 * Copyright 2014, Dario Casalinuovo. All rights reserved.
3 * Copyright 2005, J��r��me Duval. All rights reserved.
4 * Distributed under the terms of the MIT License.
5 *
6 * Inspired by SoundCapture from Be newsletter (Media Kit Basics:
7 *	Consumers and Producers)
8 */
9
10#include <Application.h>
11#include <Alert.h>
12#include <Debug.h>
13#include <Screen.h>
14#include <Button.h>
15#include <CheckBox.h>
16#include <TextControl.h>
17#include <MenuField.h>
18#include <PopUpMenu.h>
19#include <MenuItem.h>
20#include <Box.h>
21#include <ScrollView.h>
22#include <Beep.h>
23#include <StringView.h>
24#include <String.h>
25#include <Slider.h>
26#include <Message.h>
27
28#include <Path.h>
29#include <FindDirectory.h>
30#include <MediaAddOn.h>
31
32#include <SoundPlayer.h>
33
34#include <assert.h>
35#include <stdio.h>
36#include <strings.h>
37#include <stdlib.h>
38#include <ctype.h>
39#include <unistd.h>
40#include <fcntl.h>
41
42#include <MediaRoster.h>
43#include <TimeSource.h>
44#include <NodeInfo.h>
45
46#include "RecorderWindow.h"
47#include "FileUtils.h"
48
49#if ! NDEBUG
50#define FPRINTF(args) fprintf args
51#else
52#define FPRINTF(args)
53#endif
54
55#define DEATH FPRINTF
56#define CONNECT FPRINTF
57#define WINDOW FPRINTF
58
59#undef B_TRANSLATION_CONTEXT
60#define B_TRANSLATION_CONTEXT "RecorderWindow"
61
62
63// default window positioning
64static const float MIN_WIDTH = 400.0f;
65static const float MIN_HEIGHT = 175.0f;
66static const float XPOS = 100.0f;
67static const float YPOS = 200.0f;
68
69#define FOURCC(a,b,c,d)	((((uint32)(d)) << 24) | (((uint32)(c)) << 16) \
70	| (((uint32)(b)) << 8) | ((uint32)(a)))
71
72struct riff_struct
73{
74	uint32 riff_id; // 'RIFF'
75	uint32 len;
76	uint32 wave_id;	// 'WAVE'
77};
78
79struct chunk_struct
80{
81	uint32 fourcc;
82	uint32 len;
83};
84
85struct format_struct
86{
87	uint16 format_tag;
88	uint16 channels;
89	uint32 samples_per_sec;
90	uint32 avg_bytes_per_sec;
91	uint16 block_align;
92	uint16 bits_per_sample;
93};
94
95
96struct wave_struct
97{
98	struct riff_struct riff;
99	struct chunk_struct format_chunk;
100	struct format_struct format;
101	struct chunk_struct data_chunk;
102};
103
104
105RecorderWindow::RecorderWindow()
106	:
107	BWindow(BRect(XPOS, YPOS, XPOS + MIN_WIDTH, YPOS + MIN_HEIGHT),
108		B_TRANSLATE_SYSTEM_NAME("SoundRecorder"), B_TITLED_WINDOW,
109		B_ASYNCHRONOUS_CONTROLS | B_NOT_V_RESIZABLE | B_NOT_ZOOMABLE),
110	fPlayer(NULL),
111	fSoundList(NULL),
112	fPlayFile(NULL),
113	fPlayTrack(NULL),
114	fPlayFrames(0),
115	fLooping(false),
116	fSavePanel(NULL),
117	fInitCheck(B_OK)
118{
119	fRoster = NULL;
120	fRecordButton = NULL;
121	fPlayButton = NULL;
122	fStopButton = NULL;
123	fSaveButton = NULL;
124	fLoopButton = NULL;
125	fInputField = NULL;
126	fRecorder = NULL;
127	fRecording = false;
128	fExternalConnection = false;
129	fTempCount = -1;
130	fButtonState = btnPaused;
131
132	CalcSizes(MIN_WIDTH, MIN_HEIGHT);
133
134	fInitCheck = InitWindow();
135	if (fInitCheck != B_OK) {
136		if (fInitCheck == B_NAME_NOT_FOUND)
137			ErrorAlert(B_TRANSLATE("Cannot find default audio hardware"),
138				fInitCheck);
139		else
140			ErrorAlert(B_TRANSLATE("Cannot connect to media server"),
141				fInitCheck);
142		PostMessage(B_QUIT_REQUESTED);
143	} else
144		Show();
145}
146
147
148RecorderWindow::~RecorderWindow()
149{
150	//  The MediaRecorder have to be deleted, the dtor
151	//  disconnect it from the media_kit.
152	delete fRecorder;
153
154	delete fPlayer;
155
156	if (fPlayTrack && fPlayFile)
157		fPlayFile->ReleaseTrack(fPlayTrack);
158
159	if (fPlayFile)
160		delete fPlayFile;
161	fPlayTrack = NULL;
162	fPlayFile = NULL;
163
164	//	Clean up items in list view.
165	if (fSoundList) {
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));
170			if (item) {
171				if (item->IsTemp())
172					item->Entry().Remove();	//	delete temp file
173				delete item;
174			}
175		}
176		fSoundList->MakeEmpty();
177	}
178	//	Clean up currently recording file, if any.
179	fRecEntry.Remove();
180	fRecEntry.Unset();
181
182	delete fSavePanel;
183}
184
185
186status_t
187RecorderWindow::InitCheck()
188{
189	return fInitCheck;
190}
191
192
193void
194RecorderWindow::CalcSizes(float min_width, float min_height)
195{
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);
201
202	//	Don't zoom to cover all of screen; user can resize last bit if necessary.
203	//	This leaves other windows visible.
204	if (width > 640)
205		width = 640 + (width - 640) / 2;
206	SetZoomLimits(width, rect.Height() - 24);
207}
208
209
210status_t
211RecorderWindow::InitWindow()
212{
213	BPopUpMenu * popup = 0;
214	status_t error;
215
216	try {
217		//	Find temp directory for recorded sounds.
218		BPath path;
219		if (!(error = find_directory(B_SYSTEM_TEMP_DIRECTORY, &path)))
220			error = fTempDir.SetTo(path.Path());
221		if (error < 0)
222			goto bad_mojo;
223
224		//	Make sure the media roster is there (which means the server is there).
225		fRoster = BMediaRoster::Roster(&error);
226		if (!fRoster)
227			goto bad_mojo;
228
229		error = fRoster->GetAudioInput(&fAudioInputNode);
230		if (error < B_OK) //	there's no input?
231			goto bad_mojo;
232
233		error = fRoster->GetAudioMixer(&fAudioMixerNode);
234		if (error < B_OK) //	there's no mixer?
235			goto bad_mojo;
236
237		fRecorder = new BMediaRecorder("Sound Recorder",
238			B_MEDIA_RAW_AUDIO);
239
240		if (fRecorder->InitCheck() < B_OK)
241			goto bad_mojo;
242
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);
248
249		//	Create the window header with controls
250		BRect r(Bounds());
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);
255
256		AddChild(background);
257
258		r = background->Bounds();
259		r.left = 0;
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);
264
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);
270
271		r = background->Bounds();
272		r.left = 2;
273		r.right -= 26;
274		r.top = 115;
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);
279
280		BRect buttonRect;
281
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);
289
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);
297
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);
304
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);
312
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);
319
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);
328
329		//	Button Loop
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);
337
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);
344
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);
351
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);
358
359		r = Bounds();
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);
364
365		//	The actual list of recorded sounds (initially empty) sits
366		//	below the header with the controls.
367		r = fBottomBox->Bounds();
368		r.left += 190;
369		r.InsetBy(10, 10);
370		r.left -= 10;
371		r.top += 4;
372		r.right -= B_V_SCROLL_BAR_WIDTH;
373		r.bottom -= 25;
374		fSoundList = new SoundListView(r, B_TRANSLATE("Sound List"),
375			B_FOLLOW_ALL);
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);
381
382		r = fBottomBox->Bounds();
383		r.right = r.left + 190;
384		r.bottom -= 25;
385		r.InsetBy(10, 8);
386		r.top -= 1;
387		fFileInfoBox = new BBox(r, "fileinfo", B_FOLLOW_LEFT);
388		fFileInfoBox->SetLabel(B_TRANSLATE("File info"));
389
390		fFileInfoBox->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
391
392		BFont font = be_plain_font;
393		font.SetSize(font.Size() * 0.92f);
394		font_height height;
395		font.GetHeight(&height);
396		float fontHeight = height.ascent + height.leading + height.descent;
397
398		r = fFileInfoBox->Bounds();
399		r.left = 8;
400		r.top = fontHeight + 6;
401		r.bottom = r.top + fontHeight + 3;
402		r.right -= 10;
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));
407
408		r.top += fontHeight;
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));
414
415		r.top += fontHeight;
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));
422
423		r.top += fontHeight;
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));
429
430		r.top += fontHeight;
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));
437
438		r.top += fontHeight;
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));
445
446		r.top += fontHeight;
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));
452
453		fFileInfoBox->ResizeTo(fFileInfoBox->Frame().Width(),
454			r.bottom + fontHeight / 2.0f);
455		fDeployedHeight = MIN_HEIGHT + fFileInfoBox->Bounds().Height() + 40.0f;
456
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];
462
463		int32 real_count = maxInputCount;
464
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;
470		}
471		char selected_name[B_MEDIA_NAME_LENGTH] = "Default input";
472		BMessage * msg;
473		BMenuItem * item;
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];
480			int32 ni_count = 12;
481			error = fRoster->GetInstancesFor(dni[i].addon, dni[i].flavor_id,
482				ni, &ni_count);
483			if (error == B_OK) {
484				for (int j = 0; j < ni_count; j++) {
485					if (ni[j] == fAudioInputNode.node) {
486						strcpy(selected_name, dni[i].name);
487						break;
488					}
489				}
490			}
491		}
492
493		//	Create the actual widget
494		r = fFileInfoBox->Bounds();
495		r.top = r.bottom + 2;
496		r.bottom = r.top + 18;
497		r.InsetBy(10, 10);
498		fInputField = new BMenuField(r, "Input", B_TRANSLATE("Input:"), popup);
499		fInputField->SetDivider(fInputField->StringWidth(B_TRANSLATE("Input:"))
500			+ 4.0f);
501		fBottomBox->AddChild(fInputField);
502
503		fBottomBox->AddChild(fFileInfoBox);
504
505		fBottomBox->Hide();
506		CalcSizes(MIN_WIDTH, MIN_HEIGHT);
507		ResizeTo(Frame().Width(), MIN_HEIGHT);
508
509		popup->Superitem()->SetLabel(selected_name);
510
511		// Make sure the save panel is happy.
512		fSavePanel = new BFilePanel(B_SAVE_PANEL);
513		fSavePanel->SetTarget(this);
514	}
515	catch (...) {
516		goto bad_mojo;
517	}
518	UpdateButtons();
519	return B_OK;
520
521	//	Error handling.
522bad_mojo:
523	if (error >= 0)
524		error = B_ERROR;
525	if (fRecorder)
526		delete fRecorder;
527
528	delete fPlayer;
529	if (!fInputField)
530		delete popup;
531	return error;
532}
533
534
535bool
536RecorderWindow::QuitRequested()	//	this means Close pressed
537{
538	StopRecording();
539	StopPlaying();
540	be_app->PostMessage(B_QUIT_REQUESTED);
541	return true;
542}
543
544
545void
546RecorderWindow::MessageReceived(BMessage * message)
547{
548	//	Your average generic message dispatching switch() statement.
549	switch (message->what) {
550	case INPUT_SELECTED:
551		Input(message);
552		break;
553	case SOUND_SELECTED:
554		Selected(message);
555		break;
556	case STOP_PLAYING:
557		StopPlaying();
558		break;
559	case STOP_RECORDING:
560		StopRecording();
561		break;
562	case PLAY_PERIOD:
563		if (fPlayer) {
564			if (fPlayer->HasData())
565				fPlayButton->SetPlaying();
566			else
567				fPlayButton->SetPaused();
568		}
569		break;
570	case RECORD_PERIOD:
571		fRecordButton->SetRecording();
572		break;
573	case RECORD:
574		Record(message);
575		break;
576	case STOP:
577		Stop(message);
578		break;
579	case PLAY:
580		Play(message);
581		break;
582	case SAVE:
583		Save(message);
584		break;
585	case B_SAVE_REQUESTED:
586		DoSave(message);
587		break;
588	case VIEW_LIST:
589		if (fUpDownButton->Value() == B_CONTROL_ON) {
590			fBottomBox->Show();
591			CalcSizes(MIN_WIDTH, fDeployedHeight);
592			ResizeTo(Frame().Width(), fDeployedHeight);
593		} else {
594			fBottomBox->Hide();
595			CalcSizes(MIN_WIDTH, MIN_HEIGHT);
596			ResizeTo(Frame().Width(), MIN_HEIGHT);
597
598		}
599		break;
600	case UPDATE_TRACKSLIDER:
601		{
602			bigtime_t timestamp = fPlayTrack->CurrentTime();
603			fTrackSlider->SetMainTime(timestamp, false);
604			fScopeView->SetMainTime(timestamp);
605		}
606		break;
607	case POSITION_CHANGED:
608		{
609			bigtime_t right, left, main;
610			if (message->FindInt64("main", &main) == B_OK) {
611				if (fPlayTrack) {
612					fPlayTrack->SeekToTime(fTrackSlider->MainTime());
613					fPlayFrame = fPlayTrack->CurrentFrame();
614				}
615				fScopeView->SetMainTime(main);
616			}
617			if (message->FindInt64("right", &right) == B_OK) {
618				if (fPlayTrack) {
619					fPlayLimit = MIN(fPlayFrames,
620						(off_t)(right * fPlayFormat.u.raw_audio.frame_rate
621							/ 1000000LL));
622				}
623				fScopeView->SetRightTime(right);
624			}
625			if (message->FindInt64("left", &left) == B_OK)
626				fScopeView->SetLeftTime(left);
627			break;
628		}
629	case LOOP:
630		fLooping = fLoopButton->ButtonState();
631		break;
632	case B_SIMPLE_DATA:
633	case B_REFS_RECEIVED:
634		{
635			RefsReceived(message);
636			break;
637		}
638	case B_COPY_TARGET:
639		CopyTarget(message);
640		break;
641	default:
642		BWindow::MessageReceived(message);
643		break;
644	}
645}
646
647
648void
649RecorderWindow::Record(BMessage * message)
650{
651	//	User pressed Record button
652	fRecording = true;
653	if (fButtonState != btnPaused) {
654		StopRecording();
655		return;			//	user is too fast on the mouse
656	}
657	SetButtonState(btnRecording);
658	fRecordButton->SetRecording();
659
660	char name[256];
661	//	Create a file with a temporary name
662	status_t err = NewTempName(name);
663	if (err < B_OK) {
664		ErrorAlert(B_TRANSLATE("Cannot find an unused name to use for the "
665			"new recording"), err);
666		return;
667	}
668	//	Find the file so we can refer to it later
669	err = fTempDir.FindEntry(name, &fRecEntry);
670	if (err < B_OK) {
671		ErrorAlert(B_TRANSLATE("Cannot find the temporary file created to "
672			"hold the new recording"), err);
673		return;
674	}
675	err = fRecFile.SetTo(&fTempDir, name, O_RDWR);
676	if (err < B_OK) {
677		ErrorAlert(B_TRANSLATE("Cannot open the temporary file created to "
678			"hold the new recording"), err);
679		fRecEntry.Unset();
680		return;
681	}
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));
687	if (err < B_OK) {
688		ErrorAlert(B_TRANSLATE("Cannot record a sound that long"), err);
689		fRecEntry.Remove();
690		fRecEntry.Unset();
691		return;
692	}
693	fRecSize = 0;
694
695	fRecFile.Seek(sizeof(struct wave_struct), SEEK_SET);
696
697	// Hook up input
698	err = MakeRecordConnection(fAudioInputNode);
699	if (err < B_OK) {
700		ErrorAlert(B_TRANSLATE("Cannot connect to the selected sound input"),
701			err);
702		fRecEntry.Remove();
703		fRecEntry.Unset();
704		return;
705	}
706	fRecorder->Start();
707}
708
709
710void
711RecorderWindow::Play(BMessage * message)
712{
713	if (fPlayer) {
714		//	User pressed Play button and playing
715		if (fPlayer->HasData())
716			fPlayButton->SetPaused();
717		else
718			fPlayButton->SetPlaying();
719		fPlayer->SetHasData(!fPlayer->HasData());
720		return;
721	}
722
723	SetButtonState(btnPlaying);
724	fPlayButton->SetPlaying();
725
726	if (!fPlayTrack) {
727		ErrorAlert(B_TRANSLATE("Cannot get the file to play"), B_ERROR);
728		return;
729	}
730
731	fPlayLimit = MIN(fPlayFrames, (off_t)(fTrackSlider->RightTime()
732		* fPlayFormat.u.raw_audio.frame_rate / 1000000LL));
733	fPlayTrack->SeekToTime(fTrackSlider->MainTime());
734	fPlayFrame = fPlayTrack->CurrentFrame();
735
736	// Create our internal Node which plays sound, and register it.
737	fPlayer = new BSoundPlayer(fAudioMixerNode, &fPlayFormat.u.raw_audio,
738		"Sound Player");
739	status_t err = fPlayer->InitCheck();
740	if (err < B_OK)
741		return;
742
743	fVolumeSlider->SetSoundPlayer(fPlayer);
744	fPlayer->SetCallbacks(PlayFile, NotifyPlayFile, this);
745
746	//	And get it going...
747	fPlayer->Start();
748	fPlayer->SetHasData(true);
749}
750
751
752void
753RecorderWindow::Stop(BMessage * message)
754{
755	//	User pressed Stop button.
756	//	Stop recorder.
757	StopRecording();
758	//	Stop player.
759	StopPlaying();
760}
761
762
763void
764RecorderWindow::Save(BMessage * message)
765{
766	//	User pressed Save button.
767	//	Find the item to save.
768	int32 index = fSoundList->CurrentSelection();
769	SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(index));
770	if ((! pItem) || (pItem->Entry().InitCheck() != B_OK))
771		return;
772
773	// Update the save panel and show it.
774	char filename[B_FILE_NAME_LENGTH];
775	pItem->Entry().GetName(filename);
776	BMessage saveMsg(B_SAVE_REQUESTED);
777	entry_ref ref;
778	pItem->Entry().GetRef(&ref);
779
780	if (saveMsg.AddPointer("sound list item", pItem) != B_OK)
781		fprintf(stderr, "failed to add pItem\n");
782	fSavePanel->SetSaveText(filename);
783	fSavePanel->SetMessage(&saveMsg);
784	fSavePanel->Show();
785}
786
787
788void
789RecorderWindow::DoSave(BMessage * message)
790{
791	// User picked a place to put the file.
792	// Find the location of the old (e.g.
793	// temporary file), and the name of the
794	// new file to save.
795	entry_ref old_ref, new_dir_ref;
796	const char* new_name;
797	SoundListItem* pItem;
798
799	if ((message->FindPointer("sound list item", (void**) &pItem) == B_OK)
800		&& (message->FindRef("directory", &new_dir_ref) == B_OK)
801		&& (message->FindString("name", &new_name) == B_OK)) {
802		BEntry& oldEntry = pItem->Entry();
803		BFile oldFile(&oldEntry, B_READ_WRITE);
804		if (oldFile.InitCheck() != B_OK)
805			return;
806
807		BDirectory newDir(&new_dir_ref);
808		if (newDir.InitCheck() != B_OK)
809			return;
810
811		BFile newFile;
812		newDir.CreateFile(new_name, &newFile);
813
814		if (newFile.InitCheck() != B_OK)
815			return;
816
817		status_t err = CopyFile(newFile, oldFile);
818
819		if (err == B_OK) {
820			// clean up the sound list and item
821			if (pItem->IsTemp())
822				oldEntry.Remove(); // blows away temp file!
823			oldEntry.SetTo(&newDir, new_name);
824			pItem->SetTemp(false);	// don't blow the new entry away when we exit!
825			fSoundList->Invalidate();
826		}
827	} else {
828		WINDOW((stderr, "Couldn't save file.\n"));
829	}
830}
831
832
833void
834RecorderWindow::Input(BMessage * message)
835{
836	//	User selected input from pop-up
837	const dormant_node_info * dni = 0;
838	ssize_t size = 0;
839	if (message->FindData("node", B_RAW_TYPE, (const void **)&dni, &size))
840		return;		//	bad input selection message
841
842	media_node_id node_id;
843	status_t error = fRoster->GetInstancesFor(dni->addon, dni->flavor_id, &node_id);
844	if (error != B_OK)
845		fRoster->InstantiateDormantNode(*dni, &fAudioInputNode);
846	else
847		fRoster->GetNodeFor(node_id, &fAudioInputNode);
848}
849
850
851void
852RecorderWindow::Selected(BMessage * message)
853{
854	//	User selected a sound in list view
855	int32 selIdx = fSoundList->CurrentSelection();
856	SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(selIdx));
857	if (!pItem)
858		return;
859	status_t err = UpdatePlayFile(pItem, true);
860	if (err != B_OK) {
861		ErrorAlert(B_TRANSLATE("Cannot recognize this file as a media file"),
862			err == B_MEDIA_NO_HANDLER ? B_OK : err);
863		RemoveCurrentSoundItem();
864	}
865	UpdateButtons();
866}
867
868
869status_t
870RecorderWindow::MakeRecordConnection(const media_node & input)
871{
872	CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n"));
873
874	status_t err = B_OK;
875	media_output audioOutput;
876
877	if (!fRecorder->IsConnected()) {
878		//	Find an available output for the given input node.
879		int32 count = 0;
880		err = fRoster->GetFreeOutputsFor(input, &audioOutput, 1,
881			&count, B_MEDIA_RAW_AUDIO);
882
883		if (err < B_OK) {
884			CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
885				" couldn't get free outputs from audio input node\n"));
886			return err;
887		}
888
889		if (count < 1) {
890			CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
891				" no free outputs from audio input node\n"));
892			return B_BUSY;
893		}
894
895		// Get a format, any format.
896		fRecordFormat.u.raw_audio = audioOutput.format.u.raw_audio;
897		fExternalConnection = false;
898	} else {
899		fRecordFormat.u.raw_audio = fRecorder->AcceptedFormat().u.raw_audio;
900		fExternalConnection = true;
901	}
902
903	fRecordFormat.type = B_MEDIA_RAW_AUDIO;
904
905	//	Tell the consumer where we want data to go.
906	err = fRecorder->SetHooks(RecordFile, NotifyRecordFile, this);
907
908	if (err < B_OK) {
909		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
910			" couldn't set the sound recorder's hook functions\n"));
911		return err;
912	}
913
914	if (!fRecorder->IsConnected()) {
915
916		err = fRecorder->Connect(input, &audioOutput, &fRecordFormat);
917
918		if (err < B_OK) {
919			CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
920				" failed to connect sound recorder to audio input node.\n"));
921
922			fRecorder->SetHooks(NULL, NULL, NULL);
923			return err;
924		}
925	}
926
927	return B_OK;
928}
929
930
931status_t
932RecorderWindow::BreakRecordConnection()
933{
934	return fRecorder->Disconnect();
935}
936
937
938status_t
939RecorderWindow::StopRecording()
940{
941	if (!fRecording)
942		return B_OK;
943	fRecording = false;
944
945	status_t err = B_OK;
946	err = fRecorder->Stop(true);
947	if (err < B_OK)
948		return err;
949
950	// We maintain the connection active
951	// if the user connected us from Cortex.
952	if (!fExternalConnection) {
953		BreakRecordConnection();
954	}
955
956	fRecorder->SetHooks(NULL, NULL, NULL);
957
958	if (fRecSize > 0) {
959
960		wave_struct header;
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));
979
980		fRecFile.SetSize(fRecSize + sizeof(header));
981		//	We reserve space; make sure we cut off any excess at the end.
982		AddSoundItem(fRecEntry, true);
983	} else
984		fRecEntry.Remove();
985
986	//	We're done for this time.
987	fRecEntry.Unset();
988	//	Close the file.
989	fRecFile.Unset();
990	//	No more recording going on.
991	fRecSize = 0;
992	SetButtonState(btnPaused);
993	fRecordButton->SetStopped();
994
995	return B_OK;
996}
997
998
999status_t
1000RecorderWindow::StopPlaying()
1001{
1002	if (fPlayer) {
1003		fPlayer->Stop();
1004		fPlayer->SetCallbacks(0, 0, 0);
1005		fVolumeSlider->SetSoundPlayer(NULL);
1006		delete fPlayer;
1007		fPlayer = NULL;
1008	}
1009	SetButtonState(btnPaused);
1010	fPlayButton->SetStopped();
1011	fTrackSlider->ResetMainTime();
1012	fScopeView->SetMainTime(*fTrackSlider->MainTime());
1013	return B_OK;
1014}
1015
1016
1017void
1018RecorderWindow::SetButtonState(BtnState state)
1019{
1020	fButtonState = state;
1021	UpdateButtons();
1022}
1023
1024
1025void
1026RecorderWindow::UpdateButtons()
1027{
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);
1036}
1037
1038#ifndef __HAIKU__
1039extern "C" status_t DecodedFormat__11BMediaTrackP12media_format(
1040	BMediaTrack *self, media_format *inout_format);
1041#endif
1042
1043
1044status_t
1045RecorderWindow::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 = B_TRANSLATE("File name: ");
1097	filename << ref.name;
1098	fFilename->SetText(filename.String());
1099
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");
1109		else
1110			compression << codec_info.short_name;
1111	}
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");
1122
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());
1129
1130	fTrackSlider->SetTotalTime(duration, true);
1131	fScopeView->SetTotalTime(duration, true);
1132	fScopeView->RenderTrack(fPlayTrack, fPlayFormat);
1133
1134	fPlayFrames = fPlayTrack->CountFrames();
1135	return B_OK;
1136}
1137
1138
1139void
1140RecorderWindow::ErrorAlert(const char * action, status_t err)
1141{
1142	char msg[300];
1143	if (err != B_OK)
1144		sprintf(msg, "%s: %s. [%" B_PRIx32 "]", action, strerror(err), (int32) err);
1145	else
1146		sprintf(msg, "%s.", action);
1147	BAlert* alert = new BAlert("", msg, B_TRANSLATE("Stop"));
1148	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1149	alert->Go();
1150}
1151
1152
1153status_t
1154RecorderWindow::NewTempName(char * name)
1155{
1156	int init_count = fTempCount;
1157again:
1158	if (fTempCount-init_count > 25) {
1159		return B_ERROR;
1160	}
1161	else {
1162		fTempCount++;
1163		if (fTempCount==0)
1164			sprintf(name, "Audio Clip");
1165		else
1166			sprintf(name, "Audio Clip %d", fTempCount);
1167		BPath path;
1168		status_t err;
1169		BEntry tempEnt;
1170		if ((err = fTempDir.GetEntry(&tempEnt)) < B_OK) {
1171			return err;
1172		}
1173		if ((err = tempEnt.GetPath(&path)) < B_OK) {
1174			return err;
1175		}
1176		path.Append(name);
1177		int fd;
1178		//	Use O_EXCL so we know we created the file (sync with other instances)
1179		if ((fd = open(path.Path(), O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) {
1180			goto again;
1181		}
1182		close(fd);
1183	}
1184	return B_OK;
1185}
1186
1187
1188void
1189RecorderWindow::AddSoundItem(const BEntry& entry, bool temp)
1190{
1191	//	Create list item to display.
1192	SoundListItem * listItem = new SoundListItem(entry, temp);
1193	fSoundList->AddItem(listItem);
1194	fSoundList->Invalidate();
1195	fSoundList->Select(fSoundList->IndexOf(listItem));
1196}
1197
1198
1199void
1200RecorderWindow::RemoveCurrentSoundItem() {
1201	int32 index = fSoundList->CurrentSelection();
1202	BListItem *item = fSoundList->RemoveItem(index);
1203	delete item;
1204	if (index >= fSoundList->CountItems())
1205		index = fSoundList->CountItems() - 1;
1206	fSoundList->Select(index);
1207}
1208
1209
1210void
1211RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp,
1212	void* data, size_t size, const media_format &format)
1213{
1214	//	Callback called from the SoundConsumer when receiving buffers.
1215	RecorderWindow * window = (RecorderWindow *)cookie;
1216
1217	if (window->fRecording) {
1218		//	Write the data to file (we don't buffer or guard file access
1219		//	or anything)
1220		window->fRecFile.WriteAt(window->fRecSize, data, size);
1221		window->fVUView->ComputeLevels(data, size, format.u.raw_audio.format);
1222		window->fRecSize += size;
1223	}
1224}
1225
1226
1227void
1228RecorderWindow::NotifyRecordFile(void * cookie,
1229	BMediaRecorder::notification code, ...)
1230{
1231	if (code == BMediaRecorder::B_WILL_STOP) {
1232		RecorderWindow * window = (RecorderWindow *)cookie;
1233		// Tell the window we've stopped, if it doesn't
1234		// already know.
1235		window->PostMessage(STOP_RECORDING);
1236	}
1237}
1238
1239
1240void
1241RecorderWindow::PlayFile(void * cookie, void * data, size_t size,
1242	const media_raw_audio_format & format)
1243{
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;
1248
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();
1254		}
1255		int64 frames = 0;
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);
1260	} else {
1261		//	we're done!
1262		window->PostMessage(STOP_PLAYING);
1263	}
1264}
1265
1266
1267void
1268RecorderWindow::NotifyPlayFile(void * cookie,
1269	BSoundPlayer::sound_player_notification code, ...)
1270{
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
1274		// already know.
1275		window->PostMessage(STOP_PLAYING);
1276	}
1277}
1278
1279
1280void
1281RecorderWindow::RefsReceived(BMessage *msg)
1282{
1283	entry_ref ref;
1284	int32 i = 0;
1285	int32 countGood = 0;
1286	int32 countBad = 0;
1287
1288	while (msg->FindRef("refs", i++, &ref) == B_OK) {
1289
1290		BEntry entry(&ref, true);
1291		BPath path(&entry);
1292		BNode node(&entry);
1293
1294		if (node.IsFile()) {
1295			SoundListItem * listItem = new SoundListItem(entry, false);
1296			if (UpdatePlayFile(listItem) == B_OK) {
1297				fSoundList->AddItem(listItem);
1298				countGood++;
1299				continue;
1300			}
1301			delete listItem;
1302		} else if(node.IsDirectory()) {
1303
1304		}
1305		countBad++;
1306	}
1307
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);
1313		alert->Go();
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);
1319		alert->Go();
1320	} else if (countGood > 0) {
1321		if (countBad > 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,
1325				B_WARNING_ALERT);
1326			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1327			alert->Go();
1328		}
1329		fSoundList->Select(fSoundList->CountItems() - 1);
1330	}
1331}
1332
1333
1334void
1335RecorderWindow::CopyTarget(BMessage *msg)
1336{
1337	const char *type = NULL;
1338	if (msg->FindString("be:types", &type) == B_OK) {
1339		if (!strcasecmp(type, B_FILE_MIME_TYPE)) {
1340			const char *name;
1341			entry_ref dir;
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);
1347
1348				// seek time
1349				bigtime_t start = fTrackSlider->LeftTime();
1350
1351				// write data
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;
1358
1359				wave_struct header;
1360				header.riff.riff_id = FOURCC('R','I','F','F');
1361				header.riff.len
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));
1381
1382				char *data = (char *)malloc(fPlayFormat.u.raw_audio.buffer_size);
1383
1384				fPlayTrack->SeekToTime(&start);
1385				fPlayFrame = fPlayTrack->CurrentFrame();
1386				while (framesToWrite > 0) {
1387					int64 frames = 0;
1388					status_t err = fPlayTrack->ReadFrames(data, &frames);
1389					if (frames <= 0 || err != B_OK) {
1390						if (err != B_OK)
1391							fprintf(stderr, "CopyTarget: ReadFrames failed\n");
1392						break;
1393					}
1394					file.Write(data, frames * frameSize);
1395					framesToWrite -= frames;
1396				}
1397
1398				file.Sync();
1399				free(data);
1400				BNodeInfo nodeInfo(&file);
1401				// set type
1402			}
1403		} else {
1404
1405		}
1406	}
1407}
1408