2 * Copyright (c) 1999-2000, Eric Moon.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions, and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions, and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote products
17 * derived from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 #include "FlangerNode.h"
37 #include "AudioBuffer.h"
38 #include "SoundUtils.h"
41 #include <BufferGroup.h>
42 #include <ByteOrder.h>
44 #include <ParameterWeb.h>
45 #include <TimeSource.h>
52 // -------------------------------------------------------- //
54 // -------------------------------------------------------- //
56 float calc_sweep_delta(
57 const media_raw_audio_format
& format
,
60 float calc_sweep_base(
61 const media_raw_audio_format
& format
,
62 float fDelay
, float fDepth
);
64 float calc_sweep_factor(
65 const media_raw_audio_format
& format
,
68 // -------------------------------------------------------- //
70 // -------------------------------------------------------- //
80 //ID_AUDIO_WET_OUTPUT ...
85 P_MIX_RATIO_LABEL
= 100,
88 P_SWEEP_RATE_LABEL
= 200,
97 P_FEEDBACK_LABEL
= 500,
101 const float FlangerNode::s_fMaxDelay
= 100.0;
102 const char* const FlangerNode::s_nodeName
= "FlangerNode";
105 // -------------------------------------------------------- //
107 // -------------------------------------------------------- //
109 FlangerNode::~FlangerNode() {
115 delete m_pDelayBuffer
;
118 FlangerNode::FlangerNode(BMediaAddOn
* pAddOn
) :
119 // * init base classes
120 BMediaNode(s_nodeName
), // (virtual base)
121 BBufferConsumer(B_MEDIA_RAW_AUDIO
),
122 BBufferProducer(B_MEDIA_RAW_AUDIO
),
126 // * init connection state
127 m_outputEnabled(true),
128 m_downstreamLatency(0),
129 m_processingLatency(0),
131 // * init filter state
134 // * init add-on stuff
139 // "--*-- FlangerNode() [%s] --*--\n\n",
142 // the rest of the initialization happens in NodeRegistered().
146 // -------------------------------------------------------- //
148 // -------------------------------------------------------- //
150 status_t
FlangerNode::HandleMessage(
155 // pass off to each base class
157 BBufferConsumer::HandleMessage(code
, pData
, size
) &&
158 BBufferProducer::HandleMessage(code
, pData
, size
) &&
159 BControllable::HandleMessage(code
, pData
, size
) &&
160 BMediaNode::HandleMessage(code
, pData
, size
))
161 BMediaNode::HandleBadMessage(code
, pData
, size
);
163 // +++++ return error on bad message?
167 BMediaAddOn
* FlangerNode::AddOn(
175 void FlangerNode::SetRunMode(
178 // disallow offline mode for now
180 if(mode
== B_OFFLINE
)
181 ReportError(B_NODE_FAILED_SET_RUN_MODE
);
183 // +++++ any other work to do?
186 BMediaEventLooper::SetRunMode(mode
);
189 // -------------------------------------------------------- //
190 // *** BMediaEventLooper
191 // -------------------------------------------------------- //
193 void FlangerNode::HandleEvent(
194 const media_timed_event
* pEvent
,
196 bool realTimeEvent
) {
200 switch(pEvent
->type
) {
201 case BTimedEventQueue::B_PARAMETER
:
202 handleParameterEvent(pEvent
);
205 case BTimedEventQueue::B_START
:
206 handleStartEvent(pEvent
);
209 case BTimedEventQueue::B_STOP
:
210 handleStopEvent(pEvent
);
219 // "The Media Server calls this hook function after the node has
220 // been registered. This is derived from BMediaNode; BMediaEventLooper
221 // implements it to call Run() automatically when the node is registered;
222 // if you implement NodeRegistered() you should call through to
223 // BMediaEventLooper::NodeRegistered() after you've done your custom
226 void FlangerNode::NodeRegistered() {
228 PRINT(("FlangerNode::NodeRegistered()\n"));
230 // figure preferred ('template') format
231 m_preferredFormat
.type
= B_MEDIA_RAW_AUDIO
;
232 getPreferredFormat(m_preferredFormat
);
234 // initialize current format
235 m_format
.type
= B_MEDIA_RAW_AUDIO
;
236 m_format
.u
.raw_audio
= media_raw_audio_format::wildcard
;
239 m_input
.destination
.port
= ControlPort();
240 m_input
.destination
.id
= ID_AUDIO_INPUT
;
241 m_input
.node
= Node();
242 m_input
.source
= media_source::null
;
243 m_input
.format
= m_format
;
244 strncpy(m_input
.name
, "Audio Input", B_MEDIA_NAME_LENGTH
);
247 m_output
.source
.port
= ControlPort();
248 m_output
.source
.id
= ID_AUDIO_MIX_OUTPUT
;
249 m_output
.node
= Node();
250 m_output
.destination
= media_destination::null
;
251 m_output
.format
= m_format
;
252 strncpy(m_output
.name
, "Mix Output", B_MEDIA_NAME_LENGTH
);
255 initParameterValues();
258 // Start the BMediaEventLooper thread
259 SetPriority(B_REAL_TIME_PRIORITY
);
263 // "Augment OfflineTime() to compute the node's current time; it's called
264 // by the Media Kit when it's in offline mode. Update any appropriate
265 // internal information as well, then call through to the BMediaEventLooper
268 bigtime_t
FlangerNode::OfflineTime() {
273 // -------------------------------------------------------- //
274 // *** BBufferConsumer
275 // -------------------------------------------------------- //
277 status_t
FlangerNode::AcceptFormat(
278 const media_destination
& destination
,
279 media_format
* pioFormat
) {
281 PRINT(("FlangerNode::AcceptFormat()\n"));
284 if(destination
!= m_input
.destination
) {
285 PRINT(("\tbad destination\n"));
286 return B_MEDIA_BAD_DESTINATION
;
288 if(pioFormat
->type
!= B_MEDIA_RAW_AUDIO
) {
289 PRINT(("\tnot B_MEDIA_RAW_AUDIO\n"));
290 return B_MEDIA_BAD_FORMAT
;
293 validateProposedFormat(
294 (m_format
.u
.raw_audio
.format
!= media_raw_audio_format::wildcard
.format
) ?
295 m_format
: m_preferredFormat
,
300 // "If you're writing a node, and receive a buffer with the B_SMALL_BUFFER
301 // flag set, you must recycle the buffer before returning."
303 void FlangerNode::BufferReceived(
307 // check buffer destination
308 if(pBuffer
->Header()->destination
!=
309 m_input
.destination
.id
) {
310 PRINT(("FlangerNode::BufferReceived():\n"
311 "\tBad destination.\n"));
316 if(pBuffer
->Header()->time_source
!= TimeSource()->ID()) {
317 PRINT(("* timesource mismatch\n"));
321 if(m_output
.destination
== media_destination::null
||
327 // process and retransmit buffer
328 filterBuffer(pBuffer
);
330 status_t err
= SendBuffer(pBuffer
, m_output
.source
, m_output
.destination
);
332 PRINT(("FlangerNode::BufferReceived():\n"
333 "\tSendBuffer() failed: %s\n", strerror(err
)));
339 // * make sure to fill in poInput->format with the contents of
340 // pFormat; as of R4.5 the Media Kit passes poInput->format to
341 // the producer in BBufferProducer::Connect().
344 FlangerNode::Connected(const media_source
& source
,
345 const media_destination
& destination
, const media_format
& format
,
346 media_input
* poInput
)
348 PRINT(("FlangerNode::Connected()\n"
349 "\tto source %" B_PRId32
"\n", source
.id
));
352 if(destination
!= m_input
.destination
) {
353 PRINT(("\tbad destination\n"));
354 return B_MEDIA_BAD_DESTINATION
;
356 if(m_input
.source
!= media_source::null
) {
357 PRINT(("\talready connected\n"));
358 return B_MEDIA_ALREADY_CONNECTED
;
362 m_input
.source
= source
;
363 m_input
.format
= format
;
366 // store format (this now constrains the output format)
372 void FlangerNode::Disconnected(
373 const media_source
& source
,
374 const media_destination
& destination
) {
376 PRINT(("FlangerNode::Disconnected()\n"));
379 if(m_input
.source
!= source
) {
380 PRINT(("\tsource mismatch: expected ID %" B_PRId32
", got %" B_PRId32
381 "\n", m_input
.source
.id
, source
.id
));
384 if(destination
!= m_input
.destination
) {
385 PRINT(("\tdestination mismatch: expected ID %" B_PRId32
", got %"
386 B_PRId32
"\n", m_input
.destination
.id
, destination
.id
));
391 m_input
.source
= media_source::null
;
393 // no output? clear format:
394 if(m_output
.destination
== media_destination::null
) {
395 m_format
.u
.raw_audio
= media_raw_audio_format::wildcard
;
398 m_input
.format
= m_format
;
402 void FlangerNode::DisposeInputCookie(
405 // "You should implement this function so your node will know that the data
406 // format is going to change. Note that this may be called in response to
407 // your AcceptFormat() call, if your AcceptFormat() call alters any wildcard
408 // fields in the specified format.
410 // Because FormatChanged() is called by the producer, you don't need to (and
411 // shouldn't) ask it if the new format is acceptable.
413 // If the format change isn't possible, return an appropriate error from
414 // FormatChanged(); this error will be passed back to the producer that
415 // initiated the new format negotiation in the first place."
417 status_t
FlangerNode::FormatChanged(
418 const media_source
& source
,
419 const media_destination
& destination
,
421 const media_format
& newFormat
) {
423 // flat-out deny format changes
424 return B_MEDIA_BAD_FORMAT
;
427 status_t
FlangerNode::GetLatencyFor(
428 const media_destination
& destination
,
429 bigtime_t
* poLatency
,
430 media_node_id
* poTimeSource
) {
432 PRINT(("FlangerNode::GetLatencyFor()\n"));
435 if(destination
!= m_input
.destination
) {
436 PRINT(("\tbad destination\n"));
437 return B_MEDIA_BAD_DESTINATION
;
440 *poLatency
= m_downstreamLatency
+ m_processingLatency
;
441 PRINT(("\treturning %" B_PRIdBIGTIME
"\n", *poLatency
));
442 *poTimeSource
= TimeSource()->ID();
446 status_t
FlangerNode::GetNextInput(
448 media_input
* poInput
) {
458 void FlangerNode::ProducerDataStatus(
459 const media_destination
& destination
,
463 PRINT(("FlangerNode::ProducerDataStatus()\n"));
466 if(destination
!= m_input
.destination
) {
467 PRINT(("\tbad destination\n"));
470 if(m_output
.destination
!= media_destination::null
) {
471 // pass status downstream
472 status_t err
= SendDataStatus(
474 m_output
.destination
,
477 PRINT(("\tSendDataStatus(): %s\n", strerror(err
)));
482 // "This function is provided to aid in supporting media formats in which the
483 // outer encapsulation layer doesn't supply timing information. Producers will
484 // tag the buffers they generate with seek tags; these tags can be used to
485 // locate key frames in the media data."
487 status_t
FlangerNode::SeekTagRequested(
488 const media_destination
& destination
,
489 bigtime_t targetTime
,
491 media_seek_tag
* poSeekTag
,
492 bigtime_t
* poTaggedTime
,
495 PRINT(("FlangerNode::SeekTagRequested()\n"
496 "\tNot implemented.\n"));
500 // -------------------------------------------------------- //
501 // *** BBufferProducer
502 // -------------------------------------------------------- //
504 // "When a consumer calls BBufferConsumer::RequestAdditionalBuffer(), this
505 // function is called as a result. Its job is to call SendBuffer() to
506 // immediately send the next buffer to the consumer. The previousBufferID,
507 // previousTime, and previousTag arguments identify the last buffer the
508 // consumer received. Your node should respond by sending the next buffer
509 // after the one described.
511 // The previousTag may be NULL.
512 // Return B_OK if all is well; otherwise return an appropriate error code."
514 void FlangerNode::AdditionalBufferRequested(
515 const media_source
& source
,
516 media_buffer_id previousBufferID
,
517 bigtime_t previousTime
,
518 const media_seek_tag
* pPreviousTag
) {
520 PRINT(("FlangerNode::AdditionalBufferRequested\n"
521 "\tOffline mode not implemented."));
524 void FlangerNode::Connect(
526 const media_source
& source
,
527 const media_destination
& destination
,
528 const media_format
& format
,
531 PRINT(("FlangerNode::Connect()\n"));
534 // connection failed?
536 PRINT(("\tStatus: %s\n", strerror(status
)));
537 // 'unreserve' the output
538 m_output
.destination
= media_destination::null
;
542 // connection established:
543 strncpy(pioName
, m_output
.name
, B_MEDIA_NAME_LENGTH
);
544 m_output
.destination
= destination
;
547 // figure downstream latency
548 media_node_id timeSource
;
549 err
= FindLatencyFor(m_output
.destination
, &m_downstreamLatency
, &timeSource
);
551 PRINT(("\t!!! FindLatencyFor(): %s\n", strerror(err
)));
553 PRINT(("\tdownstream latency = %" B_PRIdBIGTIME
"\n", m_downstreamLatency
));
555 // prepare the filter
558 // figure processing time
559 m_processingLatency
= calcProcessingLatency();
560 PRINT(("\tprocessing latency = %" B_PRIdBIGTIME
"\n", m_processingLatency
));
562 // store summed latency
563 SetEventLatency(m_downstreamLatency
+ m_processingLatency
);
565 if(m_input
.source
!= media_source::null
) {
566 // pass new latency upstream
567 err
= SendLatencyChange(
570 EventLatency() + SchedulingLatency());
572 PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err
)));
575 // cache buffer duration
578 m_format
.u
.raw_audio
));
581 void FlangerNode::Disconnect(
582 const media_source
& source
,
583 const media_destination
& destination
) {
585 PRINT(("FlangerNode::Disconnect()\n"));
588 if(source
!= m_output
.source
) {
589 PRINT(("\tbad source\n"));
592 if(destination
!= m_output
.destination
) {
593 PRINT(("\tbad destination\n"));
598 m_output
.destination
= media_destination::null
;
600 // no input? clear format:
601 if(m_input
.source
== media_source::null
) {
602 m_format
.u
.raw_audio
= media_raw_audio_format::wildcard
;
605 m_output
.format
= m_format
;
607 // +++++ other cleanup goes here
610 status_t
FlangerNode::DisposeOutputCookie(
615 void FlangerNode::EnableOutput(
616 const media_source
& source
,
618 int32
* _deprecated_
) {
619 PRINT(("FlangerNode::EnableOutput()\n"));
620 if(source
!= m_output
.source
) {
621 PRINT(("\tbad source\n"));
625 m_outputEnabled
= enabled
;
628 status_t
FlangerNode::FormatChangeRequested(
629 const media_source
& source
,
630 const media_destination
& destination
,
631 media_format
* pioFormat
,
632 int32
* _deprecated_
) {
635 PRINT(("FlangerNode::FormatChangeRequested()\n"
636 "\tNot supported.\n"));
638 return B_MEDIA_BAD_FORMAT
;
641 status_t
FlangerNode::FormatProposal(
642 const media_source
& source
,
643 media_format
* pioFormat
) {
645 PRINT(("FlangerNode::FormatProposal()\n"));
647 if(source
!= m_output
.source
) {
648 PRINT(("\tbad source\n"));
649 return B_MEDIA_BAD_SOURCE
;
652 if(pioFormat
->type
!= B_MEDIA_RAW_AUDIO
) {
653 PRINT(("\tbad type\n"));
654 return B_MEDIA_BAD_FORMAT
;
657 validateProposedFormat(
658 (m_format
.u
.raw_audio
.format
!= media_raw_audio_format::wildcard
.format
) ?
665 status_t
FlangerNode::FormatSuggestionRequested(
668 media_format
* poFormat
) {
670 PRINT(("FlangerNode::FormatSuggestionRequested()\n"));
671 if(type
!= B_MEDIA_RAW_AUDIO
) {
672 PRINT(("\tbad type\n"));
673 return B_MEDIA_BAD_FORMAT
;
676 if(m_format
.u
.raw_audio
.format
!= media_raw_audio_format::wildcard
.format
)
677 *poFormat
= m_format
;
679 *poFormat
= m_preferredFormat
;
683 status_t
FlangerNode::GetLatency(
684 bigtime_t
* poLatency
) {
686 PRINT(("FlangerNode::GetLatency()\n"));
687 *poLatency
= EventLatency() + SchedulingLatency();
688 PRINT(("\treturning %" B_PRIdBIGTIME
"\n", *poLatency
));
693 status_t
FlangerNode::GetNextOutput(
695 media_output
* poOutput
) {
701 *poOutput
= m_output
;
706 // "This hook function is called when a BBufferConsumer that's receiving data
707 // from you determines that its latency has changed. It will call its
708 // BBufferConsumer::SendLatencyChange() function, and in response, the Media
709 // Server will call your LatencyChanged() function. The source argument
710 // indicates your output that's involved in the connection, and destination
711 // specifies the input on the consumer to which the connection is linked.
712 // newLatency is the consumer's new latency. The flags are currently unused."
713 void FlangerNode::LatencyChanged(
714 const media_source
& source
,
715 const media_destination
& destination
,
716 bigtime_t newLatency
,
719 PRINT(("FlangerNode::LatencyChanged()\n"));
721 if(source
!= m_output
.source
) {
722 PRINT(("\tBad source.\n"));
725 if(destination
!= m_output
.destination
) {
726 PRINT(("\tBad destination.\n"));
730 m_downstreamLatency
= newLatency
;
731 SetEventLatency(m_downstreamLatency
+ m_processingLatency
);
733 if(m_input
.source
!= media_source::null
) {
734 // pass new latency upstream
735 status_t err
= SendLatencyChange(
738 EventLatency() + SchedulingLatency());
740 PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err
)));
744 void FlangerNode::LateNoticeReceived(
745 const media_source
& source
,
749 PRINT(("FlangerNode::LateNoticeReceived()\n"
750 "\thowLate == %" B_PRIdBIGTIME
"\n"
751 "\twhen == %" B_PRIdBIGTIME
"\n", howLate
, tpWhen
));
753 if(source
!= m_output
.source
) {
754 PRINT(("\tBad source.\n"));
758 if(m_input
.source
== media_source::null
) {
759 PRINT(("\t!!! No input to blame.\n"));
763 // +++++ check run mode?
765 // pass the buck, since this node doesn't schedule buffer
773 // PrepareToConnect() is the second stage of format negotiations that happens
774 // inside BMediaRoster::Connect(). At this point, the consumer's AcceptFormat()
775 // method has been called, and that node has potentially changed the proposed
776 // format. It may also have left wildcards in the format. PrepareToConnect()
777 // *must* fully specialize the format before returning!
779 status_t
FlangerNode::PrepareToConnect(
780 const media_source
& source
,
781 const media_destination
& destination
,
782 media_format
* pioFormat
,
783 media_source
* poSource
,
787 string_for_format(*pioFormat
, formatStr
, 255);
788 PRINT(("FlangerNode::PrepareToConnect()\n"
789 "\tproposed format: %s\n", formatStr
));
791 if(source
!= m_output
.source
) {
792 PRINT(("\tBad source.\n"));
793 return B_MEDIA_BAD_SOURCE
;
795 if(m_output
.destination
!= media_destination::null
) {
796 PRINT(("\tAlready connected.\n"));
797 return B_MEDIA_ALREADY_CONNECTED
;
800 if(pioFormat
->type
!= B_MEDIA_RAW_AUDIO
) {
801 PRINT(("\tBad format type.\n"));
802 return B_MEDIA_BAD_FORMAT
;
805 // do a final validity check:
806 status_t err
= validateProposedFormat(
807 (m_format
.u
.raw_audio
.format
!= media_raw_audio_format::wildcard
.format
) ?
818 specializeOutputFormat(*pioFormat
);
820 // reserve the output
821 m_output
.destination
= destination
;
822 m_output
.format
= *pioFormat
;
824 // pass back source & output name
825 *poSource
= m_output
.source
;
826 strncpy(poName
, m_output
.name
, B_MEDIA_NAME_LENGTH
);
831 status_t
FlangerNode::SetBufferGroup(
832 const media_source
& source
,
833 BBufferGroup
* pGroup
) {
835 PRINT(("FlangerNode::SetBufferGroup()\n"));
836 if(source
!= m_output
.source
) {
837 PRINT(("\tBad source.\n"));
838 return B_MEDIA_BAD_SOURCE
;
841 if(m_input
.source
== media_source::null
) {
842 PRINT(("\tNo producer to send buffers to.\n"));
846 // +++++ is this right? buffer-group selection gets
847 // all asynchronous and weird...
849 return SetOutputBuffersFor(
856 status_t
FlangerNode::SetPlayRate(
863 status_t
FlangerNode::VideoClippingChanged(
864 const media_source
& source
,
867 const media_video_display_info
& display
,
868 int32
* poFromChangeTag
) {
873 // -------------------------------------------------------- //
875 // -------------------------------------------------------- //
877 status_t
FlangerNode::GetParameterValue(
879 bigtime_t
* poLastChangeTime
,
883 // PRINT(("FlangerNode::GetParameterValue()\n"));
885 // all parameters are floats
886 if(*pioSize
< sizeof(float)) {
890 *pioSize
= sizeof(float);
893 *(float*)poValue
= m_fMixRatio
;
894 *poLastChangeTime
= m_tpMixRatioChanged
;
898 *(float*)poValue
= m_fSweepRate
;
899 *poLastChangeTime
= m_tpSweepRateChanged
;
903 *(float*)poValue
= m_fDelay
;
904 *poLastChangeTime
= m_tpDelayChanged
;
908 *(float*)poValue
= m_fDepth
;
909 *poLastChangeTime
= m_tpDepthChanged
;
913 *(float*)poValue
= m_fFeedback
;
914 *poLastChangeTime
= m_tpFeedbackChanged
;
924 void FlangerNode::SetParameterValue(
926 bigtime_t changeTime
,
936 if(size
< sizeof(float))
939 // this is from ToneProducer. it's fishy.
940 // if(size > sizeof(float))
941 // size = sizeof(float);
943 media_timed_event
ev(
945 BTimedEventQueue::B_PARAMETER
,
947 BTimedEventQueue::B_NO_CLEANUP
,
950 (char*)pValue
, size
);
951 EventQueue()->AddEvent(ev
);
957 // -------------------------------------------------------- //
958 // *** HandleEvent() impl
959 // -------------------------------------------------------- //
961 void FlangerNode::handleParameterEvent(
962 const media_timed_event
* pEvent
) {
964 float value
= *(float*)pEvent
->user_data
;
965 int32 id
= pEvent
->bigdata
;
966 size_t size
= pEvent
->data
;
967 bigtime_t now
= TimeSource()->Now();
971 if(value
== m_fMixRatio
)
976 m_tpMixRatioChanged
= now
;
978 BroadcastNewParameterValue(
986 if(value
== m_fSweepRate
)
990 m_fSweepRate
= value
;
991 m_tpSweepRateChanged
= now
;
993 if(m_output
.destination
!= media_destination::null
) {
994 m_fThetaInc
= calc_sweep_delta(
995 m_format
.u
.raw_audio
,
1000 BroadcastNewParameterValue(
1008 if(value
== m_fDelay
)
1013 m_tpDelayChanged
= now
;
1015 if(m_output
.destination
!= media_destination::null
) {
1016 m_fSweepBase
= calc_sweep_base(
1017 m_format
.u
.raw_audio
,
1018 m_fDelay
, m_fDepth
);
1022 BroadcastNewParameterValue(
1030 if(value
== m_fDepth
)
1035 m_tpDepthChanged
= now
;
1037 if(m_output
.destination
!= media_destination::null
) {
1038 m_fSweepBase
= calc_sweep_base(
1039 m_format
.u
.raw_audio
,
1040 m_fDelay
, m_fDepth
);
1041 m_fSweepFactor
= calc_sweep_factor(
1042 m_format
.u
.raw_audio
,
1047 BroadcastNewParameterValue(
1055 if(value
== m_fFeedback
)
1059 m_fFeedback
= value
;
1060 m_tpFeedbackChanged
= now
;
1062 BroadcastNewParameterValue(
1071 void FlangerNode::handleStartEvent(
1072 const media_timed_event
* pEvent
) {
1073 PRINT(("FlangerNode::handleStartEvent\n"));
1078 void FlangerNode::handleStopEvent(
1079 const media_timed_event
* pEvent
) {
1080 PRINT(("FlangerNode::handleStopEvent\n"));
1085 void FlangerNode::ignoreEvent(
1086 const media_timed_event
* pEvent
) {
1087 PRINT(("FlangerNode::ignoreEvent\n"));
1092 // -------------------------------------------------------- //
1093 // *** internal operations
1094 // -------------------------------------------------------- //
1097 // figure the preferred format: any fields left as wildcards
1099 void FlangerNode::getPreferredFormat(
1100 media_format
& ioFormat
) {
1101 ASSERT(ioFormat
.type
== B_MEDIA_RAW_AUDIO
);
1103 ioFormat
.u
.raw_audio
= media_raw_audio_format::wildcard
;
1104 ioFormat
.u
.raw_audio
.channel_count
= 1;
1105 ioFormat
.u
.raw_audio
.format
= media_raw_audio_format::B_AUDIO_FLOAT
;
1107 // ioFormat.u.raw_audio.frame_rate = 44100.0;
1108 // ioFormat.u.raw_audio.buffer_size = 0x1000;
1111 // test the given template format against a proposed format.
1112 // specialize wildcards for fields where the template contains
1113 // non-wildcard data; write required fields into proposed format
1114 // if they mismatch.
1115 // Returns B_OK if the proposed format doesn't conflict with the
1116 // template, or B_MEDIA_BAD_FORMAT otherwise.
1118 status_t
FlangerNode::validateProposedFormat(
1119 const media_format
& preferredFormat
,
1120 media_format
& ioProposedFormat
) {
1122 char formatStr
[256];
1123 PRINT(("FlangerNode::validateProposedFormat()\n"));
1125 ASSERT(preferredFormat
.type
== B_MEDIA_RAW_AUDIO
);
1127 string_for_format(preferredFormat
, formatStr
, 255);
1128 PRINT(("\ttemplate format: %s\n", formatStr
));
1130 string_for_format(ioProposedFormat
, formatStr
, 255);
1131 PRINT(("\tproposed format: %s\n", formatStr
));
1133 status_t err
= B_OK
;
1135 if(ioProposedFormat
.type
!= B_MEDIA_RAW_AUDIO
) {
1136 // out of the ballpark
1137 ioProposedFormat
= preferredFormat
;
1138 return B_MEDIA_BAD_FORMAT
;
1142 media_raw_audio_format
& wild
= media_raw_audio_format::wildcard
;
1144 media_raw_audio_format
& f
= ioProposedFormat
.u
.raw_audio
;
1146 const media_raw_audio_format
& pref
= preferredFormat
.u
.raw_audio
;
1148 if(pref
.frame_rate
!= wild
.frame_rate
) {
1149 if(f
.frame_rate
!= pref
.frame_rate
) {
1150 if(f
.frame_rate
!= wild
.frame_rate
)
1151 err
= B_MEDIA_BAD_FORMAT
;
1152 f
.frame_rate
= pref
.frame_rate
;
1156 if(pref
.channel_count
!= wild
.channel_count
) {
1157 if(f
.channel_count
!= pref
.channel_count
) {
1158 if(f
.channel_count
!= wild
.channel_count
)
1159 err
= B_MEDIA_BAD_FORMAT
;
1160 f
.channel_count
= pref
.channel_count
;
1164 if(pref
.format
!= wild
.format
) {
1165 if(f
.format
!= pref
.format
) {
1166 if(f
.format
!= wild
.format
)
1167 err
= B_MEDIA_BAD_FORMAT
;
1168 f
.format
= pref
.format
;
1172 if(pref
.byte_order
!= wild
.byte_order
) {
1173 if(f
.byte_order
!= pref
.byte_order
) {
1174 if(f
.byte_order
!= wild
.byte_order
)
1175 err
= B_MEDIA_BAD_FORMAT
;
1176 f
.byte_order
= pref
.byte_order
;
1180 if(pref
.buffer_size
!= wild
.buffer_size
) {
1181 if(f
.buffer_size
!= pref
.buffer_size
) {
1182 if(f
.buffer_size
!= wild
.buffer_size
)
1183 err
= B_MEDIA_BAD_FORMAT
;
1184 f
.buffer_size
= pref
.buffer_size
;
1189 string_for_format(ioProposedFormat
, formatStr
, 255);
1191 "\tformat conflict; suggesting:\n\tformat %s\n", formatStr
));
1197 // fill in wildcards in the given format.
1198 // (assumes the format passes validateProposedFormat().)
1199 void FlangerNode::specializeOutputFormat(
1200 media_format
& ioFormat
) {
1202 char formatStr
[256];
1203 string_for_format(ioFormat
, formatStr
, 255);
1204 PRINT(("FlangerNode::specializeOutputFormat()\n"
1205 "\tinput format: %s\n", formatStr
));
1207 ASSERT(ioFormat
.type
== B_MEDIA_RAW_AUDIO
);
1209 // carpal_tunnel_paranoia
1210 media_raw_audio_format
& f
= ioFormat
.u
.raw_audio
;
1211 media_raw_audio_format
& w
= media_raw_audio_format::wildcard
;
1213 if (f
.frame_rate
== w
.frame_rate
)
1214 f
.frame_rate
= 44100.0;
1215 if (f
.channel_count
== w
.channel_count
) {
1216 //+++++ tweaked 15sep99
1217 if (m_input
.source
!= media_source::null
)
1218 f
.channel_count
= m_input
.format
.u
.raw_audio
.channel_count
;
1220 f
.channel_count
= 1;
1222 if (f
.format
== w
.format
)
1223 f
.format
= media_raw_audio_format::B_AUDIO_FLOAT
;
1224 if (f
.byte_order
== w
.byte_order
)
1225 f
.byte_order
= (B_HOST_IS_BENDIAN
) ? B_MEDIA_BIG_ENDIAN
: B_MEDIA_LITTLE_ENDIAN
;
1226 if (f
.buffer_size
== w
.buffer_size
)
1227 f
.buffer_size
= 2048;
1229 string_for_format(ioFormat
, formatStr
, 255);
1230 PRINT(("\toutput format: %s\n", formatStr
));
1233 // set parameters to their default settings
1234 void FlangerNode::initParameterValues() {
1236 m_tpMixRatioChanged
= 0LL;
1239 m_tpSweepRateChanged
= 0LL;
1242 m_tpDelayChanged
= 0LL;
1245 m_tpDepthChanged
= 0LL;
1248 m_tpFeedbackChanged
= 0LL;
1251 // create and register a parameter web
1252 void FlangerNode::initParameterWeb() {
1253 BParameterWeb
* pWeb
= new BParameterWeb();
1254 BParameterGroup
* pTopGroup
= pWeb
->MakeGroup("FlangerNode Parameters");
1256 BNullParameter
* label
;
1257 BContinuousParameter
* value
;
1261 g
= pTopGroup
->MakeGroup("Mix ratio");
1262 label
= g
->MakeNullParameter(
1268 value
= g
->MakeContinuousParameter(
1272 B_GAIN
, "", 0.0, 1.0, 0.05);
1273 label
->AddOutput(value
);
1274 value
->AddInput(label
);
1277 g
= pTopGroup
->MakeGroup("Sweep rate");
1278 label
= g
->MakeNullParameter(
1284 value
= g
->MakeContinuousParameter(
1288 B_GAIN
, "Hz", 0.01, 10.0, 0.01);
1289 label
->AddOutput(value
);
1290 value
->AddInput(label
);
1292 // sweep range: minimum delay
1293 g
= pTopGroup
->MakeGroup("Delay");
1294 label
= g
->MakeNullParameter(
1300 value
= g
->MakeContinuousParameter(
1304 B_GAIN
, "ms", 0.1, s_fMaxDelay
/2.0, 0.1);
1305 label
->AddOutput(value
);
1306 value
->AddInput(label
);
1308 // sweep range: maximum
1309 g
= pTopGroup
->MakeGroup("Depth");
1310 label
= g
->MakeNullParameter(
1316 value
= g
->MakeContinuousParameter(
1320 B_GAIN
, "ms", 1.0, s_fMaxDelay
/4.0, 0.1);
1321 label
->AddOutput(value
);
1322 value
->AddInput(label
);
1325 g
= pTopGroup
->MakeGroup("Feedback");
1326 label
= g
->MakeNullParameter(
1332 value
= g
->MakeContinuousParameter(
1336 B_GAIN
, "", 0.0, 1.0, 0.01);
1337 label
->AddOutput(value
);
1338 value
->AddInput(label
);
1340 // * Install parameter web
1341 SetParameterWeb(pWeb
);
1344 // construct delay line if necessary, reset filter state
1345 void FlangerNode::initFilter() {
1346 PRINT(("FlangerNode::initFilter()\n"));
1347 ASSERT(m_format
.u
.raw_audio
.format
!= media_raw_audio_format::wildcard
.format
);
1349 if(!m_pDelayBuffer
) {
1350 m_pDelayBuffer
= new AudioBuffer(
1351 m_format
.u
.raw_audio
,
1352 frames_for_duration(
1353 m_format
.u
.raw_audio
,
1354 (bigtime_t
)s_fMaxDelay
*1000LL));
1355 m_pDelayBuffer
->zero();
1359 m_delayWriteFrame
= 0;
1361 m_fThetaInc
= calc_sweep_delta(m_format
.u
.raw_audio
, m_fSweepRate
);
1362 m_fSweepBase
= calc_sweep_base(m_format
.u
.raw_audio
, m_fDelay
, m_fDepth
);
1363 m_fSweepFactor
= calc_sweep_factor(m_format
.u
.raw_audio
, m_fDepth
);
1370 // "\tSweepBase %.2f\n"
1371 // "\tSweepFactor %.2f\n",
1372 // m_pDelayBuffer->frames(),
1373 // m_fDelay, m_fDepth, m_fSweepBase, m_fSweepFactor));
1376 void FlangerNode::startFilter() {
1377 PRINT(("FlangerNode::startFilter()\n"));
1379 void FlangerNode::stopFilter() {
1380 PRINT(("FlangerNode::stopFilter()\n"));
1383 // figure processing latency by doing 'dry runs' of filterBuffer()
1384 bigtime_t
FlangerNode::calcProcessingLatency() {
1385 PRINT(("FlangerNode::calcProcessingLatency()\n"));
1387 if(m_output
.destination
== media_destination::null
) {
1388 PRINT(("\tNot connected.\n"));
1392 // allocate a temporary buffer group
1393 BBufferGroup
* pTestGroup
= new BBufferGroup(
1394 m_output
.format
.u
.raw_audio
.buffer_size
, 1);
1397 BBuffer
* pBuffer
= pTestGroup
->RequestBuffer(
1398 m_output
.format
.u
.raw_audio
.buffer_size
);
1401 pBuffer
->Header()->type
= B_MEDIA_RAW_AUDIO
;
1402 pBuffer
->Header()->size_used
= m_output
.format
.u
.raw_audio
.buffer_size
;
1405 bigtime_t preTest
= system_time();
1406 filterBuffer(pBuffer
);
1407 bigtime_t elapsed
= system_time()-preTest
;
1413 // reset filter state
1419 // filter buffer data in place
1421 // +++++ add 2-channel support 15sep991
1424 const size_t MAX_CHANNELS
= 2;
1427 float channel
[MAX_CHANNELS
];
1430 void FlangerNode::filterBuffer(
1436 // for each input frame:
1438 // - write delay line(writeFrame)
1439 // - read delay line(writeFrame-readOffset) [interpolate]
1441 // - advance writeFrame
1442 // - update readOffset
1444 AudioBuffer
input(m_format
.u
.raw_audio
, pBuffer
);
1447 m_format
.u
.raw_audio
.channel_count
== 1 ||
1448 m_format
.u
.raw_audio
.channel_count
== 2);
1449 uint32 channels
= m_format
.u
.raw_audio
.channel_count
;
1450 bool stereo
= m_format
.u
.raw_audio
.channel_count
== 2;
1452 uint32 samples
= input
.frames() * channels
;
1453 for(uint32 inPos
= 0; inPos
< samples
; ++inPos
) {
1455 // read from input buffer
1456 _frame inFrame
= {};
1457 inFrame
.channel
[0] = ((float*)input
.data())[inPos
];
1459 inFrame
.channel
[1] = ((float*)input
.data())[inPos
+ 1];
1461 // interpolate from delay buffer
1462 float readOffset
= m_fSweepBase
+ (m_fSweepFactor
* sin(m_fTheta
));
1463 float fReadFrame
= (float)m_delayWriteFrame
- readOffset
;
1464 if(fReadFrame
< 0.0)
1465 fReadFrame
+= m_pDelayBuffer
->frames();
1470 // read low-index (possibly only) frame
1471 _frame delayedFrame
= {};
1473 int32 readFrameLo
= (int32
)floor(fReadFrame
);
1474 uint32 pos
= readFrameLo
* channels
;
1475 delayedFrame
.channel
[0] = ((float*)m_pDelayBuffer
->data())[pos
];
1477 delayedFrame
.channel
[1] = ((float*)m_pDelayBuffer
->data())[pos
+1];
1479 if(readFrameLo
!= (int32
)fReadFrame
) {
1482 uint32 readFrameHi
= (int32
)ceil(fReadFrame
);
1483 delayedFrame
.channel
[0] *= ((float)readFrameHi
- fReadFrame
);
1485 delayedFrame
.channel
[1] *= ((float)readFrameHi
- fReadFrame
);
1487 // read high-index frame
1488 int32 hiWrap
= (readFrameHi
== m_pDelayBuffer
->frames()) ? 0 : readFrameHi
;
1489 ASSERT(hiWrap
>= 0);
1490 pos
= (uint32
)hiWrap
* channels
;
1492 hiFrame
.channel
[0] = ((float*)m_pDelayBuffer
->data())[pos
];
1494 hiFrame
.channel
[1] = ((float*)m_pDelayBuffer
->data())[pos
+1];
1497 delayedFrame
.channel
[0] +=
1498 hiFrame
.channel
[0] * (fReadFrame
- (float)readFrameLo
);
1500 delayedFrame
.channel
[1] +=
1501 hiFrame
.channel
[1] * (fReadFrame
- (float)readFrameLo
);
1504 // mix back to output buffer
1505 ((float*)input
.data())[inPos
] =
1506 (inFrame
.channel
[0] * (1.0-m_fMixRatio
)) +
1507 (delayedFrame
.channel
[0] * m_fMixRatio
);
1509 ((float*)input
.data())[inPos
+1] =
1510 (inFrame
.channel
[1] * (1.0-m_fMixRatio
)) +
1511 (delayedFrame
.channel
[1] * m_fMixRatio
);
1513 // write to delay buffer
1514 uint32 delayWritePos
= m_delayWriteFrame
* channels
;
1515 ((float*)m_pDelayBuffer
->data())[delayWritePos
] =
1516 inFrame
.channel
[0] +
1517 (delayedFrame
.channel
[0] * m_fFeedback
);
1519 ((float*)m_pDelayBuffer
->data())[delayWritePos
+1] =
1520 inFrame
.channel
[1] +
1521 (delayedFrame
.channel
[1] * m_fFeedback
);
1523 // advance write position
1524 if(++m_delayWriteFrame
>= m_pDelayBuffer
->frames())
1525 m_delayWriteFrame
= 0;
1527 // advance read offset ('LFO')
1528 m_fTheta
+= m_fThetaInc
;
1529 if(m_fTheta
> 2 * M_PI
)
1530 m_fTheta
-= 2 * M_PI
;
1532 // if(m_fDelayReadDelta < 0.0) {
1533 // if(m_fDelayReadOffset < m_fDelay)
1534 // m_fDelayReadDelta = -m_fDelayReadDelta;
1536 // if(m_fDelayReadOffset > m_fDepth)
1537 // m_fDelayReadDelta = -m_fDelayReadDelta;
1539 // m_fDelayReadOffset += m_fDelayReadDelta;
1544 /*! Figure the rate at which the (radial) read offset changes,
1545 based on the given sweep rate (in Hz)
1548 calc_sweep_delta(const media_raw_audio_format
& format
, float fRate
)
1550 return 2 * M_PI
* fRate
/ format
.frame_rate
;
1553 /*! Figure the base delay (in frames) based on the given
1554 sweep delay/depth (in msec)
1557 calc_sweep_base(const media_raw_audio_format
& format
, float delay
, float depth
)
1559 return (format
.frame_rate
* (delay
+ depth
)) / 1000.0;
1564 calc_sweep_factor(const media_raw_audio_format
& format
, float depth
)
1566 return (format
.frame_rate
* depth
) / 1000.0;
1570 // END -- FlangerNode.cpp --