xref: /haiku/src/apps/soundrecorder/RecorderWindow.cpp (revision f2b4344867e97c3f4e742a1b4a15e6879644601a)
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_TRANSLATE_CONTEXT
60 #define B_TRANSLATE_CONTEXT "RecorderWindow"
61 
62 
63 // default window positioning
64 static const float MIN_WIDTH = 400.0f;
65 static const float MIN_HEIGHT = 336.0f;
66 static const float XPOS = 100.0f;
67 static 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 
72 struct riff_struct
73 {
74 	uint32 riff_id; // 'RIFF'
75 	uint32 len;
76 	uint32 wave_id;	// 'WAVE'
77 };
78 
79 struct chunk_struct
80 {
81 	uint32 fourcc;
82 	uint32 len;
83 };
84 
85 struct 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 
96 struct 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 
105 RecorderWindow::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 
147 RecorderWindow::~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 
184 status_t
185 RecorderWindow::InitCheck()
186 {
187 	return fInitCheck;
188 }
189 
190 
191 void
192 RecorderWindow::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 
208 status_t
209 RecorderWindow::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(216, 216, 216);
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 | B_FOLLOW_BOTTOM);
378 		fFileInfoBox->SetLabel(B_TRANSLATE("File info"));
379 
380 		r = fFileInfoBox->Bounds();
381 		r.left = 8;
382 		r.top = 13;
383 		r.bottom = r.top + 15;
384 		r.right -= 10;
385 		fFilename = new BStringView(r, "filename", B_TRANSLATE("File name:"));
386 		fFileInfoBox->AddChild(fFilename);
387 		r.top += 13;
388 		r.bottom = r.top + 15;
389 		fFormat = new BStringView(r, "format", B_TRANSLATE("Format:"));
390 		fFileInfoBox->AddChild(fFormat);
391 		r.top += 13;
392 		r.bottom = r.top + 15;
393 		fCompression = new BStringView(r, "compression",
394 			B_TRANSLATE("Compression:"));
395 		fFileInfoBox->AddChild(fCompression);
396 		r.top += 13;
397 		r.bottom = r.top + 15;
398 		fChannels = new BStringView(r, "channels", B_TRANSLATE("Channels:"));
399 		fFileInfoBox->AddChild(fChannels);
400 		r.top += 13;
401 		r.bottom = r.top + 15;
402 		fSampleSize = new BStringView(r, "samplesize",
403 			B_TRANSLATE("Sample size:"));
404 		fFileInfoBox->AddChild(fSampleSize);
405 		r.top += 13;
406 		r.bottom = r.top + 15;
407 		fSampleRate = new BStringView(r, "samplerate",
408 			B_TRANSLATE("Sample rate:"));
409 		fFileInfoBox->AddChild(fSampleRate);
410 		r.top += 13;
411 		r.bottom = r.top + 15;
412 		fDuration = new BStringView(r, "duration", B_TRANSLATE("Duration:"));
413 		fFileInfoBox->AddChild(fDuration);
414 
415 		//	Input selection lists all available physical inputs that produce
416 		//	buffers with B_MEDIA_RAW_AUDIO format data.
417 		popup = new BPopUpMenu(B_TRANSLATE("Input"));
418 		const int maxInputCount = 64;
419 		dormant_node_info dni[maxInputCount];
420 
421 		int32 real_count = maxInputCount;
422 		media_format output_format;
423 		output_format.type = B_MEDIA_RAW_AUDIO;
424 		output_format.u.raw_audio = media_raw_audio_format::wildcard;
425 		error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format,
426 			0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT);
427 		if (real_count > maxInputCount) {
428 			WINDOW((stderr, "dropped %ld inputs\n", real_count - maxInputCount));
429 			real_count = maxInputCount;
430 		}
431 		char selected_name[B_MEDIA_NAME_LENGTH] = "Default input";
432 		BMessage * msg;
433 		BMenuItem * item;
434 		for (int i = 0; i < real_count; i++) {
435 			msg = new BMessage(INPUT_SELECTED);
436 			msg->AddData("node", B_RAW_TYPE, &dni[i], sizeof(dni[i]));
437 			item = new BMenuItem(dni[i].name, msg);
438 			popup->AddItem(item);
439 			media_node_id ni[12];
440 			int32 ni_count = 12;
441 			error = fRoster->GetInstancesFor(dni[i].addon, dni[i].flavor_id,
442 				ni, &ni_count);
443 			if (error == B_OK) {
444 				for (int j = 0; j < ni_count; j++) {
445 					if (ni[j] == fAudioInputNode.node) {
446 						strcpy(selected_name, dni[i].name);
447 						break;
448 					}
449 				}
450 			}
451 		}
452 
453 		//	Create the actual widget
454 		BRect frame(fBottomBox->Bounds());
455 		r = frame;
456 		r.left = 42;
457 		r.right = (r.left + r.right) / 2;
458 		r.InsetBy(10,10);
459 		r.top = r.bottom - 18;
460 		fInputField = new BMenuField(r, "Input", B_TRANSLATE("Input:"), popup);
461 		fInputField->SetDivider(fInputField->StringWidth(B_TRANSLATE("Input:"))
462 			+ 4.0f);
463 		fBottomBox->AddChild(fInputField);
464 
465 		fBottomBox->AddChild(fFileInfoBox);
466 
467 		fBottomBox->Hide();
468 		CalcSizes(Frame().Width(), MIN_HEIGHT-161);
469 		ResizeTo(Frame().Width(), MIN_HEIGHT-161);
470 
471 
472 		popup->Superitem()->SetLabel(selected_name);
473 
474 		// Make sure the save panel is happy.
475 		fSavePanel = new BFilePanel(B_SAVE_PANEL);
476 		fSavePanel->SetTarget(this);
477 	}
478 	catch (...) {
479 		goto bad_mojo;
480 	}
481 	UpdateButtons();
482 	return B_OK;
483 
484 	//	Error handling.
485 bad_mojo:
486 	if (error >= 0)
487 		error = B_ERROR;
488 	if (fRecordNode)
489 		fRecordNode->Release();
490 
491 	delete fPlayer;
492 	if (!fInputField)
493 		delete popup;
494 	return error;
495 }
496 
497 
498 bool
499 RecorderWindow::QuitRequested()	//	this means Close pressed
500 {
501 	StopRecording();
502 	StopPlaying();
503 	be_app->PostMessage(B_QUIT_REQUESTED);
504 	return true;
505 }
506 
507 
508 void
509 RecorderWindow::MessageReceived(BMessage * message)
510 {
511 	//	Your average generic message dispatching switch() statement.
512 	switch (message->what) {
513 	case INPUT_SELECTED:
514 		Input(message);
515 		break;
516 	case SOUND_SELECTED:
517 		Selected(message);
518 		break;
519 	case STOP_PLAYING:
520 		StopPlaying();
521 		break;
522 	case STOP_RECORDING:
523 		StopRecording();
524 		break;
525 	case PLAY_PERIOD:
526 		if (fPlayer) {
527 			if (fPlayer->HasData())
528 				fPlayButton->SetPlaying();
529 			else
530 				fPlayButton->SetPaused();
531 		}
532 		break;
533 	case RECORD_PERIOD:
534 		fRecordButton->SetRecording();
535 		break;
536 	case RECORD:
537 		Record(message);
538 		break;
539 	case STOP:
540 		Stop(message);
541 		break;
542 	case PLAY:
543 		Play(message);
544 		break;
545 	case SAVE:
546 		Save(message);
547 		break;
548 	case B_SAVE_REQUESTED:
549 		DoSave(message);
550 		break;
551 	case VIEW_LIST:
552 		if (fUpDownButton->Value() == B_CONTROL_ON) {
553 			fBottomBox->Show();
554 			CalcSizes(Frame().Width(), MIN_HEIGHT);
555 			ResizeTo(Frame().Width(), MIN_HEIGHT);
556 		} else {
557 			fBottomBox->Hide();
558 			CalcSizes(Frame().Width(), MIN_HEIGHT-161);
559 			ResizeTo(Frame().Width(), MIN_HEIGHT-161);
560 
561 		}
562 		break;
563 	case UPDATE_TRACKSLIDER:
564 		{
565 			bigtime_t timestamp = fPlayTrack->CurrentTime();
566 			fTrackSlider->SetMainTime(timestamp, false);
567 			fScopeView->SetMainTime(timestamp);
568 		}
569 		break;
570 	case POSITION_CHANGED:
571 		{
572 			bigtime_t right, left, main;
573 			if (message->FindInt64("main", &main) == B_OK) {
574 				if (fPlayTrack) {
575 					fPlayTrack->SeekToTime(fTrackSlider->MainTime());
576 					fPlayFrame = fPlayTrack->CurrentFrame();
577 				}
578 				fScopeView->SetMainTime(main);
579 			}
580 			if (message->FindInt64("right", &right) == B_OK) {
581 				if (fPlayTrack) {
582 					fPlayLimit = MIN(fPlayFrames,
583 						(off_t)(right * fPlayFormat.u.raw_audio.frame_rate
584 							/ 1000000LL));
585 				}
586 				fScopeView->SetRightTime(right);
587 			}
588 			if (message->FindInt64("left", &left) == B_OK)
589 				fScopeView->SetLeftTime(left);
590 			break;
591 		}
592 	case LOOP:
593 		fLooping = fLoopButton->ButtonState();
594 		break;
595 	case B_SIMPLE_DATA:
596 	case B_REFS_RECEIVED:
597 		{
598 			RefsReceived(message);
599 			break;
600 		}
601 	case B_COPY_TARGET:
602 		CopyTarget(message);
603 		break;
604 	default:
605 		BWindow::MessageReceived(message);
606 		break;
607 	}
608 }
609 
610 
611 void
612 RecorderWindow::Record(BMessage * message)
613 {
614 	//	User pressed Record button
615 	fRecording = true;
616 	if (fButtonState != btnPaused) {
617 		StopRecording();
618 		return;			//	user is too fast on the mouse
619 	}
620 	SetButtonState(btnRecording);
621 	fRecordButton->SetRecording();
622 
623 	char name[256];
624 	//	Create a file with a temporary name
625 	status_t err = NewTempName(name);
626 	if (err < B_OK) {
627 		ErrorAlert(B_TRANSLATE("Cannot find an unused name to use for the "
628 			"new recording"), err);
629 		return;
630 	}
631 	//	Find the file so we can refer to it later
632 	err = fTempDir.FindEntry(name, &fRecEntry);
633 	if (err < B_OK) {
634 		ErrorAlert(B_TRANSLATE("Cannot find the temporary file created to "
635 			"hold the new recording"), err);
636 		return;
637 	}
638 	err = fRecFile.SetTo(&fTempDir, name, O_RDWR);
639 	if (err < B_OK) {
640 		ErrorAlert(B_TRANSLATE("Cannot open the temporary file created to "
641 			"hold the new recording"), err);
642 		fRecEntry.Unset();
643 		return;
644 	}
645 	//	Reserve space on disk (creates fewer fragments)
646 	err = fRecFile.SetSize(4 * fRecordFormat.u.raw_audio.channel_count
647 		* fRecordFormat.u.raw_audio.frame_rate
648 		* (fRecordFormat.u.raw_audio.format
649 			& media_raw_audio_format::B_AUDIO_SIZE_MASK));
650 	if (err < B_OK) {
651 		ErrorAlert(B_TRANSLATE("Cannot record a sound that long"), err);
652 		fRecEntry.Remove();
653 		fRecEntry.Unset();
654 		return;
655 	}
656 	fRecSize = 0;
657 
658 	fRecFile.Seek(sizeof(struct wave_struct), SEEK_SET);
659 
660 	//	Hook up input
661 	err = MakeRecordConnection(fAudioInputNode);
662 	if (err < B_OK) {
663 		ErrorAlert(B_TRANSLATE("Cannot connect to the selected sound input"),
664 			err);
665 		fRecEntry.Remove();
666 		fRecEntry.Unset();
667 		return;
668 	}
669 
670 	//	And get it going...
671 	bigtime_t then = fRecordNode->TimeSource()->Now() + 50000LL;
672 	fRoster->StartNode(fRecordNode->Node(), then);
673 	if (fAudioInputNode.kind & B_TIME_SOURCE) {
674 		fRoster->StartNode(fAudioInputNode,
675 			fRecordNode->TimeSource()->RealTimeFor(then, 0));
676 	} else
677 		fRoster->StartNode(fAudioInputNode, then);
678 }
679 
680 
681 void
682 RecorderWindow::Play(BMessage * message)
683 {
684 	if (fPlayer) {
685 		//	User pressed Play button and playing
686 		if (fPlayer->HasData())
687 			fPlayButton->SetPaused();
688 		else
689 			fPlayButton->SetPlaying();
690 		fPlayer->SetHasData(!fPlayer->HasData());
691 		return;
692 	}
693 
694 	SetButtonState(btnPlaying);
695 	fPlayButton->SetPlaying();
696 
697 	if (!fPlayTrack) {
698 		ErrorAlert(B_TRANSLATE("Cannot get the file to play"), B_ERROR);
699 		return;
700 	}
701 
702 	fPlayLimit = MIN(fPlayFrames, (off_t)(fTrackSlider->RightTime()
703 		* fPlayFormat.u.raw_audio.frame_rate / 1000000LL));
704 	fPlayTrack->SeekToTime(fTrackSlider->MainTime());
705 	fPlayFrame = fPlayTrack->CurrentFrame();
706 
707 	// Create our internal Node which plays sound, and register it.
708 	fPlayer = new BSoundPlayer(fAudioMixerNode, &fPlayFormat.u.raw_audio,
709 		"Sound Player");
710 	status_t err = fPlayer->InitCheck();
711 	if (err < B_OK)
712 		return;
713 
714 	fVolumeSlider->SetSoundPlayer(fPlayer);
715 	fPlayer->SetCallbacks(PlayFile, NotifyPlayFile, this);
716 
717 	//	And get it going...
718 	fPlayer->Start();
719 	fPlayer->SetHasData(true);
720 }
721 
722 
723 void
724 RecorderWindow::Stop(BMessage * message)
725 {
726 	//	User pressed Stop button.
727 	//	Stop recorder.
728 	StopRecording();
729 	//	Stop player.
730 	StopPlaying();
731 }
732 
733 
734 void
735 RecorderWindow::Save(BMessage * message)
736 {
737 	//	User pressed Save button.
738 	//	Find the item to save.
739 	int32 index = fSoundList->CurrentSelection();
740 	SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(index));
741 	if ((! pItem) || (pItem->Entry().InitCheck() != B_OK))
742 		return;
743 
744 	// Update the save panel and show it.
745 	char filename[B_FILE_NAME_LENGTH];
746 	pItem->Entry().GetName(filename);
747 	BMessage saveMsg(B_SAVE_REQUESTED);
748 	entry_ref ref;
749 	pItem->Entry().GetRef(&ref);
750 
751 	if (saveMsg.AddPointer("sound list item", pItem) != B_OK)
752 		fprintf(stderr, "failed to add pItem\n");
753 	fSavePanel->SetSaveText(filename);
754 	fSavePanel->SetMessage(&saveMsg);
755 	fSavePanel->Show();
756 }
757 
758 
759 void
760 RecorderWindow::DoSave(BMessage * message)
761 {
762 	// User picked a place to put the file.
763 	// Find the location of the old (e.g.
764 	// temporary file), and the name of the
765 	// new file to save.
766 	entry_ref old_ref, new_dir_ref;
767 	const char* new_name;
768 	SoundListItem* pItem;
769 
770 	if ((message->FindPointer("sound list item", (void**) &pItem) == B_OK)
771 		&& (message->FindRef("directory", &new_dir_ref) == B_OK)
772 		&& (message->FindString("name", &new_name) == B_OK)) {
773 		BEntry& oldEntry = pItem->Entry();
774 		BFile oldFile(&oldEntry, B_READ_WRITE);
775 		if (oldFile.InitCheck() != B_OK)
776 			return;
777 
778 		BDirectory newDir(&new_dir_ref);
779 		if (newDir.InitCheck() != B_OK)
780 			return;
781 
782 		BFile newFile;
783 		newDir.CreateFile(new_name, &newFile);
784 
785 		if (newFile.InitCheck() != B_OK)
786 			return;
787 
788 		status_t err = CopyFile(newFile, oldFile);
789 
790 		if (err == B_OK) {
791 			// clean up the sound list and item
792 			if (pItem->IsTemp())
793 				oldEntry.Remove(); // blows away temp file!
794 			oldEntry.SetTo(&newDir, new_name);
795 			pItem->SetTemp(false);	// don't blow the new entry away when we exit!
796 			fSoundList->Invalidate();
797 		}
798 	} else {
799 		WINDOW((stderr, "Couldn't save file.\n"));
800 	}
801 }
802 
803 
804 void
805 RecorderWindow::Input(BMessage * message)
806 {
807 	//	User selected input from pop-up
808 	const dormant_node_info * dni = 0;
809 	ssize_t size = 0;
810 	if (message->FindData("node", B_RAW_TYPE, (const void **)&dni, &size))
811 		return;		//	bad input selection message
812 
813 	media_node_id node_id;
814 	status_t error = fRoster->GetInstancesFor(dni->addon, dni->flavor_id, &node_id);
815 	if (error != B_OK)
816 		fRoster->InstantiateDormantNode(*dni, &fAudioInputNode);
817 	else
818 		fRoster->GetNodeFor(node_id, &fAudioInputNode);
819 }
820 
821 
822 void
823 RecorderWindow::Selected(BMessage * message)
824 {
825 	//	User selected a sound in list view
826 	int32 selIdx = fSoundList->CurrentSelection();
827 	SoundListItem* pItem = dynamic_cast<SoundListItem*>(fSoundList->ItemAt(selIdx));
828 	if (!pItem)
829 		return;
830 	status_t err = UpdatePlayFile(pItem, true);
831 	if (err != B_OK) {
832 		ErrorAlert(B_TRANSLATE("Cannot recognize this file as a media file"),
833 			err == B_MEDIA_NO_HANDLER ? B_OK : err);
834 		RemoveCurrentSoundItem();
835 	}
836 	UpdateButtons();
837 }
838 
839 
840 status_t
841 RecorderWindow::MakeRecordConnection(const media_node & input)
842 {
843 	CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n"));
844 
845 	//	Find an available output for the given input node.
846 	int32 count = 0;
847 	status_t err = fRoster->GetFreeOutputsFor(input, &fAudioOutput, 1, &count, B_MEDIA_RAW_AUDIO);
848 	if (err < B_OK) {
849 		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
850 			" couldn't get free outputs from audio input node\n"));
851 		return err;
852 	}
853 	if (count < 1) {
854 		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
855 			" no free outputs from audio input node\n"));
856 		return B_BUSY;
857 	}
858 
859 	//	Find an available input for our own Node. Note that we go through the
860 	//	MediaRoster; calling Media Kit methods directly on Nodes in our app is
861 	//	not OK (because synchronization happens in the service thread, not in
862 	//	the calling thread).
863 	// TODO: explain this
864 	err = fRoster->GetFreeInputsFor(fRecordNode->Node(), &fRecInput, 1, &count, B_MEDIA_RAW_AUDIO);
865 	if (err < B_OK) {
866 		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
867 			" couldn't get free inputs for sound recorder\n"));
868 		return err;
869 	}
870 	if (count < 1) {
871 		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
872 			" no free inputs for sound recorder\n"));
873 		return B_BUSY;
874 	}
875 
876 	//	Find out what the time source of the input is.
877 	//	For most nodes, we just use the preferred time source (the DAC) for synchronization.
878 	//	However, nodes that record from an input need to synchronize to the audio input node
879 	//	instead for best results.
880 	//	MakeTimeSourceFor gives us a "clone" of the time source node that we can manipulate
881 	//	to our heart's content. When we're done with it, though, we need to call Release()
882 	//	on the time source node, so that it keeps an accurate reference count and can delete
883 	//	itself when it's no longer needed.
884 	// TODO: what about filters connected to audio input?
885 	media_node use_time_source;
886 	BTimeSource * tsobj = fRoster->MakeTimeSourceFor(input);
887 	if (! tsobj) {
888 		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
889 			" couldn't clone time source from audio input node\n"));
890 		return B_MEDIA_BAD_NODE;
891 	}
892 
893 	//	Apply the time source in effect to our own Node.
894 	err = fRoster->SetTimeSourceFor(fRecordNode->Node().node, tsobj->Node().node);
895 	if (err < B_OK) {
896 		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
897 			" couldn't set the sound recorder's time source\n"));
898 		tsobj->Release();
899 		return err;
900 	}
901 
902 	//	Get a format, any format.
903 	fRecordFormat.u.raw_audio = fAudioOutput.format.u.raw_audio;
904 	fRecordFormat.type = B_MEDIA_RAW_AUDIO;
905 
906 	//	Tell the consumer where we want data to go.
907 	err = fRecordNode->SetHooks(RecordFile, NotifyRecordFile, this);
908 	if (err < B_OK) {
909 		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
910 			" couldn't set the sound recorder's hook functions\n"));
911 		tsobj->Release();
912 		return err;
913 	}
914 
915 	//	Using the same structs for input and output is OK in
916 	//  BMediaRoster::Connect().
917 	err = fRoster->Connect(fAudioOutput.source, fRecInput.destination,
918 		&fRecordFormat, &fAudioOutput, &fRecInput);
919 	if (err < B_OK) {
920 		CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
921 			" failed to connect sound recorder to audio input node.\n"));
922 		tsobj->Release();
923 		fRecordNode->SetHooks(0, 0, 0);
924 		return err;
925 	}
926 
927 	//	Start the time source if it's not running.
928 	if ((tsobj->Node() != input) && !tsobj->IsRunning())
929 		fRoster->StartNode(tsobj->Node(), BTimeSource::RealTime());
930 
931 	tsobj->Release();	//	we're done with this time source instance!
932 	return B_OK;
933 }
934 
935 
936 status_t
937 RecorderWindow::BreakRecordConnection()
938 {
939 	status_t err;
940 
941 	//	If we are the last connection, the Node will stop automatically since it
942 	//	has nowhere to send data to.
943 	err = fRoster->StopNode(fRecInput.node, 0);
944 	err = fRoster->Disconnect(fAudioOutput.node.node, fAudioOutput.source,
945 		fRecInput.node.node, fRecInput.destination);
946 	fAudioOutput.source = media_source::null;
947 	fRecInput.destination = media_destination::null;
948 	return err;
949 }
950 
951 
952 status_t
953 RecorderWindow::StopRecording()
954 {
955 	if (!fRecording)
956 		return B_OK;
957 	fRecording = false;
958 	BreakRecordConnection();
959 	fRecordNode->SetHooks(NULL,NULL,NULL);
960 	if (fRecSize > 0) {
961 
962 		wave_struct header;
963 		header.riff.riff_id = FOURCC('R','I','F','F');
964 		header.riff.len = fRecSize + sizeof(header) - 8;
965 		header.riff.wave_id = FOURCC('W','A','V','E');
966 		header.format_chunk.fourcc = FOURCC('f','m','t',' ');
967 		header.format_chunk.len = sizeof(header.format);
968 		header.format.format_tag = 1;
969 		header.format.channels = fRecordFormat.u.raw_audio.channel_count;
970 		header.format.samples_per_sec = (uint32)fRecordFormat.u.raw_audio.frame_rate;
971 		header.format.avg_bytes_per_sec = (uint32)(fRecordFormat.u.raw_audio.frame_rate
972 			* fRecordFormat.u.raw_audio.channel_count
973 			* (fRecordFormat.u.raw_audio.format & 0xf));
974 		header.format.bits_per_sample = (fRecordFormat.u.raw_audio.format & 0xf) * 8;
975 		header.format.block_align = (fRecordFormat.u.raw_audio.format & 0xf)
976 			* fRecordFormat.u.raw_audio.channel_count;
977 		header.data_chunk.fourcc = FOURCC('d','a','t','a');
978 		header.data_chunk.len = fRecSize;
979 		fRecFile.Seek(0, SEEK_SET);
980 		fRecFile.Write(&header, sizeof(header));
981 
982 		fRecFile.SetSize(fRecSize + sizeof(header));
983 		//	We reserve space; make sure we cut off any excess at the end.
984 		AddSoundItem(fRecEntry, true);
985 	} else
986 		fRecEntry.Remove();
987 
988 	//	We're done for this time.
989 	fRecEntry.Unset();
990 	//	Close the file.
991 	fRecFile.Unset();
992 	//	No more recording going on.
993 	fRecSize = 0;
994 	SetButtonState(btnPaused);
995 	fRecordButton->SetStopped();
996 
997 	return B_OK;
998 }
999 
1000 
1001 status_t
1002 RecorderWindow::StopPlaying()
1003 {
1004 	if (fPlayer) {
1005 		fPlayer->Stop();
1006 		fPlayer->SetCallbacks(0, 0, 0);
1007 		fVolumeSlider->SetSoundPlayer(NULL);
1008 		delete fPlayer;
1009 		fPlayer = NULL;
1010 	}
1011 	SetButtonState(btnPaused);
1012 	fPlayButton->SetStopped();
1013 	fTrackSlider->ResetMainTime();
1014 	fScopeView->SetMainTime(*fTrackSlider->MainTime());
1015 	return B_OK;
1016 }
1017 
1018 
1019 void
1020 RecorderWindow::SetButtonState(BtnState state)
1021 {
1022 	fButtonState = state;
1023 	UpdateButtons();
1024 }
1025 
1026 
1027 void
1028 RecorderWindow::UpdateButtons()
1029 {
1030 	bool hasSelection = (fSoundList->CurrentSelection() >= 0);
1031 	fRecordButton->SetEnabled(fButtonState != btnPlaying);
1032 	fPlayButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
1033 	fRewindButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
1034 	fForwardButton->SetEnabled((fButtonState != btnRecording) && hasSelection);
1035 	fStopButton->SetEnabled(fButtonState != btnPaused);
1036 	fSaveButton->SetEnabled(hasSelection && (fButtonState != btnRecording));
1037 	fInputField->SetEnabled(fButtonState != btnRecording);
1038 }
1039 
1040 #ifndef __HAIKU__
1041 extern "C" status_t DecodedFormat__11BMediaTrackP12media_format(
1042 	BMediaTrack *self, media_format *inout_format);
1043 #endif
1044 
1045 
1046 status_t
1047 RecorderWindow::UpdatePlayFile(SoundListItem* item, bool updateDisplay)
1048 {
1049 	fScopeView->CancelRendering();
1050 	StopPlaying();
1051 	StopRecording();
1052 
1053 	if (fPlayTrack && fPlayFile) {
1054 		fPlayFile->ReleaseTrack(fPlayTrack);
1055 		fPlayTrack = NULL;
1056 	}
1057 	if (fPlayFile) {
1058 		delete fPlayFile;
1059 		fPlayFile = NULL;
1060 	}
1061 
1062 	status_t err;
1063 	BEntry& entry = item->Entry();
1064 	entry_ref ref;
1065 	entry.GetRef(&ref);
1066 	fPlayFile = new BMediaFile(&ref); //, B_MEDIA_FILE_UNBUFFERED);
1067 	if ((err = fPlayFile->InitCheck()) < B_OK) {
1068 		delete fPlayFile;
1069 		fPlayFile = NULL;
1070 		return err;
1071 	}
1072 
1073 	for (int ix=0; ix < fPlayFile->CountTracks(); ix++) {
1074 		BMediaTrack * track = fPlayFile->TrackAt(ix);
1075 		fPlayFormat.type = B_MEDIA_RAW_AUDIO;
1076 #ifdef __HAIKU__
1077 		if ((track->DecodedFormat(&fPlayFormat) == B_OK)
1078 #else
1079 		if ((DecodedFormat__11BMediaTrackP12media_format(track, &fPlayFormat) == B_OK)
1080 #endif
1081 			&& (fPlayFormat.type == B_MEDIA_RAW_AUDIO)) {
1082 			fPlayTrack = track;
1083 			break;
1084 		}
1085 		if (track)
1086 			fPlayFile->ReleaseTrack(track);
1087 	}
1088 
1089 	if (!fPlayTrack) {
1090 		delete fPlayFile;
1091 		fPlayFile = NULL;
1092 		return B_STREAM_NOT_FOUND;
1093 	}
1094 
1095 	if (!updateDisplay)
1096 		return B_OK;
1097 
1098 	BString filename = B_TRANSLATE("File name: ");
1099 	filename << ref.name;
1100 	fFilename->SetText(filename.String());
1101 
1102 	BString format = B_TRANSLATE("Format: ");
1103 	media_file_format file_format;
1104 	if (fPlayFile->GetFileFormatInfo(&file_format) == B_OK)
1105 		format << file_format.short_name;
1106 	BString compression = B_TRANSLATE("Compression: ");
1107 	media_codec_info codec_info;
1108 	if (fPlayTrack->GetCodecInfo(&codec_info) == B_OK) {
1109 		if (strcmp(codec_info.short_name, "raw")==0)
1110 			compression << B_TRANSLATE("None");
1111 		else
1112 			compression << codec_info.short_name;
1113 	}
1114 	BString channels = B_TRANSLATE("Channels: ");
1115 	channels << fPlayFormat.u.raw_audio.channel_count;
1116 	BString samplesize = B_TRANSLATE("Sample size: ");
1117 	samplesize << 8 * (fPlayFormat.u.raw_audio.format & 0xf)
1118 		<< B_TRANSLATE(" bits");
1119 	BString samplerate = B_TRANSLATE("Sample rate: ");
1120 	samplerate << (int)fPlayFormat.u.raw_audio.frame_rate;
1121 	BString durationString = B_TRANSLATE("Duration: ");
1122 	bigtime_t duration = fPlayTrack->Duration();
1123 	durationString << (float)(duration / 1000000.0) << B_TRANSLATE(" seconds");
1124 
1125 	fFormat->SetText(format.String());
1126 	fCompression->SetText(compression.String());
1127 	fChannels->SetText(channels.String());
1128 	fSampleSize->SetText(samplesize.String());
1129 	fSampleRate->SetText(samplerate.String());
1130 	fDuration->SetText(durationString.String());
1131 
1132 	fTrackSlider->SetTotalTime(duration, true);
1133 	fScopeView->SetTotalTime(duration, true);
1134 	fScopeView->RenderTrack(fPlayTrack, fPlayFormat);
1135 
1136 	fPlayFrames = fPlayTrack->CountFrames();
1137 	return B_OK;
1138 }
1139 
1140 
1141 void
1142 RecorderWindow::ErrorAlert(const char * action, status_t err)
1143 {
1144 	char msg[300];
1145 	if (err != B_OK)
1146 		sprintf(msg, "%s: %s. [%lx]", action, strerror(err), (int32) err);
1147 	else
1148 		sprintf(msg, "%s.", action);
1149 	(new BAlert("", msg, B_TRANSLATE("Stop")))->Go();
1150 }
1151 
1152 
1153 status_t
1154 RecorderWindow::NewTempName(char * name)
1155 {
1156 	int init_count = fTempCount;
1157 again:
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 
1188 void
1189 RecorderWindow::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 
1199 void
1200 RecorderWindow::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 
1210 void
1211 RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp,
1212 	void* data, size_t size, const media_raw_audio_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.format);
1222 		window->fRecSize += size;
1223 	}
1224 }
1225 
1226 
1227 void
1228 RecorderWindow::NotifyRecordFile(void * cookie, int32 code, ...)
1229 {
1230 	if ((code == B_WILL_STOP) || (code == B_NODE_DIES)) {
1231 		RecorderWindow * window = (RecorderWindow *)cookie;
1232 		// Tell the window we've stopped, if it doesn't
1233 		// already know.
1234 		window->PostMessage(STOP_RECORDING);
1235 	}
1236 }
1237 
1238 
1239 void
1240 RecorderWindow::PlayFile(void * cookie, void * data, size_t size,
1241 	const media_raw_audio_format & format)
1242 {
1243 	//	Callback called from the SoundProducer when producing buffers.
1244 	RecorderWindow * window = (RecorderWindow *)cookie;
1245 	int32 frame_size = (window->fPlayFormat.u.raw_audio.format & 0xf) *
1246 		window->fPlayFormat.u.raw_audio.channel_count;
1247 
1248 	if ((window->fPlayFrame < window->fPlayLimit) || window->fLooping) {
1249 		if (window->fPlayFrame >= window->fPlayLimit) {
1250 			bigtime_t left = window->fTrackSlider->LeftTime();
1251 			window->fPlayTrack->SeekToTime(&left);
1252 			window->fPlayFrame = window->fPlayTrack->CurrentFrame();
1253 		}
1254 		int64 frames = 0;
1255 		window->fPlayTrack->ReadFrames(data, &frames);
1256 		window->fVUView->ComputeLevels(data, size / frame_size, format.format);
1257 		window->fPlayFrame += size/frame_size;
1258 		window->PostMessage(UPDATE_TRACKSLIDER);
1259 	} else {
1260 		//	we're done!
1261 		window->PostMessage(STOP_PLAYING);
1262 	}
1263 }
1264 
1265 
1266 void
1267 RecorderWindow::NotifyPlayFile(void * cookie,
1268 	BSoundPlayer::sound_player_notification code, ...)
1269 {
1270 	if ((code == BSoundPlayer::B_STOPPED) || (code == BSoundPlayer::B_SOUND_DONE)) {
1271 		RecorderWindow * window = (RecorderWindow *)cookie;
1272 		// tell the window we've stopped, if it doesn't
1273 		// already know.
1274 		window->PostMessage(STOP_PLAYING);
1275 	}
1276 }
1277 
1278 
1279 void
1280 RecorderWindow::RefsReceived(BMessage *msg)
1281 {
1282 	entry_ref ref;
1283 	int32 i = 0;
1284 	int32 countGood = 0;
1285 	int32 countBad = 0;
1286 
1287 	while (msg->FindRef("refs", i++, &ref) == B_OK) {
1288 
1289 		BEntry entry(&ref, true);
1290 		BPath path(&entry);
1291 		BNode node(&entry);
1292 
1293 		if (node.IsFile()) {
1294 			SoundListItem * listItem = new SoundListItem(entry, false);
1295 			if (UpdatePlayFile(listItem) == B_OK) {
1296 				fSoundList->AddItem(listItem);
1297 				countGood++;
1298 				continue;
1299 			}
1300 			delete listItem;
1301 		} else if(node.IsDirectory()) {
1302 
1303 		}
1304 		countBad++;
1305 	}
1306 
1307 	if (countBad > 0 && countGood == 0) {
1308 		(new BAlert(B_TRANSLATE("Nothing to play"), B_TRANSLATE("None of the "
1309 			"files appear to be audio files"), B_TRANSLATE("OK"), NULL, NULL,
1310 			B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go();
1311 	} else if (countGood > 0) {
1312 		if (countBad > 0)
1313 			(new BAlert(B_TRANSLATE("Invalid audio files"), B_TRANSLATE("Some "
1314 				"of the files don't appear to be audio files"),
1315 				B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
1316 				B_WARNING_ALERT))->Go();
1317 		fSoundList->Select(fSoundList->CountItems() - 1);
1318 	}
1319 }
1320 
1321 
1322 void
1323 RecorderWindow::CopyTarget(BMessage *msg)
1324 {
1325 	const char *type = NULL;
1326 	if (msg->FindString("be:types", &type) == B_OK) {
1327 		if (!strcasecmp(type, B_FILE_MIME_TYPE)) {
1328 			const char *name;
1329 			entry_ref dir;
1330 			if (msg->FindString("be:filetypes") == B_OK
1331 				&& msg->FindString("name", &name) == B_OK
1332 				&& msg->FindRef("directory", &dir) == B_OK) {
1333 				BDirectory directory(&dir);
1334 				BFile file(&directory, name, O_RDWR | O_TRUNC);
1335 
1336 				// seek time
1337 				bigtime_t start = fTrackSlider->LeftTime();
1338 
1339 				// write data
1340 				bigtime_t diffTime = fTrackSlider->RightTime()
1341 					- fTrackSlider->LeftTime();
1342 				int64 framesToWrite = (int64) (diffTime
1343 					* fPlayFormat.u.raw_audio.frame_rate / 1000000LL);
1344 				int32 frameSize = (fPlayFormat.u.raw_audio.format & 0xf)
1345 					* fPlayFormat.u.raw_audio.channel_count;
1346 
1347 				wave_struct header;
1348 				header.riff.riff_id = FOURCC('R','I','F','F');
1349 				header.riff.len
1350 					= (frameSize * framesToWrite) + sizeof(header) - 8;
1351 				header.riff.wave_id = FOURCC('W','A','V','E');
1352 				header.format_chunk.fourcc = FOURCC('f','m','t',' ');
1353 				header.format_chunk.len = sizeof(header.format);
1354 				header.format.format_tag = 1;
1355 				header.format.channels = fPlayFormat.u.raw_audio.channel_count;
1356 				header.format.samples_per_sec
1357 					= (uint32)fPlayFormat.u.raw_audio.frame_rate;
1358 				header.format.avg_bytes_per_sec
1359 					= (uint32)(fPlayFormat.u.raw_audio.frame_rate
1360 					* fPlayFormat.u.raw_audio.channel_count
1361 					* (fPlayFormat.u.raw_audio.format & 0xf));
1362 				header.format.bits_per_sample
1363 					= (fPlayFormat.u.raw_audio.format & 0xf) * 8;
1364 				header.format.block_align = frameSize;
1365 				header.data_chunk.fourcc = FOURCC('d','a','t','a');
1366 				header.data_chunk.len = frameSize * framesToWrite;
1367 				file.Seek(0, SEEK_SET);
1368 				file.Write(&header, sizeof(header));
1369 
1370 				char *data = (char *)malloc(fPlayFormat.u.raw_audio.buffer_size);
1371 
1372 				fPlayTrack->SeekToTime(&start);
1373 				fPlayFrame = fPlayTrack->CurrentFrame();
1374 				while (framesToWrite > 0) {
1375 					int64 frames = 0;
1376 					status_t err = fPlayTrack->ReadFrames(data, &frames);
1377 					if (frames <= 0 || err != B_OK) {
1378 						if (err != B_OK)
1379 							fprintf(stderr, "CopyTarget: ReadFrames failed\n");
1380 						break;
1381 					}
1382 					file.Write(data, frames * frameSize);
1383 					framesToWrite -= frames;
1384 				}
1385 
1386 				file.Sync();
1387 				free(data);
1388 				BNodeInfo nodeInfo(&file);
1389 				// set type
1390 			}
1391 		} else {
1392 
1393 		}
1394 	}
1395 }
1396