/*(LGPL) --------------------------------------------------------------------------- a_wave.c - Wava Data Manager --------------------------------------------------------------------------- * Copyright (C) 2001-2003, 2007 David Olofson * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include "kobolog.h" #include "a_globals.h" #include "a_wave.h" #include "a_struct.h" #include "a_math.h" #include "a_tools.h" #include "a_agw.h" #include "eel.h" static int _was_init = 0; void audio_wave_open(void) { if(_was_init) return; memset(wavetab, 0, sizeof(wavetab)); _was_init = 1; } void audio_wave_close(void) { if(!_was_init) return; audio_wave_free(-1); _was_init = 0; } #define CHECKINIT if(!_was_init) audio_wave_open(); /*---------------------------------------------------------- Internal Tools ----------------------------------------------------------*/ /* * Fill in the loop/interpolation extension zone with the right * data, depending on whether the waveform is looped or not. * * (This should probably be replaced with smarter mixers. Cheap * sample players like the EMU8000 chips, have the same problem * with interpolation over loop wraps, which causes various * problems with anything but plain "loop forever" samples.) */ static void _render_extension(int wid) { Sint8 *eos; if(AF_MIDI == wavetab[wid].format) return; eos = wavetab[wid].data.si8 + wavetab[wid].size; if(wavetab[wid].looped) { unsigned s, from = 0; for(s = 0; s < wavetab[wid].xsize; ++s) { eos[s] = wavetab[wid].data.si8[from++]; if(from >= wavetab[wid].size) from = 0; } } else memset(eos, 0, wavetab[wid].xsize); } static void _calc_info(int wid) { wavetab[wid].speed = (unsigned)((double)wavetab[wid].rate * 65536.0 / (double)a_settings.samplerate); switch(wavetab[wid].format) { case AF_MONO8: wavetab[wid].samples = wavetab[wid].size; break; case AF_STEREO8: case AF_MONO16: wavetab[wid].samples = wavetab[wid].size / 2; break; case AF_STEREO16: case AF_MONO32: wavetab[wid].samples = wavetab[wid].size / 4; break; case AF_STEREO32: wavetab[wid].samples = wavetab[wid].size / 8; break; case AF_MIDI: wavetab[wid].samples = 1; break; } } void audio_wave_prepare(int wid) { int w, first, last; if(wid < 0) { first = 0; last = AUDIO_MAX_WAVES - 1; } else first = last = wid; for(w = first; w <= last; ++w) { if(!wavetab[w].allocated) continue; _calc_info(w); _render_extension(w); } } static int _get_free_wid(void) { int w; for(w = 0; w < AUDIO_MAX_WAVES; ++w) if(!wavetab[w].allocated) return w; return -1; } /* * Flip back and forth between big and little endian, * unless we're on big endian hardware. */ static void flip_endian(Uint8 *data, Uint32 size, int format) { #if SDL_BYTEORDER == SDL_LITTLE_ENDIAN int i, s; switch(format) { case AF_MIDI: case AF_MONO8: case AF_STEREO8: break; case AF_MONO16: case AF_STEREO16: for(i = 0; i < size; i += 2) { s = data[i]; data[i] = data[i + 1]; data[i + 1] = s; } break; case AF_MONO32: case AF_STEREO32: for(i = 0; i < size; i += 4) { s = data[i]; data[i] = data[i + 3]; data[i + 3] = s; s = data[i + 1]; data[i + 1] = data[i + 2]; data[i + 2] = s; } break; } #endif } static int LoadRAW(const char *name, Uint8 ** data, Uint32 * size, int *format, int *rate, int *loop) { unsigned char header[8]; int startpos = 0; FILE *f = fopen(name, "rb"); if(!f) return -1; if(fread(header, sizeof(header), 1, f) == 1) { if(strncmp("RAW", (char *)header, 3) == 0) { const char *fmt; *format = (int)header[3] & 0x0f; *loop = (header[3] & 0x80) != 0; startpos = sizeof(header); *rate = header[4]; *rate |= header[5] << 8; *rate |= header[6] << 16; *rate |= header[7] << 24; switch (*format) { case AF_MONO8: fmt = "MONO8"; break; case AF_STEREO8: fmt = "STEREO8"; break; case AF_MONO16: fmt = "MONO16"; break; case AF_STEREO16: fmt = "STEREO16"; break; case AF_MONO32: fmt = "MONO32"; break; case AF_STEREO32: fmt = "STEREO32"; break; case AF_MIDI: fmt = "MIDI (Huh!?)"; break; default: fmt = "Unknown"; break; } log_printf(DLOG, "LoadRAW: %s, %d Hz", fmt, *rate); if(*loop) log_printf(DLOG, ", looped\n"); else log_printf(DLOG, ", one-shot\n"); } else fseek(f, 0, SEEK_SET); } if(fseek(f, 0, SEEK_END) == 0) { int s = (int)ftell(f) - startpos; if(s <= 0) { fclose(f); return -2; } *size = (Uint32) s; if(fseek(f, startpos, SEEK_SET) == 0) { *data = malloc(*size); if(*data) { if(fread(*data, *size, 1, f) == 1) { fclose(f); flip_endian(*data, *size, *format); return 0; } free(*data); *data = NULL; } } } fclose(f); return -3; } static int SaveRAW(const char *name, void *data, Uint32 size, int format, int rate, int loop) { unsigned char header[8] = "RAW\0rate"; int result; FILE *f = fopen(name, "wb"); if(!f) return -1; header[3] = (char)format; if(loop) header[3] |= 0x80; header[4] = rate & 0xff; header[5] = (rate >> 8) & 0xff; header[6] = (rate >> 16) & 0xff; header[7] = (rate >> 24) & 0xff; if(fwrite(header, sizeof(header), 1, f) != 1) return -2; flip_endian(data, size, format); result = fwrite(data, size, 1, f); flip_endian(data, size, format); if(result != 1) return -3; fclose(f); return 0; } /* * Calculate waveform memory needed, including the extra bytes * needed for proper end-of-waveform handling. Will set the * 'xsize' and 'play_samples' field. * * Must know sample format and original size! * * Also note that this is heavily dependent on the voice * mixer - that's where the extra samples are needed. FIXME: ...which means that this code probably belongs there, FIXME: or that the voice mixer should set the parameters. * * Returns the total size required in bytes. */ static unsigned _calc_xsize(int wid) { unsigned samples, fsamples; unsigned bytes_sample = 1; unsigned ssize = wavetab[wid].size; switch(wavetab[wid].format) { case AF_MONO8: bytes_sample = 1; break; case AF_STEREO8: case AF_MONO16: bytes_sample = 2; ssize >>= 1; break; case AF_STEREO16: case AF_MONO32: bytes_sample = 4; ssize >>= 2; break; case AF_STEREO32: bytes_sample = 8; ssize >>= 3; break; case AF_MIDI: wavetab[wid].xsize = 0; return 0; } /* Fixed part: */ /* Looping. */ samples = ssize; while(samples < MIN_LOOP) samples <<= 1; wavetab[wid].play_samples = samples; samples -= ssize; /* Interpolation. */ samples += 3; /* Freq. ratio dependent part: oversampling and looping */ fsamples = AUDIO_MAX_MIX_RATE / AUDIO_MIN_OUTPUT_RATE; fsamples *= AUDIO_MAX_OVERSAMPLING; ++fsamples; wavetab[wid].xsize = bytes_sample * (samples + fsamples); return wavetab[wid].size + wavetab[wid].xsize; } /*---------------------------------------------------------- Basic Wave API ----------------------------------------------------------*/ int audio_wave_alloc(int wid) { CHECKINIT if(wid >= AUDIO_MAX_WAVES) return -1; if(wid < 0) wid = _get_free_wid(); if(wid < 0) return -2; audio_wave_free(wid); wavetab[wid].allocated = 1; return wid; } int audio_wave_alloc_range(int first_wid, int last_wid) { int w, res, last_done; if(last_wid < first_wid) return -1; last_done = -1; res = 0; for(w = first_wid; w <= last_wid; ++w) { res = audio_wave_alloc(w); if(res < 0) break; last_done = w; } if(res < 0) { if(last_done >= 0) for(w = first_wid; w <= last_done; ++w) audio_wave_free(w); return -2; } else return first_wid; } audio_wave_t *audio_wave_get(int wid) { if(wid < 0) return NULL; if(wid >= AUDIO_MAX_WAVES) return NULL; return &wavetab[wid]; } int audio_wave_format(int wid, audio_formats_t fmt, int fs) { unsigned old_xsize, new_size; wid = audio_wave_alloc(wid); if(wid < 0) return wid; old_xsize = wavetab[wid].xsize; wavetab[wid].format = fmt; wavetab[wid].rate = fs; if(wavetab[wid].data.si8) { new_size = _calc_xsize(wid); if(wavetab[wid].xsize != old_xsize) { void *ndata = realloc(wavetab[wid].data.si8, new_size); if(!ndata) return -3; wavetab[wid].data.si8 = ndata; _calc_info(wid); } } return wid; } int audio_wave_load_mem(int wid, void *data, unsigned size, int looped) { wid = audio_wave_alloc(wid); if(wid < 0) return wid; wavetab[wid].size = size; wavetab[wid].looped = looped; wavetab[wid].data.si8 = (Sint8 *)malloc(_calc_xsize(wid)); wavetab[wid].howtofree = HTF_FREE; if(!wavetab[wid].data.si8) { audio_wave_free(wid); return -1; } if(data) memcpy(wavetab[wid].data.si8, data, size); else memset(wavetab[wid].data.si8, 0, size); #ifdef xDEBUG { int i; int peak = 0; double avg = 0; double power = 0; int iavg, ipower; for(i = 0; i < size; ++i) { int s; s = wavetab[wid].data.si8[i]; avg += s; power += labs(s); if(labs(s) > peak) peak = labs(s); } avg /= size; power /= size; iavg = (int)avg; ipower = (int)power; log_printf(D3LOG, "audio_wave_load_mem(id=%d): size=%d peak=%d" " average=%d power=%d\n", wid, size, peak, iavg, ipower); } #endif _calc_info(wid); return wid; } int audio_wave_blank(int wid, unsigned samples, int looped) { int bps = 0; wid = audio_wave_alloc(wid); if(wid < 0) return wid; switch(wavetab[wid].format) { case AF_MONO8: bps = 1; break; case AF_STEREO8: case AF_MONO16: bps = 2; break; case AF_STEREO16: case AF_MONO32: bps = 4; break; case AF_STEREO32: bps = 8; break; case AF_MIDI: return wid; } return audio_wave_load_mem(wid, NULL, samples * bps, looped); } int audio_wave_convert(int wid, int new_wid, audio_formats_t fmt, int fs, audio_resample_t resamp) { int inplace, private_pool = 0, i; audio_voice_t resampler; audio_quality_t old_quality; int *bus; Sint8 *out8; Sint16 *out16; float *out32; unsigned newlen, j; /* We need this to run the voice mixer... */ if(ptab_init(65536) < 0) { log_printf(ELOG, "audio_wave_convert(): ptab_init() failed!\n"); return -20; } if(AF_MIDI == fmt) { log_printf(ELOG, "audio_wave_convert(): Cannot convert to MIDI!\n"); return -10; } if(wid >= AUDIO_MAX_WAVES) return -1; if(wid < 0) return -2; if(AF_MIDI == wavetab[wid].format) { log_printf(ELOG, "audio_wave_convert(): Cannot convert from MIDI!\n"); return -11; } if(new_wid == wid) { inplace = 1; new_wid = audio_wave_alloc(-1); if(new_wid < 0) return new_wid; } else { inplace = 0; new_wid = audio_wave_alloc(new_wid); if(new_wid < 0) return new_wid; } audio_wave_format(new_wid, fmt, fs); newlen = (unsigned)ceil((float)wavetab[wid].samples * (float)fs / (float)wavetab[wid].rate); audio_wave_blank(new_wid, newlen, wavetab[wid].looped); /* Must prepare, as we're gonna use the wave mixer! */ audio_wave_prepare(wid); memset(&resampler, 0, sizeof(resampler)); /* We need to tweak the 'speed' to get the output rate right! */ wavetab[wid].speed = (unsigned)((double)wavetab[wid].rate * 65536.0 / (double)wavetab[new_wid].rate); old_quality = a_settings.quality; a_settings.quality = AQ_VERY_HIGH; if(!aev_event_pool) { private_pool = 1; aev_open(20); } switch(resamp) { case AR_WORST: resamp = AR_NEAREST; break; case AR_MEDIUM: resamp = AR_LINEAR_2X_R; break; case AR_BEST: resamp = AR_CUBIC_R; break; default: break; } aev_timer = 0; (void)aev_send1(&resampler.port, 0, VE_START, wid); (void)aev_sendi1(&resampler.port, 0, VE_SET, VC_PITCH, 60<<16); (void)aev_sendi1(&resampler.port, 0, VE_SET, VC_RESAMPLE, resamp); (void)aev_sendi1(&resampler.port, 0, VE_SET, VC_SEND_BUS, -1); (void)aev_sendi2(&resampler.port, 0, VE_IRAMP, VIC_LVOL, 65536 >> (16-VOL_BITS), 0); (void)aev_sendi2(&resampler.port, 0, VE_IRAMP, VIC_RVOL, 65536 >> (16-VOL_BITS), 0); bus = malloc(256 * sizeof(int) * 2); out8 = wavetab[new_wid].data.si8; out16 = wavetab[new_wid].data.si16; out32 = wavetab[new_wid].data.f32; for(i = (int)wavetab[new_wid].samples; i > 0; i -= 256) { unsigned frames; if(i > 256) frames = 256; else frames = (unsigned)i; s32clear(bus, frames); voice_process_mix(&resampler, &bus, frames); switch(wavetab[new_wid].format) { case AF_MONO8: for(j = 0; j < frames; ++j) *out8++ = (bus[j<<1] + bus[(j<<1)+1]) >> 9; break; case AF_STEREO8: for(j = 0; j < frames; ++j) { *out8++ = bus[j<<1] >> 8; *out8++ = bus[(j<<1)+1] >> 8; } break; case AF_MONO16: for(j = 0; j < frames; ++j) *out16++ = (Sint16)(bus[j<<1] + bus[(j<<1)+1]) >> 1; break; case AF_STEREO16: for(j = 0; j < frames; ++j) { *out16++ = (Sint16)(bus[j<<1]); *out16++ = (Sint16)(bus[(j<<1)+1]); } break; case AF_MONO32: for(j = 0; j < frames; ++j) *out32++ = (float)(bus[j<<1] + bus[(j<<1)+1]) * 0.5; break; case AF_STEREO32: for(j = 0; j < frames; ++j) { *out32++ = (float)bus[j<<1]; *out32++ = (float)bus[(j<<1)+1]; } break; case AF_MIDI: /* whinestopper... */ break; } } free(bus); _calc_info(new_wid); voice_kill(&resampler); a_settings.quality = old_quality; if(private_pool) aev_close(); if(inplace) { audio_wave_free(wid); memcpy(wavetab + wid, wavetab + new_wid, sizeof(audio_wave_t)); memset(wavetab + new_wid, 0, sizeof(audio_wave_t)); return wid; } else { _calc_info(wid); /* Restore after our tweaking */ return new_wid; } } int audio_wave_clone(int wid, int new_wid) { if(wid >= AUDIO_MAX_WAVES) return -1; if(wid < 0) return -2; new_wid = audio_wave_format(new_wid, wavetab[wid].format, wavetab[wid].rate); if(new_wid < 0) return new_wid; return audio_wave_load_mem(new_wid, wavetab[wid].data.si8, wavetab[wid].size, wavetab[wid].looped); } /* We're simply using the same path for everything. */ void audio_set_path(const char *path) { eel_set_path(path); } const char *audio_path(void) { return eel_path(); } static int load_midi(int wid, const char *name) { midi_file_t *mf; wid = audio_wave_alloc(wid); if(wid < 0) return wid; mf = mf_open(name); if(!mf) { log_printf(ELOG, "load_midi(): Failed to load file" " \"%s\"! (Path = \"%s\")\n", name, eel_path()); audio_wave_free(wid); return -1; } wavetab[wid].data.midi = mf; wavetab[wid].size = 1; /* Duration in ms or something? */ wavetab[wid].xsize = 0; /* N/A */ wavetab[wid].howtofree = HTF_FREE; wavetab[wid].format = AF_MIDI; wavetab[wid].rate = 120; /* PPQN? */ wavetab[wid].looped = 0; /* Not yet implemented */ wavetab[wid].speed = 120; /* ? */ wavetab[wid].samples = 1; /* Number of events? */ log_printf(DLOG, ".------------------------------------------------------\n"); log_printf(DLOG, "| MIDI File: %s\n", name); log_printf(DLOG, "| Format: %u\n", mf->format); log_printf(DLOG, "| Title: %s\n", mf->title); log_printf(DLOG, "| Author: %s\n", mf->author); log_printf(DLOG, "| Remarks: %s\n", mf->remarks); log_printf(DLOG, "'------------------------------------------------------\n"); return wid; } int audio_wave_load(int wid, const char *name, int looped) { char buf[1024]; SDL_AudioSpec spec; Uint8 *data = NULL; Uint32 size; int format = -2; int rate = 0; /* Warning suppressor */ int res; int using_loadwav = 0; /* Prepend path */ strncpy(buf, eel_path(), sizeof(buf)); #ifdef WIN32 strncat(buf, "\\", sizeof(buf)); #elif defined MACOS strncat(buf, ":", sizeof(buf)); #else strncat(buf, "/", sizeof(buf)); #endif strncat(buf, name, sizeof(buf)); /* Check extension */ if(strstr(name, ".raw") || strstr(name, ".RAW")) { format = -1; res = LoadRAW(buf, &data, &size, &format, &rate, &looped); if(res < 0) format = -1; } else if(strstr(name, ".agw") || strstr(name, ".AGW")) return agw_load(wid, name); /* No full path here! */ else if(strstr(name, ".mid") || strstr(name, ".MID")) return load_midi(wid, buf); else { using_loadwav = 1; res = SDL_LoadWAV(buf, &spec, &data, &size) ? 0 : -1; } wid = audio_wave_alloc(wid); if(wid < 0) return wid; if(format >= 0) { wavetab[wid].format = format; wavetab[wid].rate = rate; } else if(using_loadwav) { switch (spec.format) { case SDL_AUDIO_S8: wavetab[wid].format = AF_MONO8; break; case SDL_AUDIO_S16: wavetab[wid].format = AF_MONO16; break; default: log_printf(ELOG, "sound_load(): Unsupported wave format!\n"); SDL_free(wavetab[wid].data.si8); res = -1; break; } if(spec.channels == 2) ++wavetab[wid].format; wavetab[wid].rate = spec.freq; } if(res < 0) { log_printf(ELOG, "audio_wave_load(): Failed to load file" " \"%s\"! (Path = \"%s\")\n", name, eel_path()); audio_wave_free(wid); return -3; } if(data) audio_wave_load_mem(wid, data, size, looped); if(using_loadwav) SDL_free(data); else free(data); return wid; } int audio_wave_save(int wid, const char *name) { char buf[1024]; audio_wave_t *wave = audio_wave_get(wid); if(!wave) return -1; /* Prepend path */ strncpy(buf, eel_path(), sizeof(buf)); #ifdef WIN32 strncat(buf, "\\", sizeof(buf)); #elif defined MACOS strncat(buf, ":", sizeof(buf)); #else strncat(buf, "/", sizeof(buf)); #endif strncat(buf, name, sizeof(buf)); log_printf(DLOG, "Saving to \"%s\"\n", buf); /* Check extension */ if(strstr(name, ".raw") || strstr(name, ".RAW")) return SaveRAW(buf, wave->data.si8, wave->size, (int)wave->format, wave->rate, wave->looped); else return -2; } void audio_wave_free(int wid) { int w, first, last; CHECKINIT if(wid < 0) { first = 0; last = AUDIO_MAX_WAVES - 1; } else first = last = wid; for(w = first; w <= last; ++w) { if(!wavetab[w].data.si8) continue; if(HTF_FREE == wavetab[w].howtofree) switch(wavetab[w].format) { case AF_MONO8: case AF_STEREO8: case AF_MONO16: case AF_STEREO16: case AF_MONO32: case AF_STEREO32: free(wavetab[w].data.si8); break; case AF_MIDI: mf_close(wavetab[w].data.midi); break; } wavetab[w].data.si8 = NULL; wavetab[w].size = 0; wavetab[w].xsize = 0; wavetab[w].allocated = 0; } } void audio_wave_info(int wid) { int w, first, last; int count = 0; int total_size = 0; int total_time = 0; if(wid < 0) { first = 0; last = AUDIO_MAX_WAVES - 1; } else first = last = wid; log_printf(VLOG, "Waveform info:\n"); for(w = first; w <= last; ++w) { const char *f; if(!wavetab[w].allocated) continue; switch(wavetab[w].format) { case AF_MONO8: f = "MONO8 "; break; case AF_STEREO8: f = "STEREO8 "; break; case AF_MONO16: f = "MONO16 "; break; case AF_STEREO16: f = "STEREO16"; break; case AF_MONO32: f = "MONO32 "; break; case AF_STEREO32: f = "STEREO32"; break; case AF_MIDI: f = "MIDI "; break; default: f = "Unknown "; break; } if(wavetab[w].format == AF_MIDI) log_printf(VLOG, " (%3d: %s %s, %d PPQN,\t%d events)\n", w, f, wavetab[w].data.midi->title, wavetab[w].rate, wavetab[w].size); else { float d = (float)wavetab[w].samples / wavetab[w].rate; log_printf(VLOG, " %3d: %s %s, %d Hz,\t%d bytes\t" "(%.2f s)\n", w, f, wavetab[w].looped ? "LOOPED" : "ONESHOT", wavetab[w].rate, wavetab[w].size, d); total_size += wavetab[w].size; total_time += d; ++count; } } log_printf(VLOG, " Total %d waveforms, total size: %d bytes, " "total time: %d s\n", count, total_size, total_time); }