* 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.
*/
#include "FlangerNode.h"
#include "AudioBuffer.h"
#include "SoundUtils.h"
#include <Buffer.h>
#include <BufferGroup.h>
#include <ByteOrder.h>
#include <Catalog.h>
#include <Debug.h>
#include <ParameterWeb.h>
#include <TimeSource.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "CortexAddOnsFlanger"
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);
enum input_id_t {
ID_AUDIO_INPUT
};
enum output_id_t {
ID_AUDIO_MIX_OUTPUT
};
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";
FlangerNode::~FlangerNode() {
Quit();
if(m_pDelayBuffer)
delete m_pDelayBuffer;
}
FlangerNode::FlangerNode(BMediaAddOn* pAddOn) :
BMediaNode(s_nodeName),
BBufferConsumer(B_MEDIA_RAW_AUDIO),
BBufferProducer(B_MEDIA_RAW_AUDIO),
BControllable(),
BMediaEventLooper(),
m_outputEnabled(true),
m_downstreamLatency(0),
m_processingLatency(0),
m_pDelayBuffer(0),
m_pAddOn(pAddOn) {
}
status_t FlangerNode::HandleMessage(
int32 code,
const void* pData,
size_t size) {
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 B_OK;
}
BMediaAddOn* FlangerNode::AddOn(
int32* poID) const {
if(m_pAddOn)
*poID = 0;
return m_pAddOn;
}
void FlangerNode::SetRunMode(
run_mode mode) {
if(mode == B_OFFLINE)
ReportError(B_NODE_FAILED_SET_RUN_MODE);
BMediaEventLooper::SetRunMode(mode);
}
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;
}
}
void FlangerNode::NodeRegistered() {
PRINT(("FlangerNode::NodeRegistered()\n"));
m_preferredFormat.type = B_MEDIA_RAW_AUDIO;
getPreferredFormat(m_preferredFormat);
m_format.type = B_MEDIA_RAW_AUDIO;
m_format.u.raw_audio = media_raw_audio_format::wildcard;
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);
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);
initParameterValues();
initParameterWeb();
SetPriority(B_REAL_TIME_PRIORITY);
Run();
}
bigtime_t FlangerNode::OfflineTime() {
return 0LL;
}
status_t FlangerNode::AcceptFormat(
const media_destination& destination,
media_format* pioFormat) {
PRINT(("FlangerNode::AcceptFormat()\n"));
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;
}
void FlangerNode::BufferReceived(
BBuffer* pBuffer) {
ASSERT(pBuffer);
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"));
}
if(m_output.destination == media_destination::null ||
!m_outputEnabled) {
pBuffer->Recycle();
return;
}
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();
}
}
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));
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;
}
m_input.source = source;
m_input.format = format;
*poInput = m_input;
m_format = format;
return B_OK;
}
void FlangerNode::Disconnected(
const media_source& source,
const media_destination& destination) {
PRINT(("FlangerNode::Disconnected()\n"));
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;
}
m_input.source = media_source::null;
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) {}
status_t FlangerNode::FormatChanged(
const media_source& source,
const media_destination& destination,
int32 changeTag,
const media_format& newFormat) {
return B_MEDIA_BAD_FORMAT;
}
status_t FlangerNode::GetLatencyFor(
const media_destination& destination,
bigtime_t* poLatency,
media_node_id* poTimeSource) {
PRINT(("FlangerNode::GetLatencyFor()\n"));
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"));
if(destination != m_input.destination) {
PRINT(("\tbad destination\n"));
}
if(m_output.destination != media_destination::null) {
status_t err = SendDataStatus(
status,
m_output.destination,
tpWhen);
if(err < B_OK) {
PRINT(("\tSendDataStatus(): %s\n", strerror(err)));
}
}
}
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;
}
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;
if(status < B_OK) {
PRINT(("\tStatus: %s\n", strerror(status)));
m_output.destination = media_destination::null;
return;
}
strncpy(pioName, m_output.name, B_MEDIA_NAME_LENGTH);
m_output.destination = destination;
m_format = format;
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));
initFilter();
m_processingLatency = calcProcessingLatency();
PRINT(("\tprocessing latency = %" B_PRIdBIGTIME "\n", m_processingLatency));
SetEventLatency(m_downstreamLatency + m_processingLatency);
if(m_input.source != media_source::null) {
err = SendLatencyChange(
m_input.source,
m_input.destination,
EventLatency() + SchedulingLatency());
if(err < B_OK)
PRINT(("\t!!! SendLatencyChange(): %s\n", strerror(err)));
}
SetBufferDuration(
buffer_duration(
m_format.u.raw_audio));
}
void FlangerNode::Disconnect(
const media_source& source,
const media_destination& destination) {
PRINT(("FlangerNode::Disconnect()\n"));
if(source != m_output.source) {
PRINT(("\tbad source\n"));
return;
}
if(destination != m_output.destination) {
PRINT(("\tbad destination\n"));
return;
}
m_output.destination = media_destination::null;
if(m_input.source == media_source::null) {
m_format.u.raw_audio = media_raw_audio_format::wildcard;
}
m_output.format = m_format;
}
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_) {
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;
}
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) {
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;
}
NotifyLateProducer(
m_input.source,
howLate,
tpWhen);
}
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;
}
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) {
return err;
}
specializeOutputFormat(*pioFormat);
m_output.destination = destination;
m_output.format = *pioFormat;
*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;
}
int32 changeTag;
return SetOutputBuffersFor(
m_input.source,
m_input.destination,
pGroup,
0, &changeTag);
}
status_t FlangerNode::SetPlayRate(
int32 numerator,
int32 denominator) {
return B_ERROR;
}
status_t FlangerNode::VideoClippingChanged(
const media_source& source,
int16 numShorts,
int16* pClipData,
const media_video_display_info& display,
int32* poFromChangeTag) {
return B_ERROR;
}
status_t FlangerNode::GetParameterValue(
int32 id,
bigtime_t* poLastChangeTime,
void* poValue,
size_t* pioSize) {
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;
media_timed_event ev(
changeTime,
BTimedEventQueue::B_PARAMETER,
0,
BTimedEventQueue::B_NO_CLEANUP,
size,
id,
(char*)pValue, size);
EventQueue()->AddEvent(ev);
break;
}
}
}
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;
m_fMixRatio = value;
m_tpMixRatioChanged = now;
BroadcastNewParameterValue(
now,
id,
&m_fMixRatio,
size);
break;
case P_SWEEP_RATE:
if(value == m_fSweepRate)
break;
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);
}
BroadcastNewParameterValue(
now,
id,
&m_fSweepRate,
size);
break;
case P_DELAY:
if(value == m_fDelay)
break;
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);
}
BroadcastNewParameterValue(
now,
id,
&m_fDelay,
size);
break;
case P_DEPTH:
if(value == m_fDepth)
break;
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);
}
BroadcastNewParameterValue(
now,
id,
&m_fDepth,
size);
break;
case P_FEEDBACK:
if(value == m_fFeedback)
break;
m_fFeedback = value;
m_tpFeedbackChanged = now;
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"));
}
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;
}
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) {
ioProposedFormat = preferredFormat;
return B_MEDIA_BAD_FORMAT;
}
const media_raw_audio_format& wild = media_raw_audio_format::wildcard;
media_raw_audio_format& f = ioProposedFormat.u.raw_audio;
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;
}
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);
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) {
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));
}
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;
}
void FlangerNode::initParameterWeb() {
BParameterWeb* pWeb = new BParameterWeb();
BParameterGroup* pTopGroup = pWeb->MakeGroup(
B_TRANSLATE("FlangerNode parameters"));
BNullParameter* label;
BContinuousParameter* value;
BParameterGroup* g;
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);
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);
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);
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);
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);
SetParameterWeb(pWeb);
}
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);
}
void FlangerNode::startFilter() {
PRINT(("FlangerNode::startFilter()\n"));
}
void FlangerNode::stopFilter() {
PRINT(("FlangerNode::stopFilter()\n"));
}
bigtime_t FlangerNode::calcProcessingLatency() {
PRINT(("FlangerNode::calcProcessingLatency()\n"));
if(m_output.destination == media_destination::null) {
PRINT(("\tNot connected.\n"));
return 0LL;
}
BBufferGroup* pTestGroup = new BBufferGroup(
m_output.format.u.raw_audio.buffer_size, 1);
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;
bigtime_t preTest = system_time();
filterBuffer(pBuffer);
bigtime_t elapsed = system_time()-preTest;
pBuffer->Recycle();
delete pTestGroup;
initFilter();
return elapsed;
}
const size_t MAX_CHANNELS = 2;
struct _frame {
float channel[MAX_CHANNELS];
};
void FlangerNode::filterBuffer(
BBuffer* pBuffer) {
if(!m_pDelayBuffer)
return;
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) {
_frame inFrame = {};
inFrame.channel[0] = ((float*)input.data())[inPos];
if(stereo)
inFrame.channel[1] = ((float*)input.data())[inPos + 1];
float readOffset = m_fSweepBase + (m_fSweepFactor * sin(m_fTheta));
float fReadFrame = (float)m_delayWriteFrame - readOffset;
if(fReadFrame < 0.0)
fReadFrame += m_pDelayBuffer->frames();
_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) {
uint32 readFrameHi = (int32)ceil(fReadFrame);
delayedFrame.channel[0] *= ((float)readFrameHi - fReadFrame);
if(stereo)
delayedFrame.channel[1] *= ((float)readFrameHi - fReadFrame);
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];
delayedFrame.channel[0] +=
hiFrame.channel[0] * (fReadFrame - (float)readFrameLo);
if(stereo)
delayedFrame.channel[1] +=
hiFrame.channel[1] * (fReadFrame - (float)readFrameLo);
}
((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);
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);
if(++m_delayWriteFrame >= m_pDelayBuffer->frames())
m_delayWriteFrame = 0;
m_fTheta += m_fThetaInc;
if(m_fTheta > 2 * M_PI)
m_fTheta -= 2 * M_PI;
}
}
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;
}
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;
}