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