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 // Sound Buffer Utility functions ---------------------------------------- 46 template<typename T, int middle = 0> 47 static inline void 48 ApplyMod(T* data, int64 index, float* pan) 49 { 50 data[index * 2] = T(float(data[index * 2] - middle) * pan[0] + middle); 51 data[index * 2 + 1] = T(float(data[index * 2 + 1] - middle) * pan[1] 52 + middle); 53 } 54 55 56 // GameSoundBuffer ------------------------------------------------------- 57 GameSoundBuffer::GameSoundBuffer(const gs_audio_format * format) 58 : 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) 68 { 69 fConnection = new Connection; 70 fNode = new GameProducer(this, format); 71 72 fFrameSize = get_sample_size(format->format) * format->channel_count; 73 74 fFormat = *format; 75 } 76 77 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() 81 { 82 BMediaRoster* roster = BMediaRoster::Roster(); 83 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); 92 93 roster->ReleaseNode(fConnection->producer); 94 roster->ReleaseNode(fConnection->consumer); 95 } 96 97 delete fGainRamp; 98 delete fPanRamp; 99 100 delete fConnection; 101 delete fNode; 102 } 103 104 105 const gs_audio_format & 106 GameSoundBuffer::Format() const 107 { 108 return fFormat; 109 } 110 111 112 bool 113 GameSoundBuffer::IsLooping() const 114 { 115 return fLooping; 116 } 117 118 119 void 120 GameSoundBuffer::SetLooping(bool looping) 121 { 122 fLooping = looping; 123 } 124 125 126 float 127 GameSoundBuffer::Gain() const 128 { 129 return fGain; 130 } 131 132 133 status_t 134 GameSoundBuffer::SetGain(float gain, bigtime_t duration) 135 { 136 if (gain < 0.0 || gain > 1.0) 137 return B_BAD_VALUE; 138 139 delete fGainRamp; 140 fGainRamp = NULL; 141 142 if (duration > 100000) 143 fGainRamp = InitRamp(&fGain, gain, fFormat.frame_rate, duration); 144 else 145 fGain = gain; 146 147 return B_OK; 148 } 149 150 151 float 152 GameSoundBuffer::Pan() const 153 { 154 return fPan; 155 } 156 157 158 status_t 159 GameSoundBuffer::SetPan(float pan, bigtime_t duration) 160 { 161 if (pan < -1.0 || pan > 1.0) 162 return B_BAD_VALUE; 163 164 delete fPanRamp; 165 fPanRamp = NULL; 166 167 if (duration < 100000) { 168 fPan = pan; 169 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; 176 } 177 } else 178 fPanRamp = InitRamp(&fPan, pan, fFormat.frame_rate, duration); 179 180 return B_OK; 181 } 182 183 184 status_t 185 GameSoundBuffer::GetAttributes(gs_attribute * attributes, 186 size_t attributeCount) 187 { 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; 195 196 case B_GS_PAN: 197 attributes[i].value = fPan; 198 if (fPanRamp) 199 attributes[i].duration = fPanRamp->duration; 200 break; 201 202 case B_GS_LOOPING: 203 attributes[i].value = (fLooping) ? -1.0 : 0.0; 204 attributes[i].duration = bigtime_t(0); 205 break; 206 207 default: 208 attributes[i].value = 0.0; 209 attributes[i].duration = bigtime_t(0); 210 break; 211 } 212 } 213 214 return B_OK; 215 } 216 217 218 status_t 219 GameSoundBuffer::SetAttributes(gs_attribute * attributes, 220 size_t attributeCount) 221 { 222 status_t error = B_OK; 223 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; 229 230 case B_GS_PAN: 231 error = SetPan(attributes[i].value, attributes[i].duration); 232 break; 233 234 case B_GS_LOOPING: 235 fLooping = bool(attributes[i].value); 236 break; 237 238 default: 239 break; 240 } 241 } 242 243 return error; 244 } 245 246 247 void 248 GameSoundBuffer::Play(void * data, int64 frames) 249 { 250 // Mh... should we add some locking? 251 if (!fIsPlaying) 252 return; 253 254 if (fFormat.channel_count == 2) { 255 float pan[2]; 256 pan[0] = fPanRight * fGain; 257 pan[1] = fPanLeft * fGain; 258 259 FillBuffer(data, 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, 128>((uint8*)data, i, 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>((int16*)data, i, 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>((int32*)data, i, 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>((float*)data, i, pan); 296 UpdateMods(); 297 } 298 299 break; 300 } 301 } 302 } else if (fFormat.channel_count == 1) { 303 // FIXME the output should be stereo, and we could pan mono sounds 304 // here. But currently the output has the same number of channels as 305 // the sound and we can't do this. 306 // FIXME also, we don't handle the gain here. 307 FillBuffer(data, frames); 308 } else 309 debugger("Invalid number of channels."); 310 311 } 312 313 314 void 315 GameSoundBuffer::UpdateMods() 316 { 317 // adjust the gain if needed 318 if (fGainRamp) { 319 if (ChangeRamp(fGainRamp)) { 320 delete fGainRamp; 321 fGainRamp = NULL; 322 } 323 } 324 325 // adjust the ramp if needed 326 if (fPanRamp) { 327 if (ChangeRamp(fPanRamp)) { 328 delete fPanRamp; 329 fPanRamp = NULL; 330 } else { 331 if (fPan < 0.0) { 332 fPanLeft = 1.0; 333 fPanRight = 1.0 + fPan; 334 } else { 335 fPanRight = 1.0; 336 fPanLeft = 1.0 - fPan; 337 } 338 } 339 } 340 } 341 342 343 void 344 GameSoundBuffer::Reset() 345 { 346 fGain = 1.0; 347 delete fGainRamp; 348 fGainRamp = NULL; 349 350 fPan = 0.0; 351 fPanLeft = 1.0; 352 fPanRight = 1.0; 353 354 delete fPanRamp; 355 fPanRamp = NULL; 356 357 fLooping = false; 358 } 359 360 361 status_t 362 GameSoundBuffer::Connect(media_node * consumer) 363 { 364 BMediaRoster* roster = BMediaRoster::Roster(); 365 status_t err = roster->RegisterNode(fNode); 366 367 if (err != B_OK) 368 return err; 369 370 // make sure the Media Roster knows that we're using the node 371 err = roster->GetNodeFor(fNode->Node().node, &fConnection->producer); 372 373 if (err != B_OK) 374 return err; 375 376 // connect to the mixer 377 fConnection->consumer = *consumer; 378 379 // set the producer's time source to be the "default" time source, which 380 // the Mixer uses too. 381 err = roster->GetTimeSource(&fConnection->timeSource); 382 if (err != B_OK) 383 return err; 384 385 err = roster->SetTimeSourceFor(fConnection->producer.node, 386 fConnection->timeSource.node); 387 if (err != B_OK) 388 return err; 389 // got the nodes; now we find the endpoints of the connection 390 media_input mixerInput; 391 media_output soundOutput; 392 int32 count = 1; 393 err = roster->GetFreeOutputsFor(fConnection->producer, &soundOutput, 1, 394 &count); 395 396 if (err != B_OK) 397 return err; 398 count = 1; 399 err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1, 400 &count); 401 if (err != B_OK) 402 return err; 403 404 // got the endpoints; now we connect it! 405 media_format format; 406 format.type = B_MEDIA_RAW_AUDIO; 407 format.u.raw_audio = media_raw_audio_format::wildcard; 408 err = roster->Connect(soundOutput.source, mixerInput.destination, &format, 409 &soundOutput, &mixerInput); 410 if (err != B_OK) 411 return err; 412 413 // the inputs and outputs might have been reassigned during the 414 // nodes' negotiation of the Connect(). That's why we wait until 415 // after Connect() finishes to save their contents. 416 fConnection->format = format; 417 fConnection->source = soundOutput.source; 418 fConnection->destination = mixerInput.destination; 419 420 fIsConnected = true; 421 return B_OK; 422 } 423 424 425 status_t 426 GameSoundBuffer::StartPlaying() 427 { 428 if (fIsPlaying) 429 return EALREADY; 430 431 BMediaRoster* roster = BMediaRoster::Roster(); 432 BTimeSource* source = roster->MakeTimeSourceFor(fConnection->producer); 433 434 // make sure we give the producer enough time to run buffers through 435 // the node chain, otherwise it'll start up already late 436 bigtime_t latency = 0; 437 status_t status = roster->GetLatencyFor(fConnection->producer, &latency); 438 if (status == B_OK) { 439 status = roster->StartNode(fConnection->producer, 440 source->Now() + latency); 441 } 442 source->Release(); 443 444 fIsPlaying = true; 445 446 return status; 447 } 448 449 450 status_t 451 GameSoundBuffer::StopPlaying() 452 { 453 if (!fIsPlaying) 454 return EALREADY; 455 456 BMediaRoster* roster = BMediaRoster::Roster(); 457 roster->StopNode(fConnection->producer, 0, true); 458 // synchronous stop 459 460 Reset(); 461 fIsPlaying = false; 462 463 return B_OK; 464 } 465 466 467 bool 468 GameSoundBuffer::IsPlaying() 469 { 470 return fIsPlaying; 471 } 472 473 474 // SimpleSoundBuffer ------------------------------------------------------ 475 SimpleSoundBuffer::SimpleSoundBuffer(const gs_audio_format * format, 476 const void * data, int64 frames) 477 : 478 GameSoundBuffer(format), 479 fPosition(0) 480 { 481 fBufferSize = frames * fFrameSize; 482 fBuffer = (char*)data; 483 } 484 485 486 SimpleSoundBuffer::~SimpleSoundBuffer() 487 { 488 delete [] fBuffer; 489 } 490 491 492 void 493 SimpleSoundBuffer::Reset() 494 { 495 GameSoundBuffer::Reset(); 496 fPosition = 0; 497 } 498 499 500 void 501 SimpleSoundBuffer::FillBuffer(void * data, int64 frames) 502 { 503 char * buffer = (char*)data; 504 size_t bytes = fFrameSize * frames; 505 506 if (fPosition + bytes >= fBufferSize) { 507 if (fPosition < fBufferSize) { 508 // copy the remaining frames 509 size_t remainder = fBufferSize - fPosition; 510 memcpy(buffer, &fBuffer[fPosition], remainder); 511 512 bytes -= remainder; 513 buffer += remainder; 514 } 515 516 if (fLooping) { 517 // restart the sound from the beginning 518 memcpy(buffer, fBuffer, bytes); 519 fPosition = bytes; 520 bytes = 0; 521 } else { 522 fPosition = fBufferSize; 523 } 524 525 if (bytes > 0) { 526 // Fill the rest with silence 527 int middle = 0; 528 if (fFormat.format == gs_audio_format::B_GS_U8) 529 middle = 128; 530 memset(buffer, middle, bytes); 531 } 532 } else { 533 memcpy(buffer, &fBuffer[fPosition], bytes); 534 fPosition += bytes; 535 } 536 } 537 538 539 // StreamingSoundBuffer ------------------------------------------------------ 540 StreamingSoundBuffer::StreamingSoundBuffer(const gs_audio_format * format, 541 const void * streamHook, size_t inBufferFrameCount, size_t inBufferCount) 542 : 543 GameSoundBuffer(format), 544 fStreamHook(const_cast<void *>(streamHook)) 545 { 546 if (inBufferFrameCount != 0 && inBufferCount != 0) { 547 BBufferGroup *bufferGroup 548 = new BBufferGroup(inBufferFrameCount * fFrameSize, inBufferCount); 549 fNode->SetBufferGroup(fConnection->source, bufferGroup); 550 } 551 } 552 553 554 StreamingSoundBuffer::~StreamingSoundBuffer() 555 { 556 } 557 558 559 void 560 StreamingSoundBuffer::FillBuffer(void * buffer, int64 frames) 561 { 562 BStreamingGameSound* object = (BStreamingGameSound*)fStreamHook; 563 564 size_t bytes = fFrameSize * frames; 565 object->FillBuffer(buffer, bytes); 566 } 567