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