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