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