/*	$NetBSD: sun.c,v 1.11 2024/02/27 21:05:34 gson Exp $	*/

/*
 * Copyright (c) 2002, 2013, 2015 Matthew R. Green
 * All rights reserved.
 *
 * 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 ``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 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.
 */

/*
 * XXX this is slightly icky in places...
 */
#include <sys/cdefs.h>

#ifndef lint
__RCSID("$NetBSD: sun.c,v 1.11 2024/02/27 21:05:34 gson Exp $");
#endif


#include <sys/types.h>
#include <sys/audioio.h>
#include <sys/ioctl.h>
#include <sys/time.h>

#include <ctype.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "libaudio.h"
#include "auconv.h"

/*
 * SunOS/NeXT .au format helpers
 */
static const struct {
	int	file_encoding;
	int	encoding;
	int	precision;
} file2sw_encodings[] = {
	{ AUDIO_FILE_ENCODING_MULAW_8,		AUDIO_ENCODING_ULAW,	8 },
	{ AUDIO_FILE_ENCODING_LINEAR_8,		AUDIO_ENCODING_SLINEAR_BE, 8 },
	{ AUDIO_FILE_ENCODING_LINEAR_16,	AUDIO_ENCODING_SLINEAR_BE, 16 },
	{ AUDIO_FILE_ENCODING_LINEAR_24,	AUDIO_ENCODING_SLINEAR_BE, 24 },
	{ AUDIO_FILE_ENCODING_LINEAR_32,	AUDIO_ENCODING_SLINEAR_BE, 32 },
#if 0
	/*
	 * we should make some of these available.  the, eg ultrasparc, port
	 * can use the VIS instructions (if available) do do some of these
	 * mpeg ones.
	 */
	{ AUDIO_FILE_ENCODING_FLOAT,		AUDIO_ENCODING_ULAW,	32 },
	{ AUDIO_FILE_ENCODING_DOUBLE,		AUDIO_ENCODING_ULAW,	64 },
	{ AUDIO_FILE_ENCODING_ADPCM_G721,	AUDIO_ENCODING_ULAW,	4 },
	{ AUDIO_FILE_ENCODING_ADPCM_G722,	AUDIO_ENCODING_ULAW,	0 },
	{ AUDIO_FILE_ENCODING_ADPCM_G723_3,	AUDIO_ENCODING_ULAW,	3 },
	{ AUDIO_FILE_ENCODING_ADPCM_G723_5,	AUDIO_ENCODING_ULAW,	5 },
#endif
	{ AUDIO_FILE_ENCODING_ALAW_8,		AUDIO_ENCODING_ALAW,	8 },
	{ -1, -1, -1 }
};

int
audio_sun_to_encoding(int sun_encoding, u_int *encp, u_int *precp)
{
	int i;

	for (i = 0; file2sw_encodings[i].file_encoding != -1; i++)
		if (file2sw_encodings[i].file_encoding == sun_encoding) {
			*precp = file2sw_encodings[i].precision;
			*encp = file2sw_encodings[i].encoding;
			return (0);
		}
	return (1);
}

int
audio_encoding_to_sun(int encoding, int precision, int *sunep)
{
	int i;

	for (i = 0; file2sw_encodings[i].file_encoding != -1; i++)
		if (file2sw_encodings[i].encoding == encoding &&
		    file2sw_encodings[i].precision == precision) {
			*sunep = file2sw_encodings[i].file_encoding;
			return (0);
		}
	return (1);
}

int
sun_prepare_header(struct track_info *ti, void **hdrp, size_t *lenp, int *leftp)
{
	static int warned = 0;
	static sun_audioheader auh;
	int sunenc, oencoding = ti->encoding;

	/* only perform conversions if we don't specify the encoding */
	switch (ti->encoding) {

	case AUDIO_ENCODING_ULINEAR_LE:
#if BYTE_ORDER == LITTLE_ENDIAN
	case AUDIO_ENCODING_ULINEAR:
#endif
		if (ti->precision == 16 || ti->precision == 32)
			ti->encoding = AUDIO_ENCODING_SLINEAR_BE;
		break;

	case AUDIO_ENCODING_ULINEAR_BE:
#if BYTE_ORDER == BIG_ENDIAN
	case AUDIO_ENCODING_ULINEAR:
#endif
		if (ti->precision == 16 || ti->precision == 32)
			ti->encoding = AUDIO_ENCODING_SLINEAR_BE;
		break;

	case AUDIO_ENCODING_SLINEAR_LE:
#if BYTE_ORDER == LITTLE_ENDIAN
	case AUDIO_ENCODING_SLINEAR:
#endif
		if (ti->precision == 16 || ti->precision == 32)
			ti->encoding = AUDIO_ENCODING_SLINEAR_BE;
		break;

#if BYTE_ORDER == BIG_ENDIAN
	case AUDIO_ENCODING_SLINEAR:
		ti->encoding = AUDIO_ENCODING_SLINEAR_BE;
		break;
#endif
	}

	/* if we can't express this as a Sun header, don't write any */
	if (audio_encoding_to_sun(ti->encoding, ti->precision, &sunenc) != 0) {
		if (!ti->qflag && !warned) {
			const char *s = audio_enc_from_val(oencoding);

			if (s == NULL)
				s = "(unknown)";
			warnx("failed to convert to sun encoding from %s "
			      "(precision %d);\nSun audio header not written",
			      s, ti->precision);
		}
		ti->format = AUDIO_FORMAT_NONE;
		warned = 1;
		return -1;
	}

	auh.magic = htonl(AUDIO_FILE_MAGIC);
	if (ti->outfd == STDOUT_FILENO)
		auh.data_size = htonl(AUDIO_UNKNOWN_SIZE);
	else if (ti->total_size != -1)
		auh.data_size = htonl(ti->total_size);
	else
		auh.data_size = 0;
	auh.encoding = htonl(sunenc);
	auh.sample_rate = htonl(ti->sample_rate);
	auh.channels = htonl(ti->channels);
	if (ti->header_info) {
		int 	len, infolen;

		infolen = ((len = strlen(ti->header_info)) + 7) & 0xfffffff8;
		*leftp = infolen - len;
		auh.hdr_size = htonl(sizeof(auh) + infolen);
	} else {
		*leftp = sizeof(audio_default_info);
		auh.hdr_size = htonl(sizeof(auh) + *leftp);
	}
	*(sun_audioheader **)hdrp = &auh;
	*lenp = sizeof auh;
	return 0;
}

write_conv_func
sun_write_get_conv_func(struct track_info *ti)
{
	write_conv_func conv_func = NULL;

	/* only perform conversions if we don't specify the encoding */
	switch (ti->encoding) {

	case AUDIO_ENCODING_ULINEAR_LE:
#if BYTE_ORDER == LITTLE_ENDIAN
	case AUDIO_ENCODING_ULINEAR:
#endif
		if (ti->precision == 16)
			conv_func = change_sign16_swap_bytes_le;
		else if (ti->precision == 32)
			conv_func = change_sign32_swap_bytes_le;
		break;

	case AUDIO_ENCODING_ULINEAR_BE:
#if BYTE_ORDER == BIG_ENDIAN
	case AUDIO_ENCODING_ULINEAR:
#endif
		if (ti->precision == 16)
			conv_func = change_sign16_be;
		else if (ti->precision == 32)
			conv_func = change_sign32_be;
		break;

	case AUDIO_ENCODING_SLINEAR_LE:
#if BYTE_ORDER == LITTLE_ENDIAN
	case AUDIO_ENCODING_SLINEAR:
#endif
		if (ti->precision == 16)
			conv_func = swap_bytes;
		else if (ti->precision == 32)
			conv_func = swap_bytes32;
		break;
	}

	return conv_func;
}
