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: GameSoundBuffer.h 23 // Author: Christopher ML Zumwalt May (zummy@users.sf.net) 24 // Description: Interface to a single sound, managed by the GameSoundDevice. 25 //------------------------------------------------------------------------------ 26 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 31 #include <MediaRoster.h> 32 #include <MediaAddOn.h> 33 #include <MediaTheme.h> 34 #include <TimeSource.h> 35 36 #include "GameProducer.h" 37 #include "GameSoundBuffer.h" 38 #include "GameSoundDevice.h" 39 #include "StreamingGameSound.h" 40 #include "GSUtility.h" 41 42 // Sound Buffer Utility functions ---------------------------------------- 43 inline void ApplyMod(uint8 * data, uint8 * buffer, int64 index, float gain, float * pan) 44 { 45 data[index*2] += uint8(float(buffer[index*2]) * gain * pan[0]); 46 data[index*2+1] += uint8(float(buffer[index*2+1]) * gain * pan[1]); 47 } 48 49 50 inline void ApplyMod(int16 * data, int16 * buffer, int32 index, float gain, float * pan) 51 { 52 data[index*2] = int16(float(buffer[index*2]) * gain * pan[0]); 53 data[index*2+1] = int16(float(buffer[index*2+1]) * gain * pan[1]); 54 } 55 56 57 inline void ApplyMod(int32 * data, int32 * buffer, int32 index, float gain, float * pan) 58 { 59 data[index*2] += int32(float(buffer[index*2]) * gain * pan[0]); 60 data[index*2+1] += int32(float(buffer[index*2+1]) * gain * pan[1]); 61 } 62 63 64 inline void ApplyMod(float * data, float * buffer, int32 index, float gain, float * pan) 65 { 66 data[index*2] += buffer[index*2] * gain * pan[0]; 67 data[index*2+1] += buffer[index*2+1] * gain * pan[1]; 68 } 69 70 // GameSoundBuffer ------------------------------------------------------- 71 GameSoundBuffer::GameSoundBuffer(const gs_audio_format * format) 72 : fLooping(false), 73 fIsConnected(false), 74 fIsPlaying(false), 75 fGain(1.0), 76 fPan(0.0), 77 fPanLeft(1.0), 78 fPanRight(1.0), 79 fGainRamp(NULL), 80 fPanRamp(NULL) 81 { 82 fConnection = new Connection; 83 fNode = new GameProducer(this, format); 84 85 fFrameSize = get_sample_size(format->format) * format->channel_count; 86 87 memcpy(&fFormat, format, sizeof(gs_audio_format)); 88 } 89 90 // Play must stop before the distructor is called; otherwise, a fatel 91 // error occures if the playback is in a subclass. 92 GameSoundBuffer::~GameSoundBuffer() 93 { 94 BMediaRoster* r = BMediaRoster::Roster(); 95 96 if (fIsConnected) { 97 // Ordinarily we'd stop *all* of the nodes in the chain at this point. However, 98 // one of the nodes is the System Mixer, and stopping the Mixer is a Bad Idea (tm). 99 // So, we just disconnect from it, and release our references to the nodes that 100 // we're using. We *are* supposed to do that even for global nodes like the Mixer. 101 r->Disconnect(fConnection->producer.node, fConnection->source, 102 fConnection->consumer.node, fConnection->destination); 103 104 r->ReleaseNode(fConnection->producer); 105 r->ReleaseNode(fConnection->consumer); 106 } 107 108 delete fGainRamp; 109 delete fPanRamp; 110 111 delete fConnection; 112 delete fNode; 113 } 114 115 116 const gs_audio_format & 117 GameSoundBuffer::Format() const 118 { 119 return fFormat; 120 } 121 122 123 bool 124 GameSoundBuffer::IsLooping() const 125 { 126 return fLooping; 127 } 128 129 130 void 131 GameSoundBuffer::SetLooping(bool looping) 132 { 133 fLooping = looping; 134 } 135 136 137 float 138 GameSoundBuffer::Gain() const 139 { 140 return fGain; 141 } 142 143 144 status_t 145 GameSoundBuffer::SetGain(float gain, bigtime_t duration) 146 { 147 if (gain < 0.0 || gain > 1.0) 148 return B_BAD_VALUE; 149 150 if (fGainRamp) delete fGainRamp; 151 152 if (duration > 100000) 153 fGainRamp = InitRamp(&fGain, gain, fFormat.frame_rate, duration); 154 else 155 fGain = gain; 156 157 return B_OK; 158 } 159 160 161 float 162 GameSoundBuffer::Pan() const 163 { 164 return fPan; 165 } 166 167 168 status_t 169 GameSoundBuffer::SetPan(float pan, bigtime_t duration) 170 { 171 if (pan < -1.0 || pan > 1.0) 172 return B_BAD_VALUE; 173 174 if (fPanRamp) 175 delete fPanRamp; 176 177 if (duration < 100000) { 178 fPan = pan; 179 180 if (fPan < 0.0) { 181 fPanLeft = 1.0; 182 fPanRight = 1.0 + fPan; 183 } else { 184 fPanRight = 1.0; 185 fPanLeft = 1.0 - fPan; 186 } 187 } else 188 fPanRamp = InitRamp(&fPan, pan, fFormat.frame_rate, duration); 189 190 return B_OK; 191 } 192 193 194 status_t 195 GameSoundBuffer::GetAttributes(gs_attribute * attributes, 196 size_t attributeCount) 197 { 198 for (size_t i = 0; i < attributeCount; i++) { 199 switch(attributes[i].attribute) { 200 case B_GS_GAIN: 201 attributes[i].value = fGain; 202 if (fGainRamp) 203 attributes[i].duration = fGainRamp->duration; 204 break; 205 206 case B_GS_PAN: 207 attributes[i].value = fPan; 208 if (fPanRamp) 209 attributes[i].duration = fGainRamp->duration; 210 211 case B_GS_LOOPING: 212 attributes[i].value = (fLooping) ? -1.0 : 0.0; 213 attributes[i].duration = bigtime_t(0); 214 215 default: 216 attributes[i].value = 0.0; 217 attributes[i].duration = bigtime_t(0); 218 } 219 } 220 221 return B_OK; 222 } 223 224 225 status_t 226 GameSoundBuffer::SetAttributes(gs_attribute * attributes, 227 size_t attributeCount) 228 { 229 status_t error = B_OK; 230 231 for (size_t i = 0; i < attributeCount; i++) { 232 switch(attributes[i].attribute) { 233 case B_GS_GAIN: 234 error = SetGain(attributes[i].value, attributes[i].duration); 235 break; 236 237 case B_GS_PAN: 238 error = SetPan(attributes[i].value, attributes[i].duration); 239 break; 240 241 case B_GS_LOOPING: 242 fLooping = bool(attributes[i].value); 243 } 244 } 245 246 return error; 247 } 248 249 250 void 251 GameSoundBuffer::Play(void * data, int64 frames) 252 { 253 float *pan = new float[2]; 254 pan[0] = fPanRight; 255 pan[1] = fPanLeft; 256 257 char * buffer = new char[fFrameSize * frames]; 258 259 FillBuffer(buffer, frames); 260 261 switch(fFormat.format) { 262 case gs_audio_format::B_GS_U8: 263 { 264 for (int64 i = 0; i < frames; i++) { 265 ApplyMod((uint8*)data, (uint8*)buffer, i, fGain, pan); 266 UpdateMods(); 267 } 268 269 break; 270 } 271 272 case gs_audio_format::B_GS_S16: 273 { 274 for (int64 i = 0; i < frames; i++) { 275 ApplyMod((int16*)data, (int16*)buffer, i, fGain, pan); 276 UpdateMods(); 277 } 278 279 break; 280 } 281 282 case gs_audio_format::B_GS_S32: 283 { 284 for (int64 i = 0; i < frames; i++) { 285 ApplyMod((int32*)data, (int32*)buffer, i, fGain, pan); 286 UpdateMods(); 287 } 288 289 break; 290 } 291 292 case gs_audio_format::B_GS_F: 293 { 294 for (int64 i = 0; i < frames; i++) { 295 ApplyMod((float*)data, (float*)buffer, i, fGain, pan); 296 UpdateMods(); 297 } 298 299 break; 300 } 301 } 302 303 delete [] buffer; 304 delete [] pan; 305 } 306 307 308 void 309 GameSoundBuffer::UpdateMods() 310 { 311 // adjust the gain if needed 312 if (fGainRamp) { 313 if (ChangeRamp(fGainRamp)) { 314 delete fGainRamp; 315 fGainRamp = NULL; 316 } 317 } 318 319 // adjust the ramp if needed 320 if (fPanRamp) { 321 if (ChangeRamp(fPanRamp)) { 322 delete fPanRamp; 323 fPanRamp = NULL; 324 } else { 325 if (fPan < 0.0) { 326 fPanLeft = 1.0; 327 fPanRight = 1.0 + fPan; 328 } else { 329 fPanRight = 1.0; 330 fPanLeft = 1.0 - fPan; 331 } 332 } 333 } 334 } 335 336 337 void 338 GameSoundBuffer::Reset() 339 { 340 fGain = 1.0; 341 delete fGainRamp; 342 fGainRamp = NULL; 343 344 fPan = 0.0; 345 fPanLeft = 1.0; 346 fPanRight = 1.0; 347 delete fPanRamp; 348 fPanRamp = NULL; 349 350 fLooping = false; 351 } 352 353 354 status_t 355 GameSoundBuffer::Connect(media_node * consumer) 356 { 357 BMediaRoster* r = BMediaRoster::Roster(); 358 status_t err; 359 360 err = r->RegisterNode(fNode); 361 if (err != B_OK) 362 return err; 363 364 // make sure the Media Roster knows that we're using the node 365 r->GetNodeFor(fNode->Node().node, &fConnection->producer); 366 367 // connect to the mixer 368 fConnection->consumer = *consumer; 369 if (err != B_OK) 370 return err; 371 372 // set the producer's time source to be the "default" time source, which 373 // the Mixer uses too. 374 r->GetTimeSource(&fConnection->timeSource); 375 r->SetTimeSourceFor(fConnection->producer.node, fConnection->timeSource.node); 376 377 // got the nodes; now we find the endpoints of the connection 378 media_input mixerInput; 379 media_output soundOutput; 380 int32 count = 1; 381 err = r->GetFreeOutputsFor(fConnection->producer, &soundOutput, 1, &count); 382 if (err != B_OK) 383 return err; 384 count = 1; 385 err = r->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1, &count); 386 if (err != B_OK) 387 return err; 388 389 // got the endpoints; now we connect it! 390 media_format format; 391 format.type = B_MEDIA_RAW_AUDIO; 392 format.u.raw_audio = media_raw_audio_format::wildcard; 393 err = r->Connect(soundOutput.source, mixerInput.destination, &format, &soundOutput, &mixerInput); 394 if (err != B_OK) 395 return err; 396 397 // the inputs and outputs might have been reassigned during the 398 // nodes' negotiation of the Connect(). That's why we wait until 399 // after Connect() finishes to save their contents. 400 fConnection->format = format; 401 fConnection->source = soundOutput.source; 402 fConnection->destination = mixerInput.destination; 403 404 fIsConnected = true; 405 return B_OK; 406 } 407 408 409 status_t 410 GameSoundBuffer::StartPlaying() 411 { 412 if (fIsPlaying) 413 return EALREADY; 414 415 BMediaRoster* r = BMediaRoster::Roster(); 416 BTimeSource* ts = r->MakeTimeSourceFor(fConnection->producer); 417 418 // make sure we give the producer enough time to run buffers through 419 // the node chain, otherwise it'll start up already late 420 bigtime_t latency = 0; 421 r->GetLatencyFor(fConnection->producer, &latency); 422 r->StartNode(fConnection->producer, ts->Now() + latency); 423 ts->Release(); 424 425 fIsPlaying = true; 426 427 return B_OK; 428 } 429 430 431 status_t 432 GameSoundBuffer::StopPlaying() 433 { 434 if (!fIsPlaying) 435 return EALREADY; 436 437 BMediaRoster* r = BMediaRoster::Roster(); 438 r->StopNode(fConnection->producer, 0, true); // synchronous stop 439 440 Reset(); 441 fIsPlaying = false; 442 443 return B_OK; 444 } 445 446 447 bool 448 GameSoundBuffer::IsPlaying() 449 { 450 return fIsPlaying; 451 } 452 453 454 // SimpleSoundBuffer ------------------------------------------------------ 455 SimpleSoundBuffer::SimpleSoundBuffer(const gs_audio_format * format, 456 const void * data, 457 int64 frames) 458 : GameSoundBuffer(format), 459 fPosition(0) 460 461 { 462 fBuffer = new char[frames * fFrameSize]; 463 fBufferSize = frames * fFrameSize; 464 465 memcpy(fBuffer, data, fBufferSize); 466 } 467 468 469 SimpleSoundBuffer::~SimpleSoundBuffer() 470 { 471 delete [] fBuffer; 472 } 473 474 475 void 476 SimpleSoundBuffer::Reset() 477 { 478 GameSoundBuffer::Reset(); 479 fPosition = 0; 480 } 481 482 483 void 484 SimpleSoundBuffer::FillBuffer(void * data, int64 frames) 485 { 486 char * buffer = (char*)data; 487 size_t bytes = fFrameSize * frames; 488 489 if (fPosition + bytes >= fBufferSize) { 490 if (fPosition < fBufferSize) { 491 // copy the remaining frames 492 size_t remainder = fBufferSize - fPosition; 493 memcpy(buffer, &fBuffer[fPosition], remainder); 494 495 if (fLooping) { 496 // restart the sound from the begging 497 memcpy(&buffer[remainder], fBuffer, bytes - remainder); 498 fPosition = bytes - remainder; 499 } else 500 fPosition = fBufferSize; 501 } else 502 memset(data, 0, bytes); 503 // there is nothing left to play 504 } else { 505 memcpy(buffer, &fBuffer[fPosition], bytes); 506 fPosition += bytes; 507 } 508 } 509 510 511 // StreamingSoundBuffer ------------------------------------------------------ 512 StreamingSoundBuffer::StreamingSoundBuffer(const gs_audio_format * format, 513 const void * streamHook) 514 : GameSoundBuffer(format) 515 { 516 fStreamHook = (void*)streamHook; 517 } 518 519 520 StreamingSoundBuffer::~StreamingSoundBuffer() 521 { 522 } 523 524 525 void 526 StreamingSoundBuffer::FillBuffer(void * buffer, 527 int64 frames) 528 { 529 BStreamingGameSound* object = (BStreamingGameSound*)fStreamHook; 530 531 size_t bytes = fFrameSize * frames; 532 object->FillBuffer(buffer, bytes); 533 } 534 535