xref: /haiku/src/kits/game/FileGameSound.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2002, OpenBeOS
3 //
4 //	Permission is hereby granted, free of charge, to any person obtaining a
5 //	copy of this software and associated documentation files (the "Software"),
6 //	to deal in the Software without restriction, including without limitation
7 //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 //	and/or sell copies of the Software, and to permit persons to whom the
9 //	Software is furnished to do so, subject to the following conditions:
10 //
11 //	The above copyright notice and this permission notice shall be included in
12 //	all copies or substantial portions of the Software.
13 //
14 //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 //	DEALINGS IN THE SOFTWARE.
21 //
22 //	File Name:		FileGameSound.cpp
23 //	Author:			Christopher ML Zumwalt May (zummy@users.sf.net)
24 //	Description:	BFileGameSound is a class that streams data out of a file.
25 //------------------------------------------------------------------------------
26 
27 // Standard Includes -----------------------------------------------------------
28 #include <stdlib.h>
29 #include <string.h>
30 
31 // System Includes -------------------------------------------------------------
32 #include <Entry.h>
33 #include <MediaFile.h>
34 #include <MediaTrack.h>
35 #include <scheduler.h>
36 
37 // Project Includes ------------------------------------------------------------
38 #include <GameSoundDevice.h>
39 #include <GSUtility.h>
40 
41 // Local Includes --------------------------------------------------------------
42 #include <FileGameSound.h>
43 
44 // Local Defines ---------------------------------------------------------------
45 const int32 kPages = 20;
46 struct _gs_media_tracker
47 {
48 	BMediaFile *	file;
49 	BMediaTrack * 	stream;
50 	int64			frames;
51 	size_t			position;
52 };
53 
54 // Locale utility functions -----------------------------------------------
55 bool FillBuffer(_gs_ramp * ramp, uint8 * data, uint8 * buffer, size_t * bytes)
56 {
57 	int32 samples = *bytes / sizeof(int16);
58 
59 	for(int32 byte = 0; byte < samples; byte++)
60 	{
61 		float gain = *ramp->value;
62 		data[byte] = uint8(float(buffer[byte]) * gain);
63 
64 		if (ChangeRamp(ramp))
65 		{
66 			*bytes = byte * sizeof(int16);
67 			return true;
68 		}
69 	}
70 
71 	return false;
72 }
73 
74 bool FillBuffer(_gs_ramp * ramp, int16 * data, int16 * buffer, size_t * bytes)
75 {
76 	int32 samples = *bytes / sizeof(int16);
77 
78 	for(int32 byte = 0; byte < samples; byte++)
79 	{
80 		float gain = *ramp->value;
81 		data[byte] = int16(float(buffer[byte]) * gain);
82 
83 		if (ChangeRamp(ramp))
84 		{
85 			*bytes = byte * sizeof(int16);
86 			return true;
87 		}
88 	}
89 
90 	return false;
91 }
92 
93 bool FillBuffer(_gs_ramp * ramp, int32 * data, int32 * buffer, size_t * bytes)
94 {
95 	size_t byte = 0;
96 	bool bytesAreReady = (*bytes > 0);
97 
98 	while(bytesAreReady)
99 	{
100 		float gain = *ramp->value;
101 		data[byte] = int32(float(buffer[byte]) * gain);
102 
103 		if (ChangeRamp(ramp))
104 		{
105 			*bytes = byte;
106 			return true;
107 		}
108 
109 		byte++;
110 		bytesAreReady = (byte >= *bytes);
111 	}
112 
113 	return false;
114 }
115 
116 bool FillBuffer(_gs_ramp * ramp, float * data, float * buffer, size_t * bytes)
117 {
118 	size_t byte = 0;
119 	bool bytesAreReady = (*bytes > 0);
120 
121 	while(bytesAreReady)
122 	{
123 		float gain = *ramp->value;
124 		data[byte] = buffer[byte] * gain;
125 
126 		if (ChangeRamp(ramp))
127 		{
128 			*bytes = byte;
129 			return true;
130 		}
131 
132 		byte++;
133 		bytesAreReady = (byte >= *bytes);
134 	}
135 
136 	return false;
137 }
138 
139 // BFileGameSound -------------------------------------------------------
140 BFileGameSound::BFileGameSound(const entry_ref *file,
141 							   bool looping,
142 							   BGameSoundDevice *device)
143  	:	BStreamingGameSound(device),
144 		fAudioStream(NULL),
145  		fStopping(false),
146  		fLooping(looping),
147  		fBuffer(NULL),
148  		fPlayPosition(0),
149  		fPausing(NULL),
150  		fPaused(false),
151  		fPauseGain(1.0)
152 {
153 	if (InitCheck() == B_OK) SetInitError(Init(file));
154 }
155 
156 
157 BFileGameSound::BFileGameSound(const char *file,
158 							   bool looping,
159 							   BGameSoundDevice *device)
160  	:	BStreamingGameSound(device),
161 		fAudioStream(NULL),
162  		fStopping(false),
163  		fLooping(looping),
164 		fBuffer(NULL),
165  		fPlayPosition(0),
166  		fPausing(NULL),
167  		fPaused(false),
168  		fPauseGain(1.0)
169 {
170 	if (InitCheck() == B_OK)
171 	{
172 		entry_ref node;
173 
174 		if (get_ref_for_path(file, &node) != B_OK)
175 			SetInitError(B_ENTRY_NOT_FOUND);
176 		else
177 			SetInitError(Init(&node));
178 	}
179 }
180 
181 
182 BFileGameSound::~BFileGameSound()
183 {
184 	if (fReadThread >= 0) kill_thread(fReadThread);
185 
186 	if (fAudioStream)
187 	{
188 		if (fAudioStream->stream) fAudioStream->file->ReleaseTrack(fAudioStream->stream);
189 
190 		fAudioStream->file->CloseFile();
191 
192 		//delete fAudioStream->stream;
193 		delete fAudioStream->file;
194 	}
195 
196 	delete [] fBuffer;
197 	delete fAudioStream;
198 }
199 
200 
201 BGameSound *
202 BFileGameSound::Clone() const
203 {
204 	return NULL;
205 }
206 
207 
208 status_t
209 BFileGameSound::StartPlaying()
210 {
211 	// restart playback if needed
212 	if (IsPlaying()) StopPlaying();
213 
214 	fPort = create_port(kPages, "audioque");
215 
216 	// create the thread that will read the file
217 	fReadThread = spawn_thread(ReadThread, "audiostream", B_NORMAL_PRIORITY, this);
218 	if (fReadThread < B_OK) return B_NO_MORE_THREADS;
219 
220 	status_t error = resume_thread(fReadThread);
221 	if (error != B_OK) return error;
222 
223 	// start playing the file
224 	return BStreamingGameSound::StartPlaying();
225 }
226 
227 
228 status_t
229 BFileGameSound::StopPlaying()
230 {
231 	status_t error = BStreamingGameSound::StopPlaying();
232 
233 	// start reading next time from the start of the file
234 	int64 frame = 0;
235 	fAudioStream->stream->SeekToFrame(&frame);
236 
237 	fStopping = false;
238 	fAudioStream->position = 0;
239 	fPlayPosition = 0;
240 
241 	// we don't need to read any more
242 	kill_thread(fReadThread);
243 	delete_port(fPort);
244 
245 	return error;
246 }
247 
248 
249 status_t
250 BFileGameSound::Preload()
251 {
252 	if (!IsPlaying())
253 	{
254 		for(int32 i = 0; i < kPages / 2; i++) Load();
255 	}
256 
257 	return B_OK;
258 }
259 
260 
261 void
262 BFileGameSound::FillBuffer(void *inBuffer,
263 						   size_t inByteCount)
264 {
265 	int32 cookie;
266 	size_t bytes = inByteCount;
267 
268 	if (!fPaused || fPausing)
269 	{
270 		// time to read a new buffer
271 		if (fPlayPosition == 0) read_port_etc(fPort, &cookie, fBuffer, fBufferSize, B_TIMEOUT, 0);
272 
273 		if (fPausing)
274 		{
275 			Lock();
276 
277 			bool rampDone = false;
278 
279 			// Fill the requsted buffer, stopping if the paused flag is set
280 			switch(Format().format)
281 			{
282 				case gs_audio_format::B_GS_U8:
283 					rampDone = ::FillBuffer(fPausing, (uint8*)inBuffer, (uint8*)&fBuffer[fPlayPosition], &bytes);
284 					break;
285 
286 				case gs_audio_format::B_GS_S16:
287 					rampDone = ::FillBuffer(fPausing, (int16*)inBuffer, (int16*)&fBuffer[fPlayPosition], &bytes);
288 					break;
289 
290 				case gs_audio_format::B_GS_S32:
291 					rampDone = ::FillBuffer(fPausing, (int32*)inBuffer, (int32*)&fBuffer[fPlayPosition], &bytes);
292 					break;
293 
294 				case gs_audio_format::B_GS_F:
295 					rampDone = ::FillBuffer(fPausing, (float*)inBuffer, (float*)&fBuffer[fPlayPosition], &bytes);
296 					break;
297 			}
298 
299 			// We finished ramping
300 			if (rampDone)
301 			{
302 				if (bytes < inByteCount && !fPausing)
303 				{
304 					// Since are resumming play back, we need to copy any remaining samples
305 					char * buffer = (char*)inBuffer;
306 					memcpy(&buffer[bytes], &fBuffer[fPlayPosition + bytes], inByteCount - bytes);
307 				}
308 
309 				delete fPausing;
310 				fPausing = NULL;
311 			}
312 
313 			Unlock();
314 		}
315 		else
316 		{
317 			size_t byte = 0;
318 			char * buffer = (char*)inBuffer;
319 
320 			// We need to be able to stop asap when the pause flag is flipped.
321 			while(byte < bytes && (!fPaused || fPausing))
322 			{
323 				buffer[byte] = fBuffer[fPlayPosition + byte];
324 				byte++;
325 			}
326 
327 			bytes = byte;
328 		}
329 	}
330 
331 	fPlayPosition += bytes;
332 	if (fPlayPosition >= fBufferSize)
333 	{
334 		// We have finished reading the buffer. Setup for the next buffer.
335 		fPlayPosition = 0;
336 		memset(fBuffer, 0, fBufferSize);
337 	}
338 }
339 
340 
341 status_t
342 BFileGameSound::Perform(int32 selector,
343 						void *data)
344 {
345 	return B_ERROR;
346 }
347 
348 
349 status_t
350 BFileGameSound::SetPaused(bool isPaused,
351 						  bigtime_t rampTime)
352 {
353 	if (fPaused != isPaused)
354 	{
355 		Lock();
356 
357 		// Clear any old ramping
358 		delete fPausing;
359 		fPausing = NULL;
360 
361 		if (rampTime > 100000)
362 		{
363 			// Setup for ramping
364 			if (isPaused)
365 				fPausing = InitRamp(&fPauseGain, 0.0, Format().frame_rate, rampTime);
366 			else
367 				fPausing = InitRamp(&fPauseGain, 1.0, Format().frame_rate, rampTime);
368 		}
369 
370 		fPaused = isPaused;
371 		Unlock();
372 	}
373 
374 	return B_OK;
375 }
376 
377 
378 int32
379 BFileGameSound::IsPaused()
380 {
381 	if (fPausing) return B_PAUSE_IN_PROGRESS;
382 
383 	if (fPaused) return B_PAUSED;
384 
385 	return B_NOT_PAUSED;
386 }
387 
388 
389 status_t
390 BFileGameSound::Init(const entry_ref* file)
391 {
392 	fAudioStream = new _gs_media_tracker;
393 	memset(fAudioStream, 0, sizeof(_gs_media_tracker));
394 
395 	fAudioStream->file = new BMediaFile(file);
396 	status_t error = fAudioStream->file->InitCheck();
397 	if (error != B_OK) return error;
398 
399 	fAudioStream->stream = fAudioStream->file->TrackAt(0);
400 
401 	// is this is an audio file?
402 	media_format mformat;
403 	fAudioStream->stream->EncodedFormat(&mformat);
404 	if (!mformat.IsAudio()) return B_MEDIA_BAD_FORMAT;
405 
406 	gs_audio_format dformat = Device()->Format();
407 
408 	// request the format we want the sound
409 	memset(&mformat, 0, sizeof(media_format));
410 	mformat.type = B_MEDIA_RAW_AUDIO;
411 	fAudioStream->stream->DecodedFormat(&mformat);
412 
413 	// translate the format into a "GameKit" friendly one
414 	gs_audio_format gsformat;
415 	media_to_gs_format(&gsformat, &mformat.u.raw_audio);
416 
417 	// Since the buffer sized read from the file is most likely differnt
418 	// then the buffer used by the audio mixer, we must allocate a buffer
419 	// large enough to hold the largest request.
420 	fBufferSize = gsformat.buffer_size;
421 	if (fBufferSize < dformat.buffer_size) fBufferSize = dformat.buffer_size;
422 
423 	// create the buffer
424 	fBuffer = new char[fBufferSize * 2];
425 	memset(fBuffer, 0, fBufferSize * 2);
426 
427 	fFrameSize = gsformat.channel_count * get_sample_size(gsformat.format);
428 	fAudioStream->frames = fAudioStream->stream->CountFrames();
429 
430 	// Ask the device to attach our sound to it
431 	gs_id sound;
432 	error = Device()->CreateBuffer(&sound, this, &gsformat);
433 	if (error != B_OK) return error;
434 
435 	return BGameSound::Init(sound);
436 }
437 
438 
439 int32
440 BFileGameSound::ReadThread(void* arg)
441 {
442 	BFileGameSound* obj = (BFileGameSound*)arg;
443 
444 	while(true) obj->Load();
445 
446 	return 0;
447 }
448 
449 
450 bool
451 BFileGameSound::Load()
452 {
453 	int64 frames;
454 	char * buffer = &fBuffer[fBufferSize + fAudioStream->position];
455 	status_t err = fAudioStream->stream->ReadFrames(buffer, &frames);
456 	if (err < B_OK) {
457 		StopPlaying(); // XXX this is a hack, the whole class design is broken
458 	}
459 
460 	int32 frame = fAudioStream->stream->CurrentFrame();
461 
462 	fAudioStream->position += fFrameSize * frames;
463 	if (fAudioStream->position >= fBufferSize)
464 	{
465 		// we have filled the enter buffer, time to send
466 		write_port(fPort, fBufferSize, &fBuffer[fBufferSize], fBufferSize);
467 		fAudioStream->position = 0;
468 	}
469 
470 	if (frame >= fAudioStream->frames)
471 	{
472 		if (fLooping)
473 		{
474 			// since we are looping, we need to start reading from
475 			// the begining of the file again.
476 			int64 firstFrame = 0;
477 			fAudioStream->stream->SeekToFrame(&firstFrame);
478 
479 			fStopping = true;
480 		}
481 		else fStopping = true;
482 	}
483 
484 	return true;
485 }
486 
487 
488 bool
489 BFileGameSound::Read(void * buffer, size_t bytes)
490 {
491 	return false;
492 }
493 
494 
495 /* unimplemented for protection of the user:
496  *
497  * BFileGameSound::BFileGameSound()
498  * BFileGameSound::BFileGameSound(const BFileGameSound &)
499  * BFileGameSound &BFileGameSound::operator=(const BFileGameSound &)
500  */
501 
502 
503 status_t
504 BFileGameSound::_Reserved_BFileGameSound_0(int32 arg, ...)
505 {
506 	return B_ERROR;
507 }
508 
509 
510 status_t
511 BFileGameSound::_Reserved_BFileGameSound_1(int32 arg, ...)
512 {
513 	return B_ERROR;
514 }
515 
516 
517 status_t
518 BFileGameSound::_Reserved_BFileGameSound_2(int32 arg, ...)
519 {
520 	return B_ERROR;
521 }
522 
523 
524 status_t
525 BFileGameSound::_Reserved_BFileGameSound_3(int32 arg, ...)
526 {
527 	return B_ERROR;
528 }
529 
530 
531 status_t
532 BFileGameSound::_Reserved_BFileGameSound_4(int32 arg, ...)
533 {
534 	return B_ERROR;
535 }
536 
537 
538 status_t
539 BFileGameSound::_Reserved_BFileGameSound_5(int32 arg, ...)
540 {
541 	return B_ERROR;
542 }
543 
544 
545 status_t
546 BFileGameSound::_Reserved_BFileGameSound_6(int32 arg, ...)
547 {
548 	return B_ERROR;
549 }
550 
551 
552 status_t
553 BFileGameSound::_Reserved_BFileGameSound_7(int32 arg, ...)
554 {
555 	return B_ERROR;
556 }
557 
558 
559 status_t
560 BFileGameSound::_Reserved_BFileGameSound_8(int32 arg, ...)
561 {
562 	return B_ERROR;
563 }
564 
565 
566 status_t
567 BFileGameSound::_Reserved_BFileGameSound_9(int32 arg, ...)
568 {
569 	return B_ERROR;
570 }
571 
572 
573 status_t
574 BFileGameSound::_Reserved_BFileGameSound_10(int32 arg, ...)
575 {
576 	return B_ERROR;
577 }
578 
579 
580 status_t
581 BFileGameSound::_Reserved_BFileGameSound_11(int32 arg, ...)
582 {
583 	return B_ERROR;
584 }
585 
586 
587 status_t
588 BFileGameSound::_Reserved_BFileGameSound_12(int32 arg, ...)
589 {
590 	return B_ERROR;
591 }
592 
593 
594 status_t
595 BFileGameSound::_Reserved_BFileGameSound_13(int32 arg, ...)
596 {
597 	return B_ERROR;
598 }
599 
600 
601 status_t
602 BFileGameSound::_Reserved_BFileGameSound_14(int32 arg, ...)
603 {
604 	return B_ERROR;
605 }
606 
607 
608 status_t
609 BFileGameSound::_Reserved_BFileGameSound_15(int32 arg, ...)
610 {
611 	return B_ERROR;
612 }
613 
614 
615 status_t
616 BFileGameSound::_Reserved_BFileGameSound_16(int32 arg, ...)
617 {
618 	return B_ERROR;
619 }
620 
621 
622 status_t
623 BFileGameSound::_Reserved_BFileGameSound_17(int32 arg, ...)
624 {
625 	return B_ERROR;
626 }
627 
628 
629 status_t
630 BFileGameSound::_Reserved_BFileGameSound_18(int32 arg, ...)
631 {
632 	return B_ERROR;
633 }
634 
635 
636 status_t
637 BFileGameSound::_Reserved_BFileGameSound_19(int32 arg, ...)
638 {
639 	return B_ERROR;
640 }
641 
642 
643 status_t
644 BFileGameSound::_Reserved_BFileGameSound_20(int32 arg, ...)
645 {
646 	return B_ERROR;
647 }
648 
649 
650 status_t
651 BFileGameSound::_Reserved_BFileGameSound_21(int32 arg, ...)
652 {
653 	return B_ERROR;
654 }
655 
656 
657 status_t
658 BFileGameSound::_Reserved_BFileGameSound_22(int32 arg, ...)
659 {
660 	return B_ERROR;
661 }
662 
663 
664 status_t
665 BFileGameSound::_Reserved_BFileGameSound_23(int32 arg, ...)
666 {
667 	return B_ERROR;
668 }
669 
670 
671