#include #include #include #include #include #include #include "clstandardtypes.h" #include "sysdeps.h" #include "log.h" #include "mix_server.h" #include "xmalloc.h" #define _L LOG_SOUND | LOG_INFO /* MIXER SERVER */ #if defined(UNDER_UNIX) #define WRITE_TO_FILE 1 #endif #if WRITE_TO_FILE #include int snd_file; #endif /* This is a OS-generic module for mixing digitized samples together. */ void mix_init(mix_context * m, u32 hertz, u32 bufsize, bool issigned, bool eightbit, bool bigendian) { u16 x; #if WRITE_TO_FILE snd_file = open("digital.raw", O_CREAT|O_TRUNC|O_WRONLY, 0666); if (snd_file < 0) snd_file = 0; #endif x = ('A' << 8) + 'B'; m->soundhz = hertz < 4000 ? 4000 : hertz; m->issigned = issigned; m->eightbit = eightbit; m->swapendian = (*(u8 *) & x == 'B') == bigendian; logger(_L | L_1, "mix_init: swapendian=%d\n\n", m->swapendian); m->buffer = (s32 *) xmalloc(sizeof(s32) * bufsize); m->bufsize = bufsize; } void mix_term(mix_context * m) { if (m->buffer) { xfree(m->buffer); m->buffer = NULL; } #if WRITE_TO_FILE if (snd_file) close(snd_file); #endif } void mix_restart(mix_context * m) { memset(m->voices, 0, sizeof(m->voices)); m->voices[0].clock = m->voices[1].clock = m->voices[2].clock = m->voices[3].clock = m->voices[4].clock = m->voices[5].clock = m->soundhz; m->voices[3].ns1 = 0xaaaaaaaa; m->voices[3].ns2 = 1; } // Step a tone voice by one sample and return contrib. INLINE void step_tone(sample * v, s32 * chn, int * active) { v->div += v->delta; if (v->div >= v->clock) { /*if (v->vol) */ { *chn += v->vol; (*active)++; } while (v->div >= v->clock) v->div -= v->clock; } else { //*chn += -v->vol; //(*active)++; } } // Advance a tone voice by X samples. INLINE void advance_tone(sample * v, u32 samples) { v->div = (v->div + v->delta * samples) % v->clock; } // Step white noise by one sample and update dat. #define NOISE_GATE(x,y) \ do { \ x = (x<<1) | (x>>31); \ x ^= y; \ if ((y += x)==0) y++; \ } while (0) INLINE void step_white(sample * v, s32 * chn, int * active) { v->div += v->delta; while (v->div >= v->clock) { NOISE_GATE(v->ns1, v->ns2); v->div -= v->clock; } if (v->ns1 & 1) { /*if (v->vol) */ { *chn += v->vol; (*active)++; } } else { // *chn -= v->vol; // (*active)++; } } // Advance white noise by X samples INLINE void advance_white(sample * v, u32 samples) { u32 chg; u32 steps; chg = v->delta * samples; steps = (v->div + chg) / v->clock; v->div = (v->div + chg) % v->clock; while (steps--) { NOISE_GATE(v->ns1, v->ns2); } } // Step periodic noise by one sample and update dat. #define PERIODMULT 16 INLINE void step_periodic(sample * v, s32 * chn, int * active) { v->div += v->delta; if (v->div >= v->clock) { /*if (v->vol) */ { *chn += v->vol; (*active)++; } while (v->div >= v->clock) v->div -= v->clock; } else { //*chn -= v->vol; //(*active)++; } } // Advance periodic noise by X samples INLINE void advance_periodic(sample * v, u32 samples) { v->div = (v->div + v->delta * samples) % v->clock; } // Step speech by one sample and update dat. // Sample is finished when s->used==0. Caller should free memory. INLINE void step_digital(sample * v, s32 * chn, int * active) { if (v->used) { *chn += v->data[v->st] * (signed) v->vol / (signed) 256; (*active)++; v->div += v->delta; while (v->div >= v->clock) { v->div -= v->clock; v->st++; v->used--; if (v->used == 0) { v->st = v->en = 0; } else if (v->st >= v->len) { v->st -= v->len; } } } } // Advance digital data by X samples INLINE void advance_digital(sample * v, u32 samples) { u32 chg = v->delta * samples; u32 steps = (v->div + chg) / v->clock; v->div = (v->div + chg) % v->clock; v->st = (v->st + steps) % v->len; // unless we loop [we don't], // we are done when we've stepped through // the whole sample. if (v->used <= steps || v->used == 0) { v->st = v->en = 0; // don't free memory (there's a bug here) // xfree(v->data); // v->data = NULL; v->used = 0; v->len = 0; } else { v->used -= steps; } } #if 0 // Step audio gate INLINE void step_audiogate(sample * v, s32 * chn, int * active) { // since this can change so fast, we set v->clock // to indicate something should happen if (v->clock & 1) { *chn += v->vol; } else if (v->clock) { *chn -= 0x7fffff; } (*active)++; } // Advance audio gate INLINE void advance_audiogate(sample * v, u32 samples) { if (v->clock > samples) v->clock -= samples; else v->clock = 0; } #endif static int had_silent_frame = 0; static int mix_silence(mix_context * m) { // check that something is on return ( (m->voices[0].vol | m->voices[1].vol | m->voices[2]. vol | m->voices[3].vol | m->voices[4].vol | m->voices[5].vol) == 0 || // and that nothing is illegal (m->voices[0].clock && m->voices[1].clock && m->voices[2].clock && m->voices[3].clock && m->voices[4].clock && m->voices[5].clock) == 0); } /* Mix the channels together and generate a segment of sound. Does not advance the mixer's idea of time; use mix_advance() to do that. */ void mix_mixit(mix_context * m, u32 advance, u32 samples) { s32 *out = m->buffer, *end = out + samples; s32 dat = 0; int div = 0; int silent; sample myvoices[6]; // work on local copy of m->voices. memcpy(myvoices, m->voices, sizeof(myvoices)); if (advance) mix_advance(m, advance); silent = mix_silence(m); if (!silent || !had_silent_frame) { had_silent_frame = silent; while (out < end) { dat = 0; div = 0; // tones if (myvoices[0].vol) step_tone(&myvoices[0], &dat, &div); if (myvoices[1].vol) step_tone(&myvoices[1], &dat, &div); if (myvoices[2].vol) step_tone(&myvoices[2], &dat, &div); // noise if (myvoices[3].vol) { sample *n = &myvoices[3]; if (n->iswhite) { step_white(n, &dat, &div); } else { step_periodic(n, &dat, &div); } } // speech if (myvoices[4].used) { step_digital(&myvoices[4], &dat, &div); } // audio gate if (myvoices[5].used) { step_digital(&myvoices[5], &dat, &div); } if (div) { //dat /= div; dat >>= 1; if (dat) logger(_L | L_3, "dat[%d]=%08X \n", div, dat); } *out++ = dat <= -0x00800000 ? -0x007fffff : dat >= 0x00800000 ? 0x007fffff : dat; } } else { memset(m->buffer, 0, samples * sizeof(s32)); had_silent_frame = true; } /* Convert sample */ if (m->eightbit) { int idx; s8 *ptr = (s8 *) m->buffer; for (idx = 0; idx < samples; idx++) *ptr++ = m->buffer[idx] >> 16; /* 24 -> 8 */ } else { int idx; s16 *ptr = (s16 *) m->buffer; for (idx = 0; idx < samples; idx++) *ptr++ = m->buffer[idx] >> 8; /* 24 -> 16 */ } if (!m->issigned) { int idx; int step = (m->eightbit ? 1 : 2); s8 *ptr = (s8 *) m->buffer; for (idx = (m->swapendian ? step - 1 : 0); idx < samples; idx += step) ptr[idx] ^= 0x80; } if (m->swapendian && !m->eightbit) { swab((char *) m->buffer, (const char *) m->buffer, samples * sizeof(u16)); } } /* Advance mixer time by so many samples. */ void mix_advance(mix_context * m, int samples) { // tones if (m->voices[0].clock) advance_tone(&m->voices[0], samples); if (m->voices[1].clock) advance_tone(&m->voices[1], samples); if (m->voices[2].clock) advance_tone(&m->voices[2], samples); // noise if (m->voices[3].iswhite) { if (m->voices[3].clock) advance_white(&m->voices[3], samples); } else { if (m->voices[3].clock) advance_periodic(&m->voices[3], samples); } // speech if (m->voices[4].used) { advance_digital(&m->voices[4], samples); } // audio gate if (m->voices[5].used) { advance_digital(&m->voices[5], samples); } } static void stackdata(sample * s, s8 * bytes, int size) { int cnt; /* This routine is apt to occur during a speech interrupt and lead to inconsistencies. We do all our work on a copy of the sample. The worst that can happen, it appears, is for a part of the sample to be repeated when this routine resets the s->st and s->en pointers. */ sample in = *s; if (bytes == NULL) return; #if 0 // Simplest algorithm, but very bad on memory. in.data = (u8 *) xrealloc(in.data, size + in.len); memcpy(in.data + in.en, bytes, size); in.len += size; in.en += size; in.used += size; *s = in; return; #endif logger(_L | L_1, "IN: in.data=%p, in.len=%d, in.used=%d, in.st=%d, in.en=%d\n", in.data, in.len, in.used, in.st, in.en); if (in.st > in.len || in.en > in.len || (in.st >= in.en ? (in.used != (in.len - in.st + in.en)) : (in.used != (in.en - in.st)))) { logger(_L | LOG_ERROR | LOG_USER, "consistency error: in.len=%d, in.st=%d, in.en=%d, in.used=%d\n", in.len, in.st, in.en, in.used); if (in.data) xfree(in.data); in.data = NULL; in.used = in.len = in.st = in.en = 0; } /* need to shrink the ring? */ if (in.data != NULL && (in.used < in.len) && (in.used + size < in.len)) { /* two cases: (1) all data is contiguous: move to beginning, reset pointers, and continue. (2) data wraps. Move [0,...) part up, move end part to beginning. */ logger(_L | L_1, "shrinking block"); /* non-wrapping case */ if (in.used > 0 && in.st < in.en) { memmove(in.data, in.data + in.st, in.used); in.st = 0; in.en = in.used; in.data = (s8 *) xrealloc(in.data, in.used); in.len = in.used; } /* wrapping case */ else if (in.used > 0 && in.st > in.en && (in.len - in.st + in.en < in.st)) { int endsize = in.len - in.st; memmove(in.data + endsize, in.data, in.en); memmove(in.data, in.data + in.st, endsize); in.st = 0; in.en += endsize; in.data = (s8 *) xrealloc(in.data, in.en); in.len = in.en; } } /* need to grow the ring? */ if (in.data == NULL || in.used + size > in.len) { int nw; logger(_L | L_1, "resizing: in.used+size=%d, in.len=%d, in.data=%p\n\n", in.used + size, in.len, in.data); nw = in.used + size; in.data = (s8 *) xrealloc(in.data, nw); /* if we grew the buffer and the used part was wrapping, move the end of the old buffer to the end of the new buffer */ if (in.used > 0 && in.en <= in.st && in.st > 0) { int endsize = in.len - in.st; memmove(in.data + nw - endsize, in.data + in.st, endsize); in.st = nw - endsize; } if (in.en == in.st && in.en == 0) in.en = in.len; in.len = nw; } /* paste on the end of the current block, don't wrap */ cnt = (in.len - in.en <= size) ? in.len - in.en : size; memcpy(in.data + in.en, bytes, cnt); /* copy the rest to the beginning of the ring */ memcpy(in.data, bytes + cnt, size - cnt); in.used += size; in.en += size; if (in.en >= in.len) in.en -= in.len; *s = in; logger(_L | L_1, "OUT: s->data=%p, s->len=%d, s->used=%d, s->st=%d, s->en=%d\n", s->data, s->len, s->used, s->st, s->en); } /////////////////////////////////////////////////////// /* #include #include int main(void) { int x; for (x=0; x<16; x++) { double f = exp((x/15.0) * log(17)) ; printf("\t%08X,\n", (int)(f * 0x080000)); } } */ static u32 atten[16] = { 0x00000000, // 0x00080000, 0x0009A9C5, 0x000BAC10, 0x000E1945, 0x001107A1, 0x001491FC, 0x0018D8C4, 0x001E0327, 0x00244075, 0x002BC9D6, 0x0034E454, 0x003FE353, 0x004D2B8C, 0x005D36AB, 0x007097A5, 0x007FFFFF }; void mix_handle_voice(mix_context * m, u8 channel, u32 hertz, u8 volume) { sample *s; logger(_L | L_2, "mix_handle_voice: channel %d, hertz = 0x%x, volume = %d\n", channel, hertz, volume); if (channel >= mix_CHN0 && channel <= mix_CHN2) { s = &m->voices[channel]; // sounds this high-pitched won't // work at all. if (hertz * 2 >= m->soundhz) { s->delta = 0; s->vol = 0; s->clock = m->soundhz; // assure no zero divides } else { s->clock = m->soundhz; s->delta = hertz; s->div = volume ? s->div : 0; s->vol = atten[volume]; } } } void mix_handle_noise(mix_context * m, u8 channel, u32 hertz, u8 volume, int iswhite) { sample *s; logger(_L | L_2, "mix_handle_noise: channel %d, hertz = 0x%x, volume = %d, iswhite = %d\n", channel, hertz, volume, iswhite); if (channel == mix_Noise) { s = &m->voices[channel]; if (iswhite) { s->clock = m->soundhz; s->delta = hertz; s->div = volume ? s->div : 0; s->vol = atten[volume]; s->iswhite = 1; } else { s->clock = m->soundhz * PERIODMULT; s->delta = hertz; s->div = volume ? s->div : 0; s->vol = atten[volume]; s->iswhite = 0; } } } void mix_handle_data(mix_context * m, u8 channel, u32 hertz, u8 volume, u32 length, s8 * data) { logger(_L | L_1, "mix_handle_data: using %d bytes of %d Hz data on channel %d\n", length, hertz, channel); #if WRITE_TO_FILE if (snd_file) write(snd_file, data, length); #endif switch (channel) { case mix_Speech: { sample *s = &m->voices[4]; if (data && length) { s->clock = m->soundhz; s->delta = hertz; s->div = 0; s->vol = volume << 15; stackdata(s, data, length); } else { // flush if (s->delta > 0) while (s->len > s->delta) { } /* xfree(s->data); s->data = 0L; s->len = s->used = s->st = s->en = 0;*/ } break; } case mix_AudioGate: { /* for the audio gate, we only use the volume; no data need be passed. This is because we interpret 'length' as a repeat count for 'vol'. */ if (length) { s8 *tmp = (s8 *) alloca(length); sample *s = &m->voices[5]; // int x; logger(_L | L_2, "writing %d bytes of %d as audio gate\n", length, volume); // for (x=0; xclock = m->soundhz; s->delta = hertz; s->div = 0; s->vol = volume << 15; stackdata(s, tmp, length); } else { sample *s = &m->voices[5]; if (s->used > s->delta) { // if (s->data) xfree(s->data); s->data = NULL; s->st = s->en = s->len = s->used = 0; } } break; } } } /*********************************/