#include "ToneProducer.h"
#include <support/ByteOrder.h>
#include <media/BufferGroup.h>
#include <media/Buffer.h>
#include <media/TimeSource.h>
#include <media/ParameterWeb.h>
#include <media/MediaDefs.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <Messenger.h>
#include <Debug.h>
#if DEBUG
#define FPRINTF fprintf
#else
static inline void FPRINTF(FILE*, const char*, ...) { }
#endif
static BParameterWeb* make_parameter_web();
const int32 FREQUENCY_NULL_PARAM = 1;
const int32 FREQUENCY_PARAM = 2;
const int32 GAIN_NULL_PARAM = 11;
const int32 GAIN_PARAM = 12;
const int32 WAVEFORM_NULL_PARAM = 21;
const int32 WAVEFORM_PARAM = 22;
const int32 SINE_WAVE = 90;
const int32 TRIANGLE_WAVE = 91;
const int32 SAWTOOTH_WAVE = 92;
ToneProducer::ToneProducer(BMediaAddOn* pAddOn)
: BMediaNode("ToneProducer"),
BBufferProducer(B_MEDIA_RAW_AUDIO),
BControllable(),
BMediaEventLooper(),
mWeb(NULL),
mBufferGroup(NULL),
mLatency(0),
mInternalLatency(0),
mOutputEnabled(true),
mTheta(0.0),
mWaveAscending(true),
mFrequency(440),
mGain(0.25),
mWaveform(SINE_WAVE),
mFramesSent(0),
mStartTime(0),
mGainLastChanged(0),
mFreqLastChanged(0),
mWaveLastChanged(0),
m_pAddOn(pAddOn)
{
mPreferredFormat.type = B_MEDIA_RAW_AUDIO;
mPreferredFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
mPreferredFormat.u.raw_audio.byte_order = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN;
mPreferredFormat.u.raw_audio.frame_rate = media_raw_audio_format::wildcard.frame_rate;
mPreferredFormat.u.raw_audio.buffer_size = media_raw_audio_format::wildcard.buffer_size;
mPreferredFormat.u.raw_audio.channel_count = media_raw_audio_format::wildcard.channel_count;
mOutput.destination = media_destination::null;
mOutput.format = mPreferredFormat;
mOutput.source.port = ControlPort();
mOutput.source.id = 0;
mOutput.node = Node();
::strcpy(mOutput.name, "ToneProducer Output");
}
ToneProducer::~ToneProducer()
{
Quit();
mWeb = NULL;
}
BMediaAddOn *
ToneProducer::AddOn(int32 *internal_id) const
{
if(m_pAddOn) {
*internal_id = 0;
return m_pAddOn;
} else
return NULL;
}
status_t
ToneProducer::GetParameterValue(int32 id, bigtime_t* last_change, void* value, size_t* ioSize)
{
FPRINTF(stderr, "ToneProducer::GetParameterValue\n");
if (*ioSize < sizeof(float)) return B_ERROR;
switch (id)
{
case FREQUENCY_PARAM:
*last_change = mFreqLastChanged;
*((float*) value) = mFrequency;
*ioSize = sizeof(float);
break;
case GAIN_PARAM:
*last_change = mGainLastChanged;
*((float*) value) = mGain;
*ioSize = sizeof(float);
break;
case WAVEFORM_PARAM:
*last_change = mWaveLastChanged;
*((int32*) value) = mWaveform;
*ioSize = sizeof(int32);
break;
default:
FPRINTF(stderr, "\terror - asked for illegal parameter %" B_PRId32 "\n",
id);
return B_ERROR;
break;
}
return B_OK;
}
void
ToneProducer::SetParameterValue(int32 id, bigtime_t performance_time, const void* value, size_t size)
{
switch (id)
{
case FREQUENCY_PARAM:
case GAIN_PARAM:
case WAVEFORM_PARAM:
{
if (size > sizeof(float)) size = sizeof(float);
media_timed_event event(performance_time, _PARAMETER_EVENT,
NULL, BTimedEventQueue::B_NO_CLEANUP, size, id, (char*) value, size);
EventQueue()->AddEvent(event);
}
break;
default:
break;
}
}
status_t ToneProducer::StartControlPanel(
BMessenger* pMessenger) {
PRINT(("ToneProducer::StartControlPanel(%p)\n", pMessenger));
status_t err = BControllable::StartControlPanel(pMessenger);
if(pMessenger && pMessenger->IsValid()) {
PRINT(("\tgot valid control panel\n"));
}
return err;
}
status_t
ToneProducer::FormatSuggestionRequested(media_type type, int32 , media_format* format)
{
FPRINTF(stderr, "ToneProducer::FormatSuggestionRequested\n");
if (!format)
{
FPRINTF(stderr, "\tERROR - NULL format pointer passed in!\n");
return B_BAD_VALUE;
}
*format = mPreferredFormat;
if (type == B_MEDIA_UNKNOWN_TYPE) type = B_MEDIA_RAW_AUDIO;
if (type != B_MEDIA_RAW_AUDIO) return B_MEDIA_BAD_FORMAT;
else return B_OK;
}
status_t
ToneProducer::FormatProposal(const media_source& output, media_format* format)
{
FPRINTF(stderr, "ToneProducer::FormatProposal\n");
if (output != mOutput.source)
{
FPRINTF(stderr, "ToneProducer::FormatProposal returning B_MEDIA_BAD_SOURCE\n");
return B_MEDIA_BAD_SOURCE;
}
media_type requestedType = format->type;
*format = mPreferredFormat;
if ((requestedType != B_MEDIA_UNKNOWN_TYPE) && (requestedType != B_MEDIA_RAW_AUDIO))
{
FPRINTF(stderr, "ToneProducer::FormatProposal returning B_MEDIA_BAD_FORMAT\n");
return B_MEDIA_BAD_FORMAT;
}
else return B_OK;
}
status_t
ToneProducer::FormatChangeRequested(const media_source& source, const media_destination& destination, media_format* io_format, int32* _deprecated_)
{
FPRINTF(stderr, "ToneProducer::FormatChangeRequested\n");
return B_ERROR;
}
status_t
ToneProducer::GetNextOutput(int32* cookie, media_output* out_output)
{
FPRINTF(stderr, "ToneProducer::GetNextOutput\n");
if (0 == *cookie)
{
*out_output = mOutput;
*cookie += 1;
return B_OK;
}
else return B_BAD_INDEX;
}
status_t
ToneProducer::DisposeOutputCookie(int32 cookie)
{
FPRINTF(stderr, "ToneProducer::DisposeOutputCookie\n");
return B_OK;
}
status_t
ToneProducer::SetBufferGroup(const media_source& for_source, BBufferGroup* newGroup)
{
FPRINTF(stderr, "ToneProducer::SetBufferGroup\n");
if (for_source != mOutput.source) return B_MEDIA_BAD_SOURCE;
if (newGroup == mBufferGroup) return B_OK;
delete mBufferGroup;
if (newGroup != NULL)
{
mBufferGroup = newGroup;
}
else
{
size_t size = mOutput.format.u.raw_audio.buffer_size;
int32 count = int32(mLatency / BufferDuration() + 1 + 1);
mBufferGroup = new BBufferGroup(size, count);
}
return B_OK;
}
status_t
ToneProducer::GetLatency(bigtime_t* out_latency)
{
FPRINTF(stderr, "ToneProducer::GetLatency\n");
*out_latency = EventLatency() + SchedulingLatency();
return B_OK;
}
status_t
ToneProducer::PrepareToConnect(const media_source& what, const media_destination& where, media_format* format, media_source* out_source, char* out_name)
{
FPRINTF(stderr, "ToneProducer::PrepareToConnect\n");
if (what != mOutput.source) return B_MEDIA_BAD_SOURCE;
if (mOutput.destination != media_destination::null) return B_MEDIA_ALREADY_CONNECTED;
if (format->type != B_MEDIA_RAW_AUDIO)
{
FPRINTF(stderr, "\tnon-raw-audio format?!\n");
return B_MEDIA_BAD_FORMAT;
}
else if (format->u.raw_audio.format != media_raw_audio_format::B_AUDIO_FLOAT)
{
FPRINTF(stderr, "\tnon-float-audio format?!\n");
return B_MEDIA_BAD_FORMAT;
}
else if(format->u.raw_audio.channel_count > 2) {
format->u.raw_audio.channel_count = 2;
return B_MEDIA_BAD_FORMAT;
}
if(format->u.raw_audio.frame_rate == media_raw_audio_format::wildcard.frame_rate) {
format->u.raw_audio.frame_rate = 44100.0f;
FPRINTF(stderr, "\tno frame rate provided, suggesting %.1f\n", format->u.raw_audio.frame_rate);
}
if(format->u.raw_audio.channel_count == media_raw_audio_format::wildcard.channel_count) {
format->u.raw_audio.channel_count = 1;
FPRINTF(stderr, "\tno channel count provided, suggesting %" B_PRIu32 "\n", format->u.raw_audio.channel_count);
}
if(format->u.raw_audio.byte_order == media_raw_audio_format::wildcard.byte_order) {
format->u.raw_audio.byte_order = mPreferredFormat.u.raw_audio.byte_order;
FPRINTF(stderr, "\tno channel count provided, suggesting %s\n",
(format->u.raw_audio.byte_order == B_MEDIA_BIG_ENDIAN) ? "B_MEDIA_BIG_ENDIAN" : "B_MEDIA_LITTLE_ENDIAN");
}
if (format->u.raw_audio.buffer_size == media_raw_audio_format::wildcard.buffer_size)
{
format->u.raw_audio.buffer_size = 2048;
FPRINTF(stderr, "\tno buffer size provided, suggesting %lu\n", format->u.raw_audio.buffer_size);
}
else
{
FPRINTF(stderr, "\tconsumer suggested buffer_size %lu\n", format->u.raw_audio.buffer_size);
}
mOutput.destination = where;
mOutput.format = *format;
*out_source = mOutput.source;
strncpy(out_name, mOutput.name, B_MEDIA_NAME_LENGTH);
char formatStr[256];
string_for_format(*format, formatStr, 255);
FPRINTF(stderr, "\treturning format: %s\n", formatStr);
return B_OK;
}
void
ToneProducer::Connect(status_t error, const media_source& source, const media_destination& destination, const media_format& format, char* io_name)
{
FPRINTF(stderr, "ToneProducer::Connect\n");
if (error)
{
mOutput.destination = media_destination::null;
mOutput.format = mPreferredFormat;
return;
}
mOutput.destination = destination;
mOutput.format = format;
strncpy(io_name, mOutput.name, B_MEDIA_NAME_LENGTH);
media_node_id id;
FindLatencyFor(mOutput.destination, &mLatency, &id);
FPRINTF(stderr, "\tdownstream latency = %" B_PRIdBIGTIME "\n", mLatency);
bigtime_t start, produceLatency;
size_t samplesPerBuffer = mOutput.format.u.raw_audio.buffer_size / sizeof(float);
size_t framesPerBuffer = samplesPerBuffer / mOutput.format.u.raw_audio.channel_count;
float* data = new float[samplesPerBuffer];
mTheta = 0;
start = ::system_time();
FillSineBuffer(data, framesPerBuffer, mOutput.format.u.raw_audio.channel_count==2);
produceLatency = ::system_time();
mInternalLatency = produceLatency - start;
mInternalLatency += 20000LL;
delete [] data;
FPRINTF(stderr, "\tbuffer-filling took %" B_PRIdBIGTIME
" usec on this machine\n", mInternalLatency);
SetEventLatency(mLatency + mInternalLatency);
ASSERT(mOutput.format.u.raw_audio.frame_rate);
bigtime_t duration = bigtime_t(1000000) * samplesPerBuffer / bigtime_t(mOutput.format.u.raw_audio.frame_rate);
SetBufferDuration(duration);
if (!mBufferGroup) AllocateBuffers();
}
void
ToneProducer::Disconnect(const media_source& what, const media_destination& where)
{
FPRINTF(stderr, "ToneProducer::Disconnect\n");
if ((where == mOutput.destination) && (what == mOutput.source))
{
mOutput.destination = media_destination::null;
mOutput.format = mPreferredFormat;
delete mBufferGroup;
mBufferGroup = NULL;
}
else
{
FPRINTF(stderr, "\tDisconnect() called with wrong source/destination (%"
B_PRId32 "/%" B_PRId32 "), ours is (%" B_PRId32 "/%" B_PRId32 ")\n",
what.id, where.id, mOutput.source.id, mOutput.destination.id);
}
}
void
ToneProducer::LateNoticeReceived(const media_source& what, bigtime_t how_much, bigtime_t performance_time)
{
FPRINTF(stderr, "ToneProducer::LateNoticeReceived\n");
if (what == mOutput.source)
{
if (RunMode() == B_RECORDING)
{
}
else if (RunMode() == B_INCREASE_LATENCY)
{
mInternalLatency += how_much;
if (mInternalLatency > 50000)
mInternalLatency = 50000;
SetEventLatency(mLatency + mInternalLatency);
FPRINTF(stderr, "\tincreasing latency to %" B_PRIdBIGTIME "\n",
mLatency + mInternalLatency);
}
else
{
size_t nSamples = mOutput.format.u.raw_audio.buffer_size / sizeof(float);
mFramesSent += nSamples;
FPRINTF(stderr, "\tskipping a buffer to try to catch up\n");
}
}
}
void
ToneProducer::EnableOutput(const media_source& what, bool enabled, int32* _deprecated_)
{
FPRINTF(stderr, "ToneProducer::EnableOutput\n");
if (what == mOutput.source)
{
mOutputEnabled = enabled;
}
}
status_t
ToneProducer::SetPlayRate(int32 numer, int32 denom)
{
FPRINTF(stderr, "ToneProducer::SetPlayRate\n");
return B_ERROR;
}
status_t
ToneProducer::HandleMessage(int32 message, const void* data, size_t size)
{
FPRINTF(stderr, "ToneProducer::HandleMessage(%" B_PRId32 " = 0x%" B_PRIx32
")\n", message, message);
return B_ERROR;
}
void
ToneProducer::AdditionalBufferRequested(const media_source& source, media_buffer_id prev_buffer, bigtime_t prev_time, const media_seek_tag* prev_tag)
{
FPRINTF(stderr, "ToneProducer::AdditionalBufferRequested\n");
return;
}
void
ToneProducer::LatencyChanged(
const media_source& source,
const media_destination& destination,
bigtime_t new_latency,
uint32 flags)
{
PRINT(("ToneProducer::LatencyChanged(): %" B_PRIdBIGTIME "\n",
new_latency));
if ((source == mOutput.source) && (destination == mOutput.destination))
{
mLatency = new_latency;
SetEventLatency(mLatency + mInternalLatency);
}
}
void
ToneProducer::NodeRegistered()
{
FPRINTF(stderr, "ToneProducer::NodeRegistered\n");
mWeb = make_parameter_web();
SetParameterWeb(mWeb);
SetPriority(B_REAL_TIME_PRIORITY);
Run();
}
void
ToneProducer::Start(bigtime_t performance_time)
{
PRINT(("ToneProducer::Start(%" B_PRIdBIGTIME "): now %" B_PRIdBIGTIME "\n",
performance_time, TimeSource()->Now()));
if(mOutput.destination != media_destination::null)
SendDataStatus(B_DATA_AVAILABLE, mOutput.destination, performance_time);
BMediaEventLooper::Start(performance_time);
}
void
ToneProducer::Stop(bigtime_t performance_time, bool immediate)
{
if(mOutput.destination != media_destination::null) {
printf("ToneProducer: B_PRODUCER_STOPPED at %" B_PRIdBIGTIME "\n",
performance_time);
SendDataStatus(B_PRODUCER_STOPPED, mOutput.destination, performance_time);
}
BMediaEventLooper::Stop(performance_time, immediate);
}
void
ToneProducer::SetRunMode(run_mode mode)
{
FPRINTF(stderr, "ToneProducer::SetRunMode\n");
if (B_OFFLINE == mode)
{
ReportError(B_NODE_FAILED_SET_RUN_MODE);
}
}
void
ToneProducer::HandleEvent(const media_timed_event* event, bigtime_t lateness, bool realTimeEvent)
{
switch (event->type)
{
case BTimedEventQueue::B_START:
if (RunState() != B_STARTED)
{
mFramesSent = 0;
mTheta = 0;
mStartTime = event->event_time;
media_timed_event firstBufferEvent(mStartTime, BTimedEventQueue::B_HANDLE_BUFFER);
EventQueue()->AddEvent(firstBufferEvent);
}
break;
case BTimedEventQueue::B_STOP:
FPRINTF(stderr, "Handling B_STOP event\n");
EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
break;
case _PARAMETER_EVENT:
{
size_t dataSize = size_t(event->data);
int32 param = int32(event->bigdata);
if (dataSize >= sizeof(float)) switch (param)
{
case FREQUENCY_PARAM:
{
float newValue = *((float*) event->user_data);
if (mFrequency != newValue)
{
mFrequency = newValue;
mFreqLastChanged = TimeSource()->Now();
BroadcastNewParameterValue(mFreqLastChanged, param, &mFrequency, sizeof(mFrequency));
}
}
break;
case GAIN_PARAM:
{
float newValue = *((float*) event->user_data);
if (mGain != newValue)
{
mGain = newValue;
mGainLastChanged = TimeSource()->Now();
BroadcastNewParameterValue(mGainLastChanged, param, &mGain, sizeof(mGain));
}
}
break;
case WAVEFORM_PARAM:
{
int32 newValue = *((int32*) event->user_data);
if (mWaveform != newValue)
{
mWaveform = newValue;
mTheta = 0;
mWaveAscending = true;
mWaveLastChanged = TimeSource()->Now();
BroadcastNewParameterValue(mWaveLastChanged, param, &mWaveform, sizeof(mWaveform));
}
}
break;
default:
FPRINTF(stderr, "Hmmm... got a B_PARAMETER event for a parameter we don't have? (%" B_PRId32 ")\n", param);
break;
}
}
break;
case BTimedEventQueue::B_HANDLE_BUFFER:
{
if (RunState() == BMediaEventLooper::B_STARTED
&& mOutput.destination != media_destination::null) {
BBuffer* buffer = FillNextBuffer(event->event_time);
if (buffer) {
status_t err = B_ERROR;
if (mOutputEnabled) {
err = SendBuffer(buffer, mOutput.source,
mOutput.destination);
}
if (err) {
buffer->Recycle();
}
}
size_t nFrames = mOutput.format.u.raw_audio.buffer_size /
(sizeof(float) * mOutput.format.u.raw_audio.channel_count);
mFramesSent += nFrames;
bigtime_t nextEvent = mStartTime + bigtime_t(double(mFramesSent)
/ double(mOutput.format.u.raw_audio.frame_rate) * 1000000.0);
media_timed_event nextBufferEvent(nextEvent,
BTimedEventQueue::B_HANDLE_BUFFER);
EventQueue()->AddEvent(nextBufferEvent);
}
}
break;
default:
break;
}
}
void
ToneProducer::AllocateBuffers()
{
FPRINTF(stderr, "ToneProducer::AllocateBuffers\n");
size_t size = mOutput.format.u.raw_audio.buffer_size;
int32 count = int32(mLatency / BufferDuration() + 1 + 1);
FPRINTF(stderr, "\tlatency = %" B_PRIdBIGTIME ", buffer duration = %"
B_PRIdBIGTIME "\n", mLatency, BufferDuration());
FPRINTF(stderr, "\tcreating group of %" B_PRId32 " buffers, size = %"
B_PRIuSIZE "\n", count, size);
mBufferGroup = new BBufferGroup(size, count);
}
BBuffer*
ToneProducer::FillNextBuffer(bigtime_t event_time)
{
BBuffer* buf = mBufferGroup->RequestBuffer(mOutput.format.u.raw_audio.buffer_size, BufferDuration());
if (!buf)
{
return NULL;
}
size_t numFrames =
mOutput.format.u.raw_audio.buffer_size /
(sizeof(float)*mOutput.format.u.raw_audio.channel_count);
bool stereo = (mOutput.format.u.raw_audio.channel_count == 2);
if(!stereo) {
ASSERT(mOutput.format.u.raw_audio.channel_count == 1);
}
float* data = (float*) buf->Data();
switch (mWaveform)
{
case SINE_WAVE:
FillSineBuffer(data, numFrames, stereo);
break;
case TRIANGLE_WAVE:
FillTriangleBuffer(data, numFrames, stereo);
break;
case SAWTOOTH_WAVE:
FillSawtoothBuffer(data, numFrames, stereo);
break;
}
media_header* hdr = buf->Header();
hdr->type = B_MEDIA_RAW_AUDIO;
hdr->size_used = mOutput.format.u.raw_audio.buffer_size;
hdr->time_source = TimeSource()->ID();
bigtime_t stamp;
if (RunMode() == B_RECORDING)
{
stamp = event_time;
}
else
{
stamp = mStartTime + bigtime_t(double(mFramesSent) / double(mOutput.format.u.raw_audio.frame_rate) * 1000000.0);
}
hdr->start_time = stamp;
return buf;
}
void
ToneProducer::FillSineBuffer(float *data, size_t numFrames, bool stereo)
{
double dTheta = 2*M_PI * double(mFrequency) / mOutput.format.u.raw_audio.frame_rate;
for (size_t i = 0; i < numFrames; i++, data++)
{
float val = mGain * float(sin(mTheta));
*data = val;
if(stereo) {
++data;
*data = val;
}
mTheta += dTheta;
if (mTheta > 2*M_PI)
{
mTheta -= 2*M_PI;
}
}
}
void
ToneProducer::FillTriangleBuffer(float *data, size_t numFrames, bool stereo)
{
double dTheta = 4.0 * double(mFrequency) / mOutput.format.u.raw_audio.frame_rate;
if (!mWaveAscending) dTheta = -dTheta;
for (size_t i = 0; i < numFrames; i++, data++)
{
float val = mGain * mTheta;
*data = val;
if(stereo) {
++data;
*data = val;
}
mTheta += dTheta;
if (mTheta >= 1)
{
mTheta = 2 - mTheta;
mWaveAscending = false;
dTheta = -dTheta;
}
else if (mTheta <= -1)
{
mTheta = -2 - mTheta;
mWaveAscending = true;
dTheta = -dTheta;
}
}
}
void
ToneProducer::FillSawtoothBuffer(float *data, size_t numFrames, bool stereo)
{
double dTheta = 2 * double(mFrequency) / mOutput.format.u.raw_audio.frame_rate;
mWaveAscending = true;
for (size_t i = 0; i < numFrames; i++, data++)
{
float val = mGain * mTheta;
*data = val;
if(stereo) {
++data;
*data = val;
}
mTheta += dTheta;
if (mTheta > 1)
{
mTheta -= 2;
}
}
}
static BParameterWeb* make_parameter_web()
{
FPRINTF(stderr, "make_parameter_web() called\n");
BParameterWeb* web = new BParameterWeb;
BParameterGroup* mainGroup = web->MakeGroup("Tone Generator Parameters");
BParameterGroup* group = mainGroup->MakeGroup("Frequency");
BParameter* nullParam = group->MakeNullParameter(FREQUENCY_NULL_PARAM, B_MEDIA_NO_TYPE, "Frequency", B_GENERIC);
BContinuousParameter* param = group->MakeContinuousParameter(FREQUENCY_PARAM, B_MEDIA_NO_TYPE, "", B_GAIN, "Hz", 0, 2500, 0.1);
nullParam->AddOutput(param);
param->AddInput(nullParam);
group = mainGroup->MakeGroup("Amplitude");
nullParam = group->MakeNullParameter(GAIN_NULL_PARAM, B_MEDIA_NO_TYPE, "Amplitude", B_GENERIC);
param = group->MakeContinuousParameter(GAIN_PARAM, B_MEDIA_NO_TYPE, "", B_GAIN, "", 0, 1, 0.01);
nullParam->AddOutput(param);
param->AddInput(nullParam);
group = mainGroup->MakeGroup("Waveform");
nullParam = group->MakeNullParameter(WAVEFORM_NULL_PARAM, B_MEDIA_NO_TYPE, "Waveform", B_GENERIC);
BDiscreteParameter* waveParam = group->MakeDiscreteParameter(WAVEFORM_PARAM, B_MEDIA_NO_TYPE, "", B_GENERIC);
waveParam->AddItem(SINE_WAVE, "Sine wave");
waveParam->AddItem(TRIANGLE_WAVE, "Triangle");
waveParam->AddItem(SAWTOOTH_WAVE, "Sawtooth");
nullParam->AddOutput(waveParam);
waveParam->AddInput(nullParam);
return web;
}