xref: /haiku/src/apps/soundrecorder/RecorderWindow.cpp (revision c3cb090f10ebdb7a7ada7b27ff4ec9370fa4dc9c)
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
64 static const float MIN_WIDTH = 400.0f;
65 static const float MIN_HEIGHT = 175.0f;
66 static const float XPOS = 100.0f;
67 static const float YPOS = 200.0f;
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_SYSTEM_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.
514 bad_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 
527 bool
528 RecorderWindow::QuitRequested()	//	this means Close pressed
529 {
530 	StopRecording();
531 	StopPlaying();
532 	be_app->PostMessage(B_QUIT_REQUESTED);
533 	return true;
534 }
535 
536 
537 void
538 RecorderWindow::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 
640 void
641 RecorderWindow::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 
710 void
711 RecorderWindow::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 
752 void
753 RecorderWindow::Stop(BMessage * message)
754 {
755 	//	User pressed Stop button.
756 	//	Stop recorder.
757 	StopRecording();
758 	//	Stop player.
759 	StopPlaying();
760 }
761 
762 
763 void
764 RecorderWindow::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 
788 void
789 RecorderWindow::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 
833 void
834 RecorderWindow::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 
851 void
852 RecorderWindow::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 
869 status_t
870 RecorderWindow::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 
965 status_t
966 RecorderWindow::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 
981 status_t
982 RecorderWindow::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 
1030 status_t
1031 RecorderWindow::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 
1048 void
1049 RecorderWindow::SetButtonState(BtnState state)
1050 {
1051 	fButtonState = state;
1052 	UpdateButtons();
1053 }
1054 
1055 
1056 void
1057 RecorderWindow::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__
1070 extern "C" status_t DecodedFormat__11BMediaTrackP12media_format(
1071 	BMediaTrack *self, media_format *inout_format);
1072 #endif
1073 
1074 
1075 status_t
1076 RecorderWindow::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 
1170 void
1171 RecorderWindow::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 
1184 status_t
1185 RecorderWindow::NewTempName(char * name)
1186 {
1187 	int init_count = fTempCount;
1188 again:
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 
1219 void
1220 RecorderWindow::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 
1230 void
1231 RecorderWindow::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 
1241 void
1242 RecorderWindow::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 
1258 void
1259 RecorderWindow::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 
1270 void
1271 RecorderWindow::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 
1297 void
1298 RecorderWindow::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 
1310 void
1311 RecorderWindow::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 
1358 void
1359 RecorderWindow::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