root/src/add-ons/kernel/drivers/audio/ac97/geode/geode_multi.cpp
/*
 * Copyright 2009, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *      Jérôme Duval (korli@users.berlios.de)
 */


#include "hmulti_audio.h"
#include "driver.h"


#ifdef TRACE
#       undef TRACE
#endif

#define TRACE_MULTI_AUDIO
#ifdef TRACE_MULTI_AUDIO
#       define TRACE(a...) dprintf("\33[34mgeode:\33[0m " a)
#else
#       define TRACE(a...) ;
#endif


static multi_channel_info sChannels[] = {
        {  0, B_MULTI_OUTPUT_CHANNEL,   B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, 0 },
        {  1, B_MULTI_OUTPUT_CHANNEL,   B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, 0 },
        {  2, B_MULTI_INPUT_CHANNEL,    B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, 0 },
        {  3, B_MULTI_INPUT_CHANNEL,    B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, 0 },
        {  4, B_MULTI_OUTPUT_BUS,               B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS,
                        B_CHANNEL_MINI_JACK_STEREO },
        {  5, B_MULTI_OUTPUT_BUS,               B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS,
                        B_CHANNEL_MINI_JACK_STEREO },
        {  6, B_MULTI_INPUT_BUS,                B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS,
                        B_CHANNEL_MINI_JACK_STEREO },
        {  7, B_MULTI_INPUT_BUS,                B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS,
                        B_CHANNEL_MINI_JACK_STEREO },
};


static int32
format2size(uint32 format)
{
        switch (format) {
                case B_FMT_8BIT_S:
                case B_FMT_16BIT:
                        return 2;

                case B_FMT_18BIT:
                case B_FMT_20BIT:
                case B_FMT_24BIT:
                case B_FMT_32BIT:
                case B_FMT_FLOAT:
                        return 4;

                default:
                        return -1;
        }
}


static status_t
get_description(geode_controller* controller, multi_description* data)
{
        data->interface_version = B_CURRENT_INTERFACE_VERSION;
        data->interface_minimum = B_CURRENT_INTERFACE_VERSION;

        strcpy(data->friendly_name, "Geode");
        strcpy(data->vendor_info, "Haiku");

        int32 inChannels = 0;
        if (controller->record_stream != NULL)
                inChannels = 2;

        int32 outChannels = 0;
        if (controller->playback_stream != NULL)
                outChannels = 2;

        data->output_channel_count = outChannels;
        data->output_bus_channel_count = outChannels;
        data->input_channel_count = inChannels;
        data->input_bus_channel_count = inChannels;
        data->aux_bus_channel_count = 0;

        dprintf("%s: request_channel_count: %" B_PRId32 "\n", __func__, data->request_channel_count);

        if (data->request_channel_count >= (int)(sizeof(sChannels)
                        / sizeof(sChannels[0]))) {
                memcpy(data->channels, &sChannels, sizeof(sChannels));
        }

        /* determine output/input rates */
        data->output_rates = B_SR_48000;
        data->input_rates = B_SR_48000;

        /* force existance of 48kHz if variable rates are not supported */
        if (data->output_rates == 0)
                data->output_rates = B_SR_48000;
        if (data->input_rates == 0)
                data->input_rates = B_SR_48000;

        data->max_cvsr_rate = 0;
        data->min_cvsr_rate = 0;

        data->output_formats = B_FMT_16BIT;
        data->input_formats = B_FMT_16BIT;
        data->lock_sources = B_MULTI_LOCK_INTERNAL;
        data->timecode_sources = 0;
        data->interface_flags = B_MULTI_INTERFACE_PLAYBACK | B_MULTI_INTERFACE_RECORD;
        data->start_latency = 30000;

        strcpy(data->control_panel, "");

        return B_OK;
}


static status_t
get_enabled_channels(geode_controller* controller, multi_channel_enable* data)
{
        B_SET_CHANNEL(data->enable_bits, 0, true);
        B_SET_CHANNEL(data->enable_bits, 1, true);
        B_SET_CHANNEL(data->enable_bits, 2, true);
        B_SET_CHANNEL(data->enable_bits, 3, true);
        data->lock_source = B_MULTI_LOCK_INTERNAL;

        return B_OK;
}


static status_t
get_global_format(geode_controller* controller, multi_format_info* data)
{
        data->output_latency = 0;
        data->input_latency = 0;
        data->timecode_kind = 0;

        if (controller->playback_stream != NULL) {
                data->output.format = controller->playback_stream->sample_format;
                data->output.rate = controller->playback_stream->sample_rate;
        } else {
                data->output.format = 0;
                data->output.rate = 0;
        }

        if (controller->record_stream != NULL) {
                data->input.format = controller->record_stream->sample_format;
                data->input.rate = controller->record_stream->sample_format;
        } else {
                data->input.format = 0;
                data->input.rate = 0;
        }

        return B_OK;
}


static status_t
set_global_format(geode_controller* controller, multi_format_info* data)
{
        // TODO: it looks like we're not supposed to fail; fix this!
#if 0
        if ((data->output.format & audioGroup->supported_formats) == 0)
                || (data->output.rate & audioGroup->supported_rates) == 0)
                return B_BAD_VALUE;
#endif

        if (controller->playback_stream != NULL) {
                controller->playback_stream->sample_format = data->output.format;
                controller->playback_stream->sample_rate = data->output.rate;
                controller->playback_stream->sample_size = format2size(
                        controller->playback_stream->sample_format);
        }

        if (controller->record_stream != NULL) {
                controller->record_stream->sample_rate = data->input.rate;
                controller->record_stream->sample_format = data->input.format;
                controller->record_stream->sample_size = format2size(
                        controller->record_stream->sample_format);
        }

        return B_OK;
}


static void
geode_ac97_get_mix(geode_controller *controller, const void *cookie, int32 type, float *values) {
        ac97_source_info *info = (ac97_source_info *)cookie;
        uint16 value, mask;
        float gain;

        switch(type) {
                case B_MIX_GAIN:
                        value = ac97_reg_cached_read(controller->ac97, info->reg);
                        //TRACE("B_MIX_GAIN value : %u\n", value);
                        if (info->type & B_MIX_STEREO) {
                                mask = ((1 << (info->bits + 1)) - 1) << 8;
                                gain = ((value & mask) >> 8) * info->granularity;
                                if (info->polarity == 1)
                                        values[0] = info->max_gain - gain;
                                else
                                        values[0] = gain - info->min_gain;

                                mask = ((1 << (info->bits + 1)) - 1);
                                gain = (value & mask) * info->granularity;
                                if (info->polarity == 1)
                                        values[1] = info->max_gain - gain;
                                else
                                        values[1] = gain - info->min_gain;
                        } else {
                                mask = ((1 << (info->bits + 1)) - 1);
                                gain = (value & mask) * info->granularity;
                                if (info->polarity == 1)
                                        values[0] = info->max_gain - gain;
                                else
                                        values[0] = gain - info->min_gain;
                        }
                        break;
                case B_MIX_MUTE:
                        mask = ((1 << 1) - 1) << 15;
                        value = ac97_reg_cached_read(controller->ac97, info->reg);
                        //TRACE("B_MIX_MUTE value : %u\n", value);
                        value &= mask;
                        values[0] = ((value >> 15) == 1) ? 1.0 : 0.0;
                        break;
                case B_MIX_MICBOOST:
                        mask = ((1 << 1) - 1) << 6;
                        value = ac97_reg_cached_read(controller->ac97, info->reg);
                        //TRACE("B_MIX_MICBOOST value : %u\n", value);
                        value &= mask;
                        values[0] = ((value >> 6) == 1) ? 1.0 : 0.0;
                        break;
                case B_MIX_MUX:
                        mask = ((1 << 3) - 1);
                        value = ac97_reg_cached_read(controller->ac97, AC97_RECORD_SELECT);
                        value &= mask;
                        //TRACE("B_MIX_MUX value : %u\n", value);
                        values[0] = (float)value;
                        break;
        }
}


static void
geode_ac97_set_mix(geode_controller *controller, const void *cookie, int32 type, float *values) {
        ac97_source_info *info = (ac97_source_info *)cookie;
        uint16 value, mask;
        float gain;

        switch(type) {
                case B_MIX_GAIN:
                        value = ac97_reg_cached_read(controller->ac97, info->reg);
                        if (info->type & B_MIX_STEREO) {
                                mask = ((1 << (info->bits + 1)) - 1) << 8;
                                value &= ~mask;

                                if (info->polarity == 1)
                                        gain = info->max_gain - values[0];
                                else
                                        gain =  values[0] - info->min_gain;
                                value |= ((uint16)(gain / info->granularity) << 8) & mask;

                                mask = ((1 << (info->bits + 1)) - 1);
                                value &= ~mask;
                                if (info->polarity == 1)
                                        gain = info->max_gain - values[1];
                                else
                                        gain =  values[1] - info->min_gain;
                                value |= ((uint16)(gain / info->granularity)) & mask;
                        } else {
                                mask = ((1 << (info->bits + 1)) - 1);
                                value &= ~mask;
                                if (info->polarity == 1)
                                        gain = info->max_gain - values[0];
                                else
                                        gain =  values[0] - info->min_gain;
                                value |= ((uint16)(gain / info->granularity)) & mask;
                        }
                        //TRACE("B_MIX_GAIN value : %u\n", value);
                        ac97_reg_cached_write(controller->ac97, info->reg, value);
                        break;
                case B_MIX_MUTE:
                        mask = ((1 << 1) - 1) << 15;
                        value = ac97_reg_cached_read(controller->ac97, info->reg);
                        value &= ~mask;
                        value |= ((values[0] == 1.0 ? 1 : 0 ) << 15 & mask);
                        if (info->reg == AC97_SURR_VOLUME) {
                                // there is a independent mute for each channel
                                mask = ((1 << 1) - 1) << 7;
                                value &= ~mask;
                                value |= ((values[0] == 1.0 ? 1 : 0 ) << 7 & mask);
                        }
                        //TRACE("B_MIX_MUTE value : %u\n", value);
                        ac97_reg_cached_write(controller->ac97, info->reg, value);
                        break;
                case B_MIX_MICBOOST:
                        mask = ((1 << 1) - 1) << 6;
                        value = ac97_reg_cached_read(controller->ac97, info->reg);
                        value &= ~mask;
                        value |= ((values[0] == 1.0 ? 1 : 0 ) << 6 & mask);
                        //TRACE("B_MIX_MICBOOST value : %u\n", value);
                        ac97_reg_cached_write(controller->ac97, info->reg, value);
                        break;
                case B_MIX_MUX:
                        mask = ((1 << 3) - 1);
                        value = ((int32)values[0]) & mask;
                        value = value | (value << 8);
                        //TRACE("B_MIX_MUX value : %u\n", value);
                        ac97_reg_cached_write(controller->ac97, AC97_RECORD_SELECT, value);
                        break;
        }

}


static int32
create_group_control(geode_multi *multi, uint32 *index, uint32 parent,
        strind_id string, const char* name) {
        int32 i = *index;
        (*index)++;
        multi->controls[i].mix_control.id = MULTI_CONTROL_FIRSTID + i;
        multi->controls[i].mix_control.parent = parent;
        multi->controls[i].mix_control.flags = B_MULTI_MIX_GROUP;
        multi->controls[i].mix_control.master = MULTI_CONTROL_MASTERID;
        multi->controls[i].mix_control.string = string;
        if (name)
                strcpy(multi->controls[i].mix_control.name, name);

        return multi->controls[i].mix_control.id;
}


static status_t
create_controls_list(geode_multi *multi)
{
        uint32  i = 0, index = 0, count, id, parent, parent2, parent3;
        const ac97_source_info *info;

        /* AC97 Mixer */
        parent = create_group_control(multi, &index, 0, S_null, "AC97 mixer");

        count = source_info_size;
        //Note that we ignore first item in source_info
        //It's for recording, but do match this with ac97.c's source_info
        for (i = 1; i < count ; i++) {
                info = &source_info[i];
                TRACE("name : %s\n", info->name);

                parent2 = create_group_control(multi, &index, parent, S_null, info->name);

                if (info->type & B_MIX_GAIN) {
                        if (info->type & B_MIX_MUTE) {
                                multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                                multi->controls[index].mix_control.flags = B_MULTI_MIX_ENABLE;
                                multi->controls[index].mix_control.master = MULTI_CONTROL_MASTERID;
                                multi->controls[index].mix_control.parent = parent2;
                                multi->controls[index].mix_control.string = S_MUTE;
                                multi->controls[index].cookie = info;
                                multi->controls[index].type = B_MIX_MUTE;
                                multi->controls[index].get = &geode_ac97_get_mix;
                                multi->controls[index].set = &geode_ac97_set_mix;
                                index++;
                        }

                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_GAIN;
                        multi->controls[index].mix_control.master = MULTI_CONTROL_MASTERID;
                        multi->controls[index].mix_control.parent = parent2;
                        strcpy(multi->controls[index].mix_control.name, info->name);
                        multi->controls[index].mix_control.gain.min_gain = info->min_gain;
                        multi->controls[index].mix_control.gain.max_gain = info->max_gain;
                        multi->controls[index].mix_control.gain.granularity = info->granularity;
                        multi->controls[index].cookie = info;
                        multi->controls[index].type = B_MIX_GAIN;
                        multi->controls[index].get = &geode_ac97_get_mix;
                        multi->controls[index].set = &geode_ac97_set_mix;
                        id = multi->controls[index].mix_control.id;
                        index++;

                        if (info->type & B_MIX_STEREO) {
                                multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                                multi->controls[index].mix_control.flags = B_MULTI_MIX_GAIN;
                                multi->controls[index].mix_control.master = id;
                                multi->controls[index].mix_control.parent = parent2;
                                strcpy(multi->controls[index].mix_control.name, info->name);
                                multi->controls[index].mix_control.gain.min_gain = info->min_gain;
                                multi->controls[index].mix_control.gain.max_gain = info->max_gain;
                                multi->controls[index].mix_control.gain.granularity = info->granularity;
                                multi->controls[index].cookie = info;
                                multi->controls[index].type = B_MIX_GAIN;
                                multi->controls[index].get = &geode_ac97_get_mix;
                                multi->controls[index].set = &geode_ac97_set_mix;
                                index++;
                        }

                        if (info->type & B_MIX_MICBOOST) {
                                multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                                multi->controls[index].mix_control.flags = B_MULTI_MIX_ENABLE;
                                multi->controls[index].mix_control.master = MULTI_CONTROL_MASTERID;
                                multi->controls[index].mix_control.parent = parent2;
                                strcpy(multi->controls[index].mix_control.name, "+20 dB");
                                multi->controls[index].cookie = info;
                                multi->controls[index].type = B_MIX_MICBOOST;
                                multi->controls[index].get = &geode_ac97_get_mix;
                                multi->controls[index].set = &geode_ac97_set_mix;
                                index++;
                        }
                }
        }

        /* AC97 Record */
        parent = create_group_control(multi, &index, 0, S_null, "Recording");

        info = &source_info[0];
        TRACE("name : %s\n", info->name);

        parent2 = create_group_control(multi, &index, parent, S_null, info->name);

        if (info->type & B_MIX_GAIN) {
                if (info->type & B_MIX_MUTE) {
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_ENABLE;
                        multi->controls[index].mix_control.master = MULTI_CONTROL_MASTERID;
                        multi->controls[index].mix_control.parent = parent2;
                        multi->controls[index].mix_control.string = S_MUTE;
                        multi->controls[index].cookie = info;
                        multi->controls[index].type = B_MIX_MUTE;
                        multi->controls[index].get = &geode_ac97_get_mix;
                        multi->controls[index].set = &geode_ac97_set_mix;
                        index++;
                }

                multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                multi->controls[index].mix_control.flags = B_MULTI_MIX_GAIN;
                multi->controls[index].mix_control.master = MULTI_CONTROL_MASTERID;
                multi->controls[index].mix_control.parent = parent2;
                strcpy(multi->controls[index].mix_control.name, info->name);
                multi->controls[index].mix_control.gain.min_gain = info->min_gain;
                multi->controls[index].mix_control.gain.max_gain = info->max_gain;
                multi->controls[index].mix_control.gain.granularity = info->granularity;
                multi->controls[index].cookie = info;
                multi->controls[index].type = B_MIX_GAIN;
                multi->controls[index].get = &geode_ac97_get_mix;
                multi->controls[index].set = &geode_ac97_set_mix;
                id = multi->controls[index].mix_control.id;
                index++;

                if (info->type & B_MIX_STEREO) {
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_GAIN;
                        multi->controls[index].mix_control.master = id;
                        multi->controls[index].mix_control.parent = parent2;
                        strcpy(multi->controls[index].mix_control.name, info->name);
                        multi->controls[index].mix_control.gain.min_gain = info->min_gain;
                        multi->controls[index].mix_control.gain.max_gain = info->max_gain;
                        multi->controls[index].mix_control.gain.granularity = info->granularity;
                        multi->controls[index].cookie = info;
                        multi->controls[index].type = B_MIX_GAIN;
                        multi->controls[index].get = &geode_ac97_get_mix;
                        multi->controls[index].set = &geode_ac97_set_mix;
                        index++;
                }

                if (info->type & B_MIX_RECORDMUX) {
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX;
                        multi->controls[index].mix_control.parent = parent2;
                        strcpy(multi->controls[index].mix_control.name, "Record mux");
                        multi->controls[index].cookie = info;
                        multi->controls[index].type = B_MIX_MUX;
                        multi->controls[index].get = &geode_ac97_get_mix;
                        multi->controls[index].set = &geode_ac97_set_mix;
                        parent3 = multi->controls[index].mix_control.id;
                        index++;

                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX_VALUE;
                        multi->controls[index].mix_control.parent = parent3;
                        multi->controls[index].mix_control.string = S_MIC;
                        index++;
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX_VALUE;
                        multi->controls[index].mix_control.parent = parent3;
                        strcpy(multi->controls[index].mix_control.name, "CD in");
                        index++;
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX_VALUE;
                        multi->controls[index].mix_control.parent = parent3;
                        strcpy(multi->controls[index].mix_control.name, "Video in");
                        index++;
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX_VALUE;
                        multi->controls[index].mix_control.parent = parent3;
                        strcpy(multi->controls[index].mix_control.name, "Aux in");
                        index++;
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX_VALUE;
                        multi->controls[index].mix_control.parent = parent3;
                        strcpy(multi->controls[index].mix_control.name, "Line in");
                        index++;
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX_VALUE;
                        multi->controls[index].mix_control.parent = parent3;
                        multi->controls[index].mix_control.string = S_STEREO_MIX;
                        index++;
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX_VALUE;
                        multi->controls[index].mix_control.parent = parent3;
                        multi->controls[index].mix_control.string = S_MONO_MIX;
                        index++;
                        multi->controls[index].mix_control.id = MULTI_CONTROL_FIRSTID + index;
                        multi->controls[index].mix_control.flags = B_MULTI_MIX_MUX_VALUE;
                        multi->controls[index].mix_control.parent = parent3;
                        strcpy(multi->controls[index].mix_control.name, "TAD");
                        index++;
                }
        }

        multi->control_count = index;
        TRACE("multi->control_count %" B_PRIu32 "\n", multi->control_count);
        return B_OK;
}


static status_t
list_mix_controls(geode_controller* controller, multi_mix_control_info* mmci)
{
        multi_mix_control* mmc = mmci->controls;
        if (mmci->control_count < 24)
                return B_ERROR;

        if (create_controls_list(controller->multi) < B_OK)
                return B_ERROR;
        for (uint32 i = 0; i < controller->multi->control_count; i++) {
                mmc[i] = controller->multi->controls[i].mix_control;
        }

        mmci->control_count = controller->multi->control_count;
        return B_OK;
}


static status_t
list_mix_connections(geode_controller* controller,
        multi_mix_connection_info* data)
{
        data->actual_count = 0;
        return B_OK;
}


static status_t
list_mix_channels(geode_controller* controller, multi_mix_channel_info *data)
{
        return B_OK;
}


static status_t
get_mix(geode_controller *controller, multi_mix_value_info * mmvi)
{
        for (int32 i = 0; i < mmvi->item_count; i++) {
                uint32 id = mmvi->values[i].id - MULTI_CONTROL_FIRSTID;
                if (id < 0 || id >= controller->multi->control_count) {
                        dprintf("geode_get_mix : invalid control id requested : %" B_PRId32 "\n", id);
                        continue;
                }
                multi_mixer_control *control = &controller->multi->controls[id];

                if (control->mix_control.flags & B_MULTI_MIX_GAIN) {
                        if (control->get) {
                                float values[2];
                                control->get(controller, control->cookie, control->type, values);
                                if (control->mix_control.master == MULTI_CONTROL_MASTERID)
                                        mmvi->values[i].gain = values[0];
                                else
                                        mmvi->values[i].gain = values[1];
                        }
                }

                if (control->mix_control.flags & B_MULTI_MIX_ENABLE && control->get) {
                        float values[1];
                        control->get(controller, control->cookie, control->type, values);
                        mmvi->values[i].enable = (values[0] == 1.0);
                }

                if (control->mix_control.flags & B_MULTI_MIX_MUX && control->get) {
                        float values[1];
                        control->get(controller, control->cookie, control->type, values);
                        mmvi->values[i].mux = (int32)values[0];
                }
        }
        return B_OK;
}


static status_t
set_mix(geode_controller *controller, multi_mix_value_info * mmvi)
{
        geode_multi *multi = controller->multi;
        for (int32 i = 0; i < mmvi->item_count; i++) {
                uint32 id = mmvi->values[i].id - MULTI_CONTROL_FIRSTID;
                if (id < 0 || id >= multi->control_count) {
                        dprintf("geode_set_mix : invalid control id requested : %" B_PRId32 "\n", id);
                        continue;
                }
                multi_mixer_control *control = &multi->controls[id];

                if (control->mix_control.flags & B_MULTI_MIX_GAIN) {
                        multi_mixer_control *control2 = NULL;
                        if (i+1<mmvi->item_count) {
                                id = mmvi->values[i + 1].id - MULTI_CONTROL_FIRSTID;
                                if (id < 0 || id >= multi->control_count) {
                                        dprintf("geode_set_mix : invalid control id requested : %" B_PRId32 "\n", id);
                                } else {
                                        control2 = &multi->controls[id];
                                        if (control2->mix_control.master != control->mix_control.id)
                                                control2 = NULL;
                                }
                        }

                        if (control->set) {
                                float values[2];
                                values[0] = 0.0;
                                values[1] = 0.0;

                                if (control->mix_control.master == MULTI_CONTROL_MASTERID)
                                        values[0] = mmvi->values[i].gain;
                                else
                                        values[1] = mmvi->values[i].gain;

                                if (control2 && control2->mix_control.master != MULTI_CONTROL_MASTERID)
                                        values[1] = mmvi->values[i+1].gain;

                                control->set(controller, control->cookie, control->type, values);
                        }

                        if (control2)
                                i++;
                }

                if (control->mix_control.flags & B_MULTI_MIX_ENABLE && control->set) {
                        float values[1];

                        values[0] = mmvi->values[i].enable ? 1.0 : 0.0;
                        control->set(controller, control->cookie, control->type, values);
                }

                if (control->mix_control.flags & B_MULTI_MIX_MUX && control->set) {
                        float values[1];

                        values[0] = (float)mmvi->values[i].mux;
                        control->set(controller, control->cookie, control->type, values);
                }
        }
        return B_OK;
}


static status_t
get_buffers(geode_controller* controller, multi_buffer_list* data)
{
        TRACE("playback: %" B_PRId32 " buffers, %" B_PRId32 " channels, %" B_PRIu32 " samples\n",
                data->request_playback_buffers, data->request_playback_channels,
                data->request_playback_buffer_size);
        TRACE("record: %" B_PRId32 " buffers, %" B_PRId32 " channels, %" B_PRIu32 " samples\n",
                data->request_record_buffers, data->request_record_channels,
                data->request_record_buffer_size);

        /* Determine what buffers we return given the request */

        data->return_playback_buffers = data->request_playback_buffers;
        data->return_playback_channels = data->request_playback_channels;
        data->return_playback_buffer_size = data->request_playback_buffer_size;
        data->return_record_buffers = data->request_record_buffers;
        data->return_record_channels = data->request_record_channels;
        data->return_record_buffer_size = data->request_record_buffer_size;

        /* Workaround for Haiku multi_audio API, since it prefers to let the
           driver pick values, while the BeOS multi_audio actually gives the
           user's defaults. */
        if (data->return_playback_buffers > STREAM_MAX_BUFFERS
                || data->return_playback_buffers < STREAM_MIN_BUFFERS)
                data->return_playback_buffers = STREAM_MIN_BUFFERS;

        if (data->return_record_buffers > STREAM_MAX_BUFFERS
                || data->return_record_buffers < STREAM_MIN_BUFFERS)
                data->return_record_buffers = STREAM_MIN_BUFFERS;

        if (data->return_playback_buffer_size == 0)
                data->return_playback_buffer_size = DEFAULT_FRAMES_PER_BUFFER;

        if (data->return_record_buffer_size == 0)
                data->return_record_buffer_size = DEFAULT_FRAMES_PER_BUFFER;

        /* ... from here on, we can assume again that a reasonable request is
           being made */

        data->flags = B_MULTI_BUFFER_PLAYBACK | B_MULTI_BUFFER_RECORD;

        /* Copy the settings into the streams */

        if (controller->playback_stream != NULL) {
                controller->playback_stream->num_buffers = data->return_playback_buffers;
                controller->playback_stream->num_channels = data->return_playback_channels;
                controller->playback_stream->buffer_length
                        = data->return_playback_buffer_size;

                status_t status = geode_stream_setup_buffers(
                        controller->playback_stream, "Playback");
                if (status != B_OK) {
                        dprintf("geode: Error setting up playback buffers: %s\n",
                                strerror(status));
                        return status;
                }
        }

        if (controller->record_stream != NULL) {
                controller->record_stream->num_buffers = data->return_record_buffers;
                controller->record_stream->num_channels = data->return_record_channels;
                controller->record_stream->buffer_length
                        = data->return_record_buffer_size;

                status_t status = geode_stream_setup_buffers(
                        controller->record_stream, "Recording");
                if (status != B_OK) {
                        dprintf("geode: Error setting up recording buffers: %s\n",
                                strerror(status));
                        return status;
                }
        }

        /* Setup data structure for multi_audio API... */

        if (controller->playback_stream != NULL) {
                uint32 playbackSampleSize = controller->playback_stream->sample_size;

                for (int32 i = 0; i < data->return_playback_buffers; i++) {
                        for (int32 channelIndex = 0;
                                        channelIndex < data->return_playback_channels; channelIndex++) {
                                data->playback_buffers[i][channelIndex].base
                                        = (char*)controller->playback_stream->buffers[i]
                                                + playbackSampleSize * channelIndex;
                                data->playback_buffers[i][channelIndex].stride
                                        = playbackSampleSize * data->return_playback_channels;
                        }
                }
        }

        if (controller->record_stream != NULL) {
                uint32 recordSampleSize = controller->record_stream->sample_size;

                for (int32 i = 0; i < data->return_record_buffers; i++) {
                        for (int32 channelIndex = 0;
                                        channelIndex < data->return_record_channels; channelIndex++) {
                                data->record_buffers[i][channelIndex].base
                                        = (char*)controller->record_stream->buffers[i]
                                                + recordSampleSize * channelIndex;
                                data->record_buffers[i][channelIndex].stride
                                        = recordSampleSize * data->return_record_channels;
                        }
                }
        }

        return B_OK;
}


/*! playback_buffer_cycle is the buffer we want to have played */
static status_t
buffer_exchange(geode_controller* controller, multi_buffer_info* data)
{
        static int debug_buffers_exchanged = 0;
        cpu_status status;
        status_t err;
        multi_buffer_info buffer_info;

        if (controller->playback_stream == NULL)
                return B_ERROR;

        if (!controller->playback_stream->running) {
                geode_stream_start(controller->playback_stream);
        }
        if (controller->record_stream && !controller->record_stream->running) {
                geode_stream_start(controller->record_stream);
        }

#ifdef __HAIKU__
        if (user_memcpy(&buffer_info, data, sizeof(buffer_info)) < B_OK)
                return B_BAD_ADDRESS;
#else
        memcpy(&buffer_info, data, sizeof(buffer_info));
#endif

        /* do playback */
        err = acquire_sem_etc(controller->playback_stream->buffer_ready_sem,
                1, B_CAN_INTERRUPT, 0);
        if (err != B_OK) {
                dprintf("%s: Error waiting for playback buffer to finish (%s)!\n", __func__,
                        strerror(err));
                return err;
        }

        status = disable_interrupts();
        acquire_spinlock(&controller->playback_stream->lock);

        buffer_info.playback_buffer_cycle = controller->playback_stream->buffer_cycle;
        buffer_info.played_real_time = controller->playback_stream->real_time;
        buffer_info.played_frames_count = controller->playback_stream->frames_count;

        release_spinlock(&controller->playback_stream->lock);

        if (controller->record_stream) {
                acquire_spinlock(&controller->record_stream->lock);
                buffer_info.record_buffer_cycle = controller->record_stream->buffer_cycle;
                buffer_info.recorded_real_time = controller->record_stream->real_time;
                buffer_info.recorded_frames_count = controller->record_stream->frames_count;
                release_spinlock(&controller->record_stream->lock);
        }

        restore_interrupts(status);

#ifdef __HAIKU__
        if (user_memcpy(data, &buffer_info, sizeof(buffer_info)) < B_OK)
                return B_BAD_ADDRESS;
#else
        memcpy(data, &buffer_info, sizeof(buffer_info));
#endif

        debug_buffers_exchanged++;
        if (((debug_buffers_exchanged % 100) == 1) && (debug_buffers_exchanged < 1111)) {
                dprintf("%s: %d buffers processed\n", __func__, debug_buffers_exchanged);
        }

        return B_OK;
}


static status_t
buffer_force_stop(geode_controller* controller)
{
        if (controller->playback_stream != NULL) {
                geode_stream_stop(controller->playback_stream);
        }
        if (controller->record_stream != NULL) {
                geode_stream_stop(controller->record_stream);
        }

        return B_OK;
}


status_t
multi_audio_control(geode_controller* controller, uint32 op, void* arg, size_t len)
{
        // TODO: make userland-safe when built for Haiku!

        switch (op) {
                case B_MULTI_GET_DESCRIPTION:
                {
#ifdef __HAIKU__
                        multi_description description;
                        multi_channel_info channels[16];
                        multi_channel_info* originalChannels;

                        if (user_memcpy(&description, arg, sizeof(multi_description))
                                        != B_OK)
                                return B_BAD_ADDRESS;

                        originalChannels = description.channels;
                        description.channels = channels;
                        if (description.request_channel_count > 16)
                                description.request_channel_count = 16;

                        status_t status = get_description(controller, &description);
                        if (status != B_OK)
                                return status;

                        description.channels = originalChannels;
                        if (user_memcpy(arg, &description, sizeof(multi_description))
                                        != B_OK)
                                return B_BAD_ADDRESS;
                        return user_memcpy(originalChannels, channels, sizeof(multi_channel_info)
                                        * description.request_channel_count);
#else
                        return get_description(controller, (multi_description*)arg);
#endif
                }

                case B_MULTI_GET_ENABLED_CHANNELS:
                        return get_enabled_channels(controller, (multi_channel_enable*)arg);
                case B_MULTI_SET_ENABLED_CHANNELS:
                        return B_OK;

                case B_MULTI_GET_GLOBAL_FORMAT:
                        return get_global_format(controller, (multi_format_info*)arg);
                case B_MULTI_SET_GLOBAL_FORMAT:
                        return set_global_format(controller, (multi_format_info*)arg);

                case B_MULTI_LIST_MIX_CHANNELS:
                        return list_mix_channels(controller, (multi_mix_channel_info*)arg);
                case B_MULTI_LIST_MIX_CONTROLS:
                        return list_mix_controls(controller, (multi_mix_control_info*)arg);
                case B_MULTI_LIST_MIX_CONNECTIONS:
                        return list_mix_connections(controller,
                                (multi_mix_connection_info*)arg);
                case B_MULTI_GET_MIX:
                        return get_mix(controller, (multi_mix_value_info *)arg);
                case B_MULTI_SET_MIX:
                        return set_mix(controller, (multi_mix_value_info *)arg);

                case B_MULTI_GET_BUFFERS:
                        return get_buffers(controller, (multi_buffer_list*)arg);

                case B_MULTI_BUFFER_EXCHANGE:
                        return buffer_exchange(controller, (multi_buffer_info*)arg);
                case B_MULTI_BUFFER_FORCE_STOP:
                        return buffer_force_stop(controller);

                case B_MULTI_GET_EVENT_INFO:
                case B_MULTI_SET_EVENT_INFO:
                case B_MULTI_GET_EVENT:
                case B_MULTI_GET_CHANNEL_FORMATS:
                case B_MULTI_SET_CHANNEL_FORMATS:
                case B_MULTI_SET_BUFFERS:
                case B_MULTI_SET_START_TIME:
                        return B_ERROR;
        }

        return B_BAD_VALUE;
}