root/src/add-ons/media/media-add-ons/multi_audio/MultiAudioNode.cpp
/*
 * Copyright (c) 2002, 2003 Jerome Duval (jerome.duval@free.fr)
 * Distributed under the terms of the MIT License.
 */


//! Media add-on for drivers that use the multi audio interface


#include "MultiAudioNode.h"

#include <stdio.h>
#include <string.h>

#include <Autolock.h>
#include <Buffer.h>
#include <BufferGroup.h>
#include <Catalog.h>
#include <ParameterWeb.h>
#include <String.h>

#include <Referenceable.h>

#include "MultiAudioUtility.h"
#ifdef DEBUG
#       define PRINTING
#endif
#include "debug.h"
#include "Resampler.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "MultiAudio"

#define PARAMETER_ID_INPUT_FREQUENCY    1
#define PARAMETER_ID_OUTPUT_FREQUENCY   2


// This represents a hardware output.
class node_input {
public:
        node_input(media_input& input, media_format preferredFormat);
        ~node_input();

        int32                           fChannelId;
        media_input                     fInput;
        media_format            fPreferredFormat;
        media_format            fFormat;

        volatile uint32         fBufferCycle;
        int32                           fOldBufferCycle;
        BBuffer*                        fBuffer;
        Resampler                       *fResampler;
};


// This represents a hardware input.
class node_output {
public:
        node_output(media_output& output, media_format preferredFormat);
        ~node_output();

        int32                           fChannelId;
        media_output            fOutput;
        media_format            fPreferredFormat;

        BBufferGroup*           fBufferGroup;
        bool                            fOutputEnabled;
        uint64                          fSamplesSent;
        volatile uint32         fBufferCycle;
        int32                           fOldBufferCycle;
        Resampler*                      fResampler;
};


struct FrameRateChangeCookie : public BReferenceable {
        float   oldFrameRate;
        uint32  id;
};


struct sample_rate_info {
        uint32          multiAudioRate;
        const char*     name;
};


static const sample_rate_info kSampleRateInfos[] = {
        {B_SR_8000,             "8000"},
        {B_SR_11025,    "11025"},
        {B_SR_12000,    "12000"},
        {B_SR_16000,    "16000"},
        {B_SR_22050,    "22050"},
        {B_SR_24000,    "24000"},
        {B_SR_32000,    "32000"},
        {B_SR_44100,    "44100"},
        {B_SR_48000,    "48000"},
        {B_SR_64000,    "64000"},
        {B_SR_88200,    "88200"},
        {B_SR_96000,    "96000"},
        {B_SR_176400,   "176400"},
        {B_SR_192000,   "192000"},
        {B_SR_384000,   "384000"},
        {B_SR_1536000,  "1536000"},
        {}
};


const char* kMultiControlString[] = {
        "NAME IS ATTACHED",
        B_TRANSLATE("Output"), B_TRANSLATE("Input"), B_TRANSLATE("Setup"),
        B_TRANSLATE("Tone control"), B_TRANSLATE("Extended Setup"),
        B_TRANSLATE("Enhanced Setup"), B_TRANSLATE("Master"), B_TRANSLATE("Beep"),
        B_TRANSLATE("Phone"), B_TRANSLATE("Mic"), B_TRANSLATE("Line"),
        B_TRANSLATE("CD"), B_TRANSLATE("Video"), B_TRANSLATE("Aux"),
        B_TRANSLATE("Wave"), B_TRANSLATE("Gain"), B_TRANSLATE("Level"),
        B_TRANSLATE("Volume"), B_TRANSLATE("Mute"), B_TRANSLATE("Enable"),
        B_TRANSLATE("Stereo mix"), B_TRANSLATE("Mono mix"),
        B_TRANSLATE("Output stereo mix"), B_TRANSLATE("Output mono mix"),
        B_TRANSLATE("Output bass"), B_TRANSLATE("Output treble"),
        B_TRANSLATE("Output 3D center"), B_TRANSLATE("Output 3D depth"),
        B_TRANSLATE("Headphones"), B_TRANSLATE("SPDIF")
};


//      #pragma mark -


node_input::node_input(media_input& input, media_format preferredFormat)
{
        CALLED();
        fInput = input;
        fPreferredFormat = preferredFormat;
        fBufferCycle = 1;
        fOldBufferCycle = -1;
        fBuffer = NULL;
        fResampler = NULL;
}


node_input::~node_input()
{
        CALLED();
}


//      #pragma mark -


node_output::node_output(media_output& output, media_format preferredFormat)
        :
        fBufferGroup(NULL),
        fOutputEnabled(true)
{
        CALLED();
        fOutput = output;
        fPreferredFormat = preferredFormat;
        fBufferCycle = 1;
        fOldBufferCycle = -1;
        fResampler = NULL;
}


node_output::~node_output()
{
        CALLED();
}


//      #pragma mark -


MultiAudioNode::MultiAudioNode(BMediaAddOn* addon, const char* name,
                MultiAudioDevice* device, int32 internalID, BMessage* config)
        :
        BMediaNode(name),
        BBufferConsumer(B_MEDIA_RAW_AUDIO),
        BBufferProducer(B_MEDIA_RAW_AUDIO),
        BMediaEventLooper(),
        fBufferLock("multi audio buffers"),
        fQuitThread(0),
        fThread(-1),
        fDevice(device),
        fTimeSourceStarted(false),
        fWeb(NULL),
        fConfig()
{
        CALLED();
        fInitStatus = B_NO_INIT;

        if (!device)
                return;

        fAddOn = addon;
        fId = internalID;

        if (fDevice->Description().output_channel_count > 0) {
                // initialize our preferred format objects
                fOutputPreferredFormat.type = B_MEDIA_RAW_AUDIO;
                fOutputPreferredFormat.u.raw_audio.format
                        = MultiAudio::convert_to_media_format(
                                fDevice->FormatInfo().output.format);
                fOutputPreferredFormat.u.raw_audio.valid_bits
                        = MultiAudio::convert_to_valid_bits(
                                fDevice->FormatInfo().output.format);
                fOutputPreferredFormat.u.raw_audio.channel_count = 2;
                fOutputPreferredFormat.u.raw_audio.frame_rate
                        = MultiAudio::convert_to_sample_rate(fDevice->FormatInfo().output.rate);
                        // measured in Hertz
                fOutputPreferredFormat.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN;

                if (fOutputPreferredFormat.u.raw_audio.format != 0) {
                        AddNodeKind(B_PHYSICAL_OUTPUT);

                        // we'll use the consumer's preferred buffer size, if any
                        fOutputPreferredFormat.u.raw_audio.buffer_size
                                = fDevice->BufferList().return_playback_buffer_size
                                        * (fOutputPreferredFormat.u.raw_audio.format
                                                        & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                                        * fOutputPreferredFormat.u.raw_audio.channel_count;
                }
        }

        if (fDevice->Description().input_channel_count > 0) {
                // initialize our preferred format objects
                fInputPreferredFormat.type = B_MEDIA_RAW_AUDIO;
                fInputPreferredFormat.u.raw_audio.format
                        = MultiAudio::convert_to_media_format(
                                fDevice->FormatInfo().input.format);
                fInputPreferredFormat.u.raw_audio.valid_bits
                        = MultiAudio::convert_to_valid_bits(fDevice->FormatInfo().input.format);
                fInputPreferredFormat.u.raw_audio.channel_count
                        = fDevice->Description().input_channel_count;
                fInputPreferredFormat.u.raw_audio.frame_rate
                        = MultiAudio::convert_to_sample_rate(fDevice->FormatInfo().input.rate);
                        // measured in Hertz
                fInputPreferredFormat.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN;

                if (fInputPreferredFormat.u.raw_audio.format != 0) {
                        AddNodeKind(B_PHYSICAL_INPUT);

                        // we'll use the consumer's preferred buffer size, if any
                        fInputPreferredFormat.u.raw_audio.buffer_size
                                = fDevice->BufferList().return_record_buffer_size
                                        * (fInputPreferredFormat.u.raw_audio.format
                                                        & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                                        * fInputPreferredFormat.u.raw_audio.channel_count;
                }
        }

        if (config != NULL) {
                fConfig = *config;
                PRINT_OBJECT(*config);
        }

        fInitStatus = B_OK;
}


MultiAudioNode::~MultiAudioNode()
{
        CALLED();
        fAddOn->GetConfigurationFor(this, NULL);

        _StopOutputThread();
        BMediaEventLooper::Quit();
}


status_t
MultiAudioNode::InitCheck() const
{
        CALLED();
        return fInitStatus;
}


void
MultiAudioNode::GetFlavor(flavor_info* info, int32 id)
{
        CALLED();
        if (info == NULL)
                return;

        info->flavor_flags = 0;
        info->possible_count = 1;
                // one flavor at a time
        info->in_format_count = 0;
                // no inputs
        info->in_formats = 0;
        info->out_format_count = 0;
                // no outputs
        info->out_formats = 0;
        info->internal_id = id;

        info->name = const_cast<char*>("MultiAudioNode Node");
        info->info = const_cast<char*>("The MultiAudioNode node outputs to "
                "multi_audio drivers.");
        info->kinds = B_BUFFER_CONSUMER | B_BUFFER_PRODUCER | B_TIME_SOURCE
                | B_PHYSICAL_OUTPUT | B_PHYSICAL_INPUT | B_CONTROLLABLE;
        info->in_format_count = 1;
                // 1 input
        media_format* inFormats = new media_format[info->in_format_count];
        GetFormat(&inFormats[0]);
        info->in_formats = inFormats;

        info->out_format_count = 1;
                // 1 output
        media_format* outFormats = new media_format[info->out_format_count];
        GetFormat(&outFormats[0]);
        info->out_formats = outFormats;
}


void
MultiAudioNode::GetFormat(media_format* format)
{
        CALLED();
        if (format == NULL)
                return;

        format->type = B_MEDIA_RAW_AUDIO;
        format->require_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
        format->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
        format->u.raw_audio = media_raw_audio_format::wildcard;
}


//#pragma mark - BMediaNode


BMediaAddOn*
MultiAudioNode::AddOn(int32* _internalID) const
{
        CALLED();
        // BeBook says this only gets called if we were in an add-on.
        if (fAddOn != 0 && _internalID != NULL)
                *_internalID = fId;

        return fAddOn;
}


void
MultiAudioNode::Preroll()
{
        CALLED();
        // TODO: Performance opportunity
        BMediaNode::Preroll();
}


status_t
MultiAudioNode::HandleMessage(int32 message, const void* data, size_t size)
{
        CALLED();
        return B_ERROR;
}


void
MultiAudioNode::NodeRegistered()
{
        CALLED();

        if (fInitStatus != B_OK) {
                ReportError(B_NODE_IN_DISTRESS);
                return;
        }

        node_input *currentInput = NULL;
        int32 currentId = 0;

        for (int32 i = 0; i < fDevice->Description().output_channel_count; i++) {
                if (currentInput == NULL
                        || (fDevice->Description().channels[i].designations
                                        & B_CHANNEL_MONO_BUS) != 0
                        || ((fDevice->Description().channels[currentId].designations
                                        & B_CHANNEL_STEREO_BUS) != 0
                                && ((fDevice->Description().channels[i].designations
                                                & B_CHANNEL_LEFT) != 0
                                        || (fDevice->Description().channels[i].designations
                                                & B_CHANNEL_STEREO_BUS) == 0))
                        || ((fDevice->Description().channels[currentId].designations
                                        & B_CHANNEL_SURROUND_BUS) != 0
                                && ((fDevice->Description().channels[i].designations
                                                & B_CHANNEL_LEFT) != 0
                                        || (fDevice->Description().channels[i].designations
                                                & B_CHANNEL_SURROUND_BUS) == 0))) {
                        PRINT(("NodeRegistered(): creating an input for %" B_PRIi32 "\n",
                                i));
                        PRINT(("%" B_PRId32 "\t%d\t0x%" B_PRIx32 "\t0x%" B_PRIx32 "\n",
                                fDevice->Description().channels[i].channel_id,
                                fDevice->Description().channels[i].kind,
                                fDevice->Description().channels[i].designations,
                                fDevice->Description().channels[i].connectors));

                        media_input* input = new media_input;

                        input->format = fOutputPreferredFormat;
                        input->destination.port = ControlPort();
                        input->destination.id = fInputs.CountItems();
                        input->node = Node();
                        sprintf(input->name, "output %" B_PRId32, input->destination.id);

                        currentInput = new node_input(*input, fOutputPreferredFormat);
                        currentInput->fPreferredFormat.u.raw_audio.channel_count = 1;
                        currentInput->fFormat = currentInput->fPreferredFormat;
                        currentInput->fInput.format = currentInput->fPreferredFormat;

                        delete currentInput->fResampler;
                        currentInput->fResampler = new
                                Resampler(currentInput->fPreferredFormat.AudioFormat(),
                                        fOutputPreferredFormat.AudioFormat());

                        currentInput->fChannelId
                                = fDevice->Description().channels[i].channel_id;
                        fInputs.AddItem(currentInput);

                        currentId = i;
                } else {
                        PRINT(("NodeRegistered(): adding a channel\n"));
                        currentInput->fPreferredFormat.u.raw_audio.channel_count++;
                        currentInput->fFormat = currentInput->fPreferredFormat;
                        currentInput->fInput.format = currentInput->fPreferredFormat;
                }
                currentInput->fInput.format.u.raw_audio.format
                        = media_raw_audio_format::wildcard.format;
        }

        node_output *currentOutput = NULL;
        currentId = 0;

        for (int32 i = fDevice->Description().output_channel_count;
                        i < fDevice->Description().output_channel_count
                                + fDevice->Description().input_channel_count; i++) {
                if (currentOutput == NULL
                        || (fDevice->Description().channels[i].designations
                                        & B_CHANNEL_MONO_BUS) != 0
                        || ((fDevice->Description().channels[currentId].designations
                                        & B_CHANNEL_STEREO_BUS) != 0
                                && ((fDevice->Description().channels[i].designations
                                                & B_CHANNEL_LEFT) != 0
                                        || (fDevice->Description().channels[i].designations
                                                & B_CHANNEL_STEREO_BUS) == 0))
                        || ((fDevice->Description().channels[currentId].designations
                                        & B_CHANNEL_SURROUND_BUS) != 0
                                && ((fDevice->Description().channels[i].designations
                                                & B_CHANNEL_LEFT) != 0
                                        || (fDevice->Description().channels[i].designations
                                                & B_CHANNEL_SURROUND_BUS) == 0))) {
                        PRINT(("NodeRegistered(): creating an output for %" B_PRIi32 "\n",
                                i));
                        PRINT(("%" B_PRId32 "\t%d\t0x%" B_PRIx32 "\t0x%" B_PRIx32 "\n",
                                fDevice->Description().channels[i].channel_id,
                                fDevice->Description().channels[i].kind,
                                fDevice->Description().channels[i].designations,
                                fDevice->Description().channels[i].connectors));

                        media_output *output = new media_output;

                        output->format = fInputPreferredFormat;
                        output->destination = media_destination::null;
                        output->source.port = ControlPort();
                        output->source.id = fOutputs.CountItems();
                        output->node = Node();
                        sprintf(output->name, "input %" B_PRId32, output->source.id);

                        currentOutput = new node_output(*output, fInputPreferredFormat);
                        currentOutput->fPreferredFormat.u.raw_audio.channel_count = 1;
                        currentOutput->fOutput.format = currentOutput->fPreferredFormat;

                        delete currentOutput->fResampler;
                        currentOutput->fResampler = new
                                Resampler(fInputPreferredFormat.AudioFormat(),
                                        currentOutput->fPreferredFormat.AudioFormat());

                        currentOutput->fChannelId
                                = fDevice->Description().channels[i].channel_id;
                        fOutputs.AddItem(currentOutput);

                        currentId = i;
                } else {
                        PRINT(("NodeRegistered(): adding a channel\n"));
                        currentOutput->fPreferredFormat.u.raw_audio.channel_count++;
                        currentOutput->fOutput.format = currentOutput->fPreferredFormat;
                }
        }

        // Set up our parameter web
        fWeb = MakeParameterWeb();
        SetParameterWeb(fWeb);

        // Apply configuration
#ifdef PRINTING
        bigtime_t start = system_time();
#endif

        int32 index = 0;
        int32 parameterID = 0;
        const void *data;
        ssize_t size;
        while (fConfig.FindInt32("parameterID", index, &parameterID) == B_OK) {
                if (fConfig.FindData("parameterData", B_RAW_TYPE, index, &data, &size)
                                == B_OK) {
                        SetParameterValue(parameterID, TimeSource()->Now(), data, size);
                }
                index++;
        }

        PRINT(("apply configuration in: %" B_PRIdBIGTIME "\n",
                system_time() - start));

        SetPriority(B_REAL_TIME_PRIORITY);
        Run();
}


status_t
MultiAudioNode::RequestCompleted(const media_request_info& info)
{
        CALLED();

        if (info.what != media_request_info::B_REQUEST_FORMAT_CHANGE)
                return B_OK;

        FrameRateChangeCookie* cookie
                = (FrameRateChangeCookie*)info.user_data;
        if (cookie == NULL)
                return B_OK;

        BReference<FrameRateChangeCookie> cookieReference(cookie, true);

        // if the request failed, we reset the frame rate
        if (info.status != B_OK) {
                if (cookie->id == PARAMETER_ID_INPUT_FREQUENCY) {
                        _SetNodeInputFrameRate(cookie->oldFrameRate);
                        if (fDevice->Description().output_rates & B_SR_SAME_AS_INPUT)
                                _SetNodeOutputFrameRate(cookie->oldFrameRate);
                } else if (cookie->id == PARAMETER_ID_OUTPUT_FREQUENCY)
                        _SetNodeOutputFrameRate(cookie->oldFrameRate);

                // TODO: If we have multiple connections, we should request to change
                // the format back!
        }

        return B_OK;
}


void
MultiAudioNode::SetTimeSource(BTimeSource* timeSource)
{
        CALLED();
}


//      #pragma mark - BBufferConsumer


status_t
MultiAudioNode::AcceptFormat(const media_destination& dest,
        media_format* format)
{
        CALLED();

        // Check to make sure the format is okay, then remove
        // any wildcards corresponding to our requirements.
        if (format == NULL)
                return B_BAD_VALUE;

        node_input *channel = _FindInput(dest);
        if (channel == NULL)
                return B_MEDIA_BAD_DESTINATION;

        if (!format_is_compatible(*format, channel->fPreferredFormat))
                return B_MEDIA_BAD_FORMAT;

        format->SpecializeTo(&channel->fPreferredFormat);
        return B_OK;
}


status_t
MultiAudioNode::GetNextInput(int32* cookie, media_input* _input)
{
        CALLED();
        if (_input == NULL)
                return B_BAD_VALUE;

        if (*cookie >= fInputs.CountItems() || *cookie < 0)
                return B_BAD_INDEX;

        node_input* channel = (node_input*)fInputs.ItemAt(*cookie);
        *_input = channel->fInput;
        *cookie += 1;
        PRINT(("input.format: %" B_PRIu32 "\n",
                channel->fInput.format.u.raw_audio.format));
        return B_OK;
}


void
MultiAudioNode::DisposeInputCookie(int32 cookie)
{
        CALLED();
        // nothing to do since our cookies are just integers
}


void
MultiAudioNode::BufferReceived(BBuffer* buffer)
{
        //CALLED();
        switch (buffer->Header()->type) {
                /*case B_MEDIA_PARAMETERS:
                        {
                        status_t status = ApplyParameterData(buffer->Data(),buffer->SizeUsed());
                        if (status != B_OK) {
                                fprintf(stderr,"ApplyParameterData in MultiAudioNode::BufferReceived failed\n");
                        }
                        buffer->Recycle();
                        }
                        break;*/
                case B_MEDIA_RAW_AUDIO:
                        if ((buffer->Flags() & BBuffer::B_SMALL_BUFFER) != 0) {
                                fprintf(stderr, "NOT IMPLEMENTED: B_SMALL_BUFFER in "
                                        "MultiAudioNode::BufferReceived\n");
                                // TODO: implement this part
                                buffer->Recycle();
                        } else {
                                media_timed_event event(buffer->Header()->start_time,
                                        BTimedEventQueue::B_HANDLE_BUFFER, buffer,
                                        BTimedEventQueue::B_RECYCLE_BUFFER);
                                status_t status = EventQueue()->AddEvent(event);
                                if (status != B_OK) {
                                        fprintf(stderr, "EventQueue()->AddEvent(event) in "
                                                "MultiAudioNode::BufferReceived failed\n");
                                        buffer->Recycle();
                                }
                        }
                        break;
                default:
                        fprintf(stderr, "unexpected buffer type in "
                                "MultiAudioNode::BufferReceived\n");
                        buffer->Recycle();
                        break;
        }
}


void
MultiAudioNode::ProducerDataStatus(const media_destination& forWhom,
        int32 status, bigtime_t atPerformanceTime)
{
        node_input* channel = _FindInput(forWhom);
        if (channel == NULL) {
                fprintf(stderr, "invalid destination received in "
                        "MultiAudioNode::ProducerDataStatus\n");
                return;
        }

        media_timed_event event(atPerformanceTime, BTimedEventQueue::B_DATA_STATUS,
                &channel->fInput, BTimedEventQueue::B_NO_CLEANUP, status, 0, NULL);
        EventQueue()->AddEvent(event);
}


status_t
MultiAudioNode::GetLatencyFor(const media_destination& forWhom,
        bigtime_t* _latency, media_node_id* _timeSource)
{
        CALLED();
        if (_latency == NULL || _timeSource == NULL)
                return B_BAD_VALUE;

        node_input* channel = _FindInput(forWhom);
        if (channel == NULL)
                return B_MEDIA_BAD_DESTINATION;

        *_latency = EventLatency();
        *_timeSource = TimeSource()->ID();
        return B_OK;
}


status_t
MultiAudioNode::Connected(const media_source& producer,
        const media_destination& where, const media_format& with_format,
        media_input* out_input)
{
        CALLED();
        if (out_input == 0) {
                fprintf(stderr, "<- B_BAD_VALUE\n");
                return B_BAD_VALUE;
        }

        node_input* channel = _FindInput(where);
        if (channel == NULL) {
                fprintf(stderr, "<- B_MEDIA_BAD_DESTINATION\n");
                return B_MEDIA_BAD_DESTINATION;
        }

        if (with_format.u.raw_audio.frame_rate <= 0
                || with_format.u.raw_audio.channel_count <= 0
                || ((with_format.u.raw_audio.format
                        & media_raw_audio_format::B_AUDIO_SIZE_MASK) == 0))
                return B_BAD_VALUE;

        _UpdateInternalLatency(with_format);

        // record the agreed upon values
        channel->fInput.source = producer;
        channel->fInput.format = with_format;
        *out_input = channel->fInput;

        _StartOutputThreadIfNeeded();

        return B_OK;
}


void
MultiAudioNode::Disconnected(const media_source& producer,
        const media_destination& where)
{
        CALLED();

        node_input* channel = _FindInput(where);
        if (channel == NULL || channel->fInput.source != producer)
                return;

        channel->fInput.source = media_source::null;
        channel->fInput.format = channel->fPreferredFormat;

        BAutolock locker(fBufferLock);
        _FillWithZeros(*channel);
        //GetFormat(&channel->fInput.format);
}


status_t
MultiAudioNode::FormatChanged(const media_source& producer,
        const media_destination& consumer, int32 change_tag,
        const media_format& format)
{
        CALLED();

        node_input* channel = _FindInput(consumer);

        if (channel==NULL) {
                fprintf(stderr, "<- B_MEDIA_BAD_DESTINATION\n");
                return B_MEDIA_BAD_DESTINATION;
        }
        if (channel->fInput.source != producer)
                return B_MEDIA_BAD_SOURCE;

        return B_ERROR;
}


status_t
MultiAudioNode::SeekTagRequested(const media_destination& destination,
        bigtime_t targetTime, uint32 flags, media_seek_tag* _seekTag,
        bigtime_t* _taggedTime, uint32* _flags)
{
        CALLED();
        return BBufferConsumer::SeekTagRequested(destination, targetTime, flags,
                _seekTag, _taggedTime, _flags);
}


//      #pragma mark - BBufferProducer


status_t
MultiAudioNode::FormatSuggestionRequested(media_type type, int32 /*quality*/,
        media_format* format)
{
        // FormatSuggestionRequested() is not necessarily part of the format
        // negotiation process; it's simply an interrogation -- the caller
        // wants to see what the node's preferred data format is, given a
        // suggestion by the caller.
        CALLED();

        if (format == NULL) {
                fprintf(stderr, "\tERROR - NULL format pointer passed in!\n");
                return B_BAD_VALUE;
        }

        // this is the format we'll be returning (our preferred format)
        *format = fInputPreferredFormat;

        // a wildcard type is okay; we can specialize it
        if (type == B_MEDIA_UNKNOWN_TYPE)
                type = B_MEDIA_RAW_AUDIO;

        // we only support raw audio
        if (type != B_MEDIA_RAW_AUDIO)
                return B_MEDIA_BAD_FORMAT;

        return B_OK;
}


status_t
MultiAudioNode::FormatProposal(const media_source& output, media_format* format)
{
        // FormatProposal() is the first stage in the BMediaRoster::Connect()
        // process.  We hand out a suggested format, with wildcards for any
        // variations we support.
        CALLED();

        // is this a proposal for our select output?
        node_output* channel = _FindOutput(output);
        if (channel == NULL) {
                fprintf(stderr, "MultiAudioNode::FormatProposal returning "
                        "B_MEDIA_BAD_SOURCE\n");
                return B_MEDIA_BAD_SOURCE;
        }

        // We only support floating-point raw audio, so we always return that,
        // but we supply an error code depending on whether we found the proposal
        // acceptable.
        media_type requestedType = format->type;
        *format = channel->fPreferredFormat;
        if (requestedType != B_MEDIA_UNKNOWN_TYPE
                && requestedType != B_MEDIA_RAW_AUDIO) {
                fprintf(stderr, "MultiAudioNode::FormatProposal returning "
                        "B_MEDIA_BAD_FORMAT\n");
                return B_MEDIA_BAD_FORMAT;
        }
        // raw audio or wildcard type, either is okay by us
        return B_OK;
}


status_t
MultiAudioNode::FormatChangeRequested(const media_source& source,
        const media_destination& destination, media_format* format,
        int32* _deprecated_)
{
        CALLED();

        // we don't support any other formats, so we just reject any format changes.
        return B_ERROR;
}


status_t
MultiAudioNode::GetNextOutput(int32* cookie, media_output* _output)
{
        CALLED();

        if (*cookie < fOutputs.CountItems() && *cookie >= 0) {
                node_output* channel = (node_output*)fOutputs.ItemAt(*cookie);
                *_output = channel->fOutput;
                *cookie += 1;
                return B_OK;
        }
        return B_BAD_INDEX;
}


status_t
MultiAudioNode::DisposeOutputCookie(int32 cookie)
{
        CALLED();
        // do nothing because we don't use the cookie for anything special
        return B_OK;
}


status_t
MultiAudioNode::SetBufferGroup(const media_source& forSource,
        BBufferGroup* newGroup)
{
        CALLED();

        // is this our output?
        node_output* channel = _FindOutput(forSource);
        if (channel == NULL) {
                fprintf(stderr, "MultiAudioNode::SetBufferGroup returning "
                        "B_MEDIA_BAD_SOURCE\n");
                return B_MEDIA_BAD_SOURCE;
        }

        // Are we being passed the buffer group we're already using?
        if (newGroup == channel->fBufferGroup)
                return B_OK;

        // Ahh, someone wants us to use a different buffer group.  At this point
        // we delete the one we are using and use the specified one instead.
        // If the specified group is NULL, we need to recreate one ourselves, and
        // use *that*.  Note that if we're caching a BBuffer that we requested
        // earlier, we have to Recycle() that buffer *before* deleting the buffer
        // group, otherwise we'll deadlock waiting for that buffer to be recycled!
        delete channel->fBufferGroup;
                // waits for all buffers to recycle
        if (newGroup != NULL) {
                // we were given a valid group; just use that one from now on
                channel->fBufferGroup = newGroup;
        } else {
                // we were passed a NULL group pointer; that means we construct
                // our own buffer group to use from now on
                size_t size = channel->fOutput.format.u.raw_audio.buffer_size;
                int32 count = int32(fLatency / BufferDuration() + 1 + 1);
                BBufferGroup* group = new BBufferGroup(size, count);
                if (group == NULL || group->InitCheck() != B_OK) {
                        delete group;
                        fprintf(stderr, "MultiAudioNode::SetBufferGroup failed to"
                                "instantiate a new group.\n");
                        return B_ERROR;
                }
                channel->fBufferGroup = group;
        }

        return B_OK;
}


status_t
MultiAudioNode::PrepareToConnect(const media_source& what,
        const media_destination& where, media_format* format,
        media_source* source, char* name)
{
        CALLED();

        // is this our output?
        node_output* channel = _FindOutput(what);
        if (channel == NULL) {
                fprintf(stderr, "MultiAudioNode::PrepareToConnect returning "
                        "B_MEDIA_BAD_SOURCE\n");
                return B_MEDIA_BAD_SOURCE;
        }

        // are we already connected?
        if (channel->fOutput.destination != media_destination::null)
                return B_MEDIA_ALREADY_CONNECTED;

        // Allow buffer sizes other than our preferred one.
        media_format compatible = channel->fPreferredFormat;
        compatible.u.raw_audio.buffer_size
                = media_raw_audio_format::wildcard.buffer_size;

        if (!format_is_compatible(*format, compatible))
                return B_MEDIA_BAD_FORMAT;

        format->SpecializeTo(&channel->fPreferredFormat);

        // Now reserve the connection, and return information about it
        channel->fOutput.destination = where;
        channel->fOutput.format = *format;

        *source = channel->fOutput.source;
        strlcpy(name, channel->fOutput.name, B_MEDIA_NAME_LENGTH);
        return B_OK;
}


void
MultiAudioNode::Connect(status_t error, const media_source& source,
        const media_destination& destination, const media_format& format,
        char* name)
{
        CALLED();

        // is this our output?
        node_output* channel = _FindOutput(source);
        if (channel == NULL) {
                fprintf(stderr, "MultiAudioNode::Connect returning (cause: "
                        "B_MEDIA_BAD_SOURCE)\n");
                return;
        }

        // If something earlier failed, Connect() might still be called, but with
        // a non-zero error code.  When that happens we simply unreserve the
        // connection and do nothing else.
        if (error != B_OK) {
                channel->fOutput.destination = media_destination::null;
                channel->fOutput.format = channel->fPreferredFormat;
                return;
        }

        // Okay, the connection has been confirmed.  Record the destination and
        // format that we agreed on, and report our connection name again.
        channel->fOutput.destination = destination;
        channel->fOutput.format = format;
        strlcpy(name, channel->fOutput.name, B_MEDIA_NAME_LENGTH);

        // reset our buffer duration, etc. to avoid later calculations
        bigtime_t duration = channel->fOutput.format.u.raw_audio.buffer_size * 10000
                / ((channel->fOutput.format.u.raw_audio.format
                                & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                        * channel->fOutput.format.u.raw_audio.channel_count)
                / ((int32)(channel->fOutput.format.u.raw_audio.frame_rate / 100));

        SetBufferDuration(duration);

        // Now that we're connected, we can determine our downstream latency.
        // Do so, then make sure we get our events early enough.
        media_node_id id;
        FindLatencyFor(channel->fOutput.destination, &fLatency, &id);
        PRINT(("\tdownstream latency = %" B_PRIdBIGTIME "\n", fLatency));

        fInternalLatency = BufferDuration();
        PRINT(("\tbuffer-filling took %" B_PRIdBIGTIME " usec on this machine\n",
                fInternalLatency));
        //SetEventLatency(fLatency + fInternalLatency);

        // Set up the buffer group for our connection, as long as nobody handed us
        // a buffer group (via SetBufferGroup()) prior to this.  That can happen,
        // for example, if the consumer calls SetOutputBuffersFor() on us from
        // within its Connected() method.
        if (channel->fBufferGroup == NULL)
                _AllocateBuffers(*channel);

        _StartOutputThreadIfNeeded();
}


void
MultiAudioNode::Disconnect(const media_source& what,
        const media_destination& where)
{
        CALLED();

        // is this our output?
        node_output* channel = _FindOutput(what);
        if (channel == NULL) {
                fprintf(stderr, "MultiAudioNode::Disconnect() returning (cause: "
                        "B_MEDIA_BAD_SOURCE)\n");
                return;
        }

        // Make sure that our connection is the one being disconnected
        if (where == channel->fOutput.destination
                && what == channel->fOutput.source) {
                channel->fOutput.destination = media_destination::null;
                channel->fOutput.format = channel->fPreferredFormat;
                delete channel->fBufferGroup;
                channel->fBufferGroup = 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, channel->fOutput.source.id,
                        channel->fOutput.destination.id);
        }
}


void
MultiAudioNode::LateNoticeReceived(const media_source& what, bigtime_t howMuch,
        bigtime_t performanceTime)
{
        CALLED();

        // is this our output?
        node_output* channel = _FindOutput(what);
        if (channel == NULL)
                return;

        // If we're late, we need to catch up.  Respond in a manner appropriate
        // to our current run mode.
        if (RunMode() == B_RECORDING) {
                // A hardware capture node can't adjust; it simply emits buffers at
                // appropriate points.  We (partially) simulate this by not adjusting
                // our behavior upon receiving late notices -- after all, the hardware
                // can't choose to capture "sooner"....
        } else if (RunMode() == B_INCREASE_LATENCY) {
                // We're late, and our run mode dictates that we try to produce buffers
                // earlier in order to catch up.  This argues that the downstream nodes
                // are not properly reporting their latency, but there's not much we can
                // do about that at the moment, so we try to start producing buffers
                // earlier to compensate.
                fInternalLatency += howMuch;
                SetEventLatency(fLatency + fInternalLatency);

                fprintf(stderr, "\tincreasing latency to %" B_PRIdBIGTIME"\n",
                        fLatency + fInternalLatency);
        } else {
                // The other run modes dictate various strategies for sacrificing data
                // quality in the interests of timely data delivery.  The way *we* do
                // this is to skip a buffer, which catches us up in time by one buffer
                // duration.
                /*size_t nSamples = fOutput.format.u.raw_audio.buffer_size / sizeof(float);
                mSamplesSent += nSamples;*/

                fprintf(stderr, "\tskipping a buffer to try to catch up\n");
        }
}


void
MultiAudioNode::EnableOutput(const media_source& what, bool enabled,
        int32* _deprecated_)
{
        CALLED();

        // If I had more than one output, I'd have to walk my list of output
        // records to see which one matched the given source, and then
        // enable/disable that one.  But this node only has one output, so I
        // just make sure the given source matches, then set the enable state
        // accordingly.
        node_output* channel = _FindOutput(what);
        if (channel != NULL)
                channel->fOutputEnabled = enabled;
}


void
MultiAudioNode::AdditionalBufferRequested(const media_source& source,
        media_buffer_id previousBuffer, bigtime_t previousTime,
        const media_seek_tag* previousTag)
{
        CALLED();
        // we don't support offline mode
        return;
}


//      #pragma mark - BMediaEventLooper


void
MultiAudioNode::HandleEvent(const media_timed_event* event, bigtime_t lateness,
        bool realTimeEvent)
{
        switch (event->type) {
                case BTimedEventQueue::B_START:
                        _HandleStart(event, lateness, realTimeEvent);
                        break;
                case BTimedEventQueue::B_SEEK:
                        _HandleSeek(event, lateness, realTimeEvent);
                        break;
                case BTimedEventQueue::B_WARP:
                        _HandleWarp(event, lateness, realTimeEvent);
                        break;
                case BTimedEventQueue::B_STOP:
                        _HandleStop(event, lateness, realTimeEvent);
                        break;
                case BTimedEventQueue::B_HANDLE_BUFFER:
                        if (RunState() == BMediaEventLooper::B_STARTED)
                                _HandleBuffer(event, lateness, realTimeEvent);
                        break;
                case BTimedEventQueue::B_DATA_STATUS:
                        _HandleDataStatus(event, lateness, realTimeEvent);
                        break;
                case BTimedEventQueue::B_PARAMETER:
                        _HandleParameter(event, lateness, realTimeEvent);
                        break;
                default:
                        fprintf(stderr,"  unknown event type: %" B_PRId32 "\n",
                                event->type);
                        break;
        }
}


status_t
MultiAudioNode::_HandleBuffer(const media_timed_event* event,
        bigtime_t lateness, bool realTimeEvent)
{
        BBuffer* buffer = const_cast<BBuffer*>((BBuffer*)event->pointer);
        if (buffer == NULL)
                return B_BAD_VALUE;

        //PRINT(("buffer->Header()->destination: %i\n", buffer->Header()->destination));

        node_input* channel = _FindInput(buffer->Header()->destination);
        if (channel == NULL) {
                buffer->Recycle();
                return B_MEDIA_BAD_DESTINATION;
        }

        // if the buffer is late, we ignore it and report the fact to the producer
        // who sent it to us
        if (RunMode() != B_OFFLINE && RunMode() != B_RECORDING && lateness > 0) {
                // lateness doesn't matter in offline mode or in recording mode
                //mLateBuffers++;
                NotifyLateProducer(channel->fInput.source, lateness, event->event_time);
                fprintf(stderr,"        <- LATE BUFFER: %" B_PRIdBIGTIME "\n", lateness);
                buffer->Recycle();
        } else {
                //WriteBuffer(buffer, *channel);
                // TODO: This seems like a very fragile mechanism to wait until
                // the previous buffer for this channel has been processed...
                if (channel->fBuffer != NULL) {
                        PRINT(("MultiAudioNode::HandleBuffer snoozing recycling channelId: "
                                "%" B_PRIi32 ", lateness:%" B_PRIdBIGTIME "\n",
                                channel->fChannelId, lateness));
                        //channel->fBuffer->Recycle();
                        snooze(100);
                        if (channel->fBuffer != NULL)
                                buffer->Recycle();
                        else
                                channel->fBuffer = buffer;
                } else {
                        //PRINT(("MultiAudioNode::HandleBuffer writing channelId: %li, how_early:%lld\n", channel->fChannelId, howEarly));
                        channel->fBuffer = buffer;
                }
        }
        return B_OK;
}


status_t
MultiAudioNode::_HandleDataStatus(const media_timed_event* event,
        bigtime_t lateness, bool realTimeEvent)
{
        PRINT(("MultiAudioNode::HandleDataStatus status:%" B_PRIi32 ", lateness:%"
                B_PRIiBIGTIME "\n", event->data, lateness));
        switch (event->data) {
                case B_DATA_NOT_AVAILABLE:
                        break;
                case B_DATA_AVAILABLE:
                        break;
                case B_PRODUCER_STOPPED:
                        break;
                default:
                        break;
        }
        return B_OK;
}


status_t
MultiAudioNode::_HandleStart(const media_timed_event* event, bigtime_t lateness,
        bool realTimeEvent)
{
        CALLED();
        if (RunState() != B_STARTED) {
        }
        return B_OK;
}


status_t
MultiAudioNode::_HandleSeek(const media_timed_event* event, bigtime_t lateness,
        bool realTimeEvent)
{
        CALLED();
        PRINT(("MultiAudioNode::HandleSeek(t=%" B_PRIdBIGTIME ",d=%" B_PRIi32
                        ",bd=%" B_PRId64 ")\n",
                event->event_time,event->data,event->bigdata));
        return B_OK;
}


status_t
MultiAudioNode::_HandleWarp(const media_timed_event* event, bigtime_t lateness,
        bool realTimeEvent)
{
        CALLED();
        return B_OK;
}


status_t
MultiAudioNode::_HandleStop(const media_timed_event* event, bigtime_t lateness,
        bool realTimeEvent)
{
        CALLED();
        // flush the queue so downstreamers don't get any more
        EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true,
                BTimedEventQueue::B_HANDLE_BUFFER);

        //_StopOutputThread();
        return B_OK;
}


status_t
MultiAudioNode::_HandleParameter(const media_timed_event* event,
        bigtime_t lateness, bool realTimeEvent)
{
        CALLED();
        return B_OK;
}


//      #pragma mark - BTimeSource


void
MultiAudioNode::SetRunMode(run_mode mode)
{
        CALLED();
        PRINT(("MultiAudioNode::SetRunMode mode:%i\n", mode));
        //BTimeSource::SetRunMode(mode);
}


status_t
MultiAudioNode::TimeSourceOp(const time_source_op_info& op, void* _reserved)
{
        CALLED();
        switch (op.op) {
                case B_TIMESOURCE_START:
                        PRINT(("TimeSourceOp op B_TIMESOURCE_START\n"));
                        if (RunState() != BMediaEventLooper::B_STARTED) {
                                fTimeSourceStarted = true;
                                _StartOutputThreadIfNeeded();

                                media_timed_event startEvent(0, BTimedEventQueue::B_START);
                                EventQueue()->AddEvent(startEvent);
                        }
                        break;
                case B_TIMESOURCE_STOP:
                        PRINT(("TimeSourceOp op B_TIMESOURCE_STOP\n"));
                        if (RunState() == BMediaEventLooper::B_STARTED) {
                                media_timed_event stopEvent(0, BTimedEventQueue::B_STOP);
                                EventQueue()->AddEvent(stopEvent);
                                fTimeSourceStarted = false;
                                _StopOutputThread();
                                PublishTime(0, 0, 1.0f);
                        }
                        break;
                case B_TIMESOURCE_STOP_IMMEDIATELY:
                        PRINT(("TimeSourceOp op B_TIMESOURCE_STOP_IMMEDIATELY\n"));
                        if (RunState() == BMediaEventLooper::B_STARTED) {
                                media_timed_event stopEvent(0, BTimedEventQueue::B_STOP);
                                EventQueue()->AddEvent(stopEvent);
                                fTimeSourceStarted = false;
                                _StopOutputThread();
                                PublishTime(0, 0, 1.0f);
                        }
                        break;
                case B_TIMESOURCE_SEEK:
                        PRINT(("TimeSourceOp op B_TIMESOURCE_SEEK\n"));
                        BroadcastTimeWarp(op.real_time, op.performance_time);
                        break;
                default:
                        break;
        }
        return B_OK;
}


//      #pragma mark - BControllable


status_t
MultiAudioNode::GetParameterValue(int32 id, bigtime_t* lastChange, void* value,
        size_t* size)
{
        CALLED();

        PRINT(("id: %" B_PRIi32 "\n", id));
        BParameter* parameter = NULL;
        for (int32 i = 0; i < fWeb->CountParameters(); i++) {
                parameter = fWeb->ParameterAt(i);
                if (parameter->ID() == id)
                        break;
        }

        if (parameter == NULL) {
                // Hmmm, we were asked for a parameter that we don't actually
                // support.  Report an error back to the caller.
                PRINT(("\terror - asked for illegal parameter %" B_PRId32 "\n", id));
                return B_ERROR;
        }

        if (id == PARAMETER_ID_INPUT_FREQUENCY
                || id == PARAMETER_ID_OUTPUT_FREQUENCY) {
                const multi_format_info& info = fDevice->FormatInfo();

                uint32 rate = id == PARAMETER_ID_INPUT_FREQUENCY
                        ? info.input.rate : info.output.rate;

                if (*size < sizeof(rate))
                        return B_ERROR;

                memcpy(value, &rate, sizeof(rate));
                *size = sizeof(rate);
                return B_OK;
        }

        multi_mix_value_info info;
        multi_mix_value values[2];
        info.values = values;
        info.item_count = 0;
        multi_mix_control* controls = fDevice->MixControlInfo().controls;
        int32 control_id = controls[id - 100].id;

        if (*size < sizeof(float))
                return B_ERROR;

        if (parameter->Type() == BParameter::B_CONTINUOUS_PARAMETER) {
                info.item_count = 1;
                values[0].id = control_id;

                if (parameter->CountChannels() == 2) {
                        if (*size < 2*sizeof(float))
                                return B_ERROR;
                        info.item_count = 2;
                        values[1].id = controls[id + 1 - 100].id;
                }
        } else if (parameter->Type() == BParameter::B_DISCRETE_PARAMETER) {
                info.item_count = 1;
                values[0].id = control_id;
        }

        if (info.item_count > 0) {
                status_t status = fDevice->GetMix(&info);
                if (status != B_OK) {
                        fprintf(stderr, "Failed on DRIVER_GET_MIX\n");
                } else {
                        if (parameter->Type() == BParameter::B_CONTINUOUS_PARAMETER) {
                                ((float*)value)[0] = values[0].gain;
                                *size = sizeof(float);

                                if (parameter->CountChannels() == 2) {
                                        ((float*)value)[1] = values[1].gain;
                                        *size = 2*sizeof(float);
                                }

                                for (uint32 i = 0; i < *size / sizeof(float); i++) {
                                        PRINT(("GetParameterValue B_CONTINUOUS_PARAMETER value[%"
                                                B_PRIi32 "]: %f\n", i, ((float*)value)[i]));
                                }
                        } else if (parameter->Type() == BParameter::B_DISCRETE_PARAMETER) {
                                BDiscreteParameter* discrete = (BDiscreteParameter*)parameter;
                                if (discrete->CountItems() <= 2)
                                        ((int32*)value)[0] = values[0].enable ? 1 : 0;
                                else
                                        ((int32*)value)[0] = values[0].mux;

                                *size = sizeof(int32);

                                for (uint32 i = 0; i < *size / sizeof(int32); i++) {
                                        PRINT(("GetParameterValue B_DISCRETE_PARAMETER value[%"
                                                B_PRIi32 "]: %" B_PRIi32 "\n", i, ((int32*)value)[i]));
                                }
                        }
                }
        }
        return B_OK;
}


void
MultiAudioNode::SetParameterValue(int32 id, bigtime_t performanceTime,
        const void* value, size_t size)
{
        CALLED();
        PRINT(("id: %" B_PRIi32 ", performance_time: %" B_PRIdBIGTIME
                ", size: %" B_PRIuSIZE "\n", id, performanceTime, size));

        BParameter* parameter = NULL;
        for (int32 i = 0; i < fWeb->CountParameters(); i++) {
                parameter = fWeb->ParameterAt(i);
                if (parameter->ID() == id)
                        break;
        }

        if (parameter == NULL)
                return;

        if (id == PARAMETER_ID_OUTPUT_FREQUENCY
                || (id == PARAMETER_ID_INPUT_FREQUENCY
                        && (fDevice->Description().output_rates
                                & B_SR_SAME_AS_INPUT) != 0)) {
                uint32 rate;
                if (size < sizeof(rate))
                        return;
                memcpy(&rate, value, sizeof(rate));

                if (rate == fOutputPreferredFormat.u.raw_audio.frame_rate)
                        return;

                // create a cookie RequestCompleted() can get the old frame rate from,
                // if anything goes wrong
                FrameRateChangeCookie* cookie = new(std::nothrow) FrameRateChangeCookie;
                if (cookie == NULL)
                        return;

                cookie->oldFrameRate = fOutputPreferredFormat.u.raw_audio.frame_rate;
                cookie->id = id;
                BReference<FrameRateChangeCookie> cookieReference(cookie, true);

                // NOTE: What we should do is call RequestFormatChange() for all
                // connections and change the device's format in RequestCompleted().
                // Unfortunately we need the new buffer size first, which we only get
                // from the device after changing the format. So we do that now and
                // reset it in RequestCompleted(), if something went wrong. This causes
                // the buffers we receive until then to be played incorrectly leading
                // to unpleasant noise.
                float frameRate = MultiAudio::convert_to_sample_rate(rate);
                if (_SetNodeInputFrameRate(frameRate) != B_OK)
                        return;

                for (int32 i = 0; i < fInputs.CountItems(); i++) {
                        node_input* channel = (node_input*)fInputs.ItemAt(i);
                        if (channel->fInput.source == media_source::null)
                                continue;

                        media_format newFormat = channel->fInput.format;
                        newFormat.u.raw_audio.frame_rate = frameRate;
                        newFormat.u.raw_audio.buffer_size
                                = fOutputPreferredFormat.u.raw_audio.buffer_size;

                        int32 changeTag = 0;
                        status_t error = RequestFormatChange(channel->fInput.source,
                                channel->fInput.destination, newFormat, NULL, &changeTag);
                        if (error == B_OK)
                                cookie->AcquireReference();
                }

                if (id != PARAMETER_ID_INPUT_FREQUENCY)
                        return;
                //Do not return cause we should go in the next if
        }

        if (id == PARAMETER_ID_INPUT_FREQUENCY) {
                uint32 rate;
                if (size < sizeof(rate))
                        return;
                memcpy(&rate, value, sizeof(rate));

                if (rate == fInputPreferredFormat.u.raw_audio.frame_rate)
                        return;

                // create a cookie RequestCompleted() can get the old frame rate from,
                // if anything goes wrong
                FrameRateChangeCookie* cookie = new(std::nothrow) FrameRateChangeCookie;
                if (cookie == NULL)
                        return;

                cookie->oldFrameRate = fInputPreferredFormat.u.raw_audio.frame_rate;
                cookie->id = id;
                BReference<FrameRateChangeCookie> cookieReference(cookie, true);

                // NOTE: What we should do is call RequestFormatChange() for all
                // connections and change the device's format in RequestCompleted().
                // Unfortunately we need the new buffer size first, which we only get
                // from the device after changing the format. So we do that now and
                // reset it in RequestCompleted(), if something went wrong. This causes
                // the buffers we receive until then to be played incorrectly leading
                // to unpleasant noise.
                float frameRate = MultiAudio::convert_to_sample_rate(rate);
                if (_SetNodeOutputFrameRate(frameRate) != B_OK)
                        return;

                for (int32 i = 0; i < fOutputs.CountItems(); i++) {
                        node_output* channel = (node_output*)fOutputs.ItemAt(i);
                        if (channel->fOutput.source == media_source::null)
                                continue;

                        media_format newFormat = channel->fOutput.format;
                        newFormat.u.raw_audio.frame_rate = frameRate;
                        newFormat.u.raw_audio.buffer_size
                                = fInputPreferredFormat.u.raw_audio.buffer_size;

                        int32 changeTag = 0;
                        status_t error = RequestFormatChange(channel->fOutput.source,
                                channel->fOutput.destination, newFormat, NULL, &changeTag);
                        if (error == B_OK)
                                cookie->AcquireReference();
                }

                return;
        }

        multi_mix_value_info info;
        multi_mix_value values[2];
        info.values = values;
        info.item_count = 0;
        multi_mix_control* controls = fDevice->MixControlInfo().controls;
        int32 control_id = controls[id - 100].id;

        if (parameter->Type() == BParameter::B_CONTINUOUS_PARAMETER) {
                for (uint32 i = 0; i < size / sizeof(float); i++) {
                        PRINT(("SetParameterValue B_CONTINUOUS_PARAMETER value[%" B_PRIi32
                                "]: %f\n", i, ((float*)value)[i]));
                }
                info.item_count = 1;
                values[0].id = control_id;
                values[0].gain = ((float*)value)[0];

                if (parameter->CountChannels() == 2) {
                        info.item_count = 2;
                        values[1].id = controls[id + 1 - 100].id;
                        values[1].gain = ((float*)value)[1];
                }
        } else if (parameter->Type() == BParameter::B_DISCRETE_PARAMETER) {
                for (uint32 i = 0; i < size / sizeof(int32); i++) {
                        PRINT(("SetParameterValue B_DISCRETE_PARAMETER value[%" B_PRIi32
                                "]: %" B_PRIi32 "\n", i, ((int32*)value)[i]));
                }

                BDiscreteParameter* discrete = (BDiscreteParameter*)parameter;
                if (discrete->CountItems() <= 2) {
                        info.item_count = 1;
                        values[0].id = control_id;
                        values[0].enable = ((int32*)value)[0] == 1;
                } else {
                        info.item_count = 1;
                        values[0].id = control_id;
                        values[0].mux = ((uint32*)value)[0];
                }
        }

        if (info.item_count > 0) {
                status_t status = fDevice->SetMix(&info);
                if (status != B_OK)
                        fprintf(stderr, "Failed on DRIVER_SET_MIX\n");
        }
}


BParameterWeb*
MultiAudioNode::MakeParameterWeb()
{
        CALLED();
        BParameterWeb* web = new BParameterWeb();

        PRINT(("MixControlInfo().control_count: %" B_PRIi32 "\n",
                fDevice->MixControlInfo().control_count));

        BParameterGroup* generalGroup = web->MakeGroup(B_TRANSLATE("General"));

        const multi_description& description = fDevice->Description();

        if ((description.output_rates & B_SR_SAME_AS_INPUT) != 0) {
                _CreateFrequencyParameterGroup(generalGroup,
                        B_TRANSLATE("Input & Output"), PARAMETER_ID_INPUT_FREQUENCY,
                        description.input_rates);
        } else {
                _CreateFrequencyParameterGroup(generalGroup, B_TRANSLATE("Input"),
                        PARAMETER_ID_INPUT_FREQUENCY, description.input_rates);
                _CreateFrequencyParameterGroup(generalGroup, B_TRANSLATE("Output"),
                        PARAMETER_ID_OUTPUT_FREQUENCY, description.output_rates);
        }

        multi_mix_control* controls = fDevice->MixControlInfo().controls;

        for (int i = 0; i < fDevice->MixControlInfo().control_count; i++) {
                if ((controls[i].flags & B_MULTI_MIX_GROUP) != 0
                        && controls[i].parent == 0) {
                        PRINT(("NEW_GROUP\n"));
                        BParameterGroup* child = web->MakeGroup(
                                _GetControlName(controls[i]));

                        int32 numParameters = 0;
                        _ProcessGroup(child, i, numParameters);
                }
        }

        return web;
}


const char*
MultiAudioNode::_GetControlName(multi_mix_control& control)
{
        if (control.string > S_null && (size_t)control.string < B_COUNT_OF(kMultiControlString))
                return kMultiControlString[control.string];

        return control.name;
}


void
MultiAudioNode::_ProcessGroup(BParameterGroup* group, int32 index,
        int32& numParameters)
{
        CALLED();
        multi_mix_control* parent = &fDevice->MixControlInfo().controls[index];
        multi_mix_control* controls = fDevice->MixControlInfo().controls;

        for (int32 i = 0; i < fDevice->MixControlInfo().control_count; i++) {
                if (controls[i].parent != parent->id)
                        continue;

                const char* name = _GetControlName(controls[i]);

                if (controls[i].flags & B_MULTI_MIX_GROUP) {
                        PRINT(("NEW_GROUP\n"));
                        BParameterGroup* child = group->MakeGroup(name);
                        child->MakeNullParameter(100 + i, B_MEDIA_RAW_AUDIO, name,
                                B_WEB_BUFFER_OUTPUT);

                        int32 num = 1;
                        _ProcessGroup(child, i, num);
                } else if (controls[i].flags & B_MULTI_MIX_MUX) {
                        PRINT(("NEW_MUX\n"));
                        BDiscreteParameter* parameter = group->MakeDiscreteParameter(
                                100 + i, B_MEDIA_RAW_AUDIO, name, B_INPUT_MUX);
                        if (numParameters > 0) {
                                (group->ParameterAt(numParameters - 1))->AddOutput(
                                        group->ParameterAt(numParameters));
                                numParameters++;
                        }
                        _ProcessMux(parameter, i);
                } else if (controls[i].flags & B_MULTI_MIX_GAIN) {
                        PRINT(("NEW_GAIN\n"));
                        group->MakeContinuousParameter(100 + i,
                                B_MEDIA_RAW_AUDIO, "", B_MASTER_GAIN, "dB",
                                controls[i].gain.min_gain, controls[i].gain.max_gain,
                                controls[i].gain.granularity);

                        if (i + 1 < fDevice->MixControlInfo().control_count
                                && controls[i + 1].master == controls[i].id
                                && (controls[i + 1].flags & B_MULTI_MIX_GAIN) != 0) {
                                group->ParameterAt(numParameters)->SetChannelCount(
                                        group->ParameterAt(numParameters)->CountChannels() + 1);
                                i++;
                        }

                        PRINT(("num parameters: %" B_PRId32 "\n", numParameters));
                        if (numParameters > 0) {
                                group->ParameterAt(numParameters - 1)->AddOutput(
                                        group->ParameterAt(numParameters));
                                numParameters++;
                        }
                } else if (controls[i].flags & B_MULTI_MIX_ENABLE) {
                        PRINT(("NEW_ENABLE\n"));
                        if (controls[i].string == S_MUTE) {
                                group->MakeDiscreteParameter(100 + i,
                                        B_MEDIA_RAW_AUDIO, name, B_MUTE);
                        } else {
                                group->MakeDiscreteParameter(100 + i,
                                        B_MEDIA_RAW_AUDIO, name, B_ENABLE);
                        }
                        if (numParameters > 0) {
                                group->ParameterAt(numParameters - 1)->AddOutput(
                                        group->ParameterAt(numParameters));
                                numParameters++;
                        }
                }
        }
}


void
MultiAudioNode::_ProcessMux(BDiscreteParameter* parameter, int32 index)
{
        CALLED();
        multi_mix_control* parent = &fDevice->MixControlInfo().controls[index];
        multi_mix_control* controls = fDevice->MixControlInfo().controls;
        int32 itemIndex = 0;

        for (int32 i = 0; i < fDevice->MixControlInfo().control_count; i++) {
                if (controls[i].parent != parent->id)
                        continue;

                if ((controls[i].flags & B_MULTI_MIX_MUX_VALUE) != 0) {
                        PRINT(("NEW_MUX_VALUE\n"));
                        parameter->AddItem(itemIndex, _GetControlName(controls[i]));
                        itemIndex++;
                }
        }
}


void
MultiAudioNode::_CreateFrequencyParameterGroup(BParameterGroup* parentGroup,
        const char* name, int32 parameterID, uint32 rateMask)
{
        BParameterGroup* group = parentGroup->MakeGroup(name);
        BDiscreteParameter* frequencyParam = group->MakeDiscreteParameter(
                parameterID, B_MEDIA_NO_TYPE,
                BString(name) << B_TRANSLATE(" frequency:"),
                B_GENERIC);

        for (int32 i = 0; kSampleRateInfos[i].name != NULL; i++) {
                const sample_rate_info& info = kSampleRateInfos[i];
                if ((rateMask & info.multiAudioRate) != 0) {
                        frequencyParam->AddItem(info.multiAudioRate,
                                BString(info.name) << " Hz");
                }
        }
}


//      #pragma mark - MultiAudioNode specific functions


int32
MultiAudioNode::_OutputThread()
{
        CALLED();
        multi_buffer_info bufferInfo;
        bufferInfo.info_size = sizeof(multi_buffer_info);
        bufferInfo.playback_buffer_cycle = 0;
        bufferInfo.record_buffer_cycle = 0;

        // init the performance time computation
        {
                BAutolock locker(fBufferLock);
                fTimeComputer.Init(fOutputPreferredFormat.u.raw_audio.frame_rate,
                        system_time());
        }

        while (atomic_get(&fQuitThread) == 0) {
                BAutolock locker(fBufferLock);
                        // make sure the buffers don't change while we're playing with them

                // send buffer
                fDevice->BufferExchange(&bufferInfo);

                //PRINT(("MultiAudioNode::RunThread: buffer exchanged\n"));
                //PRINT(("MultiAudioNode::RunThread: played_real_time: %lld\n", bufferInfo.played_real_time));
                //PRINT(("MultiAudioNode::RunThread: played_frames_count: %lld\n", bufferInfo.played_frames_count));
                //PRINT(("MultiAudioNode::RunThread: buffer_cycle: %li\n", bufferInfo.playback_buffer_cycle));

                for (int32 i = 0; i < fInputs.CountItems(); i++) {
                        node_input* input = (node_input*)fInputs.ItemAt(i);

                        if (bufferInfo.playback_buffer_cycle >= 0
                                && bufferInfo.playback_buffer_cycle
                                                < fDevice->BufferList().return_playback_buffers
                                && (input->fOldBufferCycle != bufferInfo.playback_buffer_cycle
                                        || fDevice->BufferList().return_playback_buffers == 1)
                                && (input->fInput.source != media_source::null
                                        || input->fChannelId == 0)) {
                                //PRINT(("playback_buffer_cycle ok input: %li %ld\n", i, bufferInfo.playback_buffer_cycle));

                                input->fBufferCycle = (bufferInfo.playback_buffer_cycle - 1
                                                + fDevice->BufferList().return_playback_buffers)
                                        % fDevice->BufferList().return_playback_buffers;

                                // update the timesource
                                if (input->fChannelId == 0) {
                                        //PRINT(("updating timesource\n"));
                                        _UpdateTimeSource(bufferInfo, *input);
                                }

                                input->fOldBufferCycle = bufferInfo.playback_buffer_cycle;

                                if (input->fBuffer != NULL) {
                                        _FillNextBuffer(*input, input->fBuffer);
                                        input->fBuffer->Recycle();
                                        input->fBuffer = NULL;
                                } else {
                                        // put zeros in current buffer
                                        if (input->fInput.source != media_source::null)
                                                _WriteZeros(*input, input->fBufferCycle);
                                        //PRINT(("MultiAudioNode::Runthread WriteZeros\n"));
                                }
                        } else {
                                //PRINT(("playback_buffer_cycle non ok input: %i\n", i));
                        }
                }

#if 0
                PRINT(("MultiAudioNode::RunThread: recorded_real_time: %" B_PRIdBIGTIME
                                "\n", bufferInfo.recorded_real_time));
                PRINT(("MultiAudioNode::RunThread: recorded_frames_count: %"
                                B_PRId64 "\n", bufferInfo.recorded_frames_count));
                PRINT(("MultiAudioNode::RunThread: record_buffer_cycle: %" B_PRIi32
                                "\n", bufferInfo.record_buffer_cycle));
#endif

                for (int32 i = 0; i < fOutputs.CountItems(); i++) {
                        node_output* output = (node_output*)fOutputs.ItemAt(i);

                        // make sure we're both started *and* connected before delivering a
                        // buffer
                        if (RunState() == BMediaEventLooper::B_STARTED
                                && output->fOutput.destination != media_destination::null) {
                                if (bufferInfo.record_buffer_cycle >= 0
                                        && bufferInfo.record_buffer_cycle
                                                        < fDevice->BufferList().return_record_buffers
                                        && (output->fOldBufferCycle
                                                        != bufferInfo.record_buffer_cycle
                                                || fDevice->BufferList().return_record_buffers == 1)) {
                                        //PRINT(("record_buffer_cycle ok\n"));

                                        output->fBufferCycle = bufferInfo.record_buffer_cycle;

                                        // Get the next buffer of data
                                        BBuffer* buffer = _FillNextBuffer(bufferInfo, *output);
                                        if (buffer != NULL) {
                                                // send the buffer downstream if and only if output is
                                                // enabled
                                                status_t err = B_ERROR;
                                                if (output->fOutputEnabled) {
                                                        err = SendBuffer(buffer, output->fOutput.source,
                                                                output->fOutput.destination);
                                                }
                                                if (err != B_OK) {
                                                        buffer->Recycle();
                                                } else {
                                                        // track how much media we've delivered so far
                                                        size_t numSamples
                                                                = output->fOutput.format.u.raw_audio.buffer_size
                                                                        / (output->fOutput.format.u.raw_audio.format
                                                                                & media_raw_audio_format
                                                                                        ::B_AUDIO_SIZE_MASK);
                                                        output->fSamplesSent += numSamples;
                                                }
                                        }

                                        output->fOldBufferCycle = bufferInfo.record_buffer_cycle;
                                } else {
                                        //PRINT(("record_buffer_cycle non ok\n"));
                                }
                        }
                }
        }

        return B_OK;
}


void
MultiAudioNode::_WriteZeros(node_input& input, uint32 bufferCycle)
{
        //CALLED();

        uint32 channelCount = input.fFormat.u.raw_audio.channel_count;
        uint32 bufferSize = fDevice->BufferList().return_playback_buffer_size;
        size_t stride = fDevice->BufferList().playback_buffers[bufferCycle]
                [input.fChannelId].stride;

        switch (input.fFormat.u.raw_audio.format) {
                case media_raw_audio_format::B_AUDIO_FLOAT:
                        for (uint32 channel = 0; channel < channelCount; channel++) {
                                char* dest = _PlaybackBuffer(bufferCycle,
                                        input.fChannelId + channel);
                                for (uint32 i = bufferSize; i > 0; i--) {
                                        *(float*)dest = 0;
                                        dest += stride;
                                }
                        }
                        break;

                case media_raw_audio_format::B_AUDIO_DOUBLE:
                        for (uint32 channel = 0; channel < channelCount; channel++) {
                                char* dest = _PlaybackBuffer(bufferCycle,
                                        input.fChannelId + channel);
                                for (uint32 i = bufferSize; i > 0; i--) {
                                        *(double*)dest = 0;
                                        dest += stride;
                                }
                        }
                        break;

                case media_raw_audio_format::B_AUDIO_INT:
                        for (uint32 channel = 0; channel < channelCount; channel++) {
                                char* dest = _PlaybackBuffer(bufferCycle,
                                        input.fChannelId + channel);
                                for (uint32 i = bufferSize; i > 0; i--) {
                                        *(int32*)dest = 0;
                                        dest += stride;
                                }
                        }
                        break;

                case media_raw_audio_format::B_AUDIO_SHORT:
                        for (uint32 channel = 0; channel < channelCount; channel++) {
                                char* dest = _PlaybackBuffer(bufferCycle,
                                        input.fChannelId + channel);
                                for (uint32 i = bufferSize; i > 0; i--) {
                                        *(int16*)dest = 0;
                                        dest += stride;
                                }
                        }
                        break;

                case media_raw_audio_format::B_AUDIO_UCHAR:
                        for (uint32 channel = 0; channel < channelCount; channel++) {
                                char* dest = _PlaybackBuffer(bufferCycle,
                                        input.fChannelId + channel);
                                for (uint32 i = bufferSize; i > 0; i--) {
                                        *(uint8*)dest = 128;
                                        dest += stride;
                                }
                        }
                        break;

                case media_raw_audio_format::B_AUDIO_CHAR:
                        for (uint32 channel = 0; channel < channelCount; channel++) {
                                char* dest = _PlaybackBuffer(bufferCycle,
                                        input.fChannelId + channel);
                                for (uint32 i = bufferSize; i > 0; i--) {
                                        *(int8*)dest = 0;
                                        dest += stride;
                                }
                        }
                        break;

                default:
                        fprintf(stderr, "ERROR in WriteZeros format not handled\n");
        }
}


void
MultiAudioNode::_FillWithZeros(node_input& input)
{
        CALLED();
        for (int32 i = 0; i < fDevice->BufferList().return_playback_buffers; i++)
                _WriteZeros(input, i);
}


void
MultiAudioNode::_FillNextBuffer(node_input& input, BBuffer* buffer)
{
        uint32 channelCount = input.fInput.format.u.raw_audio.channel_count;
        size_t inputSampleSize = input.fInput.format.u.raw_audio.format
                        & media_raw_audio_format::B_AUDIO_SIZE_MASK;

        uint32 bufferSize = fDevice->BufferList().return_playback_buffer_size;

        if (buffer->SizeUsed() / inputSampleSize / channelCount != bufferSize) {
                fprintf(stderr, "MultiAudioNode: Rejecting buffer: size is different\n");
                _WriteZeros(input, input.fBufferCycle);
                return;
        }

        if (channelCount != input.fFormat.u.raw_audio.channel_count) {
                fprintf(stderr, "MultiAudioNode: Rejecting buffer: channel count is different\n");
                return;
        }

        if (input.fResampler != NULL) {
                size_t srcStride = channelCount * inputSampleSize;

                for (uint32 channel = 0; channel < channelCount; channel++) {
                        char* src = (char*)buffer->Data() + channel * inputSampleSize;
                        char* dst = _PlaybackBuffer(input.fBufferCycle,
                                                        input.fChannelId + channel);
                        size_t dstStride = _PlaybackStride(input.fBufferCycle,
                                                        input.fChannelId + channel);

                        input.fResampler->Resample(src, srcStride,
                                dst, dstStride, bufferSize);
                }
        }
}


status_t
MultiAudioNode::_StartOutputThreadIfNeeded()
{
        CALLED();
        // the thread is already started ?
        if (fThread >= 0)
                return B_OK;

        PublishTime(-50, 0, 1.0f);

        fThread = spawn_thread(_OutputThreadEntry, "multi_audio audio output",
                B_REAL_TIME_PRIORITY, this);
        if (fThread < 0)
                return fThread;

        resume_thread(fThread);
        return B_OK;
}


status_t
MultiAudioNode::_StopOutputThread()
{
        CALLED();
        atomic_set(&fQuitThread, 1);

        wait_for_thread(fThread, NULL);
        fThread = -1;
        return B_OK;
}


void
MultiAudioNode::_AllocateBuffers(node_output &channel)
{
        CALLED();

        // allocate enough buffers to span our downstream latency, plus one
        size_t size = channel.fOutput.format.u.raw_audio.buffer_size;
        int32 count = int32(fLatency / BufferDuration() + 1 + 1);

        PRINT(("\tlatency = %" B_PRIdBIGTIME ", buffer duration = %" B_PRIdBIGTIME
                        "\n", fLatency, BufferDuration()));
        PRINT(("\tcreating group of %" B_PRId32 " buffers, size = %" B_PRIuSIZE
                        "\n", count, size));
        channel.fBufferGroup = new BBufferGroup(size, count);
}


void
MultiAudioNode::_UpdateTimeSource(multi_buffer_info& info, node_input& input)
{
        //CALLED();
        if (!fTimeSourceStarted)
                return;

        // For the first playback buffer, we might get a time of 0 or in the past. Ignore it,
        // as otherwise we will wind up with incorrect computations from the TimeComputer.
        if (info.played_real_time == 0 || info.played_real_time < fTimeComputer.RealTime())
                return;

        fTimeComputer.AddTimeStamp(info.played_real_time,
                info.played_frames_count);
        PublishTime(fTimeComputer.PerformanceTime(), fTimeComputer.RealTime(),
                fTimeComputer.Drift());
}


BBuffer*
MultiAudioNode::_FillNextBuffer(multi_buffer_info& info, node_output& output)
{
        //CALLED();
        // get a buffer from our buffer group
        //PRINT(("buffer size: %i, buffer duration: %i\n", fOutput.format.u.raw_audio.buffer_size, BufferDuration()));
        //PRINT(("MBI.record_buffer_cycle: %i\n", MBI.record_buffer_cycle));
        //PRINT(("MBI.recorded_real_time: %i\n", MBI.recorded_real_time));
        //PRINT(("MBI.recorded_frames_count: %i\n", MBI.recorded_frames_count));
        if (output.fBufferGroup == NULL)
                return NULL;

        BBuffer* buffer = output.fBufferGroup->RequestBuffer(
                output.fOutput.format.u.raw_audio.buffer_size, BufferDuration());
        if (buffer == NULL) {
                // If we fail to get a buffer (for example, if the request times out),
                // we skip this buffer and go on to the next, to avoid locking up the
                // control thread.
                fprintf(stderr, "Buffer is null");
                return NULL;
        }

        if (fDevice == NULL)
                fprintf(stderr, "fDevice NULL\n");
        if (buffer->Header() == NULL)
                fprintf(stderr, "buffer->Header() NULL\n");
        if (TimeSource() == NULL)
                fprintf(stderr, "TimeSource() NULL\n");

        uint32 channelCount = output.fOutput.format.u.raw_audio.channel_count;
        size_t outputSampleSize = output.fOutput.format.u.raw_audio.format
                & media_raw_audio_format::B_AUDIO_SIZE_MASK;

        uint32 bufferSize = fDevice->BufferList().return_record_buffer_size;

        if (output.fResampler != NULL) {
                size_t dstStride = channelCount * outputSampleSize;

                uint32 channelId = output.fChannelId
                        - fDevice->Description().output_channel_count;

                for (uint32 channel = 0; channel < channelCount; channel++) {
                        char* src = _RecordBuffer(output.fBufferCycle,
                                                                        channelId + channel);
                        size_t srcStride = _RecordStride(output.fBufferCycle,
                                                                        channelId + channel);
                        char* dst = (char*)buffer->Data() + channel * outputSampleSize;

                        output.fResampler->Resample(src, srcStride, dst, dstStride,
                                bufferSize);
                }
        }

        // fill in the buffer header
        media_header* header = buffer->Header();
        header->type = B_MEDIA_RAW_AUDIO;
        header->size_used = output.fOutput.format.u.raw_audio.buffer_size;
        header->time_source = TimeSource()->ID();
        header->start_time = PerformanceTimeFor(info.recorded_real_time);

        return buffer;
}


status_t
MultiAudioNode::GetConfigurationFor(BMessage* message)
{
        CALLED();
        if (message == NULL)
                return B_BAD_VALUE;

        size_t bufferSize = 128;
        void* buffer = malloc(bufferSize);
        if (buffer == NULL)
                return B_NO_MEMORY;

        for (int32 i = 0; i < fWeb->CountParameters(); i++) {
                BParameter* parameter = fWeb->ParameterAt(i);
                if (parameter->Type() != BParameter::B_CONTINUOUS_PARAMETER
                        && parameter->Type() != BParameter::B_DISCRETE_PARAMETER)
                        continue;

                PRINT(("getting parameter %" B_PRIi32 "\n", parameter->ID()));
                size_t size = bufferSize;
                bigtime_t lastChange;
                status_t err;
                while ((err = GetParameterValue(parameter->ID(), &lastChange, buffer,
                                &size)) == B_NO_MEMORY && bufferSize < 128 * 1024) {
                        bufferSize += 128;
                        free(buffer);
                        buffer = malloc(bufferSize);
                        if (buffer == NULL)
                                return B_NO_MEMORY;
                }

                if (err == B_OK && size > 0) {
                        message->AddInt32("parameterID", parameter->ID());
                        message->AddData("parameterData", B_RAW_TYPE, buffer, size, false);
                } else {
                        PRINT(("parameter err: %s\n", strerror(err)));
                }
        }

        free(buffer);
        PRINT_OBJECT(*message);
        return B_OK;
}


node_output*
MultiAudioNode::_FindOutput(media_source source)
{
        node_output* channel = NULL;

        for (int32 i = 0; i < fOutputs.CountItems(); i++) {
                channel = (node_output*)fOutputs.ItemAt(i);
                if (source == channel->fOutput.source)
                        break;
        }

        if (source != channel->fOutput.source)
                return NULL;

        return channel;
}


node_input*
MultiAudioNode::_FindInput(media_destination dest)
{
        node_input* channel = NULL;

        for (int32 i = 0; i < fInputs.CountItems(); i++) {
                channel = (node_input*)fInputs.ItemAt(i);
                if (dest == channel->fInput.destination)
                        break;
        }

        if (dest != channel->fInput.destination)
                return NULL;

        return channel;
}


node_input*
MultiAudioNode::_FindInput(int32 destinationId)
{
        node_input* channel = NULL;

        for (int32 i = 0; i < fInputs.CountItems(); i++) {
                channel = (node_input*)fInputs.ItemAt(i);
                if (destinationId == channel->fInput.destination.id)
                        break;
        }

        if (destinationId != channel->fInput.destination.id)
                return NULL;

        return channel;
}


/*static*/ status_t
MultiAudioNode::_OutputThreadEntry(void* data)
{
        CALLED();
        return static_cast<MultiAudioNode*>(data)->_OutputThread();
}


status_t
MultiAudioNode::_SetNodeInputFrameRate(float frameRate)
{
        // check whether the frame rate is supported
        uint32 multiAudioRate = MultiAudio::convert_from_sample_rate(frameRate);
        if ((fDevice->Description().output_rates & multiAudioRate) == 0)
                return B_BAD_VALUE;

        BAutolock locker(fBufferLock);

        // already set?
        if (fDevice->FormatInfo().output.rate == multiAudioRate)
                return B_OK;

        // set the frame rate on the device
        status_t error = fDevice->SetOutputFrameRate(multiAudioRate);
        if (error != B_OK)
                return error;

        if (frameRate <= 0)
                return B_BAD_VALUE;

        // it went fine -- update all formats
        fOutputPreferredFormat.u.raw_audio.frame_rate = frameRate;
        fOutputPreferredFormat.u.raw_audio.buffer_size
                = fDevice->BufferList().return_playback_buffer_size
                        * (fOutputPreferredFormat.u.raw_audio.format
                                & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                        * fOutputPreferredFormat.u.raw_audio.channel_count;

        for (int32 i = 0; node_input* channel = (node_input*)fInputs.ItemAt(i);
                        i++) {
                channel->fPreferredFormat.u.raw_audio.frame_rate = frameRate;
                channel->fPreferredFormat.u.raw_audio.buffer_size
                        = fOutputPreferredFormat.u.raw_audio.buffer_size;

                channel->fFormat.u.raw_audio.frame_rate = frameRate;
                channel->fFormat.u.raw_audio.buffer_size
                        = fOutputPreferredFormat.u.raw_audio.buffer_size;

                channel->fInput.format.u.raw_audio.frame_rate = frameRate;
                channel->fInput.format.u.raw_audio.buffer_size
                        = fOutputPreferredFormat.u.raw_audio.buffer_size;
        }

        // make sure the time base is reset
        fTimeComputer.SetFrameRate(frameRate);

        // update internal latency
        _UpdateInternalLatency(fOutputPreferredFormat);

        return B_OK;
}


status_t
MultiAudioNode::_SetNodeOutputFrameRate(float frameRate)
{
        // check whether the frame rate is supported
        uint32 multiAudioRate = MultiAudio::convert_from_sample_rate(frameRate);
        if ((fDevice->Description().input_rates & multiAudioRate) == 0)
                return B_BAD_VALUE;

        BAutolock locker(fBufferLock);

        // already set?
        if (fDevice->FormatInfo().input.rate == multiAudioRate)
                return B_OK;

        // set the frame rate on the device
        status_t error = fDevice->SetInputFrameRate(multiAudioRate);
        if (error != B_OK)
                return error;

        if (frameRate <= 0
                || fInputPreferredFormat.u.raw_audio.channel_count <= 0
                || ((fInputPreferredFormat.u.raw_audio.format
                        & media_raw_audio_format::B_AUDIO_SIZE_MASK) == 0))
                return B_BAD_VALUE;

        // it went fine -- update all formats
        fInputPreferredFormat.u.raw_audio.frame_rate = frameRate;
        fInputPreferredFormat.u.raw_audio.buffer_size
                = fDevice->BufferList().return_record_buffer_size
                        * (fInputPreferredFormat.u.raw_audio.format
                                & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                        * fInputPreferredFormat.u.raw_audio.channel_count;

        for (int32 i = 0; node_output* channel = (node_output*)fOutputs.ItemAt(i);
                        i++) {
                channel->fPreferredFormat.u.raw_audio.frame_rate = frameRate;
                channel->fPreferredFormat.u.raw_audio.buffer_size
                        = fInputPreferredFormat.u.raw_audio.buffer_size;

                channel->fOutput.format.u.raw_audio.frame_rate = frameRate;
                channel->fOutput.format.u.raw_audio.buffer_size
                        = fInputPreferredFormat.u.raw_audio.buffer_size;
        }

        // make sure the time base is reset
        fTimeComputer.SetFrameRate(frameRate);

        // update internal latency
        _UpdateInternalLatency(fInputPreferredFormat);

        return B_OK;
}


void
MultiAudioNode::_UpdateInternalLatency(const media_format& format)
{
        // use half a buffer length latency
        fInternalLatency = format.u.raw_audio.buffer_size * 10000 / 2
                / ((format.u.raw_audio.format
                                & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                        * format.u.raw_audio.channel_count)
                / ((int32)(format.u.raw_audio.frame_rate / 100));

        PRINT(("  internal latency = %" B_PRIdBIGTIME "\n", fInternalLatency));

        SetEventLatency(fInternalLatency);
}