1 // FlangerNode.cpp 2 // e.moon 16jun99 3 4 #include "FlangerNode.h" 5 6 #include "AudioBuffer.h" 7 #include "SoundUtils.h" 8 9 #include <Buffer.h> 10 #include <BufferGroup.h> 11 #include <ByteOrder.h> 12 #include <Debug.h> 13 #include <ParameterWeb.h> 14 #include <TimeSource.h> 15 16 #include <cstdio> 17 #include <cstdlib> 18 #include <cstring> 19 #include <cmath> 20 21 // -------------------------------------------------------- // 22 // local helpers 23 // -------------------------------------------------------- // 24 25 float calc_sweep_delta( 26 const media_raw_audio_format& format, 27 float fRate); 28 29 float calc_sweep_base( 30 const media_raw_audio_format& format, 31 float fDelay, float fDepth); 32 33 float calc_sweep_factor( 34 const media_raw_audio_format& format, 35 float fDepth); 36 37 // -------------------------------------------------------- // 38 // constants 39 // -------------------------------------------------------- // 40 41 // input-ID symbols 42 enum input_id_t { 43 ID_AUDIO_INPUT 44 }; 45 46 // output-ID symbols 47 enum output_id_t { 48 ID_AUDIO_MIX_OUTPUT 49 //ID_AUDIO_WET_OUTPUT ... 50 }; 51 52 // parameter ID 53 enum param_id_t { 54 P_MIX_RATIO_LABEL = 100, 55 P_MIX_RATIO, 56 57 P_SWEEP_RATE_LABEL = 200, 58 P_SWEEP_RATE, 59 60 P_DELAY_LABEL = 300, 61 P_DELAY, 62 63 P_DEPTH_LABEL = 400, 64 P_DEPTH, 65 66 P_FEEDBACK_LABEL = 500, 67 P_FEEDBACK 68 }; 69 70 const float FlangerNode::s_fMaxDelay = 100.0; 71 const char* const FlangerNode::s_nodeName = "FlangerNode"; 72 73 74 // -------------------------------------------------------- // 75 // ctor/dtor 76 // -------------------------------------------------------- // 77 78 FlangerNode::~FlangerNode() { 79 // shut down 80 Quit(); 81 82 // free delay buffer 83 if(m_pDelayBuffer) 84 delete m_pDelayBuffer; 85 } 86 87 FlangerNode::FlangerNode(BMediaAddOn* pAddOn) : 88 // * init base classes 89 BMediaNode(s_nodeName), // (virtual base) 90 BBufferConsumer(B_MEDIA_RAW_AUDIO), 91 BBufferProducer(B_MEDIA_RAW_AUDIO), 92 BControllable(), 93 BMediaEventLooper(), 94 95 // * init connection state 96 m_outputEnabled(true), 97 m_downstreamLatency(0), 98 m_processingLatency(0), 99 100 // * init filter state 101 m_pDelayBuffer(0), 102 103 // * init add-on stuff 104 m_pAddOn(pAddOn) { 105 106 // PRINT(( 107 // "\n" 108 // "--*-- FlangerNode() [%s] --*--\n\n", 109 // __BUILD_DATE)); 110 // 111 // the rest of the initialization happens in NodeRegistered(). 112 } 113 114 115 // -------------------------------------------------------- // 116 // *** BMediaNode 117 // -------------------------------------------------------- // 118 119 status_t FlangerNode::HandleMessage( 120 int32 code, 121 const void* pData, 122 size_t size) { 123 124 // pass off to each base class 125 if( 126 BBufferConsumer::HandleMessage(code, pData, size) && 127 BBufferProducer::HandleMessage(code, pData, size) && 128 BControllable::HandleMessage(code, pData, size) && 129 BMediaNode::HandleMessage(code, pData, size)) 130 BMediaNode::HandleBadMessage(code, pData, size); 131 132 // +++++ return error on bad message? 133 return B_OK; 134 } 135 136 BMediaAddOn* FlangerNode::AddOn( 137 int32* poID) const { 138 139 if(m_pAddOn) 140 *poID = 0; 141 return m_pAddOn; 142 } 143 144 void FlangerNode::SetRunMode( 145 run_mode mode) { 146 147 // disallow offline mode for now 148 // +++++ 149 if(mode == B_OFFLINE) 150 ReportError(B_NODE_FAILED_SET_RUN_MODE); 151 152 // +++++ any other work to do? 153 154 // hand off 155 BMediaEventLooper::SetRunMode(mode); 156 } 157 158 // -------------------------------------------------------- // 159 // *** BMediaEventLooper 160 // -------------------------------------------------------- // 161 162 void FlangerNode::HandleEvent( 163 const media_timed_event* pEvent, 164 bigtime_t howLate, 165 bool realTimeEvent) { 166 167 ASSERT(pEvent); 168 169 switch(pEvent->type) { 170 case BTimedEventQueue::B_PARAMETER: 171 handleParameterEvent(pEvent); 172 break; 173 174 case BTimedEventQueue::B_START: 175 handleStartEvent(pEvent); 176 break; 177 178 case BTimedEventQueue::B_STOP: 179 handleStopEvent(pEvent); 180 break; 181 182 default: 183 ignoreEvent(pEvent); 184 break; 185 } 186 } 187 188 // "The Media Server calls this hook function after the node has 189 // been registered. This is derived from BMediaNode; BMediaEventLooper 190 // implements it to call Run() automatically when the node is registered; 191 // if you implement NodeRegistered() you should call through to 192 // BMediaEventLooper::NodeRegistered() after you've done your custom 193 // operations." 194 195 void FlangerNode::NodeRegistered() { 196 197 PRINT(("FlangerNode::NodeRegistered()\n")); 198 199 // Start the BMediaEventLooper thread 200 SetPriority(B_REAL_TIME_PRIORITY); 201 Run(); 202 203 // figure preferred ('template') format 204 m_preferredFormat.type = B_MEDIA_RAW_AUDIO; 205 getPreferredFormat(m_preferredFormat); 206 207 // initialize current format 208 m_format.type = B_MEDIA_RAW_AUDIO; 209 m_format.u.raw_audio = media_raw_audio_format::wildcard; 210 211 // init input 212 m_input.destination.port = ControlPort(); 213 m_input.destination.id = ID_AUDIO_INPUT; 214 m_input.node = Node(); 215 m_input.source = media_source::null; 216 m_input.format = m_format; 217 strncpy(m_input.name, "Audio Input", B_MEDIA_NAME_LENGTH); 218 219 // init output 220 m_output.source.port = ControlPort(); 221 m_output.source.id = ID_AUDIO_MIX_OUTPUT; 222 m_output.node = Node(); 223 m_output.destination = media_destination::null; 224 m_output.format = m_format; 225 strncpy(m_output.name, "Mix Output", B_MEDIA_NAME_LENGTH); 226 227 // init parameters 228 initParameterValues(); 229 initParameterWeb(); 230 } 231 232 // "Augment OfflineTime() to compute the node's current time; it's called 233 // by the Media Kit when it's in offline mode. Update any appropriate 234 // internal information as well, then call through to the BMediaEventLooper 235 // implementation." 236 237 bigtime_t FlangerNode::OfflineTime() { 238 // +++++ 239 return 0LL; 240 } 241 242 // -------------------------------------------------------- // 243 // *** BBufferConsumer 244 // -------------------------------------------------------- // 245 246 status_t FlangerNode::AcceptFormat( 247 const media_destination& destination, 248 media_format* pioFormat) { 249 250 PRINT(("FlangerNode::AcceptFormat()\n")); 251 252 // sanity checks 253 if(destination != m_input.destination) { 254 PRINT(("\tbad destination\n")); 255 return B_MEDIA_BAD_DESTINATION; 256 } 257 if(pioFormat->type != B_MEDIA_RAW_AUDIO) { 258 PRINT(("\tnot B_MEDIA_RAW_AUDIO\n")); 259 return B_MEDIA_BAD_FORMAT; 260 } 261 262 validateProposedFormat( 263 (m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ? 264 m_format : m_preferredFormat, 265 *pioFormat); 266 return B_OK; 267 } 268 269 // "If you're writing a node, and receive a buffer with the B_SMALL_BUFFER 270 // flag set, you must recycle the buffer before returning." 271 272 void FlangerNode::BufferReceived( 273 BBuffer* pBuffer) { 274 ASSERT(pBuffer); 275 276 // check buffer destination 277 if(pBuffer->Header()->destination != 278 m_input.destination.id) { 279 PRINT(("FlangerNode::BufferReceived():\n" 280 "\tBad destination.\n")); 281 pBuffer->Recycle(); 282 return; 283 } 284 285 if(pBuffer->Header()->time_source != TimeSource()->ID()) { 286 PRINT(("* timesource mismatch\n")); 287 } 288 289 // check output 290 if(m_output.destination == media_destination::null || 291 !m_outputEnabled) { 292 pBuffer->Recycle(); 293 return; 294 } 295 296 // process and retransmit buffer 297 filterBuffer(pBuffer); 298 299 status_t err = SendBuffer(pBuffer, m_output.source, m_output.destination); 300 if (err < B_OK) { 301 PRINT(("FlangerNode::BufferReceived():\n" 302 "\tSendBuffer() failed: %s\n", strerror(err))); 303 pBuffer->Recycle(); 304 } 305 // sent! 306 } 307 308 // * make sure to fill in poInput->format with the contents of 309 // pFormat; as of R4.5 the Media Kit passes poInput->format to 310 // the producer in BBufferProducer::Connect(). 311 312 status_t 313 FlangerNode::Connected(const media_source& source, 314 const media_destination& destination, const media_format& format, 315 media_input* poInput) 316 { 317 PRINT(("FlangerNode::Connected()\n" 318 "\tto source %ld\n", source.id)); 319 320 // sanity check 321 if(destination != m_input.destination) { 322 PRINT(("\tbad destination\n")); 323 return B_MEDIA_BAD_DESTINATION; 324 } 325 if(m_input.source != media_source::null) { 326 PRINT(("\talready connected\n")); 327 return B_MEDIA_ALREADY_CONNECTED; 328 } 329 330 // initialize input 331 m_input.source = source; 332 m_input.format = format; 333 *poInput = m_input; 334 335 // store format (this now constrains the output format) 336 m_format = format; 337 338 return B_OK; 339 } 340 341 void FlangerNode::Disconnected( 342 const media_source& source, 343 const media_destination& destination) { 344 345 PRINT(("FlangerNode::Disconnected()\n")); 346 347 // sanity checks 348 if(m_input.source != source) { 349 PRINT(("\tsource mismatch: expected ID %ld, got %ld\n", 350 m_input.source.id, source.id)); 351 return; 352 } 353 if(destination != m_input.destination) { 354 PRINT(("\tdestination mismatch: expected ID %ld, got %ld\n", 355 m_input.destination.id, destination.id)); 356 return; 357 } 358 359 // mark disconnected 360 m_input.source = media_source::null; 361 362 // no output? clear format: 363 if(m_output.destination == media_destination::null) { 364 m_format.u.raw_audio = media_raw_audio_format::wildcard; 365 } 366 367 m_input.format = m_format; 368 369 } 370 371 void FlangerNode::DisposeInputCookie( 372 int32 cookie) {} 373 374 // "You should implement this function so your node will know that the data 375 // format is going to change. Note that this may be called in response to 376 // your AcceptFormat() call, if your AcceptFormat() call alters any wildcard 377 // fields in the specified format. 378 // 379 // Because FormatChanged() is called by the producer, you don't need to (and 380 // shouldn't) ask it if the new format is acceptable. 381 // 382 // If the format change isn't possible, return an appropriate error from 383 // FormatChanged(); this error will be passed back to the producer that 384 // initiated the new format negotiation in the first place." 385 386 status_t FlangerNode::FormatChanged( 387 const media_source& source, 388 const media_destination& destination, 389 int32 changeTag, 390 const media_format& newFormat) { 391 392 // flat-out deny format changes 393 return B_MEDIA_BAD_FORMAT; 394 } 395 396 status_t FlangerNode::GetLatencyFor( 397 const media_destination& destination, 398 bigtime_t* poLatency, 399 media_node_id* poTimeSource) { 400 401 PRINT(("FlangerNode::GetLatencyFor()\n")); 402 403 // sanity check 404 if(destination != m_input.destination) { 405 PRINT(("\tbad destination\n")); 406 return B_MEDIA_BAD_DESTINATION; 407 } 408 409 *poLatency = m_downstreamLatency + m_processingLatency; 410 PRINT(("\treturning %Ld\n", *poLatency)); 411 *poTimeSource = TimeSource()->ID(); 412 return B_OK; 413 } 414 415 status_t FlangerNode::GetNextInput( 416 int32* pioCookie, 417 media_input* poInput) { 418 419 if(*pioCookie) 420 return B_BAD_INDEX; 421 422 ++*pioCookie; 423 *poInput = m_input; 424 return B_OK; 425 } 426 427 void FlangerNode::ProducerDataStatus( 428 const media_destination& destination, 429 int32 status, 430 bigtime_t tpWhen) { 431 432 PRINT(("FlangerNode::ProducerDataStatus()\n")); 433 434 // sanity check 435 if(destination != m_input.destination) { 436 PRINT(("\tbad destination\n")); 437 } 438 439 if(m_output.destination != media_destination::null) { 440 // pass status downstream 441 status_t err = SendDataStatus( 442 status, 443 m_output.destination, 444 tpWhen); 445 if(err < B_OK) { 446 PRINT(("\tSendDataStatus(): %s\n", strerror(err))); 447 } 448 } 449 } 450 451 // "This function is provided to aid in supporting media formats in which the 452 // outer encapsulation layer doesn't supply timing information. Producers will 453 // tag the buffers they generate with seek tags; these tags can be used to 454 // locate key frames in the media data." 455 456 status_t FlangerNode::SeekTagRequested( 457 const media_destination& destination, 458 bigtime_t targetTime, 459 uint32 flags, 460 media_seek_tag* poSeekTag, 461 bigtime_t* poTaggedTime, 462 uint32* poFlags) { 463 464 PRINT(("FlangerNode::SeekTagRequested()\n" 465 "\tNot implemented.\n")); 466 return B_ERROR; 467 } 468 469 // -------------------------------------------------------- // 470 // *** BBufferProducer 471 // -------------------------------------------------------- // 472 473 // "When a consumer calls BBufferConsumer::RequestAdditionalBuffer(), this 474 // function is called as a result. Its job is to call SendBuffer() to 475 // immediately send the next buffer to the consumer. The previousBufferID, 476 // previousTime, and previousTag arguments identify the last buffer the 477 // consumer received. Your node should respond by sending the next buffer 478 // after the one described. 479 // 480 // The previousTag may be NULL. 481 // Return B_OK if all is well; otherwise return an appropriate error code." 482 483 void FlangerNode::AdditionalBufferRequested( 484 const media_source& source, 485 media_buffer_id previousBufferID, 486 bigtime_t previousTime, 487 const media_seek_tag* pPreviousTag) { 488 489 PRINT(("FlangerNode::AdditionalBufferRequested\n" 490 "\tOffline mode not implemented.")); 491 } 492 493 void FlangerNode::Connect( 494 status_t status, 495 const media_source& source, 496 const media_destination& destination, 497 const media_format& format, 498 char* pioName) { 499 500 PRINT(("FlangerNode::Connect()\n")); 501 status_t err; 502 503 // connection failed? 504 if(status < B_OK) { 505 PRINT(("\tStatus: %s\n", strerror(status))); 506 // 'unreserve' the output 507 m_output.destination = media_destination::null; 508 return; 509 } 510 511 // connection established: 512 strncpy(pioName, m_output.name, B_MEDIA_NAME_LENGTH); 513 m_output.destination = destination; 514 m_format = format; 515 516 // figure downstream latency 517 media_node_id timeSource; 518 err = FindLatencyFor(m_output.destination, &m_downstreamLatency, &timeSource); 519 if(err < B_OK) { 520 PRINT(("\t!!! FindLatencyFor(): %s\n", strerror(err))); 521 } 522 PRINT(("\tdownstream latency = %Ld\n", m_downstreamLatency)); 523 524 // prepare the filter 525 initFilter(); 526 527 // figure processing time 528 m_processingLatency = calcProcessingLatency(); 529 PRINT(("\tprocessing latency = %Ld\n", m_processingLatency)); 530 531 // store summed latency 532 SetEventLatency(m_downstreamLatency + m_processingLatency); 533 534 if(m_input.source != media_source::null) { 535 // pass new latency upstream 536 err = SendLatencyChange( 537 m_input.source, 538 m_input.destination, 539 EventLatency() + SchedulingLatency()); 540 if(err < B_OK) 541 PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err))); 542 } 543 544 // cache buffer duration 545 SetBufferDuration( 546 buffer_duration( 547 m_format.u.raw_audio)); 548 } 549 550 void FlangerNode::Disconnect( 551 const media_source& source, 552 const media_destination& destination) { 553 554 PRINT(("FlangerNode::Disconnect()\n")); 555 556 // sanity checks 557 if(source != m_output.source) { 558 PRINT(("\tbad source\n")); 559 return; 560 } 561 if(destination != m_output.destination) { 562 PRINT(("\tbad destination\n")); 563 return; 564 } 565 566 // clean up 567 m_output.destination = media_destination::null; 568 569 // no input? clear format: 570 if(m_input.source == media_source::null) { 571 m_format.u.raw_audio = media_raw_audio_format::wildcard; 572 } 573 574 m_output.format = m_format; 575 576 // +++++ other cleanup goes here 577 } 578 579 status_t FlangerNode::DisposeOutputCookie( 580 int32 cookie) { 581 return B_OK; 582 } 583 584 void FlangerNode::EnableOutput( 585 const media_source& source, 586 bool enabled, 587 int32* _deprecated_) { 588 PRINT(("FlangerNode::EnableOutput()\n")); 589 if(source != m_output.source) { 590 PRINT(("\tbad source\n")); 591 return; 592 } 593 594 m_outputEnabled = enabled; 595 } 596 597 status_t FlangerNode::FormatChangeRequested( 598 const media_source& source, 599 const media_destination& destination, 600 media_format* pioFormat, 601 int32* _deprecated_) { 602 603 // deny 604 PRINT(("FlangerNode::FormatChangeRequested()\n" 605 "\tNot supported.\n")); 606 607 return B_MEDIA_BAD_FORMAT; 608 } 609 610 status_t FlangerNode::FormatProposal( 611 const media_source& source, 612 media_format* pioFormat) { 613 614 PRINT(("FlangerNode::FormatProposal()\n")); 615 616 if(source != m_output.source) { 617 PRINT(("\tbad source\n")); 618 return B_MEDIA_BAD_SOURCE; 619 } 620 621 if(pioFormat->type != B_MEDIA_RAW_AUDIO) { 622 PRINT(("\tbad type\n")); 623 return B_MEDIA_BAD_FORMAT; 624 } 625 626 validateProposedFormat( 627 (m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ? 628 m_format : 629 m_preferredFormat, 630 *pioFormat); 631 return B_OK; 632 } 633 634 status_t FlangerNode::FormatSuggestionRequested( 635 media_type type, 636 int32 quality, 637 media_format* poFormat) { 638 639 PRINT(("FlangerNode::FormatSuggestionRequested()\n")); 640 if(type != B_MEDIA_RAW_AUDIO) { 641 PRINT(("\tbad type\n")); 642 return B_MEDIA_BAD_FORMAT; 643 } 644 645 if(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) 646 *poFormat = m_format; 647 else 648 *poFormat = m_preferredFormat; 649 return B_OK; 650 } 651 652 status_t FlangerNode::GetLatency( 653 bigtime_t* poLatency) { 654 655 PRINT(("FlangerNode::GetLatency()\n")); 656 *poLatency = EventLatency() + SchedulingLatency(); 657 PRINT(("\treturning %Ld\n", *poLatency)); 658 659 return B_OK; 660 } 661 662 status_t FlangerNode::GetNextOutput( 663 int32* pioCookie, 664 media_output* poOutput) { 665 666 if(*pioCookie) 667 return B_BAD_INDEX; 668 669 ++*pioCookie; 670 *poOutput = m_output; 671 672 return B_OK; 673 } 674 675 // "This hook function is called when a BBufferConsumer that's receiving data 676 // from you determines that its latency has changed. It will call its 677 // BBufferConsumer::SendLatencyChange() function, and in response, the Media 678 // Server will call your LatencyChanged() function. The source argument 679 // indicates your output that's involved in the connection, and destination 680 // specifies the input on the consumer to which the connection is linked. 681 // newLatency is the consumer's new latency. The flags are currently unused." 682 void FlangerNode::LatencyChanged( 683 const media_source& source, 684 const media_destination& destination, 685 bigtime_t newLatency, 686 uint32 flags) { 687 688 PRINT(("FlangerNode::LatencyChanged()\n")); 689 690 if(source != m_output.source) { 691 PRINT(("\tBad source.\n")); 692 return; 693 } 694 if(destination != m_output.destination) { 695 PRINT(("\tBad destination.\n")); 696 return; 697 } 698 699 m_downstreamLatency = newLatency; 700 SetEventLatency(m_downstreamLatency + m_processingLatency); 701 702 if(m_input.source != media_source::null) { 703 // pass new latency upstream 704 status_t err = SendLatencyChange( 705 m_input.source, 706 m_input.destination, 707 EventLatency() + SchedulingLatency()); 708 if(err < B_OK) 709 PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err))); 710 } 711 } 712 713 void FlangerNode::LateNoticeReceived( 714 const media_source& source, 715 bigtime_t howLate, 716 bigtime_t tpWhen) { 717 718 PRINT(("FlangerNode::LateNoticeReceived()\n" 719 "\thowLate == %Ld\n" 720 "\twhen == %Ld\n", howLate, tpWhen)); 721 722 if(source != m_output.source) { 723 PRINT(("\tBad source.\n")); 724 return; 725 } 726 727 if(m_input.source == media_source::null) { 728 PRINT(("\t!!! No input to blame.\n")); 729 return; 730 } 731 732 // +++++ check run mode? 733 734 // pass the buck, since this node doesn't schedule buffer 735 // production 736 NotifyLateProducer( 737 m_input.source, 738 howLate, 739 tpWhen); 740 } 741 742 // PrepareToConnect() is the second stage of format negotiations that happens 743 // inside BMediaRoster::Connect(). At this point, the consumer's AcceptFormat() 744 // method has been called, and that node has potentially changed the proposed 745 // format. It may also have left wildcards in the format. PrepareToConnect() 746 // *must* fully specialize the format before returning! 747 748 status_t FlangerNode::PrepareToConnect( 749 const media_source& source, 750 const media_destination& destination, 751 media_format* pioFormat, 752 media_source* poSource, 753 char* poName) { 754 755 char formatStr[256]; 756 string_for_format(*pioFormat, formatStr, 255); 757 PRINT(("FlangerNode::PrepareToConnect()\n" 758 "\tproposed format: %s\n", formatStr)); 759 760 if(source != m_output.source) { 761 PRINT(("\tBad source.\n")); 762 return B_MEDIA_BAD_SOURCE; 763 } 764 if(m_output.destination != media_destination::null) { 765 PRINT(("\tAlready connected.\n")); 766 return B_MEDIA_ALREADY_CONNECTED; 767 } 768 769 if(pioFormat->type != B_MEDIA_RAW_AUDIO) { 770 PRINT(("\tBad format type.\n")); 771 return B_MEDIA_BAD_FORMAT; 772 } 773 774 // do a final validity check: 775 status_t err = validateProposedFormat( 776 (m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ? 777 m_format : 778 m_preferredFormat, 779 *pioFormat); 780 781 if(err < B_OK) { 782 // no go 783 return err; 784 } 785 786 // fill in wildcards 787 specializeOutputFormat(*pioFormat); 788 789 // reserve the output 790 m_output.destination = destination; 791 m_output.format = *pioFormat; 792 793 // pass back source & output name 794 *poSource = m_output.source; 795 strncpy(poName, m_output.name, B_MEDIA_NAME_LENGTH); 796 797 return B_OK; 798 } 799 800 status_t FlangerNode::SetBufferGroup( 801 const media_source& source, 802 BBufferGroup* pGroup) { 803 804 PRINT(("FlangerNode::SetBufferGroup()\n")); 805 if(source != m_output.source) { 806 PRINT(("\tBad source.\n")); 807 return B_MEDIA_BAD_SOURCE; 808 } 809 810 if(m_input.source == media_source::null) { 811 PRINT(("\tNo producer to send buffers to.\n")); 812 return B_ERROR; 813 } 814 815 // +++++ is this right? buffer-group selection gets 816 // all asynchronous and weird... 817 int32 changeTag; 818 return SetOutputBuffersFor( 819 m_input.source, 820 m_input.destination, 821 pGroup, 822 0, &changeTag); 823 } 824 825 status_t FlangerNode::SetPlayRate( 826 int32 numerator, 827 int32 denominator) { 828 // not supported 829 return B_ERROR; 830 } 831 832 status_t FlangerNode::VideoClippingChanged( 833 const media_source& source, 834 int16 numShorts, 835 int16* pClipData, 836 const media_video_display_info& display, 837 int32* poFromChangeTag) { 838 // not sane 839 return B_ERROR; 840 } 841 842 // -------------------------------------------------------- // 843 // *** BControllable 844 // -------------------------------------------------------- // 845 846 status_t FlangerNode::GetParameterValue( 847 int32 id, 848 bigtime_t* poLastChangeTime, 849 void* poValue, 850 size_t* pioSize) { 851 852 // PRINT(("FlangerNode::GetParameterValue()\n")); 853 854 // all parameters are floats 855 if(*pioSize < sizeof(float)) { 856 return B_NO_MEMORY; 857 } 858 859 *pioSize = sizeof(float); 860 switch(id) { 861 case P_MIX_RATIO: 862 *(float*)poValue = m_fMixRatio; 863 *poLastChangeTime = m_tpMixRatioChanged; 864 break; 865 866 case P_SWEEP_RATE: 867 *(float*)poValue = m_fSweepRate; 868 *poLastChangeTime = m_tpSweepRateChanged; 869 break; 870 871 case P_DELAY: 872 *(float*)poValue = m_fDelay; 873 *poLastChangeTime = m_tpDelayChanged; 874 break; 875 876 case P_DEPTH: 877 *(float*)poValue = m_fDepth; 878 *poLastChangeTime = m_tpDepthChanged; 879 break; 880 881 case P_FEEDBACK: 882 *(float*)poValue = m_fFeedback; 883 *poLastChangeTime = m_tpFeedbackChanged; 884 break; 885 886 default: 887 return B_ERROR; 888 } 889 890 return B_OK; 891 } 892 893 void FlangerNode::SetParameterValue( 894 int32 id, 895 bigtime_t changeTime, 896 const void* pValue, 897 size_t size) { 898 899 switch(id) { 900 case P_MIX_RATIO: 901 case P_SWEEP_RATE: 902 case P_DELAY: 903 case P_DEPTH: 904 case P_FEEDBACK: { 905 if(size < sizeof(float)) 906 break; 907 908 // this is from ToneProducer. it's fishy. 909 // if(size > sizeof(float)) 910 // size = sizeof(float); 911 912 media_timed_event ev( 913 changeTime, 914 BTimedEventQueue::B_PARAMETER, 915 0, 916 BTimedEventQueue::B_NO_CLEANUP, 917 size, 918 id, 919 (char*)pValue, size); 920 EventQueue()->AddEvent(ev); 921 break; 922 } 923 } 924 } 925 926 // -------------------------------------------------------- // 927 // *** HandleEvent() impl 928 // -------------------------------------------------------- // 929 930 void FlangerNode::handleParameterEvent( 931 const media_timed_event* pEvent) { 932 933 float value = *(float*)pEvent->user_data; 934 int32 id = pEvent->bigdata; 935 size_t size = pEvent->data; 936 bigtime_t now = TimeSource()->Now(); 937 938 switch(id) { 939 case P_MIX_RATIO: 940 if(value == m_fMixRatio) 941 break; 942 943 // set 944 m_fMixRatio = value; 945 m_tpMixRatioChanged = now; 946 // broadcast 947 BroadcastNewParameterValue( 948 now, 949 id, 950 &m_fMixRatio, 951 size); 952 break; 953 954 case P_SWEEP_RATE: 955 if(value == m_fSweepRate) 956 break; 957 958 // set 959 m_fSweepRate = value; 960 m_tpSweepRateChanged = now; 961 962 if(m_output.destination != media_destination::null) { 963 m_fThetaInc = calc_sweep_delta( 964 m_format.u.raw_audio, 965 m_fSweepRate); 966 } 967 968 // broadcast 969 BroadcastNewParameterValue( 970 now, 971 id, 972 &m_fSweepRate, 973 size); 974 break; 975 976 case P_DELAY: 977 if(value == m_fDelay) 978 break; 979 980 // set 981 m_fDelay = value; 982 m_tpDelayChanged = now; 983 984 if(m_output.destination != media_destination::null) { 985 m_fSweepBase = calc_sweep_base( 986 m_format.u.raw_audio, 987 m_fDelay, m_fDepth); 988 } 989 990 // broadcast 991 BroadcastNewParameterValue( 992 now, 993 id, 994 &m_fDelay, 995 size); 996 break; 997 998 case P_DEPTH: 999 if(value == m_fDepth) 1000 break; 1001 1002 // set 1003 m_fDepth = value; 1004 m_tpDepthChanged = now; 1005 1006 if(m_output.destination != media_destination::null) { 1007 m_fSweepBase = calc_sweep_base( 1008 m_format.u.raw_audio, 1009 m_fDelay, m_fDepth); 1010 m_fSweepFactor = calc_sweep_factor( 1011 m_format.u.raw_audio, 1012 m_fDepth); 1013 } 1014 1015 // broadcast 1016 BroadcastNewParameterValue( 1017 now, 1018 id, 1019 &m_fDepth, 1020 size); 1021 break; 1022 1023 case P_FEEDBACK: 1024 if(value == m_fFeedback) 1025 break; 1026 1027 // set 1028 m_fFeedback = value; 1029 m_tpFeedbackChanged = now; 1030 // broadcast 1031 BroadcastNewParameterValue( 1032 now, 1033 id, 1034 &m_fFeedback, 1035 size); 1036 break; 1037 } 1038 } 1039 1040 void FlangerNode::handleStartEvent( 1041 const media_timed_event* pEvent) { 1042 PRINT(("FlangerNode::handleStartEvent\n")); 1043 1044 startFilter(); 1045 } 1046 1047 void FlangerNode::handleStopEvent( 1048 const media_timed_event* pEvent) { 1049 PRINT(("FlangerNode::handleStopEvent\n")); 1050 1051 stopFilter(); 1052 } 1053 1054 void FlangerNode::ignoreEvent( 1055 const media_timed_event* pEvent) { 1056 PRINT(("FlangerNode::ignoreEvent\n")); 1057 1058 } 1059 1060 1061 // -------------------------------------------------------- // 1062 // *** internal operations 1063 // -------------------------------------------------------- // 1064 1065 1066 // figure the preferred format: any fields left as wildcards 1067 // are negotiable 1068 void FlangerNode::getPreferredFormat( 1069 media_format& ioFormat) { 1070 ASSERT(ioFormat.type == B_MEDIA_RAW_AUDIO); 1071 1072 ioFormat.u.raw_audio = media_raw_audio_format::wildcard; 1073 ioFormat.u.raw_audio.channel_count = 1; 1074 ioFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT; 1075 1076 // ioFormat.u.raw_audio.frame_rate = 44100.0; 1077 // ioFormat.u.raw_audio.buffer_size = 0x1000; 1078 } 1079 1080 // test the given template format against a proposed format. 1081 // specialize wildcards for fields where the template contains 1082 // non-wildcard data; write required fields into proposed format 1083 // if they mismatch. 1084 // Returns B_OK if the proposed format doesn't conflict with the 1085 // template, or B_MEDIA_BAD_FORMAT otherwise. 1086 1087 status_t FlangerNode::validateProposedFormat( 1088 const media_format& preferredFormat, 1089 media_format& ioProposedFormat) { 1090 1091 char formatStr[256]; 1092 PRINT(("FlangerNode::validateProposedFormat()\n")); 1093 1094 ASSERT(preferredFormat.type == B_MEDIA_RAW_AUDIO); 1095 1096 string_for_format(preferredFormat, formatStr, 255); 1097 PRINT(("\ttemplate format: %s\n", formatStr)); 1098 1099 string_for_format(ioProposedFormat, formatStr, 255); 1100 PRINT(("\tproposed format: %s\n", formatStr)); 1101 1102 status_t err = B_OK; 1103 1104 if(ioProposedFormat.type != B_MEDIA_RAW_AUDIO) { 1105 // out of the ballpark 1106 ioProposedFormat = preferredFormat; 1107 return B_MEDIA_BAD_FORMAT; 1108 } 1109 1110 // wildcard format 1111 media_raw_audio_format& wild = media_raw_audio_format::wildcard; 1112 // proposed format 1113 media_raw_audio_format& f = ioProposedFormat.u.raw_audio; 1114 // template format 1115 const media_raw_audio_format& pref = preferredFormat.u.raw_audio; 1116 1117 if(pref.frame_rate != wild.frame_rate) { 1118 if(f.frame_rate != pref.frame_rate) { 1119 if(f.frame_rate != wild.frame_rate) 1120 err = B_MEDIA_BAD_FORMAT; 1121 f.frame_rate = pref.frame_rate; 1122 } 1123 } 1124 1125 if(pref.channel_count != wild.channel_count) { 1126 if(f.channel_count != pref.channel_count) { 1127 if(f.channel_count != wild.channel_count) 1128 err = B_MEDIA_BAD_FORMAT; 1129 f.channel_count = pref.channel_count; 1130 } 1131 } 1132 1133 if(pref.format != wild.format) { 1134 if(f.format != pref.format) { 1135 if(f.format != wild.format) 1136 err = B_MEDIA_BAD_FORMAT; 1137 f.format = pref.format; 1138 } 1139 } 1140 1141 if(pref.byte_order != wild.byte_order) { 1142 if(f.byte_order != pref.byte_order) { 1143 if(f.byte_order != wild.byte_order) 1144 err = B_MEDIA_BAD_FORMAT; 1145 f.byte_order = pref.byte_order; 1146 } 1147 } 1148 1149 if(pref.buffer_size != wild.buffer_size) { 1150 if(f.buffer_size != pref.buffer_size) { 1151 if(f.buffer_size != wild.buffer_size) 1152 err = B_MEDIA_BAD_FORMAT; 1153 f.buffer_size = pref.buffer_size; 1154 } 1155 } 1156 1157 if(err != B_OK) { 1158 string_for_format(ioProposedFormat, formatStr, 255); 1159 PRINT(( 1160 "\tformat conflict; suggesting:\n\tformat %s\n", formatStr)); 1161 } 1162 1163 return err; 1164 } 1165 1166 // fill in wildcards in the given format. 1167 // (assumes the format passes validateProposedFormat().) 1168 void FlangerNode::specializeOutputFormat( 1169 media_format& ioFormat) { 1170 1171 char formatStr[256]; 1172 string_for_format(ioFormat, formatStr, 255); 1173 PRINT(("FlangerNode::specializeOutputFormat()\n" 1174 "\tinput format: %s\n", formatStr)); 1175 1176 ASSERT(ioFormat.type == B_MEDIA_RAW_AUDIO); 1177 1178 // carpal_tunnel_paranoia 1179 media_raw_audio_format& f = ioFormat.u.raw_audio; 1180 media_raw_audio_format& w = media_raw_audio_format::wildcard; 1181 1182 if(f.frame_rate == w.frame_rate) 1183 f.frame_rate = 44100.0; 1184 if(f.channel_count == w.channel_count) { 1185 //+++++ tweaked 15sep99 1186 if(m_input.source != media_source::null) 1187 f.channel_count = m_input.format.u.raw_audio.channel_count; 1188 else 1189 f.channel_count = 1; 1190 } 1191 if(f.format == w.format) 1192 f.format = media_raw_audio_format::B_AUDIO_FLOAT; 1193 if(f.byte_order == w.format) 1194 f.byte_order = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; 1195 if(f.buffer_size == w.buffer_size) 1196 f.buffer_size = 2048; 1197 1198 string_for_format(ioFormat, formatStr, 255); 1199 PRINT(("\toutput format: %s\n", formatStr)); 1200 } 1201 1202 // set parameters to their default settings 1203 void FlangerNode::initParameterValues() { 1204 m_fMixRatio = 0.5; 1205 m_tpMixRatioChanged = 0LL; 1206 1207 m_fSweepRate = 0.1; 1208 m_tpSweepRateChanged = 0LL; 1209 1210 m_fDelay = 10.0; 1211 m_tpDelayChanged = 0LL; 1212 1213 m_fDepth = 25.0; 1214 m_tpDepthChanged = 0LL; 1215 1216 m_fFeedback = 0.1; 1217 m_tpFeedbackChanged = 0LL; 1218 } 1219 1220 // create and register a parameter web 1221 void FlangerNode::initParameterWeb() { 1222 BParameterWeb* pWeb = new BParameterWeb(); 1223 BParameterGroup* pTopGroup = pWeb->MakeGroup("FlangerNode Parameters"); 1224 1225 BNullParameter* label; 1226 BContinuousParameter* value; 1227 BParameterGroup* g; 1228 1229 // mix ratio 1230 g = pTopGroup->MakeGroup("Mix ratio"); 1231 label = g->MakeNullParameter( 1232 P_MIX_RATIO_LABEL, 1233 B_MEDIA_NO_TYPE, 1234 "Mix ratio", 1235 B_GENERIC); 1236 1237 value = g->MakeContinuousParameter( 1238 P_MIX_RATIO, 1239 B_MEDIA_NO_TYPE, 1240 "", 1241 B_GAIN, "", 0.0, 1.0, 0.05); 1242 label->AddOutput(value); 1243 value->AddInput(label); 1244 1245 // sweep rate 1246 g = pTopGroup->MakeGroup("Sweep rate"); 1247 label = g->MakeNullParameter( 1248 P_SWEEP_RATE_LABEL, 1249 B_MEDIA_NO_TYPE, 1250 "Sweep rate", 1251 B_GENERIC); 1252 1253 value = g->MakeContinuousParameter( 1254 P_SWEEP_RATE, 1255 B_MEDIA_NO_TYPE, 1256 "", 1257 B_GAIN, "Hz", 0.01, 10.0, 0.01); 1258 label->AddOutput(value); 1259 value->AddInput(label); 1260 1261 // sweep range: minimum delay 1262 g = pTopGroup->MakeGroup("Delay"); 1263 label = g->MakeNullParameter( 1264 P_DELAY_LABEL, 1265 B_MEDIA_NO_TYPE, 1266 "Delay", 1267 B_GENERIC); 1268 1269 value = g->MakeContinuousParameter( 1270 P_DELAY, 1271 B_MEDIA_NO_TYPE, 1272 "", 1273 B_GAIN, "ms", 0.1, s_fMaxDelay/2.0, 0.1); 1274 label->AddOutput(value); 1275 value->AddInput(label); 1276 1277 // sweep range: maximum 1278 g = pTopGroup->MakeGroup("Depth"); 1279 label = g->MakeNullParameter( 1280 P_DEPTH_LABEL, 1281 B_MEDIA_NO_TYPE, 1282 "Depth", 1283 B_GENERIC); 1284 1285 value = g->MakeContinuousParameter( 1286 P_DEPTH, 1287 B_MEDIA_NO_TYPE, 1288 "", 1289 B_GAIN, "ms", 1.0, s_fMaxDelay/4.0, 0.1); 1290 label->AddOutput(value); 1291 value->AddInput(label); 1292 1293 // feedback 1294 g = pTopGroup->MakeGroup("Feedback"); 1295 label = g->MakeNullParameter( 1296 P_FEEDBACK_LABEL, 1297 B_MEDIA_NO_TYPE, 1298 "Feedback", 1299 B_GENERIC); 1300 1301 value = g->MakeContinuousParameter( 1302 P_FEEDBACK, 1303 B_MEDIA_NO_TYPE, 1304 "", 1305 B_GAIN, "", 0.0, 1.0, 0.01); 1306 label->AddOutput(value); 1307 value->AddInput(label); 1308 1309 // * Install parameter web 1310 SetParameterWeb(pWeb); 1311 } 1312 1313 // construct delay line if necessary, reset filter state 1314 void FlangerNode::initFilter() { 1315 PRINT(("FlangerNode::initFilter()\n")); 1316 ASSERT(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format); 1317 1318 if(!m_pDelayBuffer) { 1319 m_pDelayBuffer = new AudioBuffer( 1320 m_format.u.raw_audio, 1321 frames_for_duration( 1322 m_format.u.raw_audio, 1323 (bigtime_t)s_fMaxDelay*1000LL)); 1324 m_pDelayBuffer->zero(); 1325 } 1326 1327 m_framesSent = 0; 1328 m_delayWriteFrame = 0; 1329 m_fTheta = 0.0; 1330 m_fThetaInc = calc_sweep_delta(m_format.u.raw_audio, m_fSweepRate); 1331 m_fSweepBase = calc_sweep_base(m_format.u.raw_audio, m_fDelay, m_fDepth); 1332 m_fSweepFactor = calc_sweep_factor(m_format.u.raw_audio, m_fDepth); 1333 1334 // 1335 // PRINT(( 1336 // "\tFrames %ld\n" 1337 // "\tDelay %.2f\n" 1338 // "\tDepth %.2f\n" 1339 // "\tSweepBase %.2f\n" 1340 // "\tSweepFactor %.2f\n", 1341 // m_pDelayBuffer->frames(), 1342 // m_fDelay, m_fDepth, m_fSweepBase, m_fSweepFactor)); 1343 } 1344 1345 void FlangerNode::startFilter() { 1346 PRINT(("FlangerNode::startFilter()\n")); 1347 } 1348 void FlangerNode::stopFilter() { 1349 PRINT(("FlangerNode::stopFilter()\n")); 1350 } 1351 1352 // figure processing latency by doing 'dry runs' of filterBuffer() 1353 bigtime_t FlangerNode::calcProcessingLatency() { 1354 PRINT(("FlangerNode::calcProcessingLatency()\n")); 1355 1356 if(m_output.destination == media_destination::null) { 1357 PRINT(("\tNot connected.\n")); 1358 return 0LL; 1359 } 1360 1361 // allocate a temporary buffer group 1362 BBufferGroup* pTestGroup = new BBufferGroup( 1363 m_output.format.u.raw_audio.buffer_size, 1); 1364 1365 // fetch a buffer 1366 BBuffer* pBuffer = pTestGroup->RequestBuffer( 1367 m_output.format.u.raw_audio.buffer_size); 1368 ASSERT(pBuffer); 1369 1370 pBuffer->Header()->type = B_MEDIA_RAW_AUDIO; 1371 pBuffer->Header()->size_used = m_output.format.u.raw_audio.buffer_size; 1372 1373 // run the test 1374 bigtime_t preTest = system_time(); 1375 filterBuffer(pBuffer); 1376 bigtime_t elapsed = system_time()-preTest; 1377 1378 // clean up 1379 pBuffer->Recycle(); 1380 delete pTestGroup; 1381 1382 // reset filter state 1383 initFilter(); 1384 1385 return elapsed; 1386 } 1387 1388 // filter buffer data in place 1389 // 1390 // +++++ add 2-channel support 15sep991 1391 // 1392 1393 const size_t MAX_CHANNELS = 2; 1394 1395 struct _frame { 1396 float channel[MAX_CHANNELS]; 1397 }; 1398 1399 void FlangerNode::filterBuffer( 1400 BBuffer* pBuffer) { 1401 1402 if(!m_pDelayBuffer) 1403 return; 1404 1405 // for each input frame: 1406 // - fetch 1407 // - write delay line(writeFrame) 1408 // - read delay line(writeFrame-readOffset) [interpolate] 1409 // - mix (replace) 1410 // - advance writeFrame 1411 // - update readOffset 1412 1413 AudioBuffer input(m_format.u.raw_audio, pBuffer); 1414 1415 ASSERT( 1416 m_format.u.raw_audio.channel_count == 1 || 1417 m_format.u.raw_audio.channel_count == 2); 1418 uint32 channels = m_format.u.raw_audio.channel_count; 1419 bool stereo = m_format.u.raw_audio.channel_count == 2; 1420 1421 uint32 samples = input.frames() * channels; 1422 for(uint32 inPos = 0; inPos < samples; ++inPos) { 1423 1424 // read from input buffer 1425 _frame inFrame; 1426 inFrame.channel[0] = ((float*)input.data())[inPos]; 1427 if(stereo) 1428 inFrame.channel[1] = ((float*)input.data())[inPos + 1]; 1429 1430 // interpolate from delay buffer 1431 float readOffset = m_fSweepBase + (m_fSweepFactor * sin(m_fTheta)); 1432 float fReadFrame = (float)m_delayWriteFrame - readOffset; 1433 if(fReadFrame < 0.0) 1434 fReadFrame += m_pDelayBuffer->frames(); 1435 1436 // float delayed; 1437 1438 1439 // read low-index (possibly only) frame 1440 _frame delayedFrame; 1441 1442 int32 readFrameLo = (int32)floor(fReadFrame); 1443 uint32 pos = readFrameLo * channels; 1444 delayedFrame.channel[0] = ((float*)m_pDelayBuffer->data())[pos]; 1445 if(stereo) 1446 delayedFrame.channel[1] = ((float*)m_pDelayBuffer->data())[pos+1]; 1447 1448 if(readFrameLo != (int32)fReadFrame) { 1449 1450 // interpolate (A) 1451 uint32 readFrameHi = (int32)ceil(fReadFrame); 1452 delayedFrame.channel[0] *= ((float)readFrameHi - fReadFrame); 1453 if(stereo) 1454 delayedFrame.channel[1] *= ((float)readFrameHi - fReadFrame); 1455 1456 // read high-index frame 1457 int32 hiWrap = (readFrameHi == m_pDelayBuffer->frames()) ? 0 : readFrameHi; 1458 ASSERT(hiWrap >= 0); 1459 pos = (uint32)hiWrap * channels; 1460 _frame hiFrame; 1461 hiFrame.channel[0] = ((float*)m_pDelayBuffer->data())[pos]; 1462 if(stereo) 1463 hiFrame.channel[1] = ((float*)m_pDelayBuffer->data())[pos+1]; 1464 1465 // interpolate (B) 1466 delayedFrame.channel[0] += 1467 hiFrame.channel[0] * (fReadFrame - (float)readFrameLo); 1468 if(stereo) 1469 delayedFrame.channel[1] += 1470 hiFrame.channel[1] * (fReadFrame - (float)readFrameLo); 1471 } 1472 1473 // mix back to output buffer 1474 ((float*)input.data())[inPos] = 1475 (inFrame.channel[0] * (1.0-m_fMixRatio)) + 1476 (delayedFrame.channel[0] * m_fMixRatio); 1477 if(stereo) 1478 ((float*)input.data())[inPos+1] = 1479 (inFrame.channel[1] * (1.0-m_fMixRatio)) + 1480 (delayedFrame.channel[1] * m_fMixRatio); 1481 1482 // write to delay buffer 1483 uint32 delayWritePos = m_delayWriteFrame * channels; 1484 ((float*)m_pDelayBuffer->data())[delayWritePos] = 1485 inFrame.channel[0] + 1486 (delayedFrame.channel[0] * m_fFeedback); 1487 if(stereo) 1488 ((float*)m_pDelayBuffer->data())[delayWritePos+1] = 1489 inFrame.channel[1] + 1490 (delayedFrame.channel[1] * m_fFeedback); 1491 1492 // advance write position 1493 if(++m_delayWriteFrame >= m_pDelayBuffer->frames()) 1494 m_delayWriteFrame = 0; 1495 1496 // advance read offset ('LFO') 1497 m_fTheta += m_fThetaInc; 1498 if(m_fTheta > 2 * M_PI) 1499 m_fTheta -= 2 * M_PI; 1500 1501 // if(m_fDelayReadDelta < 0.0) { 1502 // if(m_fDelayReadOffset < m_fDelay) 1503 // m_fDelayReadDelta = -m_fDelayReadDelta; 1504 // } else { 1505 // if(m_fDelayReadOffset > m_fDepth) 1506 // m_fDelayReadDelta = -m_fDelayReadDelta; 1507 // } 1508 // m_fDelayReadOffset += m_fDelayReadDelta; 1509 } 1510 } 1511 1512 1513 /*! Figure the rate at which the (radial) read offset changes, 1514 based on the given sweep rate (in Hz) 1515 */ 1516 float 1517 calc_sweep_delta(const media_raw_audio_format& format, float fRate) 1518 { 1519 return 2 * M_PI * fRate / format.frame_rate; 1520 } 1521 1522 /*! Figure the base delay (in frames) based on the given 1523 sweep delay/depth (in msec) 1524 */ 1525 float 1526 calc_sweep_base(const media_raw_audio_format& format, float delay, float depth) 1527 { 1528 return (format.frame_rate * (delay + depth)) / 1000.0; 1529 } 1530 1531 1532 float 1533 calc_sweep_factor(const media_raw_audio_format& format, float depth) 1534 { 1535 return (format.frame_rate * depth) / 1000.0; 1536 } 1537 1538 1539 // END -- FlangerNode.cpp -- 1540