[Vorbis-dev] 5.1 surround channel coupling

Sebastian Olter qduaty at gmail.com
Thu Feb 22 06:02:09 PST 2007


> May I ask what this "pan filter" is?

It's an Ambisonic encoder which uses Furse-Malham equations to convert
WAVE-EX files (the format itself is a set of speaker feeds) or a .wav
file produced by an A52 decoder (mplayer is preferable as it does not
truncate the LFE channel), into a set of Ambisonic channels, up to 2nd
order. It follows the .AMB file specification, so the channel mapping
depends on their number. It basically acts as a pan effect (assuming
the Ambisonic stream is played back without decoding), thus I call it
so to not confuse people not involved in Ambisonics.

The patch applies to vorbis-tools-1.1.1. It includes an option parser,
so the actual input setup can be programmed by the user and can be any
multichannel source, with up to 255 channels placed on a sphere.

Some parts require discussion (such as stereo downmix - currently it
creates a stereo image that is completely flat, i.e. without any
depth), so I finally decided to post it here. Patch and enjoy. If you
encounter a trouble with linux, try to remove #ifdefs over my
reimplementation of strsep() for windows(R).

[file "pan.c"]
/* Ambisonic encoder module for OggEnc
*  Copyright (C) 2007 Sebastian Olter
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/

#include "pan.h"
#include <stdlib.h>
#include <math.h>
#ifdef WIN32
#include <windows.h>
#endif
#ifndef M_PI
#define M_PI        3.14159265358979323846
#endif
const char *ambichan[] = {
	NULL,
	"W",
	"WY",
	"WXY",
	"WXYZ",
	"WXYRS",
	"WXYZRS",
	NULL,
	NULL,
	"WXYZRSTUV"
};

#if defined(_WIN32)
static char* strsep(char** haystack, const char *needle)
{
    char *c, *tmp, *p;
    if (haystack == NULL)
        return NULL;
    for (c = needle; *c != '\0'; c++)
    {
        p = *haystack;
        do
            p++;
        while ((*p != '\0') && (*p != *c));
        tmp = *haystack;
        if (*p != '\0')
        {
            *p = '\0';
            *haystack = p + 1;
        }
        else
            *haystack = NULL;
        return tmp;
    }
    return NULL;
}
#endif

typedef struct
{
    int inch, outch, nbufs;
    float matrix[255][9]; /* inch x outch */
    audio_read_func real_reader;
    void *real_readdata;
    float **bufs;
}
Pan;

static float furse_malham(float A, float E, char channel)
{
    switch (channel)
    {
    case 'W':
        return 1./sqrt(2);
    case 'X':
        return cos(A) * cos(E);
    case 'Y':
        return sin(A) * cos(E);
    case 'Z':
        return sin(E);
    case 'R':
        return 1.5 * sin(E) * sin(E) - 0.5;
    case 'S':
        return cos(A) * sin(2 * E);
    case 'T':
        return sin(A) * sin(2 * E);
    case 'U':
        return cos(2 * A) * cos(E) * cos(E);
    case 'V':
        return sin(2 * A) * cos(E) * cos(E);
    default:
        return 1.;
    }
}

static int setup_pan_coeffs(oe_enc_opt *opt, const char* channels_in_s)
{
    Pan* pan = opt->readdata;
    int inch = 0;
    float A, E;
    const char *outch;
    char *p, *channels_in = malloc(strlen(channels_in_s) + 1);
    strcpy(channels_in, channels_in_s);
    do
    {
        p = strsep(&channels_in, ",");
        if (strcmp(p, "lfe"))
        {
            A = strtod(p, NULL) * 2. * M_PI / 360.;
            strsep(&p, ":");
            E = (p) ? (strtod(p, NULL) * 2. * M_PI / 360.) : 0.;
            for (outch = ambichan[opt->channels]; outch <
ambichan[opt->channels] + opt->channels; outch++)
                pan->matrix[inch][outch - ambichan[opt->channels]] =
furse_malham(A, E, *outch);
        }
        else
        {
            A = E = 0.;
            pan->matrix[inch][0] = 1.;
        }
        inch++;
    }
    while (channels_in);
    pan->inch = inch;
    pan->outch = opt->channels;
    return inch;
}

static long read_pan(void *data, float **buffer, int samples)
{
    Pan* pan = data;
    long in_samples = pan->real_reader(pan->real_readdata, pan->bufs, samples);
    int i, x, y;
    float W, Y; // for stereo downmix
    for (i=0; i < in_samples; i++)
    {
        for (y = 0; y < pan->outch; y++)
        {
            buffer[y][i] = 0.;
            for (x = 0; x < pan->inch; x++)
                buffer[y][i] += pan->bufs[x][i] * pan->matrix[x][y] / pan->inch;
        }
        if(pan->outch == 2)
        {
			W = buffer[0][i];
			Y = buffer[1][i];
			buffer[0][i] = W + Y;
			buffer[1][i] = W - Y;
		}
    }
    return in_samples;
}

void setup_pan(oe_enc_opt *opt, const char* channels_in, int channels_out)
{
	char *chan;
    int i, inch = opt->channels;
    Pan *pan = calloc(1, sizeof(Pan));
    if(!ambichan[channels_out])
    {
    	fprintf(stderr,"No suitable Ambisonic configuration for %d
channels. Panning disabled.\n",channels_out);
    	return;
	}
	chan = malloc(strlen(ambichan[channels_out]) + 1);
    pan->real_reader = opt->read_samples;
    pan->real_readdata = opt->readdata;
    opt->read_samples = read_pan;
    opt->readdata = pan;


    if (!strcmp(channels_in, "itu5.1"))
        channels_in = "30,330,0,lfe,125,235";
    else if (!strcmp(channels_in, "5.1ac3"))
        channels_in = "30,330,125,235,0,lfe";

    opt->channels = channels_out;
    setup_pan_coeffs(opt, channels_in);

    pan->bufs = malloc(inch * sizeof(float*));
    for (i = 0; i < inch; i++)
        pan->bufs[i] = malloc(4096 * sizeof(float));
	pan->nbufs = inch;

    pan->inch = min(pan->inch, inch); /* Avoid reading from
non-existing buffers */
    fprintf(stderr, "Enabling Ambisonic encoder %dch->", pan->inch);
    switch(channels_out)
    {
    	case 1:
			fprintf(stderr, "mono\n");
			break;
    	case 2:
			fprintf(stderr, "stereo\n");
			break;
    	default:
			fprintf(stderr, "%s\n", ambichan[channels_out]);
			break;
	}
	free(chan);
}

void clear_pan(oe_enc_opt *opt)
{
    int i;
    Pan* pan = opt->readdata;
    opt->read_samples = pan->real_reader;
    opt->readdata = pan->real_readdata;
    for (i = 0; i < pan->nbufs; i++)
        free(pan->bufs[i]);
    free(pan->bufs);
    free(pan);
}
/*ends here*/

[file "pan.h"]
/* Ambisonic encoder module for OggEnc, header for pan.c */

#include "encode.h"

void setup_pan(oe_enc_opt *opt, const char* channels_in, int channels_out);
void clear_pan(oe_enc_opt *opt);
/*ends here*/

[PATCH for oggenc 1.0.2]
diff -b oggenc/audio.c downloads.xiph.org/vorbis-tools-1.1.1/oggenc/audio.c
389c389
< 	if(len!=16 && len!=18)
---
> 	if(len!=16 && len!=18 && len != 40) // 40 is wave-ex
415c415
< 	if(format.format == 1)
---
> 	if(format.format == 1 || format.format == -2) // -2 is wave-ex
diff -b oggenc/encode.c downloads.xiph.org/vorbis-tools-1.1.1/oggenc/encode.c
162,163d161
<             struct ovectl_ratemanage2_arg ai;
< 	        vorbis_encode_ctl(&vi, OV_ECTL_RATEMANAGE2_GET, &ai);
177a176,178
>             struct ovectl_ratemanage2_arg ai;
> 	        vorbis_encode_ctl(&vi, OV_ECTL_RATEMANAGE2_GET, &ai);
>
diff -b oggenc/encode.h downloads.xiph.org/vorbis-tools-1.1.1/oggenc/encode.h
87a88,91
>     /* Ambisonics */
>     char *channels_in;
>     int channels_out;
>
diff -b oggenc/oggenc.c downloads.xiph.org/vorbis-tools-1.1.1/oggenc/oggenc.c
26c26
<
---
> #include "pan.h"
33a34,35
>     {"sources",1,0,0},
>     {"sinks",1,0,0},
82c84
< 			  NULL, 0, -1,-1,-1,.3,-1,0, 0,0.f, 0};
---
> 			  NULL, 0, -1,-1,-1,.3,-1,0, 0,0.f, NULL, 3, 0};
188a191
>
321a325,327
>         if(opt.channels_in)
>             setup_pan(&enc_opts, opt.channels_in, opt.channels_out);
>
340d345
<
357a363,364
>         if(opt.channels_out > 0)
>             clear_pan(&enc_opts);
359a367
>
431a440,456
> 		" Ambisonics:\n"
> 		" --sources=string     Input setup:\n"
> 		"                      <preset>\n"
> 		"                      <angle1>[:elevation1][,angle2[:elevation2]]...\n"
> 		"                      This option enables ambisonic encoding. Input channels are\n"
> 		"                      mixed together to produce an ambisonic output, using\n"
> 		"                      Furse-Malham equations. Input channels not listed here\n"
> 		"                      are omitted. Currently two presets are available:\n"
> 		"                      \"itu5.1\" and \"5.1ac3\". They mean WAVE-EX and AC3 channel\n"
> 		"                      mapping, respectively. Use them with -q0 to encode\n"
> 		"                      a ~140 kbps stream.\n"
> 		" --sinks=number       Total number of Ambisonic channels to be produced. Number\n"
> 		"                      of channels determines what setup we use, see .amb file\n"
> 		"                      format specification. Currently 3, 4, 5, 6 and 9-channel\n"
> 		"                      Ambisonic configurations are supported. 1 and 2 channels\n"
> 		"                      enable downmix.\n"
>         "\n"
592c617,631
<                 if(!strcmp(long_options[option_index].name, "managed")) {
---
>                 if(!strcmp(long_options[option_index].name, "sinks")) {
> 				    if(sscanf(optarg, "%d", &opt->channels_out) != 1) {
>                         fprintf(stderr, _("WARNING: Bad number of output channels: \"%s\". Pan filter disabled.\n"), optarg);
>     					opt->channels_out = 0;
> 				    }
> 				    else if(opt->channels_out < 1 || opt->channels_out == 7 || opt->channels_out == 8 || opt->channels_out > 9) {
>                         fprintf(stderr, _("WARNING: no %s-channel Ambisonic setup. Assuming 2nd order, full sphere.\n"), optarg);
>     					opt->channels_out = 9;
> 				    }
>                 }
>                 else if(!strcmp(long_options[option_index].name, "sources")) {
>                     opt->channels_in = malloc(strlen(optarg) + 1);
>                     strcpy(opt->channels_in, optarg);
>                 }
>                 else if(!strcmp(long_options[option_index].name, "managed")) {
Only in downloads.xiph.org/vorbis-tools-1.1.1/oggenc: pan.c
Only in downloads.xiph.org/vorbis-tools-1.1.1/oggenc: pan.h

/*ends here*/


More information about the Vorbis-dev mailing list