xref: /haiku/src/kits/game/FileGameSound.cpp (revision 71452e98334eaac603bf542d159e24788a46bebb)
1 /*
2  * Copyright 2001-2012 Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Christopher ML Zumwalt May (zummy@users.sf.net)
7  *		Jérôme Duval
8  */
9 
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #include <Entry.h>
16 #include <FileGameSound.h>
17 #include <MediaFile.h>
18 #include <MediaTrack.h>
19 #include <scheduler.h>
20 
21 #include "GameSoundDevice.h"
22 #include "GSUtility.h"
23 
24 
25 const int32 kPages = 20;
26 struct _gs_media_tracker
27 {
28 	BMediaFile*		file;
29 	BMediaTrack* 	stream;
30 	int64			frames;
31 	size_t			position;
32 };
33 
34 
35 // Local utility functions -----------------------------------------------
36 bool
37 FillBuffer(_gs_ramp* ramp, uint8* data, uint8* buffer, size_t* bytes)
38 {
39 	int32 samples = *bytes / sizeof(uint8);
40 
41 	for (int32 byte = 0; byte < samples; byte++) {
42 		float gain = *ramp->value;
43 		data[byte] = uint8(float(buffer[byte]) * gain);
44 
45 		if (ChangeRamp(ramp)) {
46 			*bytes = byte * sizeof(uint8);
47 			return true;
48 		}
49 	}
50 
51 	return false;
52 }
53 
54 
55 bool
56 FillBuffer(_gs_ramp* ramp, int16* data, int16* buffer, size_t* bytes)
57 {
58 	int32 samples = *bytes / sizeof(int16);
59 
60 	for (int32 byte = 0; byte < samples; byte++) {
61 		float gain = *ramp->value;
62 		data[byte] = int16(float(buffer[byte]) * gain);
63 
64 		if (ChangeRamp(ramp)) {
65 			*bytes = byte * sizeof(int16);
66 			return true;
67 		}
68 	}
69 
70 	return false;
71 }
72 
73 
74 bool
75 FillBuffer(_gs_ramp* ramp, int32* data, int32* buffer, size_t* bytes)
76 {
77 	size_t byte = 0;
78 	bool bytesAreReady = (*bytes > 0);
79 
80 	while (bytesAreReady) {
81 		float gain = *ramp->value;
82 		data[byte] = int32(float(buffer[byte]) * gain);
83 
84 		if (ChangeRamp(ramp)) {
85 			*bytes = byte;
86 			return true;
87 		}
88 
89 		byte++;
90 		bytesAreReady = (byte >= *bytes);
91 	}
92 
93 	return false;
94 }
95 
96 
97 bool
98 FillBuffer(_gs_ramp* ramp, float* data, float* buffer, size_t* bytes)
99 {
100 	size_t byte = 0;
101 	bool bytesAreReady = (*bytes > 0);
102 
103 	while (bytesAreReady) {
104 		float gain = *ramp->value;
105 		data[byte] = buffer[byte] * gain;
106 
107 		if (ChangeRamp(ramp)) {
108 			*bytes = byte;
109 			return true;
110 		}
111 
112 		byte++;
113 		bytesAreReady = (byte >= *bytes);
114 	}
115 
116 	return false;
117 }
118 
119 
120 // BFileGameSound -------------------------------------------------------
121 BFileGameSound::BFileGameSound(const entry_ref* file, bool looping,
122 	BGameSoundDevice* device)
123 	:
124 	BStreamingGameSound(device),
125 	fAudioStream(NULL),
126 	fStopping(false),
127 	fLooping(looping),
128 	fBuffer(NULL),
129 	fPlayPosition(0),
130 	fPausing(NULL),
131 	fPaused(false),
132 	fPauseGain(1.0)
133 {
134 	if (InitCheck() == B_OK)
135 		SetInitError(Init(file));
136 }
137 
138 
139 BFileGameSound::BFileGameSound(const char* file, bool looping,
140 	BGameSoundDevice* device)
141 	:
142 	BStreamingGameSound(device),
143 	fAudioStream(NULL),
144 	fStopping(false),
145 	fLooping(looping),
146 	fBuffer(NULL),
147 	fPlayPosition(0),
148 	fPausing(NULL),
149 	fPaused(false),
150 	fPauseGain(1.0)
151 {
152 	if (InitCheck() == B_OK) {
153 		entry_ref node;
154 
155 		if (get_ref_for_path(file, &node) != B_OK)
156 			SetInitError(B_ENTRY_NOT_FOUND);
157 		else
158 			SetInitError(Init(&node));
159 	}
160 }
161 
162 
163 BFileGameSound::~BFileGameSound()
164 {
165 	if (fReadThread >= 0) {
166 		// TODO: kill_thread() is very bad, since it will leak any resources
167 		// that the thread had allocated. It will also keep locks locked that
168 		// the thread holds! Set a flag to make the thread quit and use
169 		// wait_for_thread() here!
170 		kill_thread(fReadThread);
171 	}
172 
173 	if (fAudioStream) {
174 		if (fAudioStream->stream)
175 			fAudioStream->file->ReleaseTrack(fAudioStream->stream);
176 
177 		delete fAudioStream->file;
178 	}
179 
180 	delete [] fBuffer;
181 	delete fAudioStream;
182 }
183 
184 
185 BGameSound*
186 BFileGameSound::Clone() const
187 {
188 	return NULL;
189 }
190 
191 
192 status_t
193 BFileGameSound::StartPlaying()
194 {
195 	// restart playback if needed
196 	if (IsPlaying())
197 		StopPlaying();
198 
199 	// start playing the file
200 	return BStreamingGameSound::StartPlaying();
201 }
202 
203 
204 status_t
205 BFileGameSound::StopPlaying()
206 {
207 	status_t error = BStreamingGameSound::StopPlaying();
208 
209 	if (!fAudioStream || !fAudioStream->stream)
210 		return B_OK;
211 
212 	// start reading next time from the start of the file
213 	int64 frame = 0;
214 	fAudioStream->stream->SeekToFrame(&frame);
215 
216 	fStopping = false;
217 	fAudioStream->position = 0;
218 	fPlayPosition = 0;
219 
220 	return error;
221 }
222 
223 
224 status_t
225 BFileGameSound::Preload()
226 {
227 	if (!IsPlaying())
228 		Load();
229 
230 	return B_OK;
231 }
232 
233 
234 void
235 BFileGameSound::FillBuffer(void* inBuffer, size_t inByteCount)
236 {
237 	// Split or combine decoder buffers into mixer buffers
238 	// fPlayPosition is where we got up to in the input buffer after last call
239 
240 	size_t out_offset = 0;
241 
242 	while (inByteCount > 0 && !fPaused) {
243 		if (!fPaused || fPausing) {
244 			printf("mixout %ld, inByteCount %ld, decin %ld, BufferSize %ld\n",
245 				out_offset, inByteCount, fPlayPosition, fBufferSize);
246 			if (fPlayPosition == 0 || fPlayPosition >= fBufferSize) {
247 				Load();
248 			}
249 
250 			if (fPausing) {
251 				Lock();
252 
253 				bool rampDone = false;
254 				size_t bytes = fBufferSize - fPlayPosition;
255 
256 				if (bytes > inByteCount) {
257 					bytes = inByteCount;
258 				}
259 
260 				// Fill the requested buffer, stopping if the paused flag is set
261 				char* buffer = (char*)inBuffer;
262 
263 				switch(Format().format) {
264 					case gs_audio_format::B_GS_U8:
265 						rampDone = ::FillBuffer(fPausing,
266 							(uint8*)&buffer[out_offset],
267 							(uint8*)&fBuffer[fPlayPosition], &bytes);
268 						break;
269 
270 					case gs_audio_format::B_GS_S16:
271 						rampDone = ::FillBuffer(fPausing,
272 							(int16*)&buffer[out_offset],
273 							(int16*)&fBuffer[fPlayPosition], &bytes);
274 						break;
275 
276 					case gs_audio_format::B_GS_S32:
277 						rampDone = ::FillBuffer(fPausing,
278 							(int32*)&buffer[out_offset],
279 							(int32*)&fBuffer[fPlayPosition], &bytes);
280 						break;
281 
282 					case gs_audio_format::B_GS_F:
283 						rampDone = ::FillBuffer(fPausing,
284 							(float*)&buffer[out_offset],
285 							(float*)&fBuffer[fPlayPosition], &bytes);
286 						break;
287 				}
288 
289 				inByteCount -= bytes;
290 				out_offset += bytes;
291 				fPlayPosition += bytes;
292 
293 				// We finished ramping
294 				if (rampDone) {
295 
296 					// Need to be able to stop asap when pause flag is flipped.
297 					while (fPlayPosition < fBufferSize && (inByteCount > 0)) {
298 						buffer[out_offset++] = fBuffer[fPlayPosition++];
299 						inByteCount--;
300 					}
301 
302 					delete fPausing;
303 					fPausing = NULL;
304 				}
305 
306 				Unlock();
307 			} else {
308 
309 				char* buffer = (char*)inBuffer;
310 
311 				// Need to be able to stop asap when the pause flag is flipped.
312 				while (fPlayPosition < fBufferSize && (!fPaused || fPausing)
313 					&& (inByteCount > 0)) {
314 					buffer[out_offset++] = fBuffer[fPlayPosition++];
315 					inByteCount--;
316 				}
317 			}
318 		}
319 	}
320 }
321 
322 
323 status_t
324 BFileGameSound::Perform(int32 selector, void* data)
325 {
326 	return B_ERROR;
327 }
328 
329 
330 status_t
331 BFileGameSound::SetPaused(bool isPaused, bigtime_t rampTime)
332 {
333 	if (fPaused == isPaused)
334 		return EALREADY;
335 
336 	Lock();
337 
338 	// Clear any old ramping
339 	delete fPausing;
340 	fPausing = NULL;
341 
342 	if (rampTime > 100000) {
343 		// Setup for ramping
344 		if (isPaused)
345 			fPausing = InitRamp(&fPauseGain, 0.0,
346 								Format().frame_rate, rampTime);
347 		else
348 			fPausing = InitRamp(&fPauseGain, 1.0,
349 								Format().frame_rate, rampTime);
350 	}
351 
352 	fPaused = isPaused;
353 	Unlock();
354 
355 	return B_OK;
356 }
357 
358 
359 int32
360 BFileGameSound::IsPaused()
361 {
362 	if (fPausing)
363 		return B_PAUSE_IN_PROGRESS;
364 
365 	if (fPaused)
366 		return B_PAUSED;
367 
368 	return B_NOT_PAUSED;
369 }
370 
371 
372 status_t
373 BFileGameSound::Init(const entry_ref* file)
374 {
375 	fAudioStream = new(std::nothrow) _gs_media_tracker;
376 	if (!fAudioStream)
377 		return B_NO_MEMORY;
378 
379 	memset(fAudioStream, 0, sizeof(_gs_media_tracker));
380 	fAudioStream->file = new(std::nothrow) BMediaFile(file);
381 	if (!fAudioStream->file) {
382 		delete fAudioStream;
383 		fAudioStream = NULL;
384 		return B_NO_MEMORY;
385 	}
386 
387 	status_t error = fAudioStream->file->InitCheck();
388 	if (error != B_OK)
389 		return error;
390 
391 	fAudioStream->stream = fAudioStream->file->TrackAt(0);
392 
393 	// is this is an audio file?
394 	media_format playFormat;
395 	if ((error = fAudioStream->stream->EncodedFormat(&playFormat)) != B_OK) {
396 		fAudioStream->file->ReleaseTrack(fAudioStream->stream);
397 		fAudioStream->stream = NULL;
398 		return error;
399 	}
400 
401 	if (!playFormat.IsAudio()) {
402 		fAudioStream->file->ReleaseTrack(fAudioStream->stream);
403 		fAudioStream->stream = NULL;
404 		return B_MEDIA_BAD_FORMAT;
405 	}
406 
407 	gs_audio_format dformat = Device()->Format();
408 
409 	// request the format we want the sound
410 	memset(&playFormat, 0, sizeof(media_format));
411 	playFormat.type = B_MEDIA_RAW_AUDIO;
412 	if (fAudioStream->stream->DecodedFormat(&playFormat) != B_OK) {
413 		fAudioStream->file->ReleaseTrack(fAudioStream->stream);
414 		fAudioStream->stream = NULL;
415 		return B_MEDIA_BAD_FORMAT;
416 	}
417 
418 	// translate the format into a "GameKit" friendly one
419 	gs_audio_format gsformat;
420 	media_to_gs_format(&gsformat, &playFormat.u.raw_audio);
421 
422 	// Since the buffer sized read from the file is most likely differnt
423 	// then the buffer used by the audio mixer, we must allocate a buffer
424 	// large enough to hold the largest request.
425 	fBufferSize = gsformat.buffer_size;
426 	if (fBufferSize < dformat.buffer_size)
427 		fBufferSize = dformat.buffer_size;
428 
429 	// create the buffer
430 	fBuffer = new char[fBufferSize * 2];
431 	memset(fBuffer, 0, fBufferSize * 2);
432 
433 	fFrameSize = gsformat.channel_count * get_sample_size(gsformat.format);
434 	fAudioStream->frames = fAudioStream->stream->CountFrames();
435 
436 	// Ask the device to attach our sound to it
437 	gs_id sound;
438 	error = Device()->CreateBuffer(&sound, this, &gsformat);
439 	if (error != B_OK)
440 		return error;
441 
442 	return BGameSound::Init(sound);
443 }
444 
445 
446 bool
447 BFileGameSound::Load()
448 {
449 	if (!fAudioStream || !fAudioStream->stream)
450 		return false;
451 
452 	// read a new buffer
453 	int64 frames = 0;
454 	fAudioStream->stream->ReadFrames(fBuffer, &frames);
455 	fBufferSize = frames * fFrameSize;
456 	fPlayPosition = 0;
457 
458 	if (fBufferSize <= 0) {
459 		// EOF
460 		if (fLooping) {
461 			// start reading next time from the start of the file
462 			int64 frame = 0;
463 			fAudioStream->stream->SeekToFrame(&frame);
464 		} else {
465 			StopPlaying();
466 		}
467 	}
468 
469 	return true;
470 }
471 
472 
473 bool
474 BFileGameSound::Read(void* buffer, size_t bytes)
475 {
476 	return false;
477 }
478 
479 
480 /* unimplemented for protection of the user:
481  *
482  * BFileGameSound::BFileGameSound()
483  * BFileGameSound::BFileGameSound(const BFileGameSound &)
484  * BFileGameSound &BFileGameSound::operator=(const BFileGameSound &)
485  */
486 
487 
488 status_t
489 BFileGameSound::_Reserved_BFileGameSound_0(int32 arg, ...)
490 {
491 	return B_ERROR;
492 }
493 
494 
495 status_t
496 BFileGameSound::_Reserved_BFileGameSound_1(int32 arg, ...)
497 {
498 	return B_ERROR;
499 }
500 
501 
502 status_t
503 BFileGameSound::_Reserved_BFileGameSound_2(int32 arg, ...)
504 {
505 	return B_ERROR;
506 }
507 
508 
509 status_t
510 BFileGameSound::_Reserved_BFileGameSound_3(int32 arg, ...)
511 {
512 	return B_ERROR;
513 }
514 
515 
516 status_t
517 BFileGameSound::_Reserved_BFileGameSound_4(int32 arg, ...)
518 {
519 	return B_ERROR;
520 }
521 
522 
523 status_t
524 BFileGameSound::_Reserved_BFileGameSound_5(int32 arg, ...)
525 {
526 	return B_ERROR;
527 }
528 
529 
530 status_t
531 BFileGameSound::_Reserved_BFileGameSound_6(int32 arg, ...)
532 {
533 	return B_ERROR;
534 }
535 
536 
537 status_t
538 BFileGameSound::_Reserved_BFileGameSound_7(int32 arg, ...)
539 {
540 	return B_ERROR;
541 }
542 
543 
544 status_t
545 BFileGameSound::_Reserved_BFileGameSound_8(int32 arg, ...)
546 {
547 	return B_ERROR;
548 }
549 
550 
551 status_t
552 BFileGameSound::_Reserved_BFileGameSound_9(int32 arg, ...)
553 {
554 	return B_ERROR;
555 }
556 
557 
558 status_t
559 BFileGameSound::_Reserved_BFileGameSound_10(int32 arg, ...)
560 {
561 	return B_ERROR;
562 }
563 
564 
565 status_t
566 BFileGameSound::_Reserved_BFileGameSound_11(int32 arg, ...)
567 {
568 	return B_ERROR;
569 }
570 
571 
572 status_t
573 BFileGameSound::_Reserved_BFileGameSound_12(int32 arg, ...)
574 {
575 	return B_ERROR;
576 }
577 
578 
579 status_t
580 BFileGameSound::_Reserved_BFileGameSound_13(int32 arg, ...)
581 {
582 	return B_ERROR;
583 }
584 
585 
586 status_t
587 BFileGameSound::_Reserved_BFileGameSound_14(int32 arg, ...)
588 {
589 	return B_ERROR;
590 }
591 
592 
593 status_t
594 BFileGameSound::_Reserved_BFileGameSound_15(int32 arg, ...)
595 {
596 	return B_ERROR;
597 }
598 
599 
600 status_t
601 BFileGameSound::_Reserved_BFileGameSound_16(int32 arg, ...)
602 {
603 	return B_ERROR;
604 }
605 
606 
607 status_t
608 BFileGameSound::_Reserved_BFileGameSound_17(int32 arg, ...)
609 {
610 	return B_ERROR;
611 }
612 
613 
614 status_t
615 BFileGameSound::_Reserved_BFileGameSound_18(int32 arg, ...)
616 {
617 	return B_ERROR;
618 }
619 
620 
621 status_t
622 BFileGameSound::_Reserved_BFileGameSound_19(int32 arg, ...)
623 {
624 	return B_ERROR;
625 }
626 
627 
628 status_t
629 BFileGameSound::_Reserved_BFileGameSound_20(int32 arg, ...)
630 {
631 	return B_ERROR;
632 }
633 
634 
635 status_t
636 BFileGameSound::_Reserved_BFileGameSound_21(int32 arg, ...)
637 {
638 	return B_ERROR;
639 }
640 
641 
642 status_t
643 BFileGameSound::_Reserved_BFileGameSound_22(int32 arg, ...)
644 {
645 	return B_ERROR;
646 }
647 
648 
649 status_t
650 BFileGameSound::_Reserved_BFileGameSound_23(int32 arg, ...)
651 {
652 	return B_ERROR;
653 }
654