1/*
2 * Copyright 2005, J��r��me Duval. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Inspired by SoundCapture from Be newsletter (Media Kit Basics:
6 *	Consumers and Producers)
7 */
8
9#include <Application.h>
10#include <Alert.h>
11#include <Debug.h>
12#include <Screen.h>
13#include <Button.h>
14#include <CheckBox.h>
15#include <TextControl.h>
16#include <MenuField.h>
17#include <PopUpMenu.h>
18#include <MenuItem.h>
19#include <Box.h>
20#include <ScrollView.h>
21#include <Beep.h>
22#include <StringView.h>
23#include <String.h>
24#include <Slider.h>
25#include <Message.h>
26
27#include <Path.h>
28#include <FindDirectory.h>
29#include <MediaAddOn.h>
30
31#include <SoundPlayer.h>
32
33#include <assert.h>
34#include <stdio.h>
35#include <string.h>
36#include <stdlib.h>
37#include <ctype.h>
38#include <unistd.h>
39#include <fcntl.h>
40
41#include <MediaRoster.h>
42#include <TimeSource.h>
43#include <NodeInfo.h>
44
45#include "RecorderWindow.h"
46#include "SoundConsumer.h"
47#include "FileUtils.h"
48
49#if ! NDEBUG
50#define FPRINTF(args) fprintf args
51#else
52#define FPRINTF(args)
53#endif
54
55#define DEATH FPRINTF
56#define CONNECT FPRINTF
57#define WINDOW FPRINTF
58
59#undef B_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	fRecordNode = 0;
127	fRecording = false;
128	fTempCount = -1;
129	fButtonState = btnPaused;
130
131	CalcSizes(MIN_WIDTH, MIN_HEIGHT);
132
133	fInitCheck = InitWindow();
134	if (fInitCheck != B_OK) {
135		if (fInitCheck == B_NAME_NOT_FOUND)
136			ErrorAlert(B_TRANSLATE("Cannot find default audio hardware"),
137				fInitCheck);
138		else
139			ErrorAlert(B_TRANSLATE("Cannot connect to media server"),
140				fInitCheck);
141		PostMessage(B_QUIT_REQUESTED);
142	} else
143		Show();
144}
145
146
147RecorderWindow::~RecorderWindow()
148{
149	//	The sound consumer and producer are Nodes; it has to be released and the Roster
150	//	will reap it when it's done.
151	if (fRecordNode)
152		fRecordNode->Release();
153	delete fPlayer;
154
155	if (fPlayTrack && fPlayFile)
156		fPlayFile->ReleaseTrack(fPlayTrack);
157	if (fPlayFile)
158		delete fPlayFile;
159	fPlayTrack = NULL;
160	fPlayFile = NULL;
161
162	//	Clean up items in list view.
163	if (fSoundList) {
164		fSoundList->DeselectAll();
165		for (int i = 0; i < fSoundList->CountItems(); i++) {
166			WINDOW((stderr, "clean up item %d\n", i+1));
167			SoundListItem* item = dynamic_cast<SoundListItem *>(fSoundList->ItemAt(i));
168			if (item) {
169				if (item->IsTemp())
170					item->Entry().Remove();	//	delete temp file
171				delete item;
172			}
173		}
174		fSoundList->MakeEmpty();
175	}
176	//	Clean up currently recording file, if any.
177	fRecEntry.Remove();
178	fRecEntry.Unset();
179
180	delete fSavePanel;
181}
182
183
184status_t
185RecorderWindow::InitCheck()
186{
187	return fInitCheck;
188}
189
190
191void
192RecorderWindow::CalcSizes(float min_width, float min_height)
193{
194	//	Set up size limits based on new screen size
195	BScreen screen(this);
196	BRect rect = screen.Frame();
197	float width = rect.Width() - 12;
198	SetSizeLimits(min_width, width, min_height, rect.Height() - 24);
199
200	//	Don't zoom to cover all of screen; user can resize last bit if necessary.
201	//	This leaves other windows visible.
202	if (width > 640)
203		width = 640 + (width - 640) / 2;
204	SetZoomLimits(width, rect.Height() - 24);
205}
206
207
208status_t
209RecorderWindow::InitWindow()
210{
211	BPopUpMenu * popup = 0;
212	status_t error;
213
214	try {
215		//	Find temp directory for recorded sounds.
216		BPath path;
217		if (!(error = find_directory(B_COMMON_TEMP_DIRECTORY, &path)))
218			error = fTempDir.SetTo(path.Path());
219		if (error < 0)
220			goto bad_mojo;
221
222		//	Make sure the media roster is there (which means the server is there).
223		fRoster = BMediaRoster::Roster(&error);
224		if (!fRoster)
225			goto bad_mojo;
226
227		error = fRoster->GetAudioInput(&fAudioInputNode);
228		if (error < B_OK) //	there's no input?
229			goto bad_mojo;
230
231		error = fRoster->GetAudioMixer(&fAudioMixerNode);
232		if (error < B_OK) //	there's no mixer?
233			goto bad_mojo;
234
235		//	Create our internal Node which records sound, and register it.
236		fRecordNode = new SoundConsumer("Sound Recorder");
237		error = fRoster->RegisterNode(fRecordNode);
238		if (error < B_OK)
239			goto bad_mojo;
240
241		//	Create the window header with controls
242		BRect r(Bounds());
243		r.bottom = r.top + 175;
244		BBox *background = new BBox(r, "_background", B_FOLLOW_LEFT_RIGHT
245			| B_FOLLOW_TOP, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER);
246		AddChild(background);
247
248		r = background->Bounds();
249		r.left = 0;
250		r.right = r.left + 38;
251		r.bottom = r.top + 104;
252		fVUView = new VUView(r, B_FOLLOW_LEFT|B_FOLLOW_TOP);
253		background->AddChild(fVUView);
254
255		r = background->Bounds();
256		r.left = r.left + 40;
257		r.bottom = r.top + 104;
258		fScopeView = new ScopeView(r, B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP);
259		background->AddChild(fScopeView);
260
261		r = background->Bounds();
262		r.left = 2;
263		r.right -= 26;
264		r.top = 115;
265		r.bottom = r.top + 30;
266		fTrackSlider = new TrackSlider(r, "trackSlider", new BMessage(POSITION_CHANGED),
267			B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
268		background->AddChild(fTrackSlider);
269
270		BRect buttonRect;
271
272		//	Button for rewinding
273		buttonRect = BRect(BPoint(0,0), kSkipButtonSize);
274		buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-7, 25));
275		fRewindButton = new TransportButton(buttonRect, B_TRANSLATE("Rewind"),
276			kSkipBackBitmapBits, kPressedSkipBackBitmapBits,
277			kDisabledSkipBackBitmapBits, new BMessage(REWIND));
278		background->AddChild(fRewindButton);
279
280		//	Button for stopping recording or playback
281		buttonRect = BRect(BPoint(0,0), kStopButtonSize);
282		buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-48, 25));
283		fStopButton = new TransportButton(buttonRect, B_TRANSLATE("Stop"),
284			kStopButtonBitmapBits, kPressedStopButtonBitmapBits,
285			kDisabledStopButtonBitmapBits, new BMessage(STOP));
286		background->AddChild(fStopButton);
287
288		//	Button for starting playback of selected sound
289		BRect playRect(BPoint(0,0), kPlayButtonSize);
290		playRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-82, 25));
291		fPlayButton = new PlayPauseButton(playRect, B_TRANSLATE("Play"),
292			new BMessage(PLAY), new BMessage(PLAY_PERIOD), ' ', 0);
293		background->AddChild(fPlayButton);
294
295		//	Button for forwarding
296		buttonRect = BRect(BPoint(0,0), kSkipButtonSize);
297		buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-133, 25));
298		fForwardButton = new TransportButton(buttonRect, B_TRANSLATE("Forward"),
299			kSkipForwardBitmapBits, kPressedSkipForwardBitmapBits,
300			kDisabledSkipForwardBitmapBits, new BMessage(FORWARD));
301		background->AddChild(fForwardButton);
302
303		//	Button to start recording (or waiting for sound)
304		buttonRect = BRect(BPoint(0,0), kRecordButtonSize);
305		buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-174, 25));
306		fRecordButton = new RecordButton(buttonRect, B_TRANSLATE("Record"),
307			new BMessage(RECORD), new BMessage(RECORD_PERIOD));
308		background->AddChild(fRecordButton);
309
310		//	Button for saving selected sound
311		buttonRect = BRect(BPoint(0,0), kDiskButtonSize);
312		buttonRect.OffsetTo(background->Bounds().LeftBottom() - BPoint(-250, 21));
313		fSaveButton = new TransportButton(buttonRect, B_TRANSLATE("Save"),
314			kDiskButtonBitmapsBits, kPressedDiskButtonBitmapsBits,
315			kDisabledDiskButtonBitmapsBits, new BMessage(SAVE));
316		fSaveButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
317		background->AddChild(fSaveButton);
318
319		//	Button Loop
320		buttonRect = BRect(BPoint(0,0), kArrowSize);
321		buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(23, 48));
322		fLoopButton = new DrawButton(buttonRect, B_TRANSLATE("Loop"),
323			kLoopArrowBits, kArrowBits, new BMessage(LOOP));
324		fLoopButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
325		fLoopButton->SetTarget(this);
326		background->AddChild(fLoopButton);
327
328		buttonRect = BRect(BPoint(0,0), kSpeakerIconBitmapSize);
329		buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(121, 17));
330		SpeakerView *speakerView = new SpeakerView(buttonRect,
331			B_FOLLOW_LEFT | B_FOLLOW_TOP);
332		speakerView->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
333		background->AddChild(speakerView);
334
335		buttonRect = BRect(BPoint(0,0), BPoint(84, 19));
336		buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(107, 20));
337		fVolumeSlider = new VolumeSlider(buttonRect, "volumeSlider",
338			B_FOLLOW_LEFT | B_FOLLOW_TOP);
339		fVolumeSlider->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
340		background->AddChild(fVolumeSlider);
341
342		// Button to mask/see sounds list
343		buttonRect = BRect(BPoint(0,0), kUpDownButtonSize);
344		buttonRect.OffsetTo(background->Bounds().RightBottom() - BPoint(21, 25));
345		fUpDownButton = new UpDownButton(buttonRect, new BMessage(VIEW_LIST));
346		fUpDownButton->SetResizingMode(B_FOLLOW_RIGHT | B_FOLLOW_TOP);
347		background->AddChild(fUpDownButton);
348
349		r = Bounds();
350		r.top = background->Bounds().bottom + 1;
351		fBottomBox = new BBox(r, "bottomBox", B_FOLLOW_ALL);
352		fBottomBox->SetBorder(B_NO_BORDER);
353		AddChild(fBottomBox);
354
355		//	The actual list of recorded sounds (initially empty) sits
356		//	below the header with the controls.
357		r = fBottomBox->Bounds();
358		r.left += 190;
359		r.InsetBy(10, 10);
360		r.left -= 10;
361		r.top += 4;
362		r.right -= B_V_SCROLL_BAR_WIDTH;
363		r.bottom -= 25;
364		fSoundList = new SoundListView(r, B_TRANSLATE("Sound List"),
365			B_FOLLOW_ALL);
366		fSoundList->SetSelectionMessage(new BMessage(SOUND_SELECTED));
367		fSoundList->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
368		BScrollView *scroller = new BScrollView("scroller", fSoundList,
369			B_FOLLOW_ALL, 0, false, true, B_FANCY_BORDER);
370		fBottomBox->AddChild(scroller);
371
372		r = fBottomBox->Bounds();
373		r.right = r.left + 190;
374		r.bottom -= 25;
375		r.InsetBy(10, 8);
376		r.top -= 1;
377		fFileInfoBox = new BBox(r, "fileinfo", B_FOLLOW_LEFT);
378		fFileInfoBox->SetLabel(B_TRANSLATE("File info"));
379
380		fFileInfoBox->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
381
382		BFont font = be_plain_font;
383		font.SetSize(font.Size() * 0.92f);
384		font_height height;
385		font.GetHeight(&height);
386		float fontHeight = height.ascent + height.leading + height.descent;
387
388		r = fFileInfoBox->Bounds();
389		r.left = 8;
390		r.top = fontHeight + 6;
391		r.bottom = r.top + fontHeight + 3;
392		r.right -= 10;
393		fFilename = new BStringView(r, "filename", B_TRANSLATE("File name:"));
394		fFileInfoBox->AddChild(fFilename);
395		fFilename->SetFont(&font, B_FONT_SIZE);
396		fFilename->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
397
398		r.top += fontHeight;
399		r.bottom = r.top + fontHeight + 3;
400		fFormat = new BStringView(r, "format", B_TRANSLATE("Format:"));
401		fFileInfoBox->AddChild(fFormat);
402		fFormat->SetFont(&font, B_FONT_SIZE);
403		fFormat->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
404
405		r.top += fontHeight;
406		r.bottom = r.top + fontHeight + 3;
407		fCompression = new BStringView(r, "compression",
408			B_TRANSLATE("Compression:"));
409		fFileInfoBox->AddChild(fCompression);
410		fCompression->SetFont(&font, B_FONT_SIZE);
411		fCompression->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
412
413		r.top += fontHeight;
414		r.bottom = r.top + fontHeight + 3;
415		fChannels = new BStringView(r, "channels", B_TRANSLATE("Channels:"));
416		fFileInfoBox->AddChild(fChannels);
417		fChannels->SetFont(&font, B_FONT_SIZE);
418		fChannels->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
419
420		r.top += fontHeight;
421		r.bottom = r.top + fontHeight + 3;
422		fSampleSize = new BStringView(r, "samplesize",
423			B_TRANSLATE("Sample size:"));
424		fFileInfoBox->AddChild(fSampleSize);
425		fSampleSize->SetFont(&font, B_FONT_SIZE);
426		fSampleSize->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
427
428		r.top += fontHeight;
429		r.bottom = r.top + fontHeight + 3;
430		fSampleRate = new BStringView(r, "samplerate",
431			B_TRANSLATE("Sample rate:"));
432		fFileInfoBox->AddChild(fSampleRate);
433		fSampleRate->SetFont(&font, B_FONT_SIZE);
434		fSampleRate->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
435
436		r.top += fontHeight;
437		r.bottom = r.top + fontHeight + 3;
438		fDuration = new BStringView(r, "duration", B_TRANSLATE("Duration:"));
439		fFileInfoBox->AddChild(fDuration);
440		fDuration->SetFont(&font, B_FONT_SIZE);
441		fDuration->SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
442
443		fFileInfoBox->ResizeTo(fFileInfoBox->Frame().Width(),
444			r.bottom + fontHeight / 2.0f);
445		fDeployedHeight = MIN_HEIGHT + fFileInfoBox->Bounds().Height() + 40.0f;
446
447		//	Input selection lists all available physical inputs that produce
448		//	buffers with B_MEDIA_RAW_AUDIO format data.
449		popup = new BPopUpMenu(B_TRANSLATE("Input"));
450		const int maxInputCount = 64;
451		dormant_node_info dni[maxInputCount];
452
453		int32 real_count = maxInputCount;
454		media_format output_format;
455		output_format.type = B_MEDIA_RAW_AUDIO;
456		output_format.u.raw_audio = media_raw_audio_format::wildcard;
457		error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format,
458			0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT);
459		if (real_count > maxInputCount) {
460			WINDOW((stderr, "dropped %" B_PRId32 " inputs\n", real_count - maxInputCount));
461			real_count = maxInputCount;
462		}
463		char selected_name[B_MEDIA_NAME_LENGTH] = "Default input";
464		BMessage * msg;
465		BMenuItem * item;
466		for (int i = 0; i < real_count; i++) {
467			msg = new BMessage(INPUT_SELECTED);
468			msg->AddData("node", B_RAW_TYPE, &dni[i], sizeof(dni[i]));
469			item = new BMenuItem(dni[i].name, msg);
470			popup->AddItem(item);
471			media_node_id ni[12];
472			int32 ni_count = 12;
473			error = fRoster->GetInstancesFor(dni[i].addon, dni[i].flavor_id,
474				ni, &ni_count);
475			if (error == B_OK) {
476				for (int j = 0; j < ni_count; j++) {
477					if (ni[j] == fAudioInputNode.node) {
478						strcpy(selected_name, dni[i].name);
479						break;
480					}
481				}
482			}
483		}
484
485		//	Create the actual widget
486		r = fFileInfoBox->Bounds();
487		r.top = r.bottom + 2;
488		r.bottom = r.top + 18;
489		r.InsetBy(10, 10);
490		fInputField = new BMenuField(r, "Input", B_TRANSLATE("Input:"), popup);
491		fInputField->SetDivider(fInputField->StringWidth(B_TRANSLATE("Input:"))
492			+ 4.0f);
493		fBottomBox->AddChild(fInputField);
494
495		fBottomBox->AddChild(fFileInfoBox);
496
497		fBottomBox->Hide();
498		CalcSizes(MIN_WIDTH, MIN_HEIGHT);
499		ResizeTo(Frame().Width(), MIN_HEIGHT);
500
501		popup->Superitem()->SetLabel(selected_name);
502
503		// Make sure the save panel is happy.
504		fSavePanel = new BFilePanel(B_SAVE_PANEL);
505		fSavePanel->SetTarget(this);
506	}
507	catch (...) {
508		goto bad_mojo;
509	}
510	UpdateButtons();
511	return B_OK;
512
513	//	Error handling.
514bad_mojo:
515	if (error >= 0)
516		error = B_ERROR;
517	if (fRecordNode)
518		fRecordNode->Release();
519
520	delete fPlayer;
521	if (!fInputField)
522		delete popup;
523	return error;
524}
525
526
527bool
528RecorderWindow::QuitRequested()	//	this means Close pressed
529{
530	StopRecording();
531	StopPlaying();
532	be_app->PostMessage(B_QUIT_REQUESTED);
533	return true;
534}
535
536
537void
538RecorderWindow::MessageReceived(BMessage * message)
539{
540	//	Your average generic message dispatching switch() statement.
541	switch (message->what) {
542	case INPUT_SELECTED:
543		Input(message);
544		break;
545	case SOUND_SELECTED:
546		Selected(message);
547		break;
548	case STOP_PLAYING:
549		StopPlaying();
550		break;
551	case STOP_RECORDING:
552		StopRecording();
553		break;
554	case PLAY_PERIOD:
555		if (fPlayer) {
556			if (fPlayer->HasData())
557				fPlayButton->SetPlaying();
558			else
559				fPlayButton->SetPaused();
560		}
561		break;
562	case RECORD_PERIOD:
563		fRecordButton->SetRecording();
564		break;
565	case RECORD:
566		Record(message);
567		break;
568	case STOP:
569		Stop(message);
570		break;
571	case PLAY:
572		Play(message);
573		break;
574	case SAVE:
575		Save(message);
576		break;
577	case B_SAVE_REQUESTED:
578		DoSave(message);
579		break;
580	case VIEW_LIST:
581		if (fUpDownButton->Value() == B_CONTROL_ON) {
582			fBottomBox->Show();
583			CalcSizes(MIN_WIDTH, fDeployedHeight);
584			ResizeTo(Frame().Width(), fDeployedHeight);
585		} else {
586			fBottomBox->Hide();
587			CalcSizes(MIN_WIDTH, MIN_HEIGHT);
588			ResizeTo(Frame().Width(), MIN_HEIGHT);
589
590		}
591		break;
592	case UPDATE_TRACKSLIDER:
593		{
594			bigtime_t timestamp = fPlayTrack->CurrentTime();
595			fTrackSlider->SetMainTime(timestamp, false);
596			fScopeView->SetMainTime(timestamp);
597		}
598		break;
599	case POSITION_CHANGED:
600		{
601			bigtime_t right, left, main;
602			if (message->FindInt64("main", &main) == B_OK) {
603				if (fPlayTrack) {
604					fPlayTrack->SeekToTime(fTrackSlider->MainTime());
605					fPlayFrame = fPlayTrack->CurrentFrame();
606				}
607				fScopeView->SetMainTime(main);
608			}
609			if (message->FindInt64("right", &right) == B_OK) {
610				if (fPlayTrack) {
611					fPlayLimit = MIN(fPlayFrames,
612						(off_t)(right * fPlayFormat.u.raw_audio.frame_rate
613							/ 1000000LL));
614				}
615				fScopeView->SetRightTime(right);
616			}
617			if (message->FindInt64("left", &left) == B_OK)
618				fScopeView->SetLeftTime(left);
619			break;
620		}
621	case LOOP:
622		fLooping = fLoopButton->ButtonState();
623		break;
624	case B_SIMPLE_DATA:
625	case B_REFS_RECEIVED:
626		{
627			RefsReceived(message);
628			break;
629		}
630	case B_COPY_TARGET:
631		CopyTarget(message);
632		break;
633	default:
634		BWindow::MessageReceived(message);
635		break;
636	}
637}
638
639
640void
641RecorderWindow::Record(BMessage * message)
642{
643	//	User pressed Record button
644	fRecording = true;
645	if (fButtonState != btnPaused) {
646		StopRecording();
647		return;			//	user is too fast on the mouse
648	}
649	SetButtonState(btnRecording);
650	fRecordButton->SetRecording();
651
652	char name[256];
653	//	Create a file with a temporary name
654	status_t err = NewTempName(name);
655	if (err < B_OK) {
656		ErrorAlert(B_TRANSLATE("Cannot find an unused name to use for the "
657			"new recording"), err);
658		return;
659	}
660	//	Find the file so we can refer to it later
661	err = fTempDir.FindEntry(name, &fRecEntry);
662	if (err < B_OK) {
663		ErrorAlert(B_TRANSLATE("Cannot find the temporary file created to "
664			"hold the new recording"), err);
665		return;
666	}
667	err = fRecFile.SetTo(&fTempDir, name, O_RDWR);
668	if (err < B_OK) {
669		ErrorAlert(B_TRANSLATE("Cannot open the temporary file created to "
670			"hold the new recording"), err);
671		fRecEntry.Unset();
672		return;
673	}
674	//	Reserve space on disk (creates fewer fragments)
675	err = fRecFile.SetSize(4 * fRecordFormat.u.raw_audio.channel_count
676		* fRecordFormat.u.raw_audio.frame_rate
677		* (fRecordFormat.u.raw_audio.format
678			& media_raw_audio_format::B_AUDIO_SIZE_MASK));
679	if (err < B_OK) {
680		ErrorAlert(B_TRANSLATE("Cannot record a sound that long"), err);
681		fRecEntry.Remove();
682		fRecEntry.Unset();
683		return;
684	}
685	fRecSize = 0;
686
687	fRecFile.Seek(sizeof(struct wave_struct), SEEK_SET);
688
689	//	Hook up input
690	err = MakeRecordConnection(fAudioInputNode);
691	if (err < B_OK) {
692		ErrorAlert(B_TRANSLATE("Cannot connect to the selected sound input"),
693			err);
694		fRecEntry.Remove();
695		fRecEntry.Unset();
696		return;
697	}
698
699	//	And get it going...
700	bigtime_t then = fRecordNode->TimeSource()->Now() + 50000LL;
701	fRoster->StartNode(fRecordNode->Node(), then);
702	if (fAudioInputNode.kind & B_TIME_SOURCE) {
703		fRoster->StartNode(fAudioInputNode,
704			fRecordNode->TimeSource()->RealTimeFor(then, 0));
705	} else
706		fRoster->StartNode(fAudioInputNode, then);
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	//	Find an available output for the given input node.
875	int32 count = 0;
876	status_t err = fRoster->GetFreeOutputsFor(input, &fAudioOutput, 1, &count, B_MEDIA_RAW_AUDIO);
877	if (err < B_OK) {
878		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
879			" couldn't get free outputs from audio input node\n"));
880		return err;
881	}
882	if (count < 1) {
883		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
884			" no free outputs from audio input node\n"));
885		return B_BUSY;
886	}
887
888	//	Find an available input for our own Node. Note that we go through the
889	//	MediaRoster; calling Media Kit methods directly on Nodes in our app is
890	//	not OK (because synchronization happens in the service thread, not in
891	//	the calling thread).
892	// TODO: explain this
893	err = fRoster->GetFreeInputsFor(fRecordNode->Node(), &fRecInput, 1, &count, B_MEDIA_RAW_AUDIO);
894	if (err < B_OK) {
895		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
896			" couldn't get free inputs for sound recorder\n"));
897		return err;
898	}
899	if (count < 1) {
900		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
901			" no free inputs for sound recorder\n"));
902		return B_BUSY;
903	}
904
905	//	Find out what the time source of the input is.
906	//	For most nodes, we just use the preferred time source (the DAC) for synchronization.
907	//	However, nodes that record from an input need to synchronize to the audio input node
908	//	instead for best results.
909	//	MakeTimeSourceFor gives us a "clone" of the time source node that we can manipulate
910	//	to our heart's content. When we're done with it, though, we need to call Release()
911	//	on the time source node, so that it keeps an accurate reference count and can delete
912	//	itself when it's no longer needed.
913	// TODO: what about filters connected to audio input?
914	media_node use_time_source;
915	BTimeSource * tsobj = fRoster->MakeTimeSourceFor(input);
916	if (! tsobj) {
917		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
918			" couldn't clone time source from audio input node\n"));
919		return B_MEDIA_BAD_NODE;
920	}
921
922	//	Apply the time source in effect to our own Node.
923	err = fRoster->SetTimeSourceFor(fRecordNode->Node().node, tsobj->Node().node);
924	if (err < B_OK) {
925		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
926			" couldn't set the sound recorder's time source\n"));
927		tsobj->Release();
928		return err;
929	}
930
931	//	Get a format, any format.
932	fRecordFormat.u.raw_audio = fAudioOutput.format.u.raw_audio;
933	fRecordFormat.type = B_MEDIA_RAW_AUDIO;
934
935	//	Tell the consumer where we want data to go.
936	err = fRecordNode->SetHooks(RecordFile, NotifyRecordFile, this);
937	if (err < B_OK) {
938		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
939			" couldn't set the sound recorder's hook functions\n"));
940		tsobj->Release();
941		return err;
942	}
943
944	//	Using the same structs for input and output is OK in
945	//  BMediaRoster::Connect().
946	err = fRoster->Connect(fAudioOutput.source, fRecInput.destination,
947		&fRecordFormat, &fAudioOutput, &fRecInput);
948	if (err < B_OK) {
949		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
950			" failed to connect sound recorder to audio input node.\n"));
951		tsobj->Release();
952		fRecordNode->SetHooks(0, 0, 0);
953		return err;
954	}
955
956	//	Start the time source if it's not running.
957	if ((tsobj->Node() != input) && !tsobj->IsRunning())
958		fRoster->StartNode(tsobj->Node(), BTimeSource::RealTime());
959
960	tsobj->Release();	//	we're done with this time source instance!
961	return B_OK;
962}
963
964
965status_t
966RecorderWindow::BreakRecordConnection()
967{
968	status_t err;
969
970	//	If we are the last connection, the Node will stop automatically since it
971	//	has nowhere to send data to.
972	err = fRoster->StopNode(fRecInput.node, 0);
973	err = fRoster->Disconnect(fAudioOutput.node.node, fAudioOutput.source,
974		fRecInput.node.node, fRecInput.destination);
975	fAudioOutput.source = media_source::null;
976	fRecInput.destination = media_destination::null;
977	return err;
978}
979
980
981status_t
982RecorderWindow::StopRecording()
983{
984	if (!fRecording)
985		return B_OK;
986	fRecording = false;
987	BreakRecordConnection();
988	fRecordNode->SetHooks(NULL,NULL,NULL);
989	if (fRecSize > 0) {
990
991		wave_struct header;
992		header.riff.riff_id = FOURCC('R','I','F','F');
993		header.riff.len = fRecSize + sizeof(header) - 8;
994		header.riff.wave_id = FOURCC('W','A','V','E');
995		header.format_chunk.fourcc = FOURCC('f','m','t',' ');
996		header.format_chunk.len = sizeof(header.format);
997		header.format.format_tag = 1;
998		header.format.channels = fRecordFormat.u.raw_audio.channel_count;
999		header.format.samples_per_sec = (uint32)fRecordFormat.u.raw_audio.frame_rate;
1000		header.format.avg_bytes_per_sec = (uint32)(fRecordFormat.u.raw_audio.frame_rate
1001			* fRecordFormat.u.raw_audio.channel_count
1002			* (fRecordFormat.u.raw_audio.format & 0xf));
1003		header.format.bits_per_sample = (fRecordFormat.u.raw_audio.format & 0xf) * 8;
1004		header.format.block_align = (fRecordFormat.u.raw_audio.format & 0xf)
1005			* fRecordFormat.u.raw_audio.channel_count;
1006		header.data_chunk.fourcc = FOURCC('d','a','t','a');
1007		header.data_chunk.len = fRecSize;
1008		fRecFile.Seek(0, SEEK_SET);
1009		fRecFile.Write(&header, sizeof(header));
1010
1011		fRecFile.SetSize(fRecSize + sizeof(header));
1012		//	We reserve space; make sure we cut off any excess at the end.
1013		AddSoundItem(fRecEntry, true);
1014	} else
1015		fRecEntry.Remove();
1016
1017	//	We're done for this time.
1018	fRecEntry.Unset();
1019	//	Close the file.
1020	fRecFile.Unset();
1021	//	No more recording going on.
1022	fRecSize = 0;
1023	SetButtonState(btnPaused);
1024	fRecordButton->SetStopped();
1025
1026	return B_OK;
1027}
1028
1029
1030status_t
1031RecorderWindow::StopPlaying()
1032{
1033	if (fPlayer) {
1034		fPlayer->Stop();
1035		fPlayer->SetCallbacks(0, 0, 0);
1036		fVolumeSlider->SetSoundPlayer(NULL);
1037		delete fPlayer;
1038		fPlayer = NULL;
1039	}
1040	SetButtonState(btnPaused);
1041	fPlayButton->SetStopped();
1042	fTrackSlider->ResetMainTime();
1043	fScopeView->SetMainTime(*fTrackSlider->MainTime());
1044	return B_OK;
1045}
1046
1047
1048void
1049RecorderWindow::SetButtonState(BtnState state)
1050{
1051	fButtonState = state;
1052	UpdateButtons();
1053}
1054
1055
1056void
1057RecorderWindow::UpdateButtons()
1058{
1059	bool hasSelection = (fSoundList->CurrentSelection() >= 0);
1060	fRecordButton->SetEnabled(fButtonState != btnPlaying);
1061	fPlayButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
1062	fRewindButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
1063	fForwardButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
1064	fStopButton->SetEnabled(fButtonState != btnPaused);
1065	fSaveButton->SetEnabled(hasSelection && (fButtonState != btnRecording));
1066	fInputField->SetEnabled(fButtonState != btnRecording);
1067}
1068
1069#ifndef __HAIKU__
1070extern "C" status_t DecodedFormat__11BMediaTrackP12media_format(
1071	BMediaTrack *self, media_format *inout_format);
1072#endif
1073
1074
1075status_t
1076RecorderWindow::UpdatePlayFile(SoundListItem* item, bool updateDisplay)
1077{
1078	fScopeView->CancelRendering();
1079	StopPlaying();
1080	StopRecording();
1081
1082	if (fPlayTrack && fPlayFile) {
1083		fPlayFile->ReleaseTrack(fPlayTrack);
1084		fPlayTrack = NULL;
1085	}
1086	if (fPlayFile) {
1087		delete fPlayFile;
1088		fPlayFile = NULL;
1089	}
1090
1091	status_t err;
1092	BEntry& entry = item->Entry();
1093	entry_ref ref;
1094	entry.GetRef(&ref);
1095	fPlayFile = new BMediaFile(&ref); //, B_MEDIA_FILE_UNBUFFERED);
1096	if ((err = fPlayFile->InitCheck()) < B_OK) {
1097		delete fPlayFile;
1098		fPlayFile = NULL;
1099		return err;
1100	}
1101
1102	for (int ix=0; ix < fPlayFile->CountTracks(); ix++) {
1103		BMediaTrack * track = fPlayFile->TrackAt(ix);
1104		fPlayFormat.type = B_MEDIA_RAW_AUDIO;
1105#ifdef __HAIKU__
1106		if ((track->DecodedFormat(&fPlayFormat) == B_OK)
1107#else
1108		if ((DecodedFormat__11BMediaTrackP12media_format(track, &fPlayFormat) == B_OK)
1109#endif
1110			&& (fPlayFormat.type == B_MEDIA_RAW_AUDIO)) {
1111			fPlayTrack = track;
1112			break;
1113		}
1114		if (track)
1115			fPlayFile->ReleaseTrack(track);
1116	}
1117
1118	if (!fPlayTrack) {
1119		delete fPlayFile;
1120		fPlayFile = NULL;
1121		return B_STREAM_NOT_FOUND;
1122	}
1123
1124	if (!updateDisplay)
1125		return B_OK;
1126
1127	BString filename = B_TRANSLATE("File name: ");
1128	filename << ref.name;
1129	fFilename->SetText(filename.String());
1130
1131	BString format = B_TRANSLATE("Format: ");
1132	media_file_format file_format;
1133	if (fPlayFile->GetFileFormatInfo(&file_format) == B_OK)
1134		format << file_format.short_name;
1135	BString compression = B_TRANSLATE("Compression: ");
1136	media_codec_info codec_info;
1137	if (fPlayTrack->GetCodecInfo(&codec_info) == B_OK) {
1138		if (strcmp(codec_info.short_name, "raw")==0)
1139			compression << B_TRANSLATE("None");
1140		else
1141			compression << codec_info.short_name;
1142	}
1143	BString channels = B_TRANSLATE("Channels: ");
1144	channels << fPlayFormat.u.raw_audio.channel_count;
1145	BString samplesize = B_TRANSLATE("Sample size: ");
1146	samplesize << 8 * (fPlayFormat.u.raw_audio.format & 0xf)
1147		<< B_TRANSLATE(" bits");
1148	BString samplerate = B_TRANSLATE("Sample rate: ");
1149	samplerate << (int)fPlayFormat.u.raw_audio.frame_rate;
1150	BString durationString = B_TRANSLATE("Duration: ");
1151	bigtime_t duration = fPlayTrack->Duration();
1152	durationString << (float)(duration / 1000000.0) << B_TRANSLATE(" seconds");
1153
1154	fFormat->SetText(format.String());
1155	fCompression->SetText(compression.String());
1156	fChannels->SetText(channels.String());
1157	fSampleSize->SetText(samplesize.String());
1158	fSampleRate->SetText(samplerate.String());
1159	fDuration->SetText(durationString.String());
1160
1161	fTrackSlider->SetTotalTime(duration, true);
1162	fScopeView->SetTotalTime(duration, true);
1163	fScopeView->RenderTrack(fPlayTrack, fPlayFormat);
1164
1165	fPlayFrames = fPlayTrack->CountFrames();
1166	return B_OK;
1167}
1168
1169
1170void
1171RecorderWindow::ErrorAlert(const char * action, status_t err)
1172{
1173	char msg[300];
1174	if (err != B_OK)
1175		sprintf(msg, "%s: %s. [%" B_PRIx32 "]", action, strerror(err), (int32) err);
1176	else
1177		sprintf(msg, "%s.", action);
1178	BAlert* alert = new BAlert("", msg, B_TRANSLATE("Stop"));
1179	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1180	alert->Go();
1181}
1182
1183
1184status_t
1185RecorderWindow::NewTempName(char * name)
1186{
1187	int init_count = fTempCount;
1188again:
1189	if (fTempCount-init_count > 25) {
1190		return B_ERROR;
1191	}
1192	else {
1193		fTempCount++;
1194		if (fTempCount==0)
1195			sprintf(name, "Audio Clip");
1196		else
1197			sprintf(name, "Audio Clip %d", fTempCount);
1198		BPath path;
1199		status_t err;
1200		BEntry tempEnt;
1201		if ((err = fTempDir.GetEntry(&tempEnt)) < B_OK) {
1202			return err;
1203		}
1204		if ((err = tempEnt.GetPath(&path)) < B_OK) {
1205			return err;
1206		}
1207		path.Append(name);
1208		int fd;
1209		//	Use O_EXCL so we know we created the file (sync with other instances)
1210		if ((fd = open(path.Path(), O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) {
1211			goto again;
1212		}
1213		close(fd);
1214	}
1215	return B_OK;
1216}
1217
1218
1219void
1220RecorderWindow::AddSoundItem(const BEntry& entry, bool temp)
1221{
1222	//	Create list item to display.
1223	SoundListItem * listItem = new SoundListItem(entry, temp);
1224	fSoundList->AddItem(listItem);
1225	fSoundList->Invalidate();
1226	fSoundList->Select(fSoundList->IndexOf(listItem));
1227}
1228
1229
1230void
1231RecorderWindow::RemoveCurrentSoundItem() {
1232	int32 index = fSoundList->CurrentSelection();
1233	BListItem *item = fSoundList->RemoveItem(index);
1234	delete item;
1235	if (index >= fSoundList->CountItems())
1236		index = fSoundList->CountItems() - 1;
1237	fSoundList->Select(index);
1238}
1239
1240
1241void
1242RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp,
1243	void* data, size_t size, const media_raw_audio_format &format)
1244{
1245	//	Callback called from the SoundConsumer when receiving buffers.
1246	RecorderWindow * window = (RecorderWindow *)cookie;
1247
1248	if (window->fRecording) {
1249		//	Write the data to file (we don't buffer or guard file access
1250		//	or anything)
1251		window->fRecFile.WriteAt(window->fRecSize, data, size);
1252		window->fVUView->ComputeLevels(data, size, format.format);
1253		window->fRecSize += size;
1254	}
1255}
1256
1257
1258void
1259RecorderWindow::NotifyRecordFile(void * cookie, int32 code, ...)
1260{
1261	if ((code == B_WILL_STOP) || (code == B_NODE_DIES)) {
1262		RecorderWindow * window = (RecorderWindow *)cookie;
1263		// Tell the window we've stopped, if it doesn't
1264		// already know.
1265		window->PostMessage(STOP_RECORDING);
1266	}
1267}
1268
1269
1270void
1271RecorderWindow::PlayFile(void * cookie, void * data, size_t size,
1272	const media_raw_audio_format & format)
1273{
1274	//	Callback called from the SoundProducer when producing buffers.
1275	RecorderWindow * window = (RecorderWindow *)cookie;
1276	int32 frame_size = (window->fPlayFormat.u.raw_audio.format & 0xf) *
1277		window->fPlayFormat.u.raw_audio.channel_count;
1278
1279	if ((window->fPlayFrame < window->fPlayLimit) || window->fLooping) {
1280		if (window->fPlayFrame >= window->fPlayLimit) {
1281			bigtime_t left = window->fTrackSlider->LeftTime();
1282			window->fPlayTrack->SeekToTime(&left);
1283			window->fPlayFrame = window->fPlayTrack->CurrentFrame();
1284		}
1285		int64 frames = 0;
1286		window->fPlayTrack->ReadFrames(data, &frames);
1287		window->fVUView->ComputeLevels(data, size / frame_size, format.format);
1288		window->fPlayFrame += size/frame_size;
1289		window->PostMessage(UPDATE_TRACKSLIDER);
1290	} else {
1291		//	we're done!
1292		window->PostMessage(STOP_PLAYING);
1293	}
1294}
1295
1296
1297void
1298RecorderWindow::NotifyPlayFile(void * cookie,
1299	BSoundPlayer::sound_player_notification code, ...)
1300{
1301	if ((code == BSoundPlayer::B_STOPPED) || (code == BSoundPlayer::B_SOUND_DONE)) {
1302		RecorderWindow * window = (RecorderWindow *)cookie;
1303		// tell the window we've stopped, if it doesn't
1304		// already know.
1305		window->PostMessage(STOP_PLAYING);
1306	}
1307}
1308
1309
1310void
1311RecorderWindow::RefsReceived(BMessage *msg)
1312{
1313	entry_ref ref;
1314	int32 i = 0;
1315	int32 countGood = 0;
1316	int32 countBad = 0;
1317
1318	while (msg->FindRef("refs", i++, &ref) == B_OK) {
1319
1320		BEntry entry(&ref, true);
1321		BPath path(&entry);
1322		BNode node(&entry);
1323
1324		if (node.IsFile()) {
1325			SoundListItem * listItem = new SoundListItem(entry, false);
1326			if (UpdatePlayFile(listItem) == B_OK) {
1327				fSoundList->AddItem(listItem);
1328				countGood++;
1329				continue;
1330			}
1331			delete listItem;
1332		} else if(node.IsDirectory()) {
1333
1334		}
1335		countBad++;
1336	}
1337
1338	if (countBad > 0 && countGood == 0) {
1339		BAlert* alert = new BAlert(B_TRANSLATE("Nothing to play"),
1340			B_TRANSLATE("None of the files appear to be audio files"),
1341			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
1342		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1343		alert->Go();
1344	} else if (countGood > 0) {
1345		if (countBad > 0) {
1346			BAlert* alert = new BAlert(B_TRANSLATE("Invalid audio files"),
1347			B_TRANSLATE("Some of the files don't appear to be audio files"),
1348				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
1349				B_WARNING_ALERT);
1350			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1351			alert->Go();
1352		}
1353		fSoundList->Select(fSoundList->CountItems() - 1);
1354	}
1355}
1356
1357
1358void
1359RecorderWindow::CopyTarget(BMessage *msg)
1360{
1361	const char *type = NULL;
1362	if (msg->FindString("be:types", &type) == B_OK) {
1363		if (!strcasecmp(type, B_FILE_MIME_TYPE)) {
1364			const char *name;
1365			entry_ref dir;
1366			if (msg->FindString("be:filetypes") == B_OK
1367				&& msg->FindString("name", &name) == B_OK
1368				&& msg->FindRef("directory", &dir) == B_OK) {
1369				BDirectory directory(&dir);
1370				BFile file(&directory, name, O_RDWR | O_TRUNC);
1371
1372				// seek time
1373				bigtime_t start = fTrackSlider->LeftTime();
1374
1375				// write data
1376				bigtime_t diffTime = fTrackSlider->RightTime()
1377					- fTrackSlider->LeftTime();
1378				int64 framesToWrite = (int64) (diffTime
1379					* fPlayFormat.u.raw_audio.frame_rate / 1000000LL);
1380				int32 frameSize = (fPlayFormat.u.raw_audio.format & 0xf)
1381					* fPlayFormat.u.raw_audio.channel_count;
1382
1383				wave_struct header;
1384				header.riff.riff_id = FOURCC('R','I','F','F');
1385				header.riff.len
1386					= (frameSize * framesToWrite) + sizeof(header) - 8;
1387				header.riff.wave_id = FOURCC('W','A','V','E');
1388				header.format_chunk.fourcc = FOURCC('f','m','t',' ');
1389				header.format_chunk.len = sizeof(header.format);
1390				header.format.format_tag = 1;
1391				header.format.channels = fPlayFormat.u.raw_audio.channel_count;
1392				header.format.samples_per_sec
1393					= (uint32)fPlayFormat.u.raw_audio.frame_rate;
1394				header.format.avg_bytes_per_sec
1395					= (uint32)(fPlayFormat.u.raw_audio.frame_rate
1396					* fPlayFormat.u.raw_audio.channel_count
1397					* (fPlayFormat.u.raw_audio.format & 0xf));
1398				header.format.bits_per_sample
1399					= (fPlayFormat.u.raw_audio.format & 0xf) * 8;
1400				header.format.block_align = frameSize;
1401				header.data_chunk.fourcc = FOURCC('d','a','t','a');
1402				header.data_chunk.len = frameSize * framesToWrite;
1403				file.Seek(0, SEEK_SET);
1404				file.Write(&header, sizeof(header));
1405
1406				char *data = (char *)malloc(fPlayFormat.u.raw_audio.buffer_size);
1407
1408				fPlayTrack->SeekToTime(&start);
1409				fPlayFrame = fPlayTrack->CurrentFrame();
1410				while (framesToWrite > 0) {
1411					int64 frames = 0;
1412					status_t err = fPlayTrack->ReadFrames(data, &frames);
1413					if (frames <= 0 || err != B_OK) {
1414						if (err != B_OK)
1415							fprintf(stderr, "CopyTarget: ReadFrames failed\n");
1416						break;
1417					}
1418					file.Write(data, frames * frameSize);
1419					framesToWrite -= frames;
1420				}
1421
1422				file.Sync();
1423				free(data);
1424				BNodeInfo nodeInfo(&file);
1425				// set type
1426			}
1427		} else {
1428
1429		}
1430	}
1431}
1432