1 //------------------------------------------------------------------------------
2 // Copyright (c) 2001-2002, OpenBeOS
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"
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 ----------------------------------------
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
)
69 fConnection
= new Connection
;
70 fNode
= new GameProducer(this, format
);
72 fFrameSize
= get_sample_size(format
->format
) * format
->channel_count
;
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();
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
);
105 const gs_audio_format
&
106 GameSoundBuffer::Format() const
113 GameSoundBuffer::IsLooping() const
120 GameSoundBuffer::SetLooping(bool looping
)
127 GameSoundBuffer::Gain() const
134 GameSoundBuffer::SetGain(float gain
, bigtime_t duration
)
136 if (gain
< 0.0 || gain
> 1.0)
142 if (duration
> 100000)
143 fGainRamp
= InitRamp(&fGain
, gain
, fFormat
.frame_rate
, duration
);
152 GameSoundBuffer::Pan() const
159 GameSoundBuffer::SetPan(float pan
, bigtime_t duration
)
161 if (pan
< -1.0 || pan
> 1.0)
167 if (duration
< 100000) {
172 fPanRight
= 1.0 + fPan
;
175 fPanLeft
= 1.0 - fPan
;
178 fPanRamp
= InitRamp(&fPan
, pan
, fFormat
.frame_rate
, duration
);
185 GameSoundBuffer::GetAttributes(gs_attribute
* attributes
,
186 size_t attributeCount
)
188 for (size_t i
= 0; i
< attributeCount
; i
++) {
189 switch (attributes
[i
].attribute
) {
191 attributes
[i
].value
= fGain
;
193 attributes
[i
].duration
= fGainRamp
->duration
;
197 attributes
[i
].value
= fPan
;
199 attributes
[i
].duration
= fPanRamp
->duration
;
203 attributes
[i
].value
= (fLooping
) ? -1.0 : 0.0;
204 attributes
[i
].duration
= bigtime_t(0);
208 attributes
[i
].value
= 0.0;
209 attributes
[i
].duration
= bigtime_t(0);
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
) {
227 error
= SetGain(attributes
[i
].value
, attributes
[i
].duration
);
231 error
= SetPan(attributes
[i
].value
, attributes
[i
].duration
);
235 fLooping
= bool(attributes
[i
].value
);
248 GameSoundBuffer::Play(void * data
, int64 frames
)
250 // Mh... should we add some locking?
254 if (fFormat
.channel_count
== 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
);
274 case gs_audio_format::B_GS_S16
:
276 for (int64 i
= 0; i
< frames
; i
++) {
277 ApplyMod((int16
*)data
, (int16
*)buffer
, i
, pan
);
284 case gs_audio_format::B_GS_S32
:
286 for (int64 i
= 0; i
< frames
; i
++) {
287 ApplyMod((int32
*)data
, (int32
*)buffer
, i
, pan
);
294 case gs_audio_format::B_GS_F
:
296 for (int64 i
= 0; i
< frames
; i
++) {
297 ApplyMod((float*)data
, (float*)buffer
, i
, pan
);
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
);
312 debugger("Invalid number of channels.");
318 GameSoundBuffer::UpdateMods()
320 // adjust the gain if needed
322 if (ChangeRamp(fGainRamp
)) {
328 // adjust the ramp if needed
330 if (ChangeRamp(fPanRamp
)) {
336 fPanRight
= 1.0 + fPan
;
339 fPanLeft
= 1.0 - fPan
;
347 GameSoundBuffer::Reset()
365 GameSoundBuffer::Connect(media_node
* consumer
)
367 BMediaRoster
* roster
= BMediaRoster::Roster();
368 status_t err
= roster
->RegisterNode(fNode
);
373 // make sure the Media Roster knows that we're using the node
374 err
= roster
->GetNodeFor(fNode
->Node().node
, &fConnection
->producer
);
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
);
388 err
= roster
->SetTimeSourceFor(fConnection
->producer
.node
,
389 fConnection
->timeSource
.node
);
392 // got the nodes; now we find the endpoints of the connection
393 media_input mixerInput
;
394 media_output soundOutput
;
396 err
= roster
->GetFreeOutputsFor(fConnection
->producer
, &soundOutput
, 1,
402 err
= roster
->GetFreeInputsFor(fConnection
->consumer
, &mixerInput
, 1,
407 // got the endpoints; now we connect it!
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
);
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
;
429 GameSoundBuffer::StartPlaying()
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
);
454 GameSoundBuffer::StopPlaying()
459 BMediaRoster
* roster
= BMediaRoster::Roster();
460 roster
->StopNode(fConnection
->producer
, 0, true);
471 GameSoundBuffer::IsPlaying()
477 // SimpleSoundBuffer ------------------------------------------------------
478 SimpleSoundBuffer::SimpleSoundBuffer(const gs_audio_format
* format
,
479 const void * data
, int64 frames
)
481 GameSoundBuffer(format
),
484 fBufferSize
= frames
* fFrameSize
;
485 fBuffer
= (char*)data
;
489 SimpleSoundBuffer::~SimpleSoundBuffer()
496 SimpleSoundBuffer::Reset()
498 GameSoundBuffer::Reset();
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
);
516 // restart the sound from the begging
517 memcpy(&buffer
[remainder
], fBuffer
, bytes
- remainder
);
518 fPosition
= bytes
- remainder
;
520 fPosition
= fBufferSize
;
522 memset(data
, 0, bytes
);
523 // there is nothing left to play
525 memcpy(buffer
, &fBuffer
[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()
552 StreamingSoundBuffer::FillBuffer(void * buffer
, int64 frames
)
554 BStreamingGameSound
* object
= (BStreamingGameSound
*)fStreamHook
;
556 size_t bytes
= fFrameSize
* frames
;
557 object
->FillBuffer(buffer
, bytes
);