vfs: check userland buffers before reading them.
[haiku.git] / src / kits / game / GameSoundBuffer.cpp
blobf52cde622d1a46b020ccb0b808e3fe22e97171be
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:
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
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.
22 // File Name: GameSoundBuffer.h
23 // Author: Christopher ML Zumwalt May (zummy@users.sf.net)
24 // Description: Interface to a single sound, managed by the GameSoundDevice.
25 //------------------------------------------------------------------------------
28 #include "GameSoundBuffer.h"
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
34 #include <MediaRoster.h>
35 #include <MediaAddOn.h>
36 #include <MediaTheme.h>
37 #include <TimeSource.h>
38 #include <BufferGroup.h>
40 #include "GameProducer.h"
41 #include "GameSoundDevice.h"
42 #include "StreamingGameSound.h"
43 #include "GSUtility.h"
46 // Sound Buffer Utility functions ----------------------------------------
47 template<typename T>
48 static inline void
49 ApplyMod(T* data, T* buffer, int64 index, float * pan)
51 data[index * 2] += T(float(buffer[index * 2]) * pan[0]);
52 data[index * 2 + 1] += T(float(buffer[index * 2 + 1]) * pan[1]);
56 // GameSoundBuffer -------------------------------------------------------
57 GameSoundBuffer::GameSoundBuffer(const gs_audio_format * format)
59 fLooping(false),
60 fIsConnected(false),
61 fIsPlaying(false),
62 fGain(1.0),
63 fPan(0.0),
64 fPanLeft(1.0),
65 fPanRight(1.0),
66 fGainRamp(NULL),
67 fPanRamp(NULL)
69 fConnection = new Connection;
70 fNode = new GameProducer(this, format);
72 fFrameSize = get_sample_size(format->format) * format->channel_count;
74 fFormat = *format;
78 // Play must stop before the distructor is called; otherwise, a fatal
79 // error occures if the playback is in a subclass.
80 GameSoundBuffer::~GameSoundBuffer()
82 BMediaRoster* roster = BMediaRoster::Roster();
84 if (fIsConnected) {
85 // Ordinarily we'd stop *all* of the nodes in the chain at this point.
86 // However, one of the nodes is the System Mixer, and stopping the Mixer
87 // is a Bad Idea (tm). So, we just disconnect from it, and release our
88 // references to the nodes that we're using. We *are* supposed to do
89 // that even for global nodes like the Mixer.
90 roster->Disconnect(fConnection->producer.node, fConnection->source,
91 fConnection->consumer.node, fConnection->destination);
93 roster->ReleaseNode(fConnection->producer);
94 roster->ReleaseNode(fConnection->consumer);
97 delete fGainRamp;
98 delete fPanRamp;
100 delete fConnection;
101 delete fNode;
105 const gs_audio_format &
106 GameSoundBuffer::Format() const
108 return fFormat;
112 bool
113 GameSoundBuffer::IsLooping() const
115 return fLooping;
119 void
120 GameSoundBuffer::SetLooping(bool looping)
122 fLooping = looping;
126 float
127 GameSoundBuffer::Gain() const
129 return fGain;
133 status_t
134 GameSoundBuffer::SetGain(float gain, bigtime_t duration)
136 if (gain < 0.0 || gain > 1.0)
137 return B_BAD_VALUE;
139 delete fGainRamp;
140 fGainRamp = NULL;
142 if (duration > 100000)
143 fGainRamp = InitRamp(&fGain, gain, fFormat.frame_rate, duration);
144 else
145 fGain = gain;
147 return B_OK;
151 float
152 GameSoundBuffer::Pan() const
154 return fPan;
158 status_t
159 GameSoundBuffer::SetPan(float pan, bigtime_t duration)
161 if (pan < -1.0 || pan > 1.0)
162 return B_BAD_VALUE;
164 delete fPanRamp;
165 fPanRamp = NULL;
167 if (duration < 100000) {
168 fPan = pan;
170 if (fPan < 0.0) {
171 fPanLeft = 1.0;
172 fPanRight = 1.0 + fPan;
173 } else {
174 fPanRight = 1.0;
175 fPanLeft = 1.0 - fPan;
177 } else
178 fPanRamp = InitRamp(&fPan, pan, fFormat.frame_rate, duration);
180 return B_OK;
184 status_t
185 GameSoundBuffer::GetAttributes(gs_attribute * attributes,
186 size_t attributeCount)
188 for (size_t i = 0; i < attributeCount; i++) {
189 switch (attributes[i].attribute) {
190 case B_GS_GAIN:
191 attributes[i].value = fGain;
192 if (fGainRamp)
193 attributes[i].duration = fGainRamp->duration;
194 break;
196 case B_GS_PAN:
197 attributes[i].value = fPan;
198 if (fPanRamp)
199 attributes[i].duration = fPanRamp->duration;
200 break;
202 case B_GS_LOOPING:
203 attributes[i].value = (fLooping) ? -1.0 : 0.0;
204 attributes[i].duration = bigtime_t(0);
205 break;
207 default:
208 attributes[i].value = 0.0;
209 attributes[i].duration = bigtime_t(0);
210 break;
214 return B_OK;
218 status_t
219 GameSoundBuffer::SetAttributes(gs_attribute * attributes,
220 size_t attributeCount)
222 status_t error = B_OK;
224 for (size_t i = 0; i < attributeCount; i++) {
225 switch (attributes[i].attribute) {
226 case B_GS_GAIN:
227 error = SetGain(attributes[i].value, attributes[i].duration);
228 break;
230 case B_GS_PAN:
231 error = SetPan(attributes[i].value, attributes[i].duration);
232 break;
234 case B_GS_LOOPING:
235 fLooping = bool(attributes[i].value);
236 break;
238 default:
239 break;
243 return error;
247 void
248 GameSoundBuffer::Play(void * data, int64 frames)
250 // Mh... should we add some locking?
251 if (!fIsPlaying)
252 return;
254 if (fFormat.channel_count == 2) {
255 float pan[2];
256 pan[0] = fPanRight * fGain;
257 pan[1] = fPanLeft * fGain;
259 char * buffer = new char[fFrameSize * frames];
261 FillBuffer(buffer, frames);
263 switch (fFormat.format) {
264 case gs_audio_format::B_GS_U8:
266 for (int64 i = 0; i < frames; i++) {
267 ApplyMod((uint8*)data, (uint8*)buffer, i, pan);
268 UpdateMods();
271 break;
274 case gs_audio_format::B_GS_S16:
276 for (int64 i = 0; i < frames; i++) {
277 ApplyMod((int16*)data, (int16*)buffer, i, pan);
278 UpdateMods();
281 break;
284 case gs_audio_format::B_GS_S32:
286 for (int64 i = 0; i < frames; i++) {
287 ApplyMod((int32*)data, (int32*)buffer, i, pan);
288 UpdateMods();
291 break;
294 case gs_audio_format::B_GS_F:
296 for (int64 i = 0; i < frames; i++) {
297 ApplyMod((float*)data, (float*)buffer, i, pan);
298 UpdateMods();
301 break;
304 delete[] buffer;
305 } else if (fFormat.channel_count == 1) {
306 // FIXME the output should be stereo, and we could pan mono sounds
307 // here. But currently the output has the same number of channels as
308 // the sound and we can't do this.
309 // FIXME also, we don't handle the gain here.
310 FillBuffer(data, frames);
311 } else
312 debugger("Invalid number of channels.");
317 void
318 GameSoundBuffer::UpdateMods()
320 // adjust the gain if needed
321 if (fGainRamp) {
322 if (ChangeRamp(fGainRamp)) {
323 delete fGainRamp;
324 fGainRamp = NULL;
328 // adjust the ramp if needed
329 if (fPanRamp) {
330 if (ChangeRamp(fPanRamp)) {
331 delete fPanRamp;
332 fPanRamp = NULL;
333 } else {
334 if (fPan < 0.0) {
335 fPanLeft = 1.0;
336 fPanRight = 1.0 + fPan;
337 } else {
338 fPanRight = 1.0;
339 fPanLeft = 1.0 - fPan;
346 void
347 GameSoundBuffer::Reset()
349 fGain = 1.0;
350 delete fGainRamp;
351 fGainRamp = NULL;
353 fPan = 0.0;
354 fPanLeft = 1.0;
355 fPanRight = 1.0;
357 delete fPanRamp;
358 fPanRamp = NULL;
360 fLooping = false;
364 status_t
365 GameSoundBuffer::Connect(media_node * consumer)
367 BMediaRoster* roster = BMediaRoster::Roster();
368 status_t err = roster->RegisterNode(fNode);
370 if (err != B_OK)
371 return err;
373 // make sure the Media Roster knows that we're using the node
374 err = roster->GetNodeFor(fNode->Node().node, &fConnection->producer);
376 if (err != B_OK)
377 return err;
379 // connect to the mixer
380 fConnection->consumer = *consumer;
382 // set the producer's time source to be the "default" time source, which
383 // the Mixer uses too.
384 err = roster->GetTimeSource(&fConnection->timeSource);
385 if (err != B_OK)
386 return err;
388 err = roster->SetTimeSourceFor(fConnection->producer.node,
389 fConnection->timeSource.node);
390 if (err != B_OK)
391 return err;
392 // got the nodes; now we find the endpoints of the connection
393 media_input mixerInput;
394 media_output soundOutput;
395 int32 count = 1;
396 err = roster->GetFreeOutputsFor(fConnection->producer, &soundOutput, 1,
397 &count);
399 if (err != B_OK)
400 return err;
401 count = 1;
402 err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1,
403 &count);
404 if (err != B_OK)
405 return err;
407 // got the endpoints; now we connect it!
408 media_format format;
409 format.type = B_MEDIA_RAW_AUDIO;
410 format.u.raw_audio = media_raw_audio_format::wildcard;
411 err = roster->Connect(soundOutput.source, mixerInput.destination, &format,
412 &soundOutput, &mixerInput);
413 if (err != B_OK)
414 return err;
416 // the inputs and outputs might have been reassigned during the
417 // nodes' negotiation of the Connect(). That's why we wait until
418 // after Connect() finishes to save their contents.
419 fConnection->format = format;
420 fConnection->source = soundOutput.source;
421 fConnection->destination = mixerInput.destination;
423 fIsConnected = true;
424 return B_OK;
428 status_t
429 GameSoundBuffer::StartPlaying()
431 if (fIsPlaying)
432 return EALREADY;
434 BMediaRoster* roster = BMediaRoster::Roster();
435 BTimeSource* source = roster->MakeTimeSourceFor(fConnection->producer);
437 // make sure we give the producer enough time to run buffers through
438 // the node chain, otherwise it'll start up already late
439 bigtime_t latency = 0;
440 status_t status = roster->GetLatencyFor(fConnection->producer, &latency);
441 if (status == B_OK) {
442 status = roster->StartNode(fConnection->producer,
443 source->Now() + latency);
445 source->Release();
447 fIsPlaying = true;
449 return status;
453 status_t
454 GameSoundBuffer::StopPlaying()
456 if (!fIsPlaying)
457 return EALREADY;
459 BMediaRoster* roster = BMediaRoster::Roster();
460 roster->StopNode(fConnection->producer, 0, true);
461 // synchronous stop
463 Reset();
464 fIsPlaying = false;
466 return B_OK;
470 bool
471 GameSoundBuffer::IsPlaying()
473 return fIsPlaying;
477 // SimpleSoundBuffer ------------------------------------------------------
478 SimpleSoundBuffer::SimpleSoundBuffer(const gs_audio_format * format,
479 const void * data, int64 frames)
481 GameSoundBuffer(format),
482 fPosition(0)
484 fBufferSize = frames * fFrameSize;
485 fBuffer = (char*)data;
489 SimpleSoundBuffer::~SimpleSoundBuffer()
491 delete [] fBuffer;
495 void
496 SimpleSoundBuffer::Reset()
498 GameSoundBuffer::Reset();
499 fPosition = 0;
503 void
504 SimpleSoundBuffer::FillBuffer(void * data, int64 frames)
506 char * buffer = (char*)data;
507 size_t bytes = fFrameSize * frames;
509 if (fPosition + bytes >= fBufferSize) {
510 if (fPosition < fBufferSize) {
511 // copy the remaining frames
512 size_t remainder = fBufferSize - fPosition;
513 memcpy(buffer, &fBuffer[fPosition], remainder);
515 if (fLooping) {
516 // restart the sound from the begging
517 memcpy(&buffer[remainder], fBuffer, bytes - remainder);
518 fPosition = bytes - remainder;
519 } else
520 fPosition = fBufferSize;
521 } else
522 memset(data, 0, bytes);
523 // there is nothing left to play
524 } else {
525 memcpy(buffer, &fBuffer[fPosition], bytes);
526 fPosition += bytes;
531 // StreamingSoundBuffer ------------------------------------------------------
532 StreamingSoundBuffer::StreamingSoundBuffer(const gs_audio_format * format,
533 const void * streamHook, size_t inBufferFrameCount, size_t inBufferCount)
535 GameSoundBuffer(format),
536 fStreamHook(const_cast<void *>(streamHook))
538 if (inBufferFrameCount != 0 && inBufferCount != 0) {
539 BBufferGroup *bufferGroup
540 = new BBufferGroup(inBufferFrameCount * fFrameSize, inBufferCount);
541 fNode->SetBufferGroup(fConnection->source, bufferGroup);
546 StreamingSoundBuffer::~StreamingSoundBuffer()
551 void
552 StreamingSoundBuffer::FillBuffer(void * buffer, int64 frames)
554 BStreamingGameSound* object = (BStreamingGameSound*)fStreamHook;
556 size_t bytes = fFrameSize * frames;
557 object->FillBuffer(buffer, bytes);