root/usr.sbin/virtual_oss/virtual_oss/format.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2012-2020 Hans Petter Selasky
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/queue.h>
#include <sys/soundcard.h>

#include <stdint.h>
#include <string.h>

#include "int.h"

void
format_import(uint32_t fmt, const uint8_t *src, uint32_t len,
    int64_t *dst)
{
        const uint8_t *end = src + len;
        int64_t val;

        if (fmt & AFMT_16BIT) {
                while (src != end) {
                        if (fmt & (AFMT_S16_LE | AFMT_U16_LE))
                                val = src[0] | (src[1] << 8);
                        else
                                val = src[1] | (src[0] << 8);

                        src += 2;

                        if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
                                val = val ^ 0x8000;

                        val <<= (64 - 16);
                        val >>= (64 - 16);

                        *dst++ = val;
                }

        } else if (fmt & AFMT_24BIT) {
                while (src < end) {
                        if (fmt & (AFMT_S24_LE | AFMT_U24_LE))
                                val = src[0] | (src[1] << 8) | (src[2] << 16);
                        else
                                val = src[2] | (src[1] << 8) | (src[0] << 16);

                        src += 3;

                        if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
                                val = val ^ 0x800000;

                        val <<= (64 - 24);
                        val >>= (64 - 24);

                        *dst++ = val;
                }
        } else if (fmt & AFMT_32BIT) {
                while (src < end) {
                        int64_t e, m, s;

                        if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE))
                                val = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
                        else
                                val = src[3] | (src[2] << 8) | (src[1] << 16) | (src[0] << 24);

                        src += 4;

                        if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
                                val = val ^ 0x80000000LL;

                        if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) {
                                e = (val >> 23) & 0xff;
                                /* NaN, +/- Inf  or too small */
                                if (e == 0xff || e < 96) {
                                        val = 0;
                                        goto skip;
                                }
                                s = val & 0x80000000U;
                                if (e > 126) {
                                        val = s == 0 ? format_max(fmt) :
                                            -0x80000000LL;
                                        goto skip;
                                }
                                m = 0x800000 | (val & 0x7fffff);
                                e += 8 - 127;
                                if (e < 0)
                                        m >>= -e;
                                else
                                        m <<= e;
                                val = s == 0 ? m : -m;
                        }
skip:
                        val <<= (64 - 32);
                        val >>= (64 - 32);

                        *dst++ = val;
                }

        } else if (fmt & AFMT_8BIT) {
                while (src < end) {
                        val = src[0];

                        src += 1;

                        if (fmt & AFMT_U8)
                                val = val ^ 0x80;

                        val <<= (64 - 8);
                        val >>= (64 - 8);

                        *dst++ = val;
                }
        }
}

void
format_export(uint32_t fmt, const int64_t *src, uint8_t *dst, uint32_t len)
{
        const uint8_t *end = dst + len;
        int64_t val;

        if (fmt & AFMT_16BIT) {
                while (dst != end) {

                        val = *src++;

                        if (val > 0x7FFF)
                                val = 0x7FFF;
                        else if (val < -0x7FFF)
                                val = -0x7FFF;

                        if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
                                val = val ^ 0x8000;

                        if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) {
                                dst[0] = val;
                                dst[1] = val >> 8;
                        } else {
                                dst[1] = val;
                                dst[0] = val >> 8;
                        }

                        dst += 2;
                }

        } else if (fmt & AFMT_24BIT) {
                while (dst != end) {

                        val = *src++;

                        if (val > 0x7FFFFF)
                                val = 0x7FFFFF;
                        else if (val < -0x7FFFFF)
                                val = -0x7FFFFF;

                        if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
                                val = val ^ 0x800000;

                        if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) {
                                dst[0] = val;
                                dst[1] = val >> 8;
                                dst[2] = val >> 16;
                        } else {
                                dst[2] = val;
                                dst[1] = val >> 8;
                                dst[0] = val >> 16;
                        }

                        dst += 3;
                }
        } else if (fmt & AFMT_32BIT) {
                while (dst != end) {
                        int64_t r, e;

                        val = *src++;

                        if (val > 0x7FFFFFFFLL)
                                val = 0x7FFFFFFFLL;
                        else if (val < -0x7FFFFFFFLL)
                                val = -0x7FFFFFFFLL;

                        if (fmt & (AFMT_F32_LE | AFMT_F32_BE)) {
                                if (val == 0)
                                        r = 0;
                                else if (val == format_max(fmt))
                                        r = 0x3f800000;
                                else if (val == -0x80000000LL)
                                        r = 0x80000000U | 0x3f800000;
                                else {
                                        r = 0;
                                        if (val < 0) {
                                                r |= 0x80000000U;
                                                val = -val;
                                        }
                                        e = 127 - 8;
                                        while ((val & 0x7f000000) != 0) {
                                                val >>= 1;
                                                e++;
                                        }
                                        while ((val & 0x7f800000) == 0) {
                                                val <<= 1;
                                                e--;
                                        }
                                        r |= (e & 0xff) << 23;
                                        r |= val & 0x7fffff;
                                }
                                val = r;
                        }

                        if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
                                val = val ^ 0x80000000LL;

                        if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) {
                                dst[0] = val;
                                dst[1] = val >> 8;
                                dst[2] = val >> 16;
                                dst[3] = val >> 24;
                        } else {
                                dst[3] = val;
                                dst[2] = val >> 8;
                                dst[1] = val >> 16;
                                dst[0] = val >> 24;
                        }

                        dst += 4;
                }

        } else if (fmt & AFMT_8BIT) {
                while (dst != end) {

                        val = *src++;

                        if (val > 0x7F)
                                val = 0x7F;
                        else if (val < -0x7F)
                                val = -0x7F;

                        if (fmt & (AFMT_U8))
                                val = val ^ 0x80;

                        dst[0] = val;

                        dst += 1;
                }
        }
}

int64_t
format_max(uint32_t fmt)
{
        if (fmt & AFMT_16BIT)
                return (0x7FFF);
        else if (fmt & AFMT_24BIT)
                return (0x7FFFFF);
        else if (fmt & AFMT_32BIT)
                return (0x7FFFFFFF);
        else if (fmt & AFMT_8BIT)
                return (0x7F);
        return (0);
}

void
format_maximum(const int64_t *src, int64_t *dst, uint32_t ch,
    uint32_t samples, int8_t shift)
{
        const int64_t *end = src + (samples * ch);
        int64_t max[ch];
        int64_t temp;
        uint32_t x;

        memset(max, 0, sizeof(max));

        while (src != end) {
                for (x = 0; x != ch; x++) {
                        temp = *src++;
                        if (temp < 0)
                                temp = -temp;
                        if (temp > max[x])
                                max[x] = temp;
                }
        }

        for (x = 0; x != ch; x++) {
                if (shift < 0)
                        max[x] >>= -shift;
                else
                        max[x] <<= shift;
                if (dst[x] < max[x])
                        dst[x] = max[x];
        }
}

void
format_remix(int64_t *buffer_data, uint32_t in_chans,
    uint32_t out_chans, uint32_t samples)
{
        uint32_t x;

        if (out_chans > in_chans) {
                uint32_t dst = out_chans * (samples - 1);
                uint32_t src = in_chans * (samples - 1);
                uint32_t fill = out_chans - in_chans;

                for (x = 0; x != samples; x++) {
                        memset(buffer_data + dst + in_chans, 0, 8 * fill);
                        if (src != dst) {
                                memcpy(buffer_data + dst,
                                    buffer_data + src,
                                    in_chans * 8);
                        }
                        dst -= out_chans;
                        src -= in_chans;
                }
        } else if (out_chans < in_chans) {
                uint32_t dst = 0;
                uint32_t src = 0;

                for (x = 0; x != samples; x++) {
                        if (src != dst) {
                                memcpy(buffer_data + dst,
                                    buffer_data + src,
                                    out_chans * 8);
                        }
                        dst += out_chans;
                        src += in_chans;
                }
        }
}

void
format_silence(uint32_t fmt, uint8_t *dst, uint32_t len)
{
        const uint8_t *end = dst + len;

        if (fmt & AFMT_16BIT) {
                uint16_t val;

                if (fmt & (AFMT_U16_LE | AFMT_U16_BE))
                        val = 1U << 15;
                else
                        val = 0;

                while (dst != end) {
                        if (fmt & (AFMT_S16_LE | AFMT_U16_LE)) {
                                dst[0] = val;
                                dst[1] = val >> 8;
                        } else {
                                dst[1] = val;
                                dst[0] = val >> 8;
                        }
                        dst += 2;
                }

        } else if (fmt & AFMT_24BIT) {
                uint32_t val;

                if (fmt & (AFMT_U24_LE | AFMT_U24_BE))
                        val = 1U << 23;
                else
                        val = 0;

                while (dst != end) {
                        if (fmt & (AFMT_S24_LE | AFMT_U24_LE)) {
                                dst[0] = val;
                                dst[1] = val >> 8;
                                dst[2] = val >> 16;
                        } else {
                                dst[2] = val;
                                dst[1] = val >> 8;
                                dst[0] = val >> 16;
                        }
                        dst += 3;
                }
        } else if (fmt & AFMT_32BIT) {
                uint32_t val;

                if (fmt & (AFMT_U32_LE | AFMT_U32_BE))
                        val = 1U << 31;
                else
                        val = 0;

                while (dst != end) {
                        if (fmt & (AFMT_S32_LE | AFMT_U32_LE | AFMT_F32_LE)) {
                                dst[0] = val;
                                dst[1] = val >> 8;
                                dst[2] = val >> 16;
                                dst[3] = val >> 24;
                        } else {
                                dst[3] = val;
                                dst[2] = val >> 8;
                                dst[1] = val >> 16;
                                dst[0] = val >> 24;
                        }
                        dst += 4;
                }

        } else if (fmt & AFMT_8BIT) {
                uint8_t val;

                if (fmt & AFMT_U8)
                        val = 1U << 7;
                else
                        val = 0;

                while (dst != end) {
                        dst[0] = val;
                        dst += 1;
                }
        }
}