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