truelight@0: #include "stdafx.h" truelight@0: #include "ttd.h" tron@679: #include "map.h" truelight@0: #include "sound.h" truelight@0: #include "vehicle.h" truelight@0: #include "window.h" truelight@0: #include "viewport.h" truelight@0: #include "fileio.h" truelight@0: tron@337: typedef struct MixerChannel { truelight@0: // Mixer truelight@0: Mixer *mx; truelight@0: bool active; truelight@0: truelight@0: // pointer to allocated buffer memory tron@337: int8 *memory; truelight@0: truelight@0: // current position in memory truelight@0: uint32 pos; truelight@0: uint32 frac_pos; truelight@0: uint32 frac_speed; truelight@0: uint32 samples_left; truelight@0: truelight@0: // Mixing volume truelight@0: uint volume_left; truelight@0: uint volume_right; truelight@0: truelight@0: uint flags; truelight@0: } MixerChannel; truelight@0: tron@337: typedef struct FileEntry { truelight@0: uint32 file_offset; truelight@0: uint32 file_size; truelight@0: uint16 rate; truelight@0: uint8 bits_per_sample; truelight@0: uint8 channels; tron@337: } FileEntry; truelight@0: truelight@0: struct Mixer { truelight@0: uint32 play_rate; truelight@0: FileEntry *files; truelight@0: uint32 file_count; truelight@0: MixerChannel channels[8]; truelight@0: }; truelight@0: truelight@0: enum { truelight@0: MX_AUTOFREE = 1, truelight@0: // MX_8BIT = 2, truelight@0: // MX_STEREO = 4, truelight@0: MX_UNSIGNED = 8, truelight@0: }; truelight@0: truelight@0: #define SOUND_SLOT 31 truelight@0: truelight@0: truelight@0: static void mix_int8_to_int16(MixerChannel *sc, int16 *buffer, uint samples) truelight@0: { truelight@0: int8 *b; truelight@0: uint32 frac_pos; truelight@0: uint32 frac_speed; truelight@0: uint volume_left; truelight@0: uint volume_right; truelight@0: truelight@0: if (samples > sc->samples_left) samples = sc->samples_left; truelight@0: sc->samples_left -= samples; truelight@0: assert(samples > 0); truelight@0: tron@337: b = sc->memory + sc->pos; truelight@0: frac_pos = sc->frac_pos; truelight@0: frac_speed = sc->frac_speed; truelight@0: volume_left = sc->volume_left; truelight@0: volume_right = sc->volume_right; truelight@0: truelight@193: if (frac_speed == 0x10000) { truelight@0: // Special case when frac_speed is 0x10000 truelight@0: do { tron@337: buffer[0] += *b * volume_left >> 8; tron@337: buffer[1] += *b * volume_right >> 8; truelight@0: b++; truelight@0: buffer += 2; tron@337: } while (--samples > 0); truelight@0: } else { truelight@0: do { truelight@0: buffer[0] += *b * volume_left >> 8; truelight@0: buffer[1] += *b * volume_right >> 8; truelight@0: buffer += 2; truelight@0: frac_pos += frac_speed; truelight@0: b += frac_pos >> 16; truelight@0: frac_pos &= 0xffff; tron@337: } while (--samples > 0); truelight@0: } truelight@0: truelight@0: sc->frac_pos = frac_pos; tron@337: sc->pos = b - sc->memory; truelight@0: } truelight@0: truelight@0: static void MxCloseChannel(MixerChannel *mc) truelight@0: { tron@337: if (mc->flags & MX_AUTOFREE) free(mc->memory); truelight@0: mc->memory = NULL; truelight@0: mc->active = false; truelight@0: } truelight@0: truelight@0: void MxMixSamples(Mixer *mx, void *buffer, uint samples) truelight@0: { truelight@0: MixerChannel *mc; truelight@193: truelight@0: // Clear the buffer tron@337: memset(buffer, 0, sizeof(int16) * 2 * samples); truelight@193: truelight@0: // Mix each channel tron@337: for (mc = mx->channels; mc != endof(mx->channels); mc++) { truelight@0: if (mc->active) { tron@337: mix_int8_to_int16(mc, buffer, samples); tron@337: if (mc->samples_left == 0) MxCloseChannel(mc); truelight@0: } truelight@0: } truelight@0: tron@337: #if 0 tron@337: { tron@337: static FILE *out = NULL; tron@337: if (out == NULL) tron@337: out = fopen("d:\\dump.raw", "wb"); tron@337: fwrite(buffer, samples * 4, 1, out); tron@337: } tron@337: #endif truelight@0: } truelight@0: truelight@0: static MixerChannel *MxAllocateChannel(Mixer *mx) truelight@0: { truelight@0: MixerChannel *mc; tron@337: for (mc = mx->channels; mc != endof(mx->channels); mc++) tron@337: if (mc->memory == NULL) { truelight@0: mc->active = false; truelight@0: mc->mx = mx; truelight@0: return mc; truelight@0: } truelight@0: return NULL; truelight@0: } truelight@0: tron@337: static void MxSetChannelRawSrc(MixerChannel *mc, int8 *mem, uint size, uint rate, uint flags) truelight@0: { truelight@0: mc->memory = mem; truelight@0: mc->flags = flags; truelight@0: mc->frac_pos = 0; truelight@0: mc->pos = 0; truelight@0: tron@337: mc->frac_speed = (rate << 16) / mc->mx->play_rate; truelight@0: truelight@0: // adjust the magnitude to prevent overflow tron@337: while (size & 0xFFFF0000) { tron@337: size >>= 1; tron@337: rate = (rate >> 1) + 1; tron@337: } truelight@193: truelight@0: mc->samples_left = size * mc->mx->play_rate / rate; truelight@0: } truelight@0: truelight@0: static void MxSetChannelVolume(MixerChannel *mc, uint left, uint right) truelight@0: { truelight@0: mc->volume_left = left; truelight@0: mc->volume_right = right; truelight@0: } truelight@0: truelight@0: static void MxOpenBankFile(Mixer *mx, const char *filename) truelight@0: { truelight@0: uint count, i; truelight@0: uint32 size, tag; truelight@0: FileEntry *fe; truelight@0: truelight@0: FioOpenFile(SOUND_SLOT, filename); truelight@0: mx->file_count = count = FioReadDword() >> 3; truelight@0: fe = mx->files = calloc(sizeof(FileEntry), count); truelight@0: truelight@0: FioSeekTo(0, SEEK_SET); truelight@0: tron@337: for (i = 0; i != count; i++, fe++) { truelight@0: fe->file_offset = FioReadDword(); truelight@0: fe->file_size = FioReadDword(); truelight@0: } truelight@193: truelight@0: fe = mx->files; tron@337: for (i = 0; i != count; i++, fe++) { tron@337: char name[255]; truelight@0: tron@337: FioSeekTo(fe->file_offset, SEEK_SET); tron@337: tron@337: // Check for special case, see else case tron@337: FioReadBlock(name, FioReadByte()); // Read the name of the sound tron@337: if (strcmp(name, "Corrupt sound") != 0) { tron@337: FioSeekTo(12, SEEK_CUR); // Skip past RIFF header tron@337: tron@337: // Read riff tags tron@337: for (;;) { tron@337: tag = FioReadDword(); tron@337: size = FioReadDword(); tron@337: tron@337: if (tag == ' tmf') { tron@337: FioReadWord(); // wFormatTag tron@337: fe->channels = FioReadWord(); // wChannels tron@337: FioReadDword(); // samples per second tron@337: fe->rate = 11025; // seems like all samples should be played at this rate. tron@337: FioReadDword(); // avg bytes per second tron@337: FioReadWord(); // alignment tron@337: fe->bits_per_sample = FioReadByte(); // bits per sample tron@337: FioSeekTo(size - (2 + 2 + 4 + 4 + 2 + 1), SEEK_CUR); tron@337: } else if (tag == 'atad') { tron@337: fe->file_size = size; tron@337: fe->file_offset = FioGetPos() | (SOUND_SLOT << 24); tron@337: break; tron@337: } else { tron@337: fe->file_size = 0; tron@337: break; tron@337: } truelight@0: } tron@337: } else { tron@337: /* tron@337: * Special case for the jackhammer sound tron@337: * (name in sample.cat is "Corrupt sound") tron@337: * It's no RIFF file, but raw PCM data tron@337: */ tron@337: fe->channels = 1; tron@337: fe->rate = 11025; tron@337: fe->bits_per_sample = 8; tron@337: fe->file_offset = FioGetPos() | (SOUND_SLOT << 24); truelight@0: } truelight@0: } truelight@0: } truelight@0: truelight@0: static bool MxSetBankSource(MixerChannel *mc, uint bank) truelight@0: { truelight@0: FileEntry *fe = &mc->mx->files[bank]; tron@337: int8 *mem; truelight@0: uint i; truelight@0: truelight@0: if (fe->file_size == 0) truelight@0: return false; truelight@193: tron@337: mem = malloc(fe->file_size); /* XXX unchecked malloc */ truelight@0: FioSeekToFile(fe->file_offset); truelight@0: FioReadBlock(mem, fe->file_size); truelight@0: tron@337: for (i = 0; i != fe->file_size; i++) darkvater@1136: mem[i] += -128; // Convert unsigned sound data to signed truelight@193: truelight@0: assert(fe->bits_per_sample == 8 && fe->channels == 1 && fe->file_size != 0 && fe->rate != 0); truelight@0: truelight@0: MxSetChannelRawSrc(mc, mem, fe->file_size, fe->rate, MX_AUTOFREE | MX_UNSIGNED); truelight@0: truelight@0: return true; truelight@0: } truelight@0: truelight@0: bool MxInitialize(uint rate, const char *filename) truelight@0: { truelight@0: static Mixer mx; truelight@0: _mixer = &mx; truelight@0: mx.play_rate = rate; truelight@0: MxOpenBankFile(&mx, filename); truelight@0: return true; truelight@0: } truelight@0: truelight@0: // Low level sound player truelight@0: static void StartSound(uint sound, uint panning, uint volume) truelight@0: { truelight@0: if (volume != 0) { truelight@0: MixerChannel *mc = MxAllocateChannel(_mixer); truelight@0: if (mc == NULL) truelight@0: return; truelight@0: if (MxSetBankSource(mc, sound)) { truelight@0: MxSetChannelVolume(mc, volume << 8, volume << 8); truelight@0: mc->active = true; truelight@0: } truelight@0: } truelight@0: } truelight@0: truelight@0: truelight@0: static const byte _vol_factor_by_zoom[] = {255, 190, 134}; truelight@0: truelight@0: static const byte _sound_base_vol[] = { truelight@0: 128, 90, 128, 128, 128, 128, 128, 128, truelight@0: 128, 90, 90, 128, 128, 128, 128, 128, truelight@0: 128, 128, 128, 80, 128, 128, 128, 128, truelight@0: 128, 128, 128, 128, 128, 128, 128, 128, truelight@0: 128, 128, 90, 90, 90, 128, 90, 128, truelight@0: 128, 90, 128, 128, 128, 90, 128, 128, truelight@0: 128, 128, 128, 128, 90, 128, 128, 128, truelight@0: 128, 90, 128, 128, 128, 128, 128, 128, truelight@0: 128, 128, 90, 90, 90, 128, 128, 128, truelight@0: 90, truelight@0: }; truelight@0: truelight@0: static const byte _sound_idx[] = { truelight@0: 2, 3, 4, 5, 6, 7, 8, 9, truelight@0: 10, 11, 12, 13, 14, 15, 16, 17, truelight@0: 18, 19, 20, 21, 22, 23, 24, 25, truelight@0: 26, 27, 28, 29, 30, 31, 32, 33, truelight@0: 34, 35, 36, 37, 38, 39, 40, 0, truelight@0: 1, 41, 42, 43, 44, 45, 46, 47, truelight@0: 48, 49, 50, 51, 52, 53, 54, 55, truelight@0: 56, 57, 58, 59, 60, 61, 62, 63, truelight@0: 64, 65, 66, 67, 68, 69, 70, 71, truelight@0: 72, truelight@0: }; truelight@0: tron@337: static void SndPlayScreenCoordFx(SoundFx sound, int x, int y) truelight@0: { truelight@0: Window *w; truelight@0: ViewPort *vp; truelight@0: int left; truelight@0: truelight@0: if (msf.effect_vol == 0) truelight@0: return; truelight@0: tron@337: for (w = _windows; w != _last_window; w++) { tron@337: if ((vp = w->viewport) != NULL && truelight@0: IS_INSIDE_1D(x, vp->virtual_left, vp->virtual_width) && truelight@0: IS_INSIDE_1D(y, vp->virtual_top, vp->virtual_height)) { truelight@193: truelight@0: left = ((x - vp->virtual_left) >> vp->zoom) + vp->left; truelight@0: StartSound( truelight@193: _sound_idx[sound], truelight@0: clamp(left / 71, 0, 8), truelight@0: (_sound_base_vol[sound] * msf.effect_vol * _vol_factor_by_zoom[vp->zoom]) >> 15 truelight@0: ); truelight@0: return; truelight@0: } truelight@0: } truelight@0: truelight@0: } truelight@0: tron@337: void SndPlayTileFx(SoundFx sound, TileIndex tile) truelight@0: { tron@337: /* emits sound from center (+ 8) of the tile */ tron@926: int x = TileX(tile) * 16 + 8; tron@926: int y = TileY(tile) * 16 + 8; tron@337: Point pt = RemapCoords(x, y, GetSlopeZ(x, y)); truelight@0: SndPlayScreenCoordFx(sound, pt.x, pt.y); truelight@0: } truelight@0: tron@337: void SndPlayVehicleFx(SoundFx sound, const Vehicle *v) truelight@0: { truelight@0: SndPlayScreenCoordFx(sound, tron@337: (v->left_coord + v->right_coord) / 2, tron@337: (v->top_coord + v->bottom_coord) / 2 truelight@0: ); truelight@0: } truelight@0: tron@337: void SndPlayFx(SoundFx sound) truelight@0: { truelight@0: StartSound( truelight@193: _sound_idx[sound], truelight@193: 4, truelight@0: (_sound_base_vol[sound] * msf.effect_vol) >> 7 truelight@0: ); truelight@0: }