The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <mad.h>

/* pull in the two more complex interface headers */
#include "resample.h"
#include "dither.h"

/* now the constants that everything is in line */
#include "constants.h"

typedef struct mad_stream   * Audio_Mad_Stream;
typedef struct mad_frame    * Audio_Mad_Frame;
typedef struct mad_synth    * Audio_Mad_Synth;
typedef mad_timer_t         * Audio_Mad_Timer;
typedef struct mad_resample * Audio_Mad_Resample;
typedef struct mad_dither   * Audio_Mad_Dither;

MODULE = Audio::Mad		PACKAGE = Audio::Mad		
PROTOTYPES: ENABLE

double
constant(sv,arg)
    PREINIT:
	STRLEN		len;
    INPUT:
	SV *		sv
	char *		s = SvPV(sv, len);
	int		arg
    CODE:
	RETVAL = constant(s,len,arg);
    OUTPUT:
	RETVAL

MODULE = Audio::Mad		PACKAGE = Audio::Mad::Stream

Audio_Mad_Stream
new(CLASS, options=0)
		char *CLASS = NO_INIT
		int options
	CODE:
		Newz(0, (void *)RETVAL, sizeof(*RETVAL), char);
		mad_stream_init(RETVAL);
		
		if (options)
			mad_stream_options(RETVAL, options);
	OUTPUT:
		RETVAL
		
void
buffer(THIS, data)
		Audio_Mad_Stream THIS
		SV* data
	PREINIT:
		unsigned char *ptr;
		STRLEN len;
	CODE:
		ptr = SvPV(data, len);
		mad_stream_buffer(THIS, ptr, (unsigned long)len);
		XSRETURN(1);
		
void
skip(THIS, length)
		Audio_Mad_Stream THIS
		unsigned long length
	CODE:
		mad_stream_skip(THIS, length);
		XSRETURN(1);
		
int
sync(THIS)
		Audio_Mad_Stream THIS
	CODE:
		RETVAL = mad_stream_sync(THIS);
	OUTPUT:
		RETVAL
		
size_t
this_frame(THIS)
		Audio_Mad_Stream THIS
	CODE:
		RETVAL = THIS->this_frame - THIS->buffer;
	OUTPUT:
		RETVAL
		
size_t
next_frame(THIS)
		Audio_Mad_Stream THIS
	CODE:
		RETVAL = THIS->next_frame - THIS->buffer;
	OUTPUT:
		RETVAL

int
error(THIS)
		Audio_Mad_Stream THIS
	CODE:
		RETVAL = THIS->error;
	OUTPUT:
		RETVAL
		
int
err_ok(THIS)
		Audio_Mad_Stream THIS
	CODE:
		RETVAL = MAD_RECOVERABLE(THIS->error);
	OUTPUT:
		RETVAL
		
int
options(THIS, options=0)
		Audio_Mad_Stream THIS
		int options
	CODE:
		if (options)
			mad_stream_options(THIS, options);
			
		RETVAL = THIS->options;
	OUTPUT:
		RETVAL
		
void
DESTROY(THIS)
		Audio_Mad_Stream THIS
	CODE:
		mad_stream_finish(THIS);
		Safefree(THIS);
		
MODULE = Audio::Mad		PACKAGE = Audio::Mad::Frame

Audio_Mad_Frame
new(CLASS)
		char *CLASS = NO_INIT;
	CODE:
		Newz(0, (void *)RETVAL, sizeof(*RETVAL), char);
		mad_frame_init(RETVAL);
	OUTPUT:
		RETVAL
		
int
decode(THIS, STREAM)
		Audio_Mad_Frame THIS
		Audio_Mad_Stream STREAM
	CODE:
		RETVAL = mad_frame_decode(THIS, STREAM);
	OUTPUT:
		RETVAL
		
int
decode_header(THIS, STREAM)
		Audio_Mad_Frame THIS
		Audio_Mad_Stream STREAM
	CODE:
		RETVAL = mad_header_decode(&THIS->header, STREAM);
	OUTPUT:
		RETVAL
		
void
mute(THIS)	
		Audio_Mad_Frame THIS
	CODE:
		mad_frame_mute(THIS);
		XSRETURN(1);
		
int 
layer(THIS)
		Audio_Mad_Frame THIS
	CODE:
		RETVAL = THIS->header.layer;
	OUTPUT:
		RETVAL

int 
mode(THIS)
		Audio_Mad_Frame THIS
	CODE:
		RETVAL = THIS->header.mode;
	OUTPUT:
		RETVAL

int 
bitrate(THIS)
		Audio_Mad_Frame THIS
	CODE:
		RETVAL = THIS->header.bitrate;
	OUTPUT:
		RETVAL

int 
samplerate(THIS)
		Audio_Mad_Frame THIS
	CODE:
		RETVAL = THIS->header.samplerate;
	OUTPUT:
		RETVAL

Audio_Mad_Timer
duration(THIS)
		Audio_Mad_Frame THIS
	CODE:
		Newz(0, (void *)RETVAL, sizeof(*RETVAL), char);
		*RETVAL = THIS->header.duration;
	OUTPUT:
		RETVAL

int
flags(THIS)
		Audio_Mad_Frame THIS
	CODE:
		RETVAL = THIS->header.flags;
	OUTPUT:
		RETVAL		
		
void
DESTROY(THIS)
		Audio_Mad_Frame THIS;
	CODE:
		mad_frame_finish(THIS);
		Safefree(THIS);

MODULE = Audio::Mad		PACKAGE = Audio::Mad::Synth

Audio_Mad_Synth
new(CLASS)
		char *CLASS = NO_INIT;
	CODE:
		Newz(0, (void *)RETVAL, sizeof(*RETVAL), char);
		mad_synth_init(RETVAL);
	OUTPUT:
		RETVAL

void
synth(THIS, FRAME)
		Audio_Mad_Synth THIS
		Audio_Mad_Frame FRAME
	CODE:
		mad_synth_frame(THIS, FRAME);
		XSRETURN(1);
		
void
samples(THIS)
		Audio_Mad_Synth THIS
	PREINIT:
		struct mad_pcm  *pcm;
	PPCODE:
		pcm = &THIS->pcm;
		if (!(pcm->length > 0)) {
			XSRETURN_UNDEF;
		}
		
		EXTEND(SP, 2);
		PUSHs(sv_2mortal(newSVpvn((char *)pcm->samples[0], sizeof(mad_fixed_t) * pcm->length)));
		if (pcm->channels == 2) {
			PUSHs(sv_2mortal(newSVpvn((char *)pcm->samples[1], sizeof(mad_fixed_t) * pcm->length)));
		}
			
void
mute(THIS)
		Audio_Mad_Synth THIS
	CODE:
		mad_synth_mute(THIS);
		XSRETURN(1);
		
void
DESTROY(THIS)
		Audio_Mad_Synth THIS;
	CODE:
		mad_synth_finish(THIS);
		Safefree(THIS);

MODULE = Audio::Mad		PACKAGE = Audio::Mad::Timer

Audio_Mad_Timer
new(CLASS, seconds=0, frac=0, denom=0)
		char *CLASS = NO_INIT;
		unsigned long seconds;
		unsigned long frac;
		unsigned long denom;
	CODE:
		Newz(0, (void *)RETVAL, sizeof(*RETVAL), char);
		*RETVAL = mad_timer_zero;

		if (items == 3)
			mad_timer_set(RETVAL, seconds, frac, denom);
	OUTPUT:
		RETVAL
		
Audio_Mad_Timer
new_copy(THIS)
		Audio_Mad_Timer THIS
	CODE:
		Newz(0, (void *)RETVAL, sizeof(*RETVAL), char);
		*RETVAL = *THIS;
	OUTPUT:
		RETVAL

void
set(THIS, seconds, frac, denom)
		Audio_Mad_Timer THIS
		unsigned long seconds
		unsigned long frac
		unsigned long denom
	CODE:
		mad_timer_set(THIS, seconds, frac, denom);
		XSRETURN(1);

void
reset(THIS)
		Audio_Mad_Timer THIS
	CODE:
		*THIS = mad_timer_zero;
		XSRETURN(1);
		
void
negate(THIS)
		Audio_Mad_Timer THIS
	CODE:
		mad_timer_negate(THIS);
		XSRETURN(1);
		
int
compare(THIS, OTHER)
		Audio_Mad_Timer THIS
		Audio_Mad_Timer OTHER
	CODE:
		RETVAL = mad_timer_compare(*THIS, *OTHER);
	OUTPUT:
		RETVAL
		
int 
sign(THIS)
		Audio_Mad_Timer THIS
	CODE:	
		RETVAL = mad_timer_compare(*THIS, mad_timer_zero);
	OUTPUT:
		RETVAL

void
abs(THIS)
		Audio_Mad_Timer THIS
	PREINIT:
		Audio_Mad_Timer ABS;
	CODE:
		*ABS  = mad_timer_abs(*THIS);
		*THIS = *ABS;
		XSRETURN(1);
		
void
add(THIS, OTHER)
		Audio_Mad_Timer THIS
		Audio_Mad_Timer OTHER
	CODE:
		mad_timer_add(THIS, *OTHER);
		XSRETURN(1);
		
void
multiply(THIS, value)
		Audio_Mad_Timer THIS
		long value
	CODE:
		mad_timer_multiply(THIS, value);
		XSRETURN(1);
		
long
count(THIS, units)
		Audio_Mad_Timer THIS
		int units
	CODE:
		RETVAL = mad_timer_count(*THIS, units);
	OUTPUT:
		RETVAL
		
unsigned long
fraction(THIS, denom)
		Audio_Mad_Timer THIS
		int denom
	CODE:
		RETVAL = mad_timer_fraction(*THIS, denom);
	OUTPUT:
		RETVAL
		
void
DESTROY(THIS)
		Audio_Mad_Timer THIS;
	CODE:
		Safefree(THIS);
		XSRETURN_UNDEF;

MODULE = Audio::Mad		PACKAGE = Audio::Mad::Resample

Audio_Mad_Resample
new(CLASS, oldrate=0, newrate=0)
		char *CLASS = NO_INIT;
		unsigned int oldrate
		unsigned int newrate
	CODE:
		Newz(0, (void *)RETVAL, sizeof(*RETVAL), char);
		mad_resample_init(RETVAL, oldrate, newrate);
	OUTPUT:
		RETVAL
		
unsigned int
init(THIS, oldrate=0, newrate=0)
		Audio_Mad_Resample THIS
		unsigned int oldrate
		unsigned int newrate
	CODE:
		mad_resample_init(THIS, oldrate, newrate);
		RETVAL = THIS->mode;
	OUTPUT:
		RETVAL
		
unsigned int
mode(THIS)
		Audio_Mad_Resample THIS
	CODE:	
		RETVAL = THIS->mode;
	OUTPUT:
		RETVAL
		
void
resample(THIS, left, right=&PL_sv_undef)
		Audio_Mad_Resample THIS
		SV *left
		SV *right
	PREINIT:
		unsigned int length, old_length, bufsize;
		mad_fixed_t *resampled, *data;
		double fscale;
	PPCODE:
		if (!SvPOK(left)) {
			XSRETURN_UNDEF;
		}
		
		/* this scale value,  and the following calculation
		   based upon it are gross displays of my infantile
		   C programming abilities.  basically,  I found out
		   the buffer _grows_ when you _upscale_ -- imagine
		   that (*smacks self*).  */
		fscale = mad_f_todouble(THIS->ratio);

		old_length = SvLEN(left) / sizeof(mad_fixed_t);
		/* so I spent an hour or two,  and thought of this
		   method of approximating a resulting buffer size
		   based upon the ratio scale value,  above.  If
		   there is a better way,  let me know.  This
		   can't be a good way of doing this. */
		bufsize = old_length * ((int)((double)1 / fscale) + 1);
		
		data = (mad_fixed_t *)SvPV_nolen(left);
		Newz(0, (void *)resampled, bufsize, mad_fixed_t);
			
		length = mad_resample_block(THIS, &THIS->state[0], old_length, data, resampled);
		PUSHs(sv_2mortal(newSVpvn((char *)resampled, sizeof(mad_fixed_t) * length)));
			
		if (right != &PL_sv_undef) {
			if (!SvPOK(right)) {
				XSRETURN_UNDEF;
			}
			
			old_length = SvLEN(right) / sizeof(mad_fixed_t);
			bufsize = old_length * ((int)((double)1 / fscale) + 1);
			/* ugh..  that always looks ugly..  but it works */
			
			data = (mad_fixed_t *)SvPV_nolen(right);
			Renew((void *)resampled, bufsize, mad_fixed_t);
			
			length = mad_resample_block(THIS, &THIS->state[1], old_length, data, resampled);
			PUSHs(sv_2mortal(newSVpvn((char *)resampled, sizeof(mad_fixed_t) * length)));
		}
		
		Safefree((void *)resampled);
		
void
DESTROY(THIS)
		Audio_Mad_Resample THIS;
	CODE:
		Safefree(THIS);
		XSRETURN_UNDEF;

MODULE = Audio::Mad		PACKAGE = Audio::Mad::Dither

Audio_Mad_Dither
new(CLASS, type=MAD_DITHER_S16_LE)
		char *CLASS = NO_INIT;
		int type
	CODE:
		Newz(0, (void *)RETVAL, sizeof(*RETVAL), char);
		mad_dither_init(RETVAL, type);
	OUTPUT:
		RETVAL
		
void
init(THIS, type=MAD_DITHER_S16_LE)
		Audio_Mad_Dither THIS
		int type
	CODE:
		mad_dither_init(THIS, type);
		XSRETURN_UNDEF;
		
void
dither(THIS, leftsv, rightsv=&PL_sv_undef)
		Audio_Mad_Dither THIS
		SV *leftsv
		SV *rightsv
	PREINIT:
		STRLEN old_length, length;
		mad_fixed_t *left, *right = NULL;
		unsigned char *cooked;
	PPCODE:
		if (THIS->pcmfunc == NULL) {
			XSRETURN_UNDEF;
		}
		
		if (!SvPOK(leftsv)) {
			XSRETURN_UNDEF;
		} 
		
		old_length = SvLEN(leftsv) / sizeof(mad_fixed_t);
		left = (mad_fixed_t *)SvPV_nolen(leftsv);
		if (!SvPOK(rightsv)) {
			length = old_length * THIS->pcmlength;
			Newz(0, (void *)cooked, length, char);
		} else {
			if (old_length != (SvLEN(rightsv) / sizeof(mad_fixed_t))) {
				XSRETURN_UNDEF;
			}
			length = old_length * THIS->pcmlength * 2;
			Newz(0, (void *)cooked, length, char);
			right = (mad_fixed_t *)SvPV_nolen(rightsv);
		}

		THIS->pcmfunc(&THIS->info, cooked, old_length, left, right);
		PUSHs(sv_2mortal(newSVpvn(cooked, length)));
		
		Safefree(cooked);
		
void
DESTROY(THIS)
		Audio_Mad_Dither THIS;
	CODE:
		Safefree(THIS);
		XSRETURN_UNDEF;