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