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