/*
 *  (c) Copyright 1990, Charles L. Perkins, clp@wjh12.harvard.edu
 */

/* (Originally) Generated by Interface Builder */

#import "Reverb.h"
#import "DEController.h"
#import <appkit/Button.h>
#import <appkit/Panel.h>
#import <appkit/Slider.h>
#import <sound/sound.h>
#import <stdio.h>
#import <strings.h>
#import <math.h>

@implementation Reverb

short	USE_DSP;	/* TRUE for DSP input, FASLE for CODEC input */
int	BUFSIZE;	/* integer * (2048 = commonest datasamples size) */

short	N;		/* # of buffers total */
short	N_PREPLAY;	/* # of buffers initially played (>= 1) */
short	N_PRECALC;	/* # of buffers initially computed (>= N_PREPLAY) */

int    reverb;		/* reverb in samples backward; 0 means no reverb */
short  decayShift;	/* amount to shift reverb sample: 1==>decay of 0.5 */

static short  continuePlaying, firstTime;		/* "local" variables */
static short  buf_ptr, currentSound, reverbCurrentSound;
static int    sindex, reverbIndex;	     /*  setup by appDidInit: method */
static SNDSoundStruct **buffers = NULL;  /* which also allocates buffer ptrs */

+ new {
    return [[super new] initialize];
}

/* Outlet methods: */

#define	DEF(selName, instVar)	\
- selName anObject {		\
    instVar = anObject;		\
    return self;		\
}

DEF(setSourceInputButton:,  sourceInputButton)

DEF( setDecayTextField:,  decayTextField)
DEF(setReverbTextField:, reverbTextField)

DEF(    setBufferSizeTextField:,     bufferSizeTextField)
DEF(      setNBuffersTextField:,       nBuffersTextField)
DEF(setPrecalcBuffersTextField:, precalcBuffersTextField)
DEF(setPreplayBuffersTextField:, preplayBuffersTextField)

/* Action methods: */

- activate: sender {
    if (firstTime = continuePlaying = [sender state]) {
	buf_ptr = 0;	/* (so starting up won't make buf_ptr >= N) */
	[controller startTrace];
    } else
	[controller stopTrace];
    return self;
}

#define	SAMPLE_RATE	(USE_DSP ? 44100.0 : 8012.821)
#define	SHOW_REVERB	[reverbTextField setDoubleValue: reverb / SAMPLE_RATE]

- changeParameters: sender {
    if (continuePlaying)
	NXRunAlertPanel("Alert",
			"You must de-activate before setting parameters.",
			NULL, NULL, NULL);
    else {
	[self appDidInit: sender];
	SHOW_REVERB;
    }
    return self;
}

- setDecay: sender {
    if (decayShift != [sender intValue]) {
	decayShift = [sender intValue];
	[decayTextField setDoubleValue: 1.0 / (1 << decayShift)];
    }
    return self;
}

- switchInputSource: sender {
    int  size = [bufferSizeTextField intValue];

    size = ([sender state] ? (size << 2) : (size >> 2));
    [bufferSizeTextField setIntValue: size];
    return self;
}

#define	NCHAN			(USE_DSP ? 2 : 1)
#define	SECS_TO_SAMPLES(s)	((int) (s * SAMPLE_RATE + 0.5))

- setReverb: sender {
    if (reverb != SECS_TO_SAMPLES([sender doubleValue])) {
	if (reverb = SECS_TO_SAMPLES([sender doubleValue])) {
	    reverbCurrentSound = currentSound;
	    reverbIndex = sindex - NCHAN * reverb;
	    while (reverbIndex < 0) {
		reverbIndex += NCHAN * BUFSIZE;
		if (--reverbCurrentSound < 0)
		    reverbCurrentSound = N - 1;
	    }
	}
	SHOW_REVERB;
    }
    return self;
}

#define	PLAY(n)		SNDStartPlaying(buffers[n], buf_ptr, 4, 0, 0, end)

/* below function gets called when the previously played sound completes */
int
end(SNDSoundStruct *s, int tag, int err) {
    if (continuePlaying) {
	PLAY(buf_ptr);
	if (++buf_ptr == N)
	    buf_ptr = 0;
/* fprintf(stderr, "%d%d ", buf_ptr, currentSound); */
    }
}

#define	OFFSET_PTR(TYPE, p, s)						   \
    if (buffers[s]->dataLocation == sizeof(SNDSoundStruct))		   \
	p = (TYPE *) (((char *) buffers[s]) + buffers[s]->dataLocation);   \
    else								   \
	p = (TYPE *) buffers[s]->dataLocation

#define	SET_PTR(TYPE, p, s, index)					   \
    OFFSET_PTR(TYPE, p, s);						   \
    if (index > 0)							   \
	p += index

#define	TEST_INDEX(TYPE, p, s, index, code)	\
    if (index == limit) {			\
	if (++s == N)				\
	    s = 0;				\
	code;					\
	index = 0;				\
	OFFSET_PTR(TYPE, p, s);			\
    }

#define	DO_SOUND_LOOP(TYPE, loopCode)					\
    SET_PTR(TYPE, soundData, currentSound, soundIndex);			\
    for (i = 0; i < datasamples; ++i, ++soundIndex) {			\
	TEST_INDEX(TYPE, soundData, currentSound, soundIndex, {		\
	    if (firstTime && currentSound == N_PRECALC) {		\
		for (j = 0; j < N_PREPLAY; ++j, ++buf_ptr)		\
		    PLAY(j);						\
		firstTime = FALSE;					\
	    }								\
	});								\
	loopCode;							\
    }									\
    sindex = soundIndex

#define	RUN(TYPE, reverbCode) {						      \
    register TYPE  *soundData, *inputData = (TYPE *) data;		      \
    register int    i, soundIndex = sindex;				      \
    register int    shift = decayShift, limit = NCHAN * BUFSIZE;	      \
    register TYPE  *reverbSoundData;					      \
    register int    reverbSoundIndex = reverbIndex;			      \
									      \
    if (reverb) {							      \
	SET_PTR(TYPE, reverbSoundData, reverbCurrentSound, reverbSoundIndex); \
	DO_SOUND_LOOP(TYPE, {						      \
	    TEST_INDEX(TYPE, reverbSoundData, reverbCurrentSound,	      \
						       reverbSoundIndex, {}); \
	    reverbCode;							      \
	    ++reverbSoundIndex;						      \
	});								      \
	reverbIndex = reverbSoundIndex;					      \
    } else {								      \
	DO_SOUND_LOOP(TYPE, *soundData++ = *inputData++);		      \
    }		/* correct startup requires separate loops (apparently) */    \
}

- dataBeingRecorded: sender : (char *) data : (int) datasamples; {
    int             j;
    NXEvent        *evt;

    if (evt = [NXApp peekAndGetNextEvent: NX_MOUSEDOWNMASK])
	[NXApp sendEvent: evt];
    else
	NXPing();	  	   /* this NXPing() seems to be necessary?!? */
    datasamples = NCHAN * datasamples;		/* precalculate to save time */
    if (USE_DSP)
	RUN(short, *soundData++ = *inputData++ + (*reverbSoundData++ >> shift))
    else
	RUN(unsigned char, *soundData++ = SNDMulaw(SNDiMulaw(*inputData++) +
				     (SNDiMulaw(*reverbSoundData++) >> shift)))
/* fprintf(stderr, "%d %d %d	", datasamples, sindex, reverbIndex); */
    return self;
}

/* Private methods: */

#define	SND_RATE	(USE_DSP ?    SND_RATE_HIGH     :   SND_RATE_CODEC)
#define	SND_FORMAT	(USE_DSP ? SND_FORMAT_LINEAR_16 : SND_FORMAT_MULAW_8)

- appDidInit: sender {
    short  i, oldN = N;

    USE_DSP    = [sourceInputButton state];	/* defaults for these can be */
    BUFSIZE    =     [bufferSizeTextField intValue];	/* set via IB fields */
    N          =       [nBuffersTextField intValue];
    N_PREPLAY  = [preplayBuffersTextField intValue];
    N_PRECALC  = [precalcBuffersTextField intValue];
    reverb     = 0;				/* default is no reverb */
    continuePlaying = firstTime = FALSE;
    if (controller != nil) {
	[controller stopTrace];
	[controller free];
    }
    [(controller = [DEController new]) setDataSize: NCHAN dataFormat:
	    SND_FORMAT samplingRate: SND_RATE channelCount: NCHAN infoSize: 0]; 
    [controller setDelegate: self];
    buf_ptr = currentSound = reverbCurrentSound = sindex = reverbIndex = 0;
    if (buffers != NULL) {
	for (i = 0; i < oldN; ++i)
	    free(buffers[i]);
	free(buffers); 
    }
    buffers = (SNDSoundStruct **) malloc(N * sizeof(SNDSoundStruct *));
    for (i = 0; i < N; ++i)    /* NCHAN also = sizeof(short) or sizeof(char) */
	SNDAlloc(&buffers[i], NCHAN * BUFSIZE * NCHAN, SND_FORMAT, SND_RATE,
								     NCHAN, 0);
    return self;
}

- initialize {
    id   aSound = [DEController new];

    [aSound setDataSize: 2 dataFormat: SND_FORMAT samplingRate: SND_RATE
						  channelCount: 1 infoSize: 0];
    [aSound play];
    [aSound waitUntilStopped];	/* above seems a necessary initialization!?! */
    [aSound free];
    [self setDelegate: self];		/* to get appDidInit: message */
    decayShift = 1;			/* a decay of 0.5 by default */
    return self;
}

@end
