/* * Copyright (c) 1999-2000, Eric Moon. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions, and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // FlangerNode.cpp // e.moon 16jun99 #include "FlangerNode.h" #include "AudioBuffer.h" #include "SoundUtils.h" #include #include #include #include #include #include #include #include #include #include #include #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "CortexAddOnsFlanger" // -------------------------------------------------------- // // local helpers // -------------------------------------------------------- // float calc_sweep_delta( const media_raw_audio_format& format, float fRate); float calc_sweep_base( const media_raw_audio_format& format, float fDelay, float fDepth); float calc_sweep_factor( const media_raw_audio_format& format, float fDepth); // -------------------------------------------------------- // // constants // -------------------------------------------------------- // // input-ID symbols enum input_id_t { ID_AUDIO_INPUT }; // output-ID symbols enum output_id_t { ID_AUDIO_MIX_OUTPUT //ID_AUDIO_WET_OUTPUT ... }; // parameter ID enum param_id_t { P_MIX_RATIO_LABEL = 100, P_MIX_RATIO, P_SWEEP_RATE_LABEL = 200, P_SWEEP_RATE, P_DELAY_LABEL = 300, P_DELAY, P_DEPTH_LABEL = 400, P_DEPTH, P_FEEDBACK_LABEL = 500, P_FEEDBACK }; const float FlangerNode::s_fMaxDelay = 100.0; const char* const FlangerNode::s_nodeName = "FlangerNode"; // -------------------------------------------------------- // // ctor/dtor // -------------------------------------------------------- // FlangerNode::~FlangerNode() { // shut down Quit(); // free delay buffer if(m_pDelayBuffer) delete m_pDelayBuffer; } FlangerNode::FlangerNode(BMediaAddOn* pAddOn) : // * init base classes BMediaNode(s_nodeName), // (virtual base) BBufferConsumer(B_MEDIA_RAW_AUDIO), BBufferProducer(B_MEDIA_RAW_AUDIO), BControllable(), BMediaEventLooper(), // * init connection state m_outputEnabled(true), m_downstreamLatency(0), m_processingLatency(0), // * init filter state m_pDelayBuffer(0), // * init add-on stuff m_pAddOn(pAddOn) { // PRINT(( // "\n" // "--*-- FlangerNode() [%s] --*--\n\n", // __BUILD_DATE)); // // the rest of the initialization happens in NodeRegistered(). } // -------------------------------------------------------- // // *** BMediaNode // -------------------------------------------------------- // status_t FlangerNode::HandleMessage( int32 code, const void* pData, size_t size) { // pass off to each base class if( BBufferConsumer::HandleMessage(code, pData, size) && BBufferProducer::HandleMessage(code, pData, size) && BControllable::HandleMessage(code, pData, size) && BMediaNode::HandleMessage(code, pData, size)) BMediaNode::HandleBadMessage(code, pData, size); // +++++ return error on bad message? return B_OK; } BMediaAddOn* FlangerNode::AddOn( int32* poID) const { if(m_pAddOn) *poID = 0; return m_pAddOn; } void FlangerNode::SetRunMode( run_mode mode) { // disallow offline mode for now // +++++ if(mode == B_OFFLINE) ReportError(B_NODE_FAILED_SET_RUN_MODE); // +++++ any other work to do? // hand off BMediaEventLooper::SetRunMode(mode); } // -------------------------------------------------------- // // *** BMediaEventLooper // -------------------------------------------------------- // void FlangerNode::HandleEvent( const media_timed_event* pEvent, bigtime_t howLate, bool realTimeEvent) { ASSERT(pEvent); switch(pEvent->type) { case BTimedEventQueue::B_PARAMETER: handleParameterEvent(pEvent); break; case BTimedEventQueue::B_START: handleStartEvent(pEvent); break; case BTimedEventQueue::B_STOP: handleStopEvent(pEvent); break; default: ignoreEvent(pEvent); break; } } // "The Media Server calls this hook function after the node has // been registered. This is derived from BMediaNode; BMediaEventLooper // implements it to call Run() automatically when the node is registered; // if you implement NodeRegistered() you should call through to // BMediaEventLooper::NodeRegistered() after you've done your custom // operations." void FlangerNode::NodeRegistered() { PRINT(("FlangerNode::NodeRegistered()\n")); // figure preferred ('template') format m_preferredFormat.type = B_MEDIA_RAW_AUDIO; getPreferredFormat(m_preferredFormat); // initialize current format m_format.type = B_MEDIA_RAW_AUDIO; m_format.u.raw_audio = media_raw_audio_format::wildcard; // init input m_input.destination.port = ControlPort(); m_input.destination.id = ID_AUDIO_INPUT; m_input.node = Node(); m_input.source = media_source::null; m_input.format = m_format; strlcpy(m_input.name, B_TRANSLATE("Audio input"), B_MEDIA_NAME_LENGTH); // init output m_output.source.port = ControlPort(); m_output.source.id = ID_AUDIO_MIX_OUTPUT; m_output.node = Node(); m_output.destination = media_destination::null; m_output.format = m_format; strlcpy(m_output.name, B_TRANSLATE("Mix output"), B_MEDIA_NAME_LENGTH); // init parameters initParameterValues(); initParameterWeb(); // Start the BMediaEventLooper thread SetPriority(B_REAL_TIME_PRIORITY); Run(); } // "Augment OfflineTime() to compute the node's current time; it's called // by the Media Kit when it's in offline mode. Update any appropriate // internal information as well, then call through to the BMediaEventLooper // implementation." bigtime_t FlangerNode::OfflineTime() { // +++++ return 0LL; } // -------------------------------------------------------- // // *** BBufferConsumer // -------------------------------------------------------- // status_t FlangerNode::AcceptFormat( const media_destination& destination, media_format* pioFormat) { PRINT(("FlangerNode::AcceptFormat()\n")); // sanity checks if(destination != m_input.destination) { PRINT(("\tbad destination\n")); return B_MEDIA_BAD_DESTINATION; } if(pioFormat->type != B_MEDIA_RAW_AUDIO) { PRINT(("\tnot B_MEDIA_RAW_AUDIO\n")); return B_MEDIA_BAD_FORMAT; } validateProposedFormat( (m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ? m_format : m_preferredFormat, *pioFormat); return B_OK; } // "If you're writing a node, and receive a buffer with the B_SMALL_BUFFER // flag set, you must recycle the buffer before returning." void FlangerNode::BufferReceived( BBuffer* pBuffer) { ASSERT(pBuffer); // check buffer destination if(pBuffer->Header()->destination != m_input.destination.id) { PRINT(("FlangerNode::BufferReceived():\n" "\tBad destination.\n")); pBuffer->Recycle(); return; } if(pBuffer->Header()->time_source != TimeSource()->ID()) { PRINT(("* timesource mismatch\n")); } // check output if(m_output.destination == media_destination::null || !m_outputEnabled) { pBuffer->Recycle(); return; } // process and retransmit buffer filterBuffer(pBuffer); status_t err = SendBuffer(pBuffer, m_output.source, m_output.destination); if (err < B_OK) { PRINT(("FlangerNode::BufferReceived():\n" "\tSendBuffer() failed: %s\n", strerror(err))); pBuffer->Recycle(); } // sent! } // * make sure to fill in poInput->format with the contents of // pFormat; as of R4.5 the Media Kit passes poInput->format to // the producer in BBufferProducer::Connect(). status_t FlangerNode::Connected(const media_source& source, const media_destination& destination, const media_format& format, media_input* poInput) { PRINT(("FlangerNode::Connected()\n" "\tto source %" B_PRId32 "\n", source.id)); // sanity check if(destination != m_input.destination) { PRINT(("\tbad destination\n")); return B_MEDIA_BAD_DESTINATION; } if(m_input.source != media_source::null) { PRINT(("\talready connected\n")); return B_MEDIA_ALREADY_CONNECTED; } // initialize input m_input.source = source; m_input.format = format; *poInput = m_input; // store format (this now constrains the output format) m_format = format; return B_OK; } void FlangerNode::Disconnected( const media_source& source, const media_destination& destination) { PRINT(("FlangerNode::Disconnected()\n")); // sanity checks if(m_input.source != source) { PRINT(("\tsource mismatch: expected ID %" B_PRId32 ", got %" B_PRId32 "\n", m_input.source.id, source.id)); return; } if(destination != m_input.destination) { PRINT(("\tdestination mismatch: expected ID %" B_PRId32 ", got %" B_PRId32 "\n", m_input.destination.id, destination.id)); return; } // mark disconnected m_input.source = media_source::null; // no output? clear format: if(m_output.destination == media_destination::null) { m_format.u.raw_audio = media_raw_audio_format::wildcard; } m_input.format = m_format; } void FlangerNode::DisposeInputCookie( int32 cookie) {} // "You should implement this function so your node will know that the data // format is going to change. Note that this may be called in response to // your AcceptFormat() call, if your AcceptFormat() call alters any wildcard // fields in the specified format. // // Because FormatChanged() is called by the producer, you don't need to (and // shouldn't) ask it if the new format is acceptable. // // If the format change isn't possible, return an appropriate error from // FormatChanged(); this error will be passed back to the producer that // initiated the new format negotiation in the first place." status_t FlangerNode::FormatChanged( const media_source& source, const media_destination& destination, int32 changeTag, const media_format& newFormat) { // flat-out deny format changes return B_MEDIA_BAD_FORMAT; } status_t FlangerNode::GetLatencyFor( const media_destination& destination, bigtime_t* poLatency, media_node_id* poTimeSource) { PRINT(("FlangerNode::GetLatencyFor()\n")); // sanity check if(destination != m_input.destination) { PRINT(("\tbad destination\n")); return B_MEDIA_BAD_DESTINATION; } *poLatency = m_downstreamLatency + m_processingLatency; PRINT(("\treturning %" B_PRIdBIGTIME "\n", *poLatency)); *poTimeSource = TimeSource()->ID(); return B_OK; } status_t FlangerNode::GetNextInput( int32* pioCookie, media_input* poInput) { if(*pioCookie) return B_BAD_INDEX; ++*pioCookie; *poInput = m_input; return B_OK; } void FlangerNode::ProducerDataStatus( const media_destination& destination, int32 status, bigtime_t tpWhen) { PRINT(("FlangerNode::ProducerDataStatus()\n")); // sanity check if(destination != m_input.destination) { PRINT(("\tbad destination\n")); } if(m_output.destination != media_destination::null) { // pass status downstream status_t err = SendDataStatus( status, m_output.destination, tpWhen); if(err < B_OK) { PRINT(("\tSendDataStatus(): %s\n", strerror(err))); } } } // "This function is provided to aid in supporting media formats in which the // outer encapsulation layer doesn't supply timing information. Producers will // tag the buffers they generate with seek tags; these tags can be used to // locate key frames in the media data." status_t FlangerNode::SeekTagRequested( const media_destination& destination, bigtime_t targetTime, uint32 flags, media_seek_tag* poSeekTag, bigtime_t* poTaggedTime, uint32* poFlags) { PRINT(("FlangerNode::SeekTagRequested()\n" "\tNot implemented.\n")); return B_ERROR; } // -------------------------------------------------------- // // *** BBufferProducer // -------------------------------------------------------- // // "When a consumer calls BBufferConsumer::RequestAdditionalBuffer(), this // function is called as a result. Its job is to call SendBuffer() to // immediately send the next buffer to the consumer. The previousBufferID, // previousTime, and previousTag arguments identify the last buffer the // consumer received. Your node should respond by sending the next buffer // after the one described. // // The previousTag may be NULL. // Return B_OK if all is well; otherwise return an appropriate error code." void FlangerNode::AdditionalBufferRequested( const media_source& source, media_buffer_id previousBufferID, bigtime_t previousTime, const media_seek_tag* pPreviousTag) { PRINT(("FlangerNode::AdditionalBufferRequested\n" "\tOffline mode not implemented.")); } void FlangerNode::Connect( status_t status, const media_source& source, const media_destination& destination, const media_format& format, char* pioName) { PRINT(("FlangerNode::Connect()\n")); status_t err; // connection failed? if(status < B_OK) { PRINT(("\tStatus: %s\n", strerror(status))); // 'unreserve' the output m_output.destination = media_destination::null; return; } // connection established: strncpy(pioName, m_output.name, B_MEDIA_NAME_LENGTH); m_output.destination = destination; m_format = format; // figure downstream latency media_node_id timeSource; err = FindLatencyFor(m_output.destination, &m_downstreamLatency, &timeSource); if(err < B_OK) { PRINT(("\t!!! FindLatencyFor(): %s\n", strerror(err))); } PRINT(("\tdownstream latency = %" B_PRIdBIGTIME "\n", m_downstreamLatency)); // prepare the filter initFilter(); // figure processing time m_processingLatency = calcProcessingLatency(); PRINT(("\tprocessing latency = %" B_PRIdBIGTIME "\n", m_processingLatency)); // store summed latency SetEventLatency(m_downstreamLatency + m_processingLatency); if(m_input.source != media_source::null) { // pass new latency upstream err = SendLatencyChange( m_input.source, m_input.destination, EventLatency() + SchedulingLatency()); if(err < B_OK) PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err))); } // cache buffer duration SetBufferDuration( buffer_duration( m_format.u.raw_audio)); } void FlangerNode::Disconnect( const media_source& source, const media_destination& destination) { PRINT(("FlangerNode::Disconnect()\n")); // sanity checks if(source != m_output.source) { PRINT(("\tbad source\n")); return; } if(destination != m_output.destination) { PRINT(("\tbad destination\n")); return; } // clean up m_output.destination = media_destination::null; // no input? clear format: if(m_input.source == media_source::null) { m_format.u.raw_audio = media_raw_audio_format::wildcard; } m_output.format = m_format; // +++++ other cleanup goes here } status_t FlangerNode::DisposeOutputCookie( int32 cookie) { return B_OK; } void FlangerNode::EnableOutput( const media_source& source, bool enabled, int32* _deprecated_) { PRINT(("FlangerNode::EnableOutput()\n")); if(source != m_output.source) { PRINT(("\tbad source\n")); return; } m_outputEnabled = enabled; } status_t FlangerNode::FormatChangeRequested( const media_source& source, const media_destination& destination, media_format* pioFormat, int32* _deprecated_) { // deny PRINT(("FlangerNode::FormatChangeRequested()\n" "\tNot supported.\n")); return B_MEDIA_BAD_FORMAT; } status_t FlangerNode::FormatProposal( const media_source& source, media_format* pioFormat) { PRINT(("FlangerNode::FormatProposal()\n")); if(source != m_output.source) { PRINT(("\tbad source\n")); return B_MEDIA_BAD_SOURCE; } if(pioFormat->type != B_MEDIA_RAW_AUDIO) { PRINT(("\tbad type\n")); return B_MEDIA_BAD_FORMAT; } validateProposedFormat( (m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ? m_format : m_preferredFormat, *pioFormat); return B_OK; } status_t FlangerNode::FormatSuggestionRequested( media_type type, int32 quality, media_format* poFormat) { PRINT(("FlangerNode::FormatSuggestionRequested()\n")); if(type != B_MEDIA_RAW_AUDIO) { PRINT(("\tbad type\n")); return B_MEDIA_BAD_FORMAT; } if(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) *poFormat = m_format; else *poFormat = m_preferredFormat; return B_OK; } status_t FlangerNode::GetLatency( bigtime_t* poLatency) { PRINT(("FlangerNode::GetLatency()\n")); *poLatency = EventLatency() + SchedulingLatency(); PRINT(("\treturning %" B_PRIdBIGTIME "\n", *poLatency)); return B_OK; } status_t FlangerNode::GetNextOutput( int32* pioCookie, media_output* poOutput) { if(*pioCookie) return B_BAD_INDEX; ++*pioCookie; *poOutput = m_output; return B_OK; } // "This hook function is called when a BBufferConsumer that's receiving data // from you determines that its latency has changed. It will call its // BBufferConsumer::SendLatencyChange() function, and in response, the Media // Server will call your LatencyChanged() function. The source argument // indicates your output that's involved in the connection, and destination // specifies the input on the consumer to which the connection is linked. // newLatency is the consumer's new latency. The flags are currently unused." void FlangerNode::LatencyChanged( const media_source& source, const media_destination& destination, bigtime_t newLatency, uint32 flags) { PRINT(("FlangerNode::LatencyChanged()\n")); if(source != m_output.source) { PRINT(("\tBad source.\n")); return; } if(destination != m_output.destination) { PRINT(("\tBad destination.\n")); return; } m_downstreamLatency = newLatency; SetEventLatency(m_downstreamLatency + m_processingLatency); if(m_input.source != media_source::null) { // pass new latency upstream status_t err = SendLatencyChange( m_input.source, m_input.destination, EventLatency() + SchedulingLatency()); if(err < B_OK) PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err))); } } void FlangerNode::LateNoticeReceived( const media_source& source, bigtime_t howLate, bigtime_t tpWhen) { PRINT(("FlangerNode::LateNoticeReceived()\n" "\thowLate == %" B_PRIdBIGTIME "\n" "\twhen == %" B_PRIdBIGTIME "\n", howLate, tpWhen)); if(source != m_output.source) { PRINT(("\tBad source.\n")); return; } if(m_input.source == media_source::null) { PRINT(("\t!!! No input to blame.\n")); return; } // +++++ check run mode? // pass the buck, since this node doesn't schedule buffer // production NotifyLateProducer( m_input.source, howLate, tpWhen); } // PrepareToConnect() is the second stage of format negotiations that happens // inside BMediaRoster::Connect(). At this point, the consumer's AcceptFormat() // method has been called, and that node has potentially changed the proposed // format. It may also have left wildcards in the format. PrepareToConnect() // *must* fully specialize the format before returning! status_t FlangerNode::PrepareToConnect( const media_source& source, const media_destination& destination, media_format* pioFormat, media_source* poSource, char* poName) { char formatStr[256]; string_for_format(*pioFormat, formatStr, 255); PRINT(("FlangerNode::PrepareToConnect()\n" "\tproposed format: %s\n", formatStr)); if(source != m_output.source) { PRINT(("\tBad source.\n")); return B_MEDIA_BAD_SOURCE; } if(m_output.destination != media_destination::null) { PRINT(("\tAlready connected.\n")); return B_MEDIA_ALREADY_CONNECTED; } if(pioFormat->type != B_MEDIA_RAW_AUDIO) { PRINT(("\tBad format type.\n")); return B_MEDIA_BAD_FORMAT; } // do a final validity check: status_t err = validateProposedFormat( (m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format) ? m_format : m_preferredFormat, *pioFormat); if(err < B_OK) { // no go return err; } // fill in wildcards specializeOutputFormat(*pioFormat); // reserve the output m_output.destination = destination; m_output.format = *pioFormat; // pass back source & output name *poSource = m_output.source; strncpy(poName, m_output.name, B_MEDIA_NAME_LENGTH); return B_OK; } status_t FlangerNode::SetBufferGroup( const media_source& source, BBufferGroup* pGroup) { PRINT(("FlangerNode::SetBufferGroup()\n")); if(source != m_output.source) { PRINT(("\tBad source.\n")); return B_MEDIA_BAD_SOURCE; } if(m_input.source == media_source::null) { PRINT(("\tNo producer to send buffers to.\n")); return B_ERROR; } // +++++ is this right? buffer-group selection gets // all asynchronous and weird... int32 changeTag; return SetOutputBuffersFor( m_input.source, m_input.destination, pGroup, 0, &changeTag); } status_t FlangerNode::SetPlayRate( int32 numerator, int32 denominator) { // not supported return B_ERROR; } status_t FlangerNode::VideoClippingChanged( const media_source& source, int16 numShorts, int16* pClipData, const media_video_display_info& display, int32* poFromChangeTag) { // not sane return B_ERROR; } // -------------------------------------------------------- // // *** BControllable // -------------------------------------------------------- // status_t FlangerNode::GetParameterValue( int32 id, bigtime_t* poLastChangeTime, void* poValue, size_t* pioSize) { // PRINT(("FlangerNode::GetParameterValue()\n")); // all parameters are floats if(*pioSize < sizeof(float)) { return B_NO_MEMORY; } *pioSize = sizeof(float); switch(id) { case P_MIX_RATIO: *(float*)poValue = m_fMixRatio; *poLastChangeTime = m_tpMixRatioChanged; break; case P_SWEEP_RATE: *(float*)poValue = m_fSweepRate; *poLastChangeTime = m_tpSweepRateChanged; break; case P_DELAY: *(float*)poValue = m_fDelay; *poLastChangeTime = m_tpDelayChanged; break; case P_DEPTH: *(float*)poValue = m_fDepth; *poLastChangeTime = m_tpDepthChanged; break; case P_FEEDBACK: *(float*)poValue = m_fFeedback; *poLastChangeTime = m_tpFeedbackChanged; break; default: return B_ERROR; } return B_OK; } void FlangerNode::SetParameterValue( int32 id, bigtime_t changeTime, const void* pValue, size_t size) { switch(id) { case P_MIX_RATIO: case P_SWEEP_RATE: case P_DELAY: case P_DEPTH: case P_FEEDBACK: { if(size < sizeof(float)) break; // this is from ToneProducer. it's fishy. // if(size > sizeof(float)) // size = sizeof(float); media_timed_event ev( changeTime, BTimedEventQueue::B_PARAMETER, 0, BTimedEventQueue::B_NO_CLEANUP, size, id, (char*)pValue, size); EventQueue()->AddEvent(ev); break; } } } // -------------------------------------------------------- // // *** HandleEvent() impl // -------------------------------------------------------- // void FlangerNode::handleParameterEvent( const media_timed_event* pEvent) { float value = *(float*)pEvent->user_data; int32 id = pEvent->bigdata; size_t size = pEvent->data; bigtime_t now = TimeSource()->Now(); switch(id) { case P_MIX_RATIO: if(value == m_fMixRatio) break; // set m_fMixRatio = value; m_tpMixRatioChanged = now; // broadcast BroadcastNewParameterValue( now, id, &m_fMixRatio, size); break; case P_SWEEP_RATE: if(value == m_fSweepRate) break; // set m_fSweepRate = value; m_tpSweepRateChanged = now; if(m_output.destination != media_destination::null) { m_fThetaInc = calc_sweep_delta( m_format.u.raw_audio, m_fSweepRate); } // broadcast BroadcastNewParameterValue( now, id, &m_fSweepRate, size); break; case P_DELAY: if(value == m_fDelay) break; // set m_fDelay = value; m_tpDelayChanged = now; if(m_output.destination != media_destination::null) { m_fSweepBase = calc_sweep_base( m_format.u.raw_audio, m_fDelay, m_fDepth); } // broadcast BroadcastNewParameterValue( now, id, &m_fDelay, size); break; case P_DEPTH: if(value == m_fDepth) break; // set m_fDepth = value; m_tpDepthChanged = now; if(m_output.destination != media_destination::null) { m_fSweepBase = calc_sweep_base( m_format.u.raw_audio, m_fDelay, m_fDepth); m_fSweepFactor = calc_sweep_factor( m_format.u.raw_audio, m_fDepth); } // broadcast BroadcastNewParameterValue( now, id, &m_fDepth, size); break; case P_FEEDBACK: if(value == m_fFeedback) break; // set m_fFeedback = value; m_tpFeedbackChanged = now; // broadcast BroadcastNewParameterValue( now, id, &m_fFeedback, size); break; } } void FlangerNode::handleStartEvent( const media_timed_event* pEvent) { PRINT(("FlangerNode::handleStartEvent\n")); startFilter(); } void FlangerNode::handleStopEvent( const media_timed_event* pEvent) { PRINT(("FlangerNode::handleStopEvent\n")); stopFilter(); } void FlangerNode::ignoreEvent( const media_timed_event* pEvent) { PRINT(("FlangerNode::ignoreEvent\n")); } // -------------------------------------------------------- // // *** internal operations // -------------------------------------------------------- // // figure the preferred format: any fields left as wildcards // are negotiable void FlangerNode::getPreferredFormat( media_format& ioFormat) { ASSERT(ioFormat.type == B_MEDIA_RAW_AUDIO); ioFormat.u.raw_audio = media_raw_audio_format::wildcard; ioFormat.u.raw_audio.channel_count = 1; ioFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT; // ioFormat.u.raw_audio.frame_rate = 44100.0; // ioFormat.u.raw_audio.buffer_size = 0x1000; } // test the given template format against a proposed format. // specialize wildcards for fields where the template contains // non-wildcard data; write required fields into proposed format // if they mismatch. // Returns B_OK if the proposed format doesn't conflict with the // template, or B_MEDIA_BAD_FORMAT otherwise. status_t FlangerNode::validateProposedFormat( const media_format& preferredFormat, media_format& ioProposedFormat) { char formatStr[256]; PRINT(("FlangerNode::validateProposedFormat()\n")); ASSERT(preferredFormat.type == B_MEDIA_RAW_AUDIO); string_for_format(preferredFormat, formatStr, 255); PRINT(("\ttemplate format: %s\n", formatStr)); string_for_format(ioProposedFormat, formatStr, 255); PRINT(("\tproposed format: %s\n", formatStr)); status_t err = B_OK; if(ioProposedFormat.type != B_MEDIA_RAW_AUDIO) { // out of the ballpark ioProposedFormat = preferredFormat; return B_MEDIA_BAD_FORMAT; } // wildcard format const media_raw_audio_format& wild = media_raw_audio_format::wildcard; // proposed format media_raw_audio_format& f = ioProposedFormat.u.raw_audio; // template format const media_raw_audio_format& pref = preferredFormat.u.raw_audio; if(pref.frame_rate != wild.frame_rate) { if(f.frame_rate != pref.frame_rate) { if(f.frame_rate != wild.frame_rate) err = B_MEDIA_BAD_FORMAT; f.frame_rate = pref.frame_rate; } } if(pref.channel_count != wild.channel_count) { if(f.channel_count != pref.channel_count) { if(f.channel_count != wild.channel_count) err = B_MEDIA_BAD_FORMAT; f.channel_count = pref.channel_count; } } if(pref.format != wild.format) { if(f.format != pref.format) { if(f.format != wild.format) err = B_MEDIA_BAD_FORMAT; f.format = pref.format; } } if(pref.byte_order != wild.byte_order) { if(f.byte_order != pref.byte_order) { if(f.byte_order != wild.byte_order) err = B_MEDIA_BAD_FORMAT; f.byte_order = pref.byte_order; } } if(pref.buffer_size != wild.buffer_size) { if(f.buffer_size != pref.buffer_size) { if(f.buffer_size != wild.buffer_size) err = B_MEDIA_BAD_FORMAT; f.buffer_size = pref.buffer_size; } } if(err != B_OK) { string_for_format(ioProposedFormat, formatStr, 255); PRINT(( "\tformat conflict; suggesting:\n\tformat %s\n", formatStr)); } return err; } // fill in wildcards in the given format. // (assumes the format passes validateProposedFormat().) void FlangerNode::specializeOutputFormat( media_format& ioFormat) { char formatStr[256]; string_for_format(ioFormat, formatStr, 255); PRINT(("FlangerNode::specializeOutputFormat()\n" "\tinput format: %s\n", formatStr)); ASSERT(ioFormat.type == B_MEDIA_RAW_AUDIO); // carpal_tunnel_paranoia media_raw_audio_format& f = ioFormat.u.raw_audio; const media_raw_audio_format& w = media_raw_audio_format::wildcard; if (f.frame_rate == w.frame_rate) f.frame_rate = 44100.0; if (f.channel_count == w.channel_count) { //+++++ tweaked 15sep99 if (m_input.source != media_source::null) f.channel_count = m_input.format.u.raw_audio.channel_count; else f.channel_count = 1; } if (f.format == w.format) f.format = media_raw_audio_format::B_AUDIO_FLOAT; if (f.byte_order == w.byte_order) f.byte_order = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; if (f.buffer_size == w.buffer_size) f.buffer_size = 2048; string_for_format(ioFormat, formatStr, 255); PRINT(("\toutput format: %s\n", formatStr)); } // set parameters to their default settings void FlangerNode::initParameterValues() { m_fMixRatio = 0.5; m_tpMixRatioChanged = 0LL; m_fSweepRate = 0.1; m_tpSweepRateChanged = 0LL; m_fDelay = 10.0; m_tpDelayChanged = 0LL; m_fDepth = 25.0; m_tpDepthChanged = 0LL; m_fFeedback = 0.1; m_tpFeedbackChanged = 0LL; } // create and register a parameter web void FlangerNode::initParameterWeb() { BParameterWeb* pWeb = new BParameterWeb(); BParameterGroup* pTopGroup = pWeb->MakeGroup( B_TRANSLATE("FlangerNode parameters")); BNullParameter* label; BContinuousParameter* value; BParameterGroup* g; // mix ratio g = pTopGroup->MakeGroup(B_TRANSLATE("Mix ratio")); label = g->MakeNullParameter( P_MIX_RATIO_LABEL, B_MEDIA_NO_TYPE, B_TRANSLATE("Mix ratio"), B_GENERIC); value = g->MakeContinuousParameter( P_MIX_RATIO, B_MEDIA_NO_TYPE, "", B_GAIN, "", 0.0, 1.0, 0.05); label->AddOutput(value); value->AddInput(label); // sweep rate g = pTopGroup->MakeGroup(B_TRANSLATE("Sweep rate")); label = g->MakeNullParameter( P_SWEEP_RATE_LABEL, B_MEDIA_NO_TYPE, B_TRANSLATE("Sweep rate"), B_GENERIC); value = g->MakeContinuousParameter( P_SWEEP_RATE, B_MEDIA_NO_TYPE, "", B_GAIN, "Hz", 0.01, 10.0, 0.01); label->AddOutput(value); value->AddInput(label); // sweep range: minimum delay g = pTopGroup->MakeGroup(B_TRANSLATE("Delay")); label = g->MakeNullParameter( P_DELAY_LABEL, B_MEDIA_NO_TYPE, B_TRANSLATE("Delay"), B_GENERIC); value = g->MakeContinuousParameter( P_DELAY, B_MEDIA_NO_TYPE, "", B_GAIN, "ms", 0.1, s_fMaxDelay/2.0, 0.1); label->AddOutput(value); value->AddInput(label); // sweep range: maximum g = pTopGroup->MakeGroup(B_TRANSLATE("Depth")); label = g->MakeNullParameter( P_DEPTH_LABEL, B_MEDIA_NO_TYPE, B_TRANSLATE("Depth"), B_GENERIC); value = g->MakeContinuousParameter( P_DEPTH, B_MEDIA_NO_TYPE, "", B_GAIN, "ms", 1.0, s_fMaxDelay/4.0, 0.1); label->AddOutput(value); value->AddInput(label); // feedback g = pTopGroup->MakeGroup(B_TRANSLATE("Feedback")); label = g->MakeNullParameter( P_FEEDBACK_LABEL, B_MEDIA_NO_TYPE, B_TRANSLATE("Feedback"), B_GENERIC); value = g->MakeContinuousParameter( P_FEEDBACK, B_MEDIA_NO_TYPE, "", B_GAIN, "", 0.0, 1.0, 0.01); label->AddOutput(value); value->AddInput(label); // * Install parameter web SetParameterWeb(pWeb); } // construct delay line if necessary, reset filter state void FlangerNode::initFilter() { PRINT(("FlangerNode::initFilter()\n")); ASSERT(m_format.u.raw_audio.format != media_raw_audio_format::wildcard.format); if(!m_pDelayBuffer) { m_pDelayBuffer = new AudioBuffer( m_format.u.raw_audio, frames_for_duration( m_format.u.raw_audio, (bigtime_t)s_fMaxDelay*1000LL)); m_pDelayBuffer->zero(); } m_framesSent = 0; m_delayWriteFrame = 0; m_fTheta = 0.0; m_fThetaInc = calc_sweep_delta(m_format.u.raw_audio, m_fSweepRate); m_fSweepBase = calc_sweep_base(m_format.u.raw_audio, m_fDelay, m_fDepth); m_fSweepFactor = calc_sweep_factor(m_format.u.raw_audio, m_fDepth); // // PRINT(( // "\tFrames %ld\n" // "\tDelay %.2f\n" // "\tDepth %.2f\n" // "\tSweepBase %.2f\n" // "\tSweepFactor %.2f\n", // m_pDelayBuffer->frames(), // m_fDelay, m_fDepth, m_fSweepBase, m_fSweepFactor)); } void FlangerNode::startFilter() { PRINT(("FlangerNode::startFilter()\n")); } void FlangerNode::stopFilter() { PRINT(("FlangerNode::stopFilter()\n")); } // figure processing latency by doing 'dry runs' of filterBuffer() bigtime_t FlangerNode::calcProcessingLatency() { PRINT(("FlangerNode::calcProcessingLatency()\n")); if(m_output.destination == media_destination::null) { PRINT(("\tNot connected.\n")); return 0LL; } // allocate a temporary buffer group BBufferGroup* pTestGroup = new BBufferGroup( m_output.format.u.raw_audio.buffer_size, 1); // fetch a buffer BBuffer* pBuffer = pTestGroup->RequestBuffer( m_output.format.u.raw_audio.buffer_size); ASSERT(pBuffer); pBuffer->Header()->type = B_MEDIA_RAW_AUDIO; pBuffer->Header()->size_used = m_output.format.u.raw_audio.buffer_size; // run the test bigtime_t preTest = system_time(); filterBuffer(pBuffer); bigtime_t elapsed = system_time()-preTest; // clean up pBuffer->Recycle(); delete pTestGroup; // reset filter state initFilter(); return elapsed; } // filter buffer data in place // // +++++ add 2-channel support 15sep991 // const size_t MAX_CHANNELS = 2; struct _frame { float channel[MAX_CHANNELS]; }; void FlangerNode::filterBuffer( BBuffer* pBuffer) { if(!m_pDelayBuffer) return; // for each input frame: // - fetch // - write delay line(writeFrame) // - read delay line(writeFrame-readOffset) [interpolate] // - mix (replace) // - advance writeFrame // - update readOffset AudioBuffer input(m_format.u.raw_audio, pBuffer); ASSERT( m_format.u.raw_audio.channel_count == 1 || m_format.u.raw_audio.channel_count == 2); uint32 channels = m_format.u.raw_audio.channel_count; bool stereo = m_format.u.raw_audio.channel_count == 2; uint32 samples = input.frames() * channels; for(uint32 inPos = 0; inPos < samples; ++inPos) { // read from input buffer _frame inFrame = {}; inFrame.channel[0] = ((float*)input.data())[inPos]; if(stereo) inFrame.channel[1] = ((float*)input.data())[inPos + 1]; // interpolate from delay buffer float readOffset = m_fSweepBase + (m_fSweepFactor * sin(m_fTheta)); float fReadFrame = (float)m_delayWriteFrame - readOffset; if(fReadFrame < 0.0) fReadFrame += m_pDelayBuffer->frames(); // float delayed; // read low-index (possibly only) frame _frame delayedFrame = {}; int32 readFrameLo = (int32)floor(fReadFrame); uint32 pos = readFrameLo * channels; delayedFrame.channel[0] = ((float*)m_pDelayBuffer->data())[pos]; if(stereo) delayedFrame.channel[1] = ((float*)m_pDelayBuffer->data())[pos+1]; if(readFrameLo != (int32)fReadFrame) { // interpolate (A) uint32 readFrameHi = (int32)ceil(fReadFrame); delayedFrame.channel[0] *= ((float)readFrameHi - fReadFrame); if(stereo) delayedFrame.channel[1] *= ((float)readFrameHi - fReadFrame); // read high-index frame int32 hiWrap = (readFrameHi == m_pDelayBuffer->frames()) ? 0 : readFrameHi; ASSERT(hiWrap >= 0); pos = (uint32)hiWrap * channels; _frame hiFrame; hiFrame.channel[0] = ((float*)m_pDelayBuffer->data())[pos]; if(stereo) hiFrame.channel[1] = ((float*)m_pDelayBuffer->data())[pos+1]; // interpolate (B) delayedFrame.channel[0] += hiFrame.channel[0] * (fReadFrame - (float)readFrameLo); if(stereo) delayedFrame.channel[1] += hiFrame.channel[1] * (fReadFrame - (float)readFrameLo); } // mix back to output buffer ((float*)input.data())[inPos] = (inFrame.channel[0] * (1.0-m_fMixRatio)) + (delayedFrame.channel[0] * m_fMixRatio); if(stereo) ((float*)input.data())[inPos+1] = (inFrame.channel[1] * (1.0-m_fMixRatio)) + (delayedFrame.channel[1] * m_fMixRatio); // write to delay buffer uint32 delayWritePos = m_delayWriteFrame * channels; ((float*)m_pDelayBuffer->data())[delayWritePos] = inFrame.channel[0] + (delayedFrame.channel[0] * m_fFeedback); if(stereo) ((float*)m_pDelayBuffer->data())[delayWritePos+1] = inFrame.channel[1] + (delayedFrame.channel[1] * m_fFeedback); // advance write position if(++m_delayWriteFrame >= m_pDelayBuffer->frames()) m_delayWriteFrame = 0; // advance read offset ('LFO') m_fTheta += m_fThetaInc; if(m_fTheta > 2 * M_PI) m_fTheta -= 2 * M_PI; // if(m_fDelayReadDelta < 0.0) { // if(m_fDelayReadOffset < m_fDelay) // m_fDelayReadDelta = -m_fDelayReadDelta; // } else { // if(m_fDelayReadOffset > m_fDepth) // m_fDelayReadDelta = -m_fDelayReadDelta; // } // m_fDelayReadOffset += m_fDelayReadDelta; } } /*! Figure the rate at which the (radial) read offset changes, based on the given sweep rate (in Hz) */ float calc_sweep_delta(const media_raw_audio_format& format, float fRate) { return 2 * M_PI * fRate / format.frame_rate; } /*! Figure the base delay (in frames) based on the given sweep delay/depth (in msec) */ float calc_sweep_base(const media_raw_audio_format& format, float delay, float depth) { return (format.frame_rate * (delay + depth)) / 1000.0; } float calc_sweep_factor(const media_raw_audio_format& format, float depth) { return (format.frame_rate * depth) / 1000.0; } // END -- FlangerNode.cpp --