#include "AudioMixer.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Buffer.h>
#include <Catalog.h>
#include <FindDirectory.h>
#include <MediaDefs.h>
#include <MediaRoster.h>
#include <ParameterWeb.h>
#include <Path.h>
#include <RealtimeAlloc.h>
#include <TimeSource.h>
#include "MixerCore.h"
#include "MixerInput.h"
#include "MixerOutput.h"
#include "MixerUtils.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "AudioMixer"
#define DB_MAX 18.0
#define DB_MIN -60.0
#define DB_EXPONENT_POSITIVE 1.4
#define DB_EXPONENT_NEGATIVE 1.8
#define DB_TO_GAIN(db) dB_to_Gain((db))
#define GAIN_TO_DB(gain) Gain_to_dB((gain))
#define PERCENT_TO_GAIN(pct) ((pct) / 100.0)
#define GAIN_TO_PERCENT(gain) ((gain) * 100.0)
#define PARAM_SRC_ENABLE(id, chan, src) (((id) << 16) | ((chan) << 10) | ((src) << 4) | 0x1)
#define PARAM_SRC_GAIN(id, chan, src) (((id) << 16) | ((chan) << 10) | ((src) << 4) | 0x2)
#define PARAM_DST_ENABLE(id, chan, dst) (((id) << 16) | ((chan) << 10) | ((dst) << 4) | 0x3)
#define PARAM_ETC(etc) (((etc) << 16) | 0x4)
#define PARAM_SRC_STR(id, chan) (((id) << 16) | ((chan) << 10) | 0x5)
#define PARAM_DST_STR(id, chan) (((id) << 16) | ((chan) << 10) | 0x6)
#define PARAM_MUTE(id) (((id) << 16) | 0x7)
#define PARAM_GAIN(id) (((id) << 16) | 0x8)
#define PARAM_BALANCE(id) (((id) << 16) | 0x9)
#define PARAM_STR1(id) (((id) << 16) | ((1) << 10) | 0xa)
#define PARAM_STR2(id) (((id) << 16) | ((2) << 10) | 0xa)
#define PARAM_STR3(id) (((id) << 16) | ((3) << 10) | 0xa)
#define PARAM_STR4(id) (((id) << 16) | ((4) << 10) | 0xa)
#define PARAM_STR5(id) (((id) << 16) | ((5) << 10) | 0xa)
#define PARAM_STR6(id) (((id) << 16) | ((6) << 10) | 0xa)
#define PARAM_STR7(id) (((id) << 16) | ((7) << 10) | 0xa)
#define PARAM(id) ((id) >> 16)
#define ETC(id) ((id) >> 16)
#define PARAM_CHAN(id) (((id) >> 10) & 0x3f)
#define PARAM_SRC(id) (((id) >> 4) & 0x3f)
#define PARAM_DST(id) (((id) >> 4) & 0x3f)
#define PARAM_IS_SRC_ENABLE(id) (((id) & 0xf) == 0x1)
#define PARAM_IS_SRC_GAIN(id) (((id) & 0xf) == 0x2)
#define PARAM_IS_DST_ENABLE(id) (((id) & 0xf) == 0x3)
#define PARAM_IS_ETC(id) (((id) & 0xf) == 0x4)
#define PARAM_IS_MUTE(id) (((id) & 0xf) == 0x7)
#define PARAM_IS_GAIN(id) (((id) & 0xf) == 0x8)
#define PARAM_IS_BALANCE(id) (((id) & 0xf) == 0x9)
#define FORMAT_USER_DATA_TYPE 0x7294a8f3
#define FORMAT_USER_DATA_MAGIC_1 0xc84173bd
#define FORMAT_USER_DATA_MAGIC_2 0x4af62b7d
const static bigtime_t kMaxLatency = 150000;
const bigtime_t kMinMixingTime = 3500;
const bigtime_t kMaxJitter = 1500;
AudioMixer::AudioMixer(BMediaAddOn *addOn, bool isSystemMixer)
:
BMediaNode("Audio Mixer"),
BBufferConsumer(B_MEDIA_RAW_AUDIO),
BBufferProducer(B_MEDIA_RAW_AUDIO),
BControllable(),
BMediaEventLooper(),
fAddOn(addOn),
fCore(new MixerCore(this)),
fWeb(NULL),
fBufferGroup(NULL),
fDownstreamLatency(1),
fInternalLatency(1),
fDisableStop(false),
fLastLateNotification(0),
fLastLateness(0)
{
BMediaNode::AddNodeKind(B_SYSTEM_MIXER);
fDefaultFormat.type = B_MEDIA_RAW_AUDIO;
fDefaultFormat.u.raw_audio.frame_rate = 96000;
fDefaultFormat.u.raw_audio.channel_count = 2;
fDefaultFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
fDefaultFormat.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN;
fDefaultFormat.u.raw_audio.buffer_size = 4096;
fDefaultFormat.u.raw_audio.channel_mask = 0;
fDefaultFormat.u.raw_audio.valid_bits = 0;
fDefaultFormat.u.raw_audio.matrix_mask = 0;
if (isSystemMixer) {
BPath path;
if (B_OK != find_directory (B_USER_SETTINGS_DIRECTORY, &path))
path.SetTo("/boot/home/config/settings/");
path.Append("System Audio Mixer");
fCore->Settings()->SetSettingsFile(path.Path());
DisableNodeStop();
}
ApplySettings();
}
AudioMixer::~AudioMixer()
{
BMediaEventLooper::Quit();
SetParameterWeb(NULL);
fCore->Lock();
fCore->Stop();
fCore->Unlock();
delete fCore;
delete fBufferGroup;
DEBUG_ONLY(fCore = 0; fBufferGroup = 0; fWeb = 0);
}
void
AudioMixer::ApplySettings()
{
fCore->Lock();
fCore->SetOutputAttenuation(fCore->Settings()->AttenuateOutput() ? 0.708 : 1.0);
fCore->Unlock();
}
void
AudioMixer::DisableNodeStop()
{
fDisableStop = true;
}
void
AudioMixer::Stop(bigtime_t performance_time, bool immediate)
{
if (fDisableStop) {
TRACE("AudioMixer STOP is disabled\n");
return;
} else {
BMediaEventLooper::Stop(performance_time, immediate);
}
}
BMediaAddOn*
AudioMixer::AddOn(int32 *internal_id) const
{
*internal_id = 0;
return fAddOn;
}
status_t
AudioMixer::HandleMessage(int32 message, const void *data, size_t size)
{
return B_ERROR;
}
status_t
AudioMixer::AcceptFormat(const media_destination &dest, media_format *ioFormat)
{
PRINT_FORMAT("AudioMixer::AcceptFormat: ", *ioFormat);
if (dest.port != ControlPort())
return B_MEDIA_BAD_DESTINATION;
if (ioFormat->type == B_MEDIA_UNKNOWN_TYPE)
ioFormat->type = B_MEDIA_RAW_AUDIO;
if (ioFormat->type != B_MEDIA_RAW_AUDIO)
return B_MEDIA_BAD_FORMAT;
fCore->Lock();
MixerOutput *output = fCore->Output();
uint32 channel_count = output ? output->MediaOutput().format.u.raw_audio.channel_count : 0;
float frame_rate = output ? output->MediaOutput().format.u.raw_audio.frame_rate : 0.0;
fCore->Unlock();
ioFormat->user_data_type = FORMAT_USER_DATA_TYPE;
*(uint32 *)&ioFormat->user_data[0] = FORMAT_USER_DATA_MAGIC_1;
*(uint32 *)&ioFormat->user_data[4] = channel_count;
*(float *)&ioFormat->user_data[20] = frame_rate;
*(uint32 *)&ioFormat->user_data[44] = FORMAT_USER_DATA_MAGIC_2;
return B_OK;
}
status_t
AudioMixer::GetNextInput(int32 *cookie, media_input *out_input)
{
TRACE("AudioMixer::GetNextInput\n");
if (*cookie == 0) {
out_input->node = Node();
out_input->source = media_source::null;
out_input->destination.port = ControlPort();
out_input->destination.id = 0;
out_input->format.Clear();
out_input->format.type = B_MEDIA_RAW_AUDIO;
strcpy(out_input->name, "Free Input");
*cookie += 1;
return B_OK;
}
fCore->Lock();
MixerInput *input;
input = fCore->Input(*cookie - 1);
if (!input) {
fCore->Unlock();
return B_BAD_INDEX;
}
*out_input = input->MediaInput();
*cookie += 1;
fCore->Unlock();
return B_OK;
}
void
AudioMixer::DisposeInputCookie(int32 cookie)
{
}
void
AudioMixer::BufferReceived(BBuffer *buffer)
{
if (buffer->Header()->type == B_MEDIA_PARAMETERS) {
TRACE("Control Buffer Received\n");
ApplyParameterData(buffer->Data(), buffer->SizeUsed());
buffer->Recycle();
return;
}
media_timed_event event(buffer->Header()->start_time,
BTimedEventQueue::B_HANDLE_BUFFER, buffer,
BTimedEventQueue::B_RECYCLE_BUFFER);
EventQueue()->AddEvent(event);
}
void
AudioMixer::HandleInputBuffer(BBuffer* buffer, bigtime_t lateness)
{
bigtime_t variation = 0;
if (lateness > fLastLateness)
variation = lateness-fLastLateness;
if (variation > kMaxJitter) {
TRACE("AudioMixer: Dequeued input buffer %" B_PRIdBIGTIME
" usec late\n", lateness);
if (RunMode() == B_DROP_DATA || RunMode() == B_DECREASE_PRECISION
|| RunMode() == B_INCREASE_LATENCY) {
TRACE("AudioMixer: sending notify\n");
media_source source = media_source::null;
source.port = buffer->Header()->source_port;
source.id = buffer->Header()->source;
NotifyLateProducer(source, variation, TimeSource()->Now());
if (RunMode() == B_DROP_DATA) {
TRACE("AudioMixer: dropping buffer\n");
return;
}
}
}
fLastLateness = lateness;
fCore->Lock();
fCore->BufferReceived(buffer, lateness);
fCore->Unlock();
}
void
AudioMixer::ProducerDataStatus(const media_destination& for_whom,
int32 status, bigtime_t at_performance_time)
{
}
status_t
AudioMixer::GetLatencyFor(const media_destination &for_whom,
bigtime_t *out_latency, media_node_id *out_timesource)
{
if (for_whom.port != ControlPort())
return B_MEDIA_BAD_DESTINATION;
*out_latency = EventLatency();
*out_timesource = TimeSource()->ID();
printf("AudioMixer::GetLatencyFor %" B_PRIdBIGTIME ", timesource is %"
B_PRId32 "\n", *out_latency, *out_timesource);
return B_OK;
}
status_t
AudioMixer::Connected(const media_source &producer,
const media_destination &where, const media_format &with_format,
media_input *out_input)
{
PRINT_FORMAT("AudioMixer::Connected: ", with_format);
if (out_input->format.u.raw_audio.frame_rate == 0) {
fprintf(stderr, "Audio Mixer Warning: "
"Producer (port %" B_PRId32 ", id %" B_PRId32 ") connected with "
"frame_rate=0\n", producer.port, producer.id);
MixerOutput *output = fCore->Output();
float frame_rate = output
? output->MediaOutput().format.u.raw_audio.frame_rate : 44100.0f;
out_input->format.u.raw_audio.frame_rate = frame_rate;
}
if (where.id != 0 || where.port != ControlPort())
return B_MEDIA_BAD_DESTINATION;
fCore->Lock();
out_input->destination.id = fCore->CreateInputID();
if (strlen(out_input->name) == 0) {
sprintf(out_input->name, "Input %" B_PRId32,
out_input->destination.id);
}
MixerInput *input;
input = fCore->AddInput(*out_input);
fCore->Settings()->LoadConnectionSettings(input);
fCore->Unlock();
UpdateParameterWeb();
return B_OK;
}
void
AudioMixer::Disconnected(const media_source &producer,
const media_destination &where)
{
if (where.port != ControlPort()) {
TRACE("AudioMixer::Disconnected wrong input port\n");
return;
}
fCore->Lock();
if (!fCore->RemoveInput(where.id)) {
TRACE("AudioMixer::Disconnected can't remove input\n");
}
fCore->Unlock();
UpdateParameterWeb();
}
status_t
AudioMixer::FormatChanged(const media_source &producer,
const media_destination &consumer, int32 change_tag,
const media_format &format)
{
TRACE("AudioMixer::FormatChanged\n");
if (consumer.port != ControlPort() || consumer.id == 0)
return B_MEDIA_BAD_DESTINATION;
if (fCore->Settings()->RefuseInputFormatChange()) {
TRACE("AudioMixer::FormatChanged: input format change refused\n");
return B_NOT_ALLOWED;
}
return B_NOT_SUPPORTED;
fCore->Lock();
fCore->InputFormatChanged(consumer.id, format.u.raw_audio);
fCore->Unlock();
return B_OK;
}
status_t
AudioMixer::FormatSuggestionRequested(media_type type, int32 quality,
media_format *format)
{
TRACE("AudioMixer::FormatSuggestionRequested\n");
if (type != B_MEDIA_RAW_AUDIO && type != B_MEDIA_UNKNOWN_TYPE)
return B_MEDIA_BAD_FORMAT;
format->Clear();
format->type = B_MEDIA_RAW_AUDIO;
return B_OK;
}
status_t
AudioMixer::FormatProposal(const media_source &output, media_format *ioFormat)
{
TRACE("AudioMixer::FormatProposal\n");
if (output.id != 0 || output.port != ControlPort())
return B_MEDIA_BAD_SOURCE;
if (ioFormat->type == B_MEDIA_UNKNOWN_TYPE)
ioFormat->type = B_MEDIA_RAW_AUDIO;
if (ioFormat->type != B_MEDIA_RAW_AUDIO)
return B_MEDIA_BAD_FORMAT;
return B_OK;
}
status_t
AudioMixer::FormatChangeRequested(const media_source &source,
const media_destination &destination, media_format *io_format,
int32 *_deprecated_)
{
TRACE("AudioMixer::FormatChangeRequested\n");
if (fCore->Settings()->RefuseOutputFormatChange()) {
TRACE("AudioMixer::FormatChangeRequested: output format change refused\n");
return B_ERROR;
}
fCore->Lock();
status_t status = B_OK;
BBufferGroup *group = NULL;
MixerOutput *output = fCore->Output();
if (!output) {
ERROR("AudioMixer::FormatChangeRequested: no output\n");
goto err;
}
if (source != output->MediaOutput().source) {
ERROR("AudioMixer::FormatChangeRequested: wrong output source\n");
goto err;
}
if (destination != output->MediaOutput().destination) {
ERROR("AudioMixer::FormatChangeRequested: wrong output destination "
"(port %ld, id %ld), our is (port %ld, id %ld)\n", destination.port,
destination.id, output->MediaOutput().destination.port,
output->MediaOutput().destination.id);
if (destination.port == output->MediaOutput().destination.port
&& destination.id == output->MediaOutput().destination.id + 1) {
ERROR("AudioMixer::FormatChangeRequested: this might be the broken "
"R5 multi audio add-on\n");
}
goto err;
}
if (io_format->type != B_MEDIA_RAW_AUDIO
&& io_format->type != B_MEDIA_UNKNOWN_TYPE) {
ERROR("AudioMixer::FormatChangeRequested: wrong format type\n");
goto err;
}
io_format->SpecializeTo(&fDefaultFormat);
media_node_id id;
FindLatencyFor(destination, &fDownstreamLatency, &id);
TRACE("AudioMixer: Downstream Latency is %" B_PRIdBIGTIME " usecs\n",
fDownstreamLatency);
SetBufferDuration(buffer_duration(io_format->u.raw_audio));
TRACE("AudioMixer: buffer duration is %" B_PRIdBIGTIME " usecs\n",
BufferDuration());
fInternalLatency = BufferDuration()
+ max((bigtime_t)4500, bigtime_t(0.5 * BufferDuration()));
TRACE("AudioMixer: Internal latency is %" B_PRIdBIGTIME " usecs\n",
fInternalLatency);
SetEventLatency(fDownstreamLatency + fInternalLatency);
PublishEventLatencyChange();
fCore->SetTimingInfo(TimeSource(), fDownstreamLatency);
fCore->OutputFormatChanged(io_format->u.raw_audio);
status = CreateBufferGroup(&group);
if (status != B_OK)
return status;
else {
delete fBufferGroup;
fBufferGroup = group;
fCore->SetOutputBufferGroup(fBufferGroup);
}
err:
fCore->Unlock();
return status;
}
status_t
AudioMixer::GetNextOutput(int32 *cookie, media_output *out_output)
{
TRACE("AudioMixer::GetNextOutput\n");
if (*cookie != 0)
return B_BAD_INDEX;
fCore->Lock();
MixerOutput *output = fCore->Output();
if (output) {
*out_output = output->MediaOutput();
} else {
out_output->node = Node();
out_output->source.port = ControlPort();
out_output->source.id = 0;
out_output->destination = media_destination::null;
out_output->format.Clear();
out_output->format.type = B_MEDIA_RAW_AUDIO;
strcpy(out_output->name, "Mixer Output");
}
fCore->Unlock();
*cookie += 1;
return B_OK;
}
status_t
AudioMixer::DisposeOutputCookie(int32 cookie)
{
return B_OK;
}
status_t
AudioMixer::SetBufferGroup(const media_source &for_source,
BBufferGroup *newGroup)
{
TRACE("AudioMixer::SetBufferGroup\n");
if (for_source.port != ControlPort() || for_source.id != 0)
return B_MEDIA_BAD_SOURCE;
if (newGroup == fBufferGroup) {
return B_OK;
}
fCore->Lock();
if (!newGroup) {
status_t status = CreateBufferGroup(&newGroup);
if (status != B_OK)
return status;
}
fCore->SetOutputBufferGroup(newGroup);
delete fBufferGroup;
fBufferGroup = newGroup;
fCore->Unlock();
return B_OK;
}
status_t
AudioMixer::GetLatency(bigtime_t *out_latency)
{
*out_latency = EventLatency() + SchedulingLatency();
TRACE("AudioMixer::GetLatency %lld\n", *out_latency);
return B_OK;
}
void
AudioMixer::LatencyChanged(const media_source& source,
const media_destination& destination, bigtime_t new_latency, uint32 flags)
{
if (source.port != ControlPort() || source.id != 0) {
ERROR("AudioMixer::LatencyChanged: received but has wrong source "
"%ld/%ld\n", source.port, source.id);
return;
}
TRACE("AudioMixer::LatencyChanged: downstream latency from %ld/%ld to "
"%ld/%ld changed from %lld to %lld\n", source.port, source.id,
destination.port, destination.id, fDownstreamLatency, new_latency);
#if DEBUG
{
media_node_id id;
bigtime_t l;
FindLatencyFor(destination, &l, &id);
TRACE("AudioMixer: Reported downstream Latency is %lld usecs\n", l);
}
#endif
fDownstreamLatency = new_latency;
SetEventLatency(fDownstreamLatency + fInternalLatency);
fCore->Lock();
fCore->SetTimingInfo(TimeSource(), fDownstreamLatency);
PublishEventLatencyChange();
fCore->Unlock();
}
status_t
AudioMixer::PrepareToConnect(const media_source &what,
const media_destination &where, media_format *format,
media_source *out_source, char *out_name)
{
TRACE("AudioMixer::PrepareToConnect\n");
PRINT_FORMAT("AudioMixer::PrepareToConnect: suggested format", *format);
if (where.port == ControlPort())
return B_MEDIA_BAD_SOURCE;
if (what.port != ControlPort() || what.id != 0)
return B_MEDIA_BAD_SOURCE;
if (format->type != B_MEDIA_RAW_AUDIO
&& format->type != B_MEDIA_UNKNOWN_TYPE) {
PRINT_FORMAT("AudioMixer::PrepareToConnect: bad format", *format);
return B_MEDIA_BAD_FORMAT;
}
fCore->Lock();
if (fCore->Output() != 0) {
fCore->Unlock();
ERROR("AudioMixer::PrepareToConnect: already connected\n");
return B_MEDIA_ALREADY_CONNECTED;
}
if (format->u.raw_audio.channel_count == 0
&& format->u.raw_audio.frame_rate < 1
&& format->user_data_type == FORMAT_USER_DATA_TYPE
&& *(uint32 *)&format->user_data[0] == FORMAT_USER_DATA_MAGIC_1
&& *(uint32 *)&format->user_data[44] == FORMAT_USER_DATA_MAGIC_2) {
uint32 channel_count = *(uint32 *)&format->user_data[4];
float frame_rate = *(float *)&format->user_data[20];
if (channel_count > 0 && frame_rate > 0) {
format->u.raw_audio.channel_count = channel_count;
format->u.raw_audio.frame_rate = frame_rate;
} else {
media_node node;
BMediaRoster *roster = BMediaRoster::Roster();
media_output out;
media_input in;
int32 count;
if (roster->GetAudioMixer(&node) == B_OK
&& roster->GetConnectedOutputsFor(node, &out, 1, &count)
== B_OK
&& count == 1) {
format->u.raw_audio.channel_count
= out.format.u.raw_audio.channel_count;
format->u.raw_audio.frame_rate
= out.format.u.raw_audio.frame_rate;
} else if (roster->GetAudioOutput(&node) == B_OK
&& roster->GetAllInputsFor(node, &in, 1, &count) == B_OK
&& count == 1) {
format->u.raw_audio.channel_count
= in.format.u.raw_audio.channel_count;
format->u.raw_audio.frame_rate
= in.format.u.raw_audio.frame_rate;
}
}
}
*out_source = what;
strcpy(out_name, "Mixer Output");
format->SpecializeTo(&fDefaultFormat);
PRINT_FORMAT("AudioMixer::PrepareToConnect: final format", *format);
media_output output;
output.node = Node();
output.source = *out_source;
output.destination = where;
output.format = *format;
strcpy(output.name, out_name);
fCore->EnableOutput(false);
fCore->AddOutput(output);
fCore->Unlock();
return B_OK;
}
void
AudioMixer::Connect(status_t error, const media_source &source,
const media_destination &dest, const media_format &format, char *io_name)
{
TRACE("AudioMixer::Connect\n");
fCore->Lock();
if (fCore->Output() == 0) {
fCore->Unlock();
ERROR("AudioMixer::Connect: no longer connected\n");
return;
}
fCore->Unlock();
if (error != B_OK) {
ERROR("AudioMixer::Connect failed with error 0x%08lX, removing "
"connection\n", error);
fCore->Lock();
fCore->RemoveOutput();
fCore->Unlock();
return;
}
fDefaultFormat.u.raw_audio.frame_rate = format.u.raw_audio.frame_rate;
fDefaultFormat.u.raw_audio.channel_count = format.u.raw_audio.channel_count;
if (strlen(io_name) == 0)
strcpy(io_name, "Mixer Output");
media_node_id id;
FindLatencyFor(dest, &fDownstreamLatency, &id);
TRACE("AudioMixer: Downstream Latency is %lld usecs\n", fDownstreamLatency);
SetBufferDuration(buffer_duration(format.u.raw_audio));
TRACE("AudioMixer: buffer duration is %lld usecs\n", BufferDuration());
fInternalLatency = BufferDuration()
+ max(kMinMixingTime, bigtime_t(0.5 * BufferDuration())) + kMaxJitter;
TRACE("AudioMixer: Internal latency is %lld usecs\n", fInternalLatency);
SetEventLatency(fDownstreamLatency + fInternalLatency);
PublishEventLatencyChange();
fCore->Lock();
if (!fBufferGroup) {
BBufferGroup *group = NULL;
if (CreateBufferGroup(&group) != B_OK)
return;
fBufferGroup = group;
}
ASSERT(fCore->Output() != 0);
ASSERT(fCore->Output()->MediaOutput().source.id == 0);
ASSERT(fCore->Output()->MediaOutput().source.port == ControlPort());
fCore->Output()->MediaOutput().destination = dest;
fCore->EnableOutput(true);
fCore->SetTimingInfo(TimeSource(), fDownstreamLatency);
fCore->SetOutputBufferGroup(fBufferGroup);
fCore->Settings()->LoadConnectionSettings(fCore->Output());
fCore->Unlock();
UpdateParameterWeb();
}
void
AudioMixer::Disconnect(const media_source& what, const media_destination& where)
{
TRACE("AudioMixer::Disconnect\n");
fCore->Lock();
MixerOutput* output = fCore->Output();
if (!output
|| output->MediaOutput().node != Node()
|| output->MediaOutput().source != what
|| output->MediaOutput().destination != where) {
ERROR("AudioMixer::Disconnect can't disconnect (wrong connection)\n");
fCore->Unlock();
return;
}
fDefaultFormat.u.raw_audio.frame_rate = 96000;
fDefaultFormat.u.raw_audio.channel_count = 2;
fCore->RemoveOutput();
delete fBufferGroup;
fBufferGroup = NULL;
fCore->SetOutputBufferGroup(0);
fCore->Unlock();
UpdateParameterWeb();
}
void
AudioMixer::LateNoticeReceived(const media_source& what, bigtime_t howMuch,
bigtime_t performanceTime)
{
ERROR("AudioMixer::LateNoticeReceived, %lld too late at %lld\n", howMuch,
performanceTime);
if (what == fCore->Output()->MediaOutput().source
&& RunMode() == B_INCREASE_LATENCY) {
if (performanceTime < fLastLateNotification)
return;
fInternalLatency += howMuch;
if (fInternalLatency > kMaxLatency)
fInternalLatency = kMaxLatency;
fLastLateNotification = TimeSource()->Now() + howMuch;
TRACE("AudioMixer: increasing internal latency to %"
B_PRIdBIGTIME " usec\n", fInternalLatency);
SetEventLatency(fDownstreamLatency + fInternalLatency);
PublishEventLatencyChange();
}
}
void
AudioMixer::EnableOutput(const media_source& what, bool enabled,
int32 *)
{
if (what.id != 0 || what.port != ControlPort())
return;
fCore->Lock();
fCore->EnableOutput(enabled);
fCore->Unlock();
}
void
AudioMixer::NodeRegistered()
{
UpdateParameterWeb();
SetPriority(B_REAL_TIME_PRIORITY);
Run();
}
void
AudioMixer::SetTimeSource(BTimeSource* timeSource)
{
TRACE("AudioMixer::SetTimeSource: timesource is now %ld\n",
timeSource->ID());
fCore->Lock();
fCore->SetTimingInfo(timeSource, fDownstreamLatency);
fCore->Unlock();
}
void
AudioMixer::HandleEvent(const media_timed_event *event, bigtime_t lateness,
bool realTimeEvent)
{
switch (event->type) {
case BTimedEventQueue::B_HANDLE_BUFFER:
{
HandleInputBuffer((BBuffer *)event->pointer, lateness);
((BBuffer *)event->pointer)->Recycle();
break;
}
case BTimedEventQueue::B_START:
{
TRACE("AudioMixer::HandleEvent: B_START\n");
if (RunState() != B_STARTED) {
fCore->Lock();
fCore->Start();
fCore->Unlock();
}
break;
}
case BTimedEventQueue::B_STOP:
{
TRACE("AudioMixer::HandleEvent: B_STOP\n");
EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true,
BTimedEventQueue::B_HANDLE_BUFFER);
fCore->Lock();
fCore->Stop();
fCore->Unlock();
break;
}
case BTimedEventQueue::B_DATA_STATUS:
{
ERROR("DataStatus message\n");
break;
}
default:
break;
}
}
void
AudioMixer::PublishEventLatencyChange()
{
TRACE("AudioMixer::PublishEventLatencyChange\n");
fCore->Lock();
MixerInput *input;
for (int i = 0; (input = fCore->Input(i)) != 0; i++) {
TRACE("AudioMixer::PublishEventLatencyChange: SendLatencyChange, "
"connection %ld/%ld to %ld/%ld event latency is now %lld\n",
input->MediaInput().source.port, input->MediaInput().source.id,
input->MediaInput().destination.port,
input->MediaInput().destination.id, EventLatency());
SendLatencyChange(input->MediaInput().source,
input->MediaInput().destination, EventLatency());
}
fCore->Unlock();
}
status_t
AudioMixer::CreateBufferGroup(BBufferGroup** buffer) const
{
int32 count = int32(fDownstreamLatency / BufferDuration()) + 2;
TRACE("AudioMixer::CreateBufferGroup: fDownstreamLatency %lld, "
"BufferDuration %lld, buffer count = %ld\n", fDownstreamLatency,
BufferDuration(), count);
if (count < 3)
count = 3;
fCore->Lock();
uint32 size = fCore->Output()->MediaOutput().format.u.raw_audio.buffer_size;
fCore->Unlock();
TRACE("AudioMixer: allocating %ld buffers of %ld bytes each\n",
count, size);
BBufferGroup* buf = new BBufferGroup(size, count);
if (buf == NULL)
return B_NO_MEMORY;
status_t status = buf->InitCheck();
if (status != B_OK)
delete buf;
else
*buffer = buf;
return status;
}
status_t
AudioMixer::SendBuffer(BBuffer* buffer, MixerOutput* output)
{
return BBufferProducer::SendBuffer(buffer, output->MediaOutput().source,
output->MediaOutput().destination);
}
float
AudioMixer::dB_to_Gain(float db)
{
TRACE("dB_to_Gain: dB in: %01.2f ", db);
if (db > 0) {
db = db * (pow(abs(DB_MAX), (1.0 / DB_EXPONENT_POSITIVE))
/ abs(DB_MAX));
db = pow(db, DB_EXPONENT_POSITIVE);
} else {
db = -db;
db = db * (pow(abs(DB_MIN), (1.0 / DB_EXPONENT_NEGATIVE))
/ abs(DB_MIN));
db = pow(db, DB_EXPONENT_NEGATIVE);
db = -db;
}
TRACE("dB out: %01.2f\n", db);
return pow(10.0, db / 20.0);
}
float
AudioMixer::Gain_to_dB(float gain)
{
float db;
db = 20.0 * log10(gain);
if (db > 0) {
db = pow(db, (1.0 / DB_EXPONENT_POSITIVE));
db = db * (abs(DB_MAX) / pow(abs(DB_MAX),
(1.0 / DB_EXPONENT_POSITIVE)));
} else {
db = -db;
db = pow(db, (1.0 / DB_EXPONENT_NEGATIVE));
db = db * (abs(DB_MIN) / pow(abs(DB_MIN),
(1.0 / DB_EXPONENT_NEGATIVE)));
db = -db;
}
return db;
}
status_t
AudioMixer::GetParameterValue(int32 id, bigtime_t *last_change, void *value,
size_t *ioSize)
{
TRACE("GetParameterValue: id 0x%08lx, ioSize %ld\n", id, *ioSize);
int param = PARAM(id);
fCore->Lock();
if (PARAM_IS_ETC(id)) {
switch (ETC(id)) {
case 10:
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = fCore->Settings()->AttenuateOutput();
break;
case 30:
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = fCore->Settings()->UseBalanceControl();
break;
case 40:
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = fCore->Settings()->AllowOutputChannelRemapping();
break;
case 50:
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = fCore->Settings()->AllowInputChannelRemapping();
break;
case 60:
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = fCore->Settings()->InputGainControls();
break;
case 70:
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = fCore->Settings()->ResamplingAlgorithm();
break;
case 80:
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = fCore->Settings()->RefuseOutputFormatChange();
break;
case 90:
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = fCore->Settings()->RefuseInputFormatChange();
break;
default:
ERROR("unhandled ETC 0x%08lx\n", id);
break;
}
} else if (param == 0) {
MixerOutput *output = fCore->Output();
if (!output || (!PARAM_IS_MUTE(id) && !PARAM_IS_GAIN(id) && !PARAM_IS_SRC_ENABLE(id) && !PARAM_IS_SRC_GAIN(id) && !PARAM_IS_BALANCE(id)))
goto err;
if (PARAM_IS_MUTE(id)) {
if (*ioSize < sizeof(int32))
goto err;
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = output->IsMuted();
}
if (PARAM_IS_GAIN(id)) {
if (fCore->Settings()->UseBalanceControl() && output->GetOutputChannelCount() == 2 && 1 ) {
if (*ioSize < sizeof(float))
goto err;
*ioSize = sizeof(float);
static_cast<float *>(value)[0] = GAIN_TO_DB((output->GetOutputChannelGain(0) + output->GetOutputChannelGain(1)) / 2);
} else {
if (*ioSize == sizeof(float)) {
float gain = 0;
for (int channel = 0;
channel < output->GetOutputChannelCount();
channel++) {
gain += GAIN_TO_DB(
output->GetOutputChannelGain(channel));
}
static_cast<float *>(value)[0] = gain
/ output->GetOutputChannelCount();
} else {
if (*ioSize < output->GetOutputChannelCount()
* sizeof(float))
goto err;
*ioSize = output->GetOutputChannelCount() * sizeof(float);
for (int channel = 0;
channel < output->GetOutputChannelCount();
channel++) {
static_cast<float *>(value)[channel]
= GAIN_TO_DB(output->GetOutputChannelGain(channel));
}
}
}
}
if (PARAM_IS_BALANCE(id)) {
float l = output->GetOutputChannelGain(0);
float r = output->GetOutputChannelGain(1);
float v = r / (l+r);
TRACE("balance l %1.3f, r %1.3f, v %1.3f\n",l,r,v);
if (*ioSize < sizeof(float))
goto err;
*ioSize = sizeof(float);
static_cast<float *>(value)[0] = v * 100;
}
if (PARAM_IS_SRC_ENABLE(id)) {
if (*ioSize < sizeof(int32))
goto err;
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = output->HasOutputChannelSource(PARAM_CHAN(id), PARAM_SRC(id));
}
if (PARAM_IS_SRC_GAIN(id)) {
if (*ioSize < sizeof(float))
goto err;
*ioSize = sizeof(float);
static_cast<float *>(value)[0] = GAIN_TO_PERCENT(output->GetOutputChannelSourceGain(PARAM_CHAN(id), PARAM_SRC(id)));
}
} else {
MixerInput *input;
for (int i = 0; (input = fCore->Input(i)); i++)
if (input->ID() == param)
break;
if (!input || (!PARAM_IS_MUTE(id) && !PARAM_IS_GAIN(id) && !PARAM_IS_DST_ENABLE(id) && !PARAM_IS_BALANCE(id)))
goto err;
if (PARAM_IS_MUTE(id)) {
if (*ioSize < sizeof(int32))
goto err;
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = !input->IsEnabled();
}
if (PARAM_IS_GAIN(id)) {
if (fCore->Settings()->InputGainControls() == 0) {
if (fCore->Settings()->UseBalanceControl() && input->GetInputChannelCount() == 2 && 1 ) {
if (*ioSize < sizeof(float))
goto err;
*ioSize = sizeof(float);
static_cast<float *>(value)[0] = GAIN_TO_DB((input->GetInputChannelGain(0) + input->GetInputChannelGain(1)) / 2);
} else {
if (*ioSize < input->GetInputChannelCount() * sizeof(float))
goto err;
*ioSize = input->GetInputChannelCount() * sizeof(float);
for (int chan = 0; chan < input->GetInputChannelCount(); chan++)
static_cast<float *>(value)[chan] = GAIN_TO_DB(input->GetInputChannelGain(chan));
}
} else {
if (fCore->Settings()->UseBalanceControl() && input->GetMixerChannelCount() == 2 && 1 ) {
if (*ioSize < sizeof(float))
goto err;
*ioSize = sizeof(float);
static_cast<float *>(value)[0] = GAIN_TO_DB((input->GetMixerChannelGain(0) + input->GetMixerChannelGain(1)) / 2);
} else {
if (*ioSize < input->GetMixerChannelCount() * sizeof(float))
goto err;
*ioSize = input->GetMixerChannelCount() * sizeof(float);
for (int chan = 0; chan < input->GetMixerChannelCount(); chan++)
static_cast<float *>(value)[chan] = GAIN_TO_DB(input->GetMixerChannelGain(chan));
}
}
}
if (PARAM_IS_BALANCE(id)) {
if (fCore->Settings()->InputGainControls() == 0) {
float l = input->GetInputChannelGain(0);
float r = input->GetInputChannelGain(1);
float v = r / (l+r);
TRACE("balance l %1.3f, r %1.3f, v %1.3f\n",l,r,v);
if (*ioSize < sizeof(float))
goto err;
*ioSize = sizeof(float);
static_cast<float *>(value)[0] = v * 100;
} else {
float l = input->GetMixerChannelGain(0);
float r = input->GetMixerChannelGain(1);
float v = r / (l+r);
TRACE("balance l %1.3f, r %1.3f, v %1.3f\n",l,r,v);
if (*ioSize < sizeof(float))
goto err;
*ioSize = sizeof(float);
static_cast<float *>(value)[0] = v * 100;
}
}
if (PARAM_IS_DST_ENABLE(id)) {
if (*ioSize < sizeof(int32))
goto err;
*ioSize = sizeof(int32);
static_cast<int32 *>(value)[0] = input->HasInputChannelDestination(PARAM_CHAN(id), PARAM_DST(id));
}
}
*last_change = TimeSource()->Now();
fCore->Unlock();
return B_OK;
err:
fCore->Unlock();
return B_ERROR;
}
void
AudioMixer::SetParameterValue(int32 id, bigtime_t when, const void *value,
size_t size)
{
TRACE("SetParameterValue: id 0x%08lx, size %ld\n", id, size);
bool update = false;
int param = PARAM(id);
fCore->Lock();
if (PARAM_IS_ETC(id)) {
switch (ETC(id)) {
case 10:
if (size != sizeof(int32))
goto err;
fCore->Settings()->SetAttenuateOutput(static_cast<const int32 *>(value)[0]);
fCore->SetOutputAttenuation((static_cast<const int32 *>(value)[0]) ? 0.708 : 1.0);
break;
case 30:
if (size != sizeof(int32))
goto err;
fCore->Settings()->SetUseBalanceControl(static_cast<const int32 *>(value)[0]);
update = true;
break;
case 40:
if (size != sizeof(int32))
goto err;
fCore->Settings()->SetAllowOutputChannelRemapping(static_cast<const int32 *>(value)[0]);
update = true;
break;
case 50:
if (size != sizeof(int32))
goto err;
fCore->Settings()->SetAllowInputChannelRemapping(static_cast<const int32 *>(value)[0]);
update = true;
break;
case 60:
if (size != sizeof(int32))
goto err;
fCore->Settings()->SetInputGainControls(static_cast<const int32 *>(value)[0]);
update = true;
break;
case 70:
if (size != sizeof(int32))
goto err;
fCore->Settings()->SetResamplingAlgorithm(static_cast<const int32 *>(value)[0]);
fCore->UpdateResamplingAlgorithm();
break;
case 80:
if (size != sizeof(int32))
goto err;
fCore->Settings()->SetRefuseOutputFormatChange(static_cast<const int32 *>(value)[0]);
break;
case 90:
if (size != sizeof(int32))
goto err;
fCore->Settings()->SetRefuseInputFormatChange(static_cast<const int32 *>(value)[0]);
break;
default:
ERROR("unhandled ETC 0x%08lx\n", id);
break;
}
} else if (param == 0) {
MixerOutput *output = fCore->Output();
if (!output || (!PARAM_IS_MUTE(id) && !PARAM_IS_GAIN(id) && !PARAM_IS_SRC_ENABLE(id) && !PARAM_IS_SRC_GAIN(id) && !PARAM_IS_BALANCE(id)))
goto err;
if (PARAM_IS_MUTE(id)) {
if (size != sizeof(int32))
goto err;
output->SetMuted(static_cast<const int32 *>(value)[0]);
}
if (PARAM_IS_GAIN(id)) {
if (fCore->Settings()->UseBalanceControl()
&& output->GetOutputChannelCount() == 2 && 1 ) {
float l = output->GetOutputChannelGain(0);
float r = output->GetOutputChannelGain(1);
float m = (l + r) / 2;
float v = DB_TO_GAIN(static_cast<const float *>(value)[0]);
float f = v / m;
TRACE("gain set l %1.3f, r %1.3f, m %1.3f, v %1.3f, f %1.3f\n",l,r,m,v,f);
output->SetOutputChannelGain(0, output->GetOutputChannelGain(0) * f);
output->SetOutputChannelGain(1, output->GetOutputChannelGain(1) * f);
} else {
if (size == sizeof(float)) {
float gain = static_cast<const float *>(value)[0];
for (int channel = 0;
channel < output->GetOutputChannelCount();
channel++) {
output->SetOutputChannelGain(channel,
DB_TO_GAIN(gain));
}
} else {
if (size < output->GetOutputChannelCount() * sizeof(float))
goto err;
for (int channel = 0;
channel < output->GetOutputChannelCount();
channel++) {
output->SetOutputChannelGain(channel,
DB_TO_GAIN(static_cast<const float *>(
value)[channel]));
}
}
}
}
if (PARAM_IS_BALANCE(id)) {
float l = output->GetOutputChannelGain(0);
float r = output->GetOutputChannelGain(1);
float m = (l + r) / 2;
float v = static_cast<const float *>(value)[0] / 100;
float fl = 2 * (1 - v);
float fr = 2 * v;
TRACE("balance set l %1.3f, r %1.3f, m %1.3f, v %1.3f, fl %1.3f, fr %1.3f\n",l,r,m,v,fl,fr);
output->SetOutputChannelGain(0, m * fl);
output->SetOutputChannelGain(1, m * fr);
}
if (PARAM_IS_SRC_ENABLE(id)) {
if (size != sizeof(int32))
goto err;
if (static_cast<const int32 *>(value)[0]) {
output->AddOutputChannelSource(PARAM_CHAN(id), PARAM_SRC(id));
} else {
output->RemoveOutputChannelSource(PARAM_CHAN(id), PARAM_SRC(id));
}
}
if (PARAM_IS_SRC_GAIN(id)) {
if (size != sizeof(float))
goto err;
output->SetOutputChannelSourceGain(PARAM_CHAN(id), PARAM_SRC(id), PERCENT_TO_GAIN(static_cast<const float *>(value)[0]));
}
fCore->Settings()->SaveConnectionSettings(output);
} else {
MixerInput *input;
for (int i = 0; (input = fCore->Input(i)); i++)
if (input->ID() == param)
break;
if (!input || (!PARAM_IS_MUTE(id) && !PARAM_IS_GAIN(id) && !PARAM_IS_DST_ENABLE(id) && !PARAM_IS_BALANCE(id)))
goto err;
if (PARAM_IS_MUTE(id)) {
if (size != sizeof(int32))
goto err;
input->SetEnabled(!static_cast<const int32 *>(value)[0]);
}
if (PARAM_IS_GAIN(id)) {
if (fCore->Settings()->InputGainControls() == 0) {
if (fCore->Settings()->UseBalanceControl() && input->GetInputChannelCount() == 2 && 1 ) {
float l = input->GetInputChannelGain(0);
float r = input->GetInputChannelGain(1);
float m = (l + r) / 2;
float v = DB_TO_GAIN(static_cast<const float *>(value)[0]);
float f = v / m;
TRACE("gain set l %1.3f, r %1.3f, m %1.3f, v %1.3f, f %1.3f\n",l,r,m,v,f);
input->SetInputChannelGain(0, input->GetInputChannelGain(0) * f);
input->SetInputChannelGain(1, input->GetInputChannelGain(1) * f);
} else {
if (size < input->GetInputChannelCount() * sizeof(float))
goto err;
for (int chan = 0; chan < input->GetInputChannelCount(); chan++)
input->SetInputChannelGain(chan, DB_TO_GAIN(static_cast<const float *>(value)[chan]));
}
} else {
if (fCore->Settings()->UseBalanceControl() && input->GetMixerChannelCount() == 2 && 1 ) {
float l = input->GetMixerChannelGain(0);
float r = input->GetMixerChannelGain(1);
float m = (l + r) / 2;
float v = DB_TO_GAIN(static_cast<const float *>(value)[0]);
float f = v / m;
TRACE("gain set l %1.3f, r %1.3f, m %1.3f, v %1.3f, f %1.3f\n",l,r,m,v,f);
input->SetMixerChannelGain(0, input->GetMixerChannelGain(0) * f);
input->SetMixerChannelGain(1, input->GetMixerChannelGain(1) * f);
} else {
if (size < input->GetMixerChannelCount() * sizeof(float))
goto err;
for (int chan = 0; chan < input->GetMixerChannelCount(); chan++)
input->SetMixerChannelGain(chan, DB_TO_GAIN(static_cast<const float *>(value)[chan]));
}
}
}
if (PARAM_IS_BALANCE(id)) {
if (fCore->Settings()->InputGainControls() == 0) {
float l = input->GetInputChannelGain(0);
float r = input->GetInputChannelGain(1);
float m = (l + r) / 2;
float v = static_cast<const float *>(value)[0] / 100;
float fl = 2 * (1 - v);
float fr = 2 * v;
TRACE("balance set l %1.3f, r %1.3f, m %1.3f, v %1.3f, fl %1.3f, fr %1.3f\n",l,r,m,v,fl,fr);
input->SetInputChannelGain(0, m * fl);
input->SetInputChannelGain(1, m * fr);
} else {
float l = input->GetMixerChannelGain(0);
float r = input->GetMixerChannelGain(1);
float m = (l + r) / 2;
float v = static_cast<const float *>(value)[0] / 100;
float fl = 2 * (1 - v);
float fr = 2 * v;
TRACE("balance set l %1.3f, r %1.3f, m %1.3f, v %1.3f, fl %1.3f, fr %1.3f\n",l,r,m,v,fl,fr);
input->SetMixerChannelGain(0, m * fl);
input->SetMixerChannelGain(1, m * fr);
}
}
if (PARAM_IS_DST_ENABLE(id)) {
if (size != sizeof(int32))
goto err;
if (static_cast<const int32 *>(value)[0]) {
int oldchan = input->GetInputChannelForDestination(PARAM_DST(id));
if (oldchan != -1) {
input->RemoveInputChannelDestination(oldchan, PARAM_DST(id));
int32 null = 0;
BroadcastNewParameterValue(when, PARAM_DST_ENABLE(PARAM(id), oldchan, PARAM_DST(id)), &null, sizeof(null));
}
input->AddInputChannelDestination(PARAM_CHAN(id), PARAM_DST(id));
} else {
input->RemoveInputChannelDestination(PARAM_CHAN(id), PARAM_DST(id));
}
BroadcastChangedParameter(PARAM_GAIN(PARAM(id)));
update = true;
}
fCore->Settings()->SaveConnectionSettings(input);
}
BroadcastNewParameterValue(when, id, const_cast<void *>(value), size);
err:
fCore->Unlock();
if (update)
UpdateParameterWeb();
}
void
AudioMixer::UpdateParameterWeb()
{
fCore->Lock();
BParameterWeb *web = new BParameterWeb();
BParameterGroup *top;
BParameterGroup *outputchannels;
BParameterGroup *inputchannels;
BParameterGroup *group;
BParameterGroup *subgroup;
BParameterGroup *subsubgroup;
BDiscreteParameter *dp;
MixerInput *in;
MixerOutput *out;
char buf[50];
top = web->MakeGroup(B_TRANSLATE("Gain controls"));
out = fCore->Output();
group = top->MakeGroup("");
group->MakeNullParameter(PARAM_STR1(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Master output"), B_WEB_BUFFER_INPUT);
if (!out) {
group->MakeNullParameter(PARAM_STR2(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("not connected"), B_GENERIC);
} else {
group->MakeNullParameter(PARAM_STR2(0), B_MEDIA_RAW_AUDIO,
StringForFormat(buf, out), B_GENERIC);
group->MakeDiscreteParameter(PARAM_MUTE(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Mute"), B_MUTE);
if (fCore->Settings()->UseBalanceControl()
&& out->GetOutputChannelCount() == 2 && 1
) {
group->MakeContinuousParameter(PARAM_GAIN(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Gain"), B_MASTER_GAIN, B_TRANSLATE("dB"),
DB_MIN, DB_MAX, 0.1);
group->MakeContinuousParameter(PARAM_BALANCE(0), B_MEDIA_RAW_AUDIO,
"", B_BALANCE, "", 0, 100, 1);
} else {
group->MakeContinuousParameter(PARAM_GAIN(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Gain"), B_MASTER_GAIN, B_TRANSLATE("dB"),
DB_MIN, DB_MAX, 0.1)
->SetChannelCount(out->GetOutputChannelCount());
}
group->MakeNullParameter(PARAM_STR3(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("To output"), B_WEB_BUFFER_OUTPUT);
}
for (int i = 0; (in = fCore->Input(i)); i++) {
group = top->MakeGroup("");
group->MakeNullParameter(PARAM_STR1(in->ID()), B_MEDIA_RAW_AUDIO,
in->MediaInput().name, B_WEB_BUFFER_INPUT);
group->MakeNullParameter(PARAM_STR2(in->ID()), B_MEDIA_RAW_AUDIO,
StringForFormat(buf, in), B_GENERIC);
group->MakeDiscreteParameter(PARAM_MUTE(in->ID()), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Mute"), B_MUTE);
if (fCore->Settings()->InputGainControls() == 0) {
if (fCore->Settings()->UseBalanceControl()
&& in->GetInputChannelCount() == 2 && 1
) {
group->MakeContinuousParameter(PARAM_GAIN(in->ID()),
B_MEDIA_RAW_AUDIO, B_TRANSLATE("Gain"), B_GAIN,
B_TRANSLATE("dB"), DB_MIN, DB_MAX, 0.1);
group->MakeContinuousParameter(PARAM_BALANCE(in->ID()),
B_MEDIA_RAW_AUDIO, "", B_BALANCE, "", 0, 100, 1);
} else {
group->MakeContinuousParameter(PARAM_GAIN(in->ID()),
B_MEDIA_RAW_AUDIO, B_TRANSLATE("Gain"), B_GAIN,
B_TRANSLATE("dB"), DB_MIN, DB_MAX, 0.1)
->SetChannelCount(in->GetInputChannelCount());
}
} else {
if (fCore->Settings()->UseBalanceControl()
&& in->GetMixerChannelCount() == 2 && 1
) {
group->MakeContinuousParameter(PARAM_GAIN(in->ID()),
B_MEDIA_RAW_AUDIO, B_TRANSLATE("Gain"), B_GAIN,
B_TRANSLATE("dB"), DB_MIN, DB_MAX, 0.1);
group->MakeContinuousParameter(PARAM_BALANCE(in->ID()),
B_MEDIA_RAW_AUDIO, "", B_BALANCE, "", 0, 100, 1);
} else {
group->MakeContinuousParameter(PARAM_GAIN(in->ID()),
B_MEDIA_RAW_AUDIO, B_TRANSLATE("Gain"), B_GAIN,
B_TRANSLATE("dB"), DB_MIN, DB_MAX, 0.1)
->SetChannelCount(in->GetMixerChannelCount());
}
}
group->MakeNullParameter(PARAM_STR3(in->ID()), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("To master"), B_WEB_BUFFER_OUTPUT);
}
if (fCore->Settings()->AllowOutputChannelRemapping()) {
top = web->MakeGroup(B_TRANSLATE("Output mapping"));
outputchannels = top->MakeGroup("");
outputchannels->MakeNullParameter(PARAM_STR4(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Output channel sources"), B_GENERIC);
group = outputchannels->MakeGroup("");
group->MakeNullParameter(PARAM_STR5(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Master output"), B_GENERIC);
group = group->MakeGroup("");
if (!out) {
group->MakeNullParameter(PARAM_STR6(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("not connected"), B_GENERIC);
} else {
for (int chan = 0; chan < out->GetOutputChannelCount(); chan++) {
subgroup = group->MakeGroup("");
subgroup->MakeNullParameter(PARAM_SRC_STR(0, chan),
B_MEDIA_RAW_AUDIO, StringForChannelType(buf,
out->GetOutputChannelType(chan)), B_GENERIC);
for (int src = 0; src < MAX_CHANNEL_TYPES; src++) {
subsubgroup = subgroup->MakeGroup("");
subsubgroup->MakeDiscreteParameter(
PARAM_SRC_ENABLE(0, chan, src), B_MEDIA_RAW_AUDIO, "",
B_ENABLE);
subsubgroup->MakeContinuousParameter(
PARAM_SRC_GAIN(0, chan, src), B_MEDIA_RAW_AUDIO,
StringForChannelType(buf, src), B_GAIN, "%", 0.0,
100.0, 0.1);
}
}
}
}
if (fCore->Settings()->AllowInputChannelRemapping()) {
top = web->MakeGroup(B_TRANSLATE("Input mapping"));
inputchannels = top->MakeGroup("");
inputchannels->MakeNullParameter(PARAM_STR7(0), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Input channel destinations"), B_GENERIC);
for (int i = 0; (in = fCore->Input(i)); i++) {
group = inputchannels->MakeGroup("");
group->MakeNullParameter(PARAM_STR4(in->ID()), B_MEDIA_RAW_AUDIO,
in->MediaInput().name, B_GENERIC);
group = group->MakeGroup("");
for (int chan = 0; chan < in->GetInputChannelCount(); chan++) {
subgroup = group->MakeGroup("");
subgroup->MakeNullParameter(PARAM_DST_STR(in->ID(), chan),
B_MEDIA_RAW_AUDIO, StringForChannelType(buf,
in->GetInputChannelType(chan)), B_GENERIC);
for (int dst = 0; dst < MAX_CHANNEL_TYPES; dst++) {
subgroup->MakeDiscreteParameter(PARAM_DST_ENABLE(in->ID(),
chan, dst), B_MEDIA_RAW_AUDIO, StringForChannelType(buf, dst),
B_ENABLE);
}
}
}
}
top = web->MakeGroup(B_TRANSLATE("Setup"));
group = top->MakeGroup("");
group->MakeDiscreteParameter(PARAM_ETC(10), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Attenuate mixer output by 3 dB"), B_ENABLE);
group->MakeDiscreteParameter(PARAM_ETC(30), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Display balance control for stereo connections"),
B_ENABLE);
group->MakeDiscreteParameter(PARAM_ETC(40), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Allow output channel remapping"), B_ENABLE);
group->MakeDiscreteParameter(PARAM_ETC(50), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Allow input channel remapping"), B_ENABLE);
dp = group->MakeDiscreteParameter(PARAM_ETC(60), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Input gain controls represent:"), B_INPUT_MUX);
dp->AddItem(0, B_TRANSLATE("Physical input channels"));
dp->AddItem(1, B_TRANSLATE("Virtual output channels"));
dp = group->MakeDiscreteParameter(PARAM_ETC(70), B_MEDIA_RAW_AUDIO,
B_TRANSLATE("Resampling algorithm:"), B_INPUT_MUX);
dp->AddItem(0, B_TRANSLATE("Low quality (drop/repeat samples)"));
dp->AddItem(2, B_TRANSLATE("High quality (linear interpolation)"));
fCore->Unlock();
SetParameterWeb(web);
}