void Mixer::MixChannel(Channel& channel, uint8* data, int length) { // Do not mix channel if channel is a sound and sound is disabled if (channel.group == MIXER_GROUP_SOUND && !gConfigSound.sound_enabled) { return; } if (channel.source && channel.source->Length() > 0 && !channel.done) { AudioFormat streamformat = channel.source->Format(); int loaded = 0; SDL_AudioCVT cvt; cvt.len_ratio = 1; do { int samplesize = format.channels * format.BytesPerSample(); int samples = length / samplesize; int samplesloaded = loaded / samplesize; double rate = 1; if (format.format == AUDIO_S16SYS) { rate = channel.rate; } int samplestoread = (int)((samples - samplesloaded) * rate); int lengthloaded = 0; if (channel.offset < channel.source->Length()) { bool mustconvert = false; if (MustConvert(*channel.source)) { if (SDL_BuildAudioCVT(&cvt, streamformat.format, streamformat.channels, streamformat.freq, Mixer::format.format, Mixer::format.channels, Mixer::format.freq) == -1) { break; } mustconvert = true; } const uint8* datastream = 0; int toread = (int)(samplestoread / cvt.len_ratio) * samplesize; int readfromstream = (channel.source->GetSome(channel.offset, &datastream, toread)); if (readfromstream == 0) { break; } uint8* dataconverted = 0; const uint8* tomix = 0; if (mustconvert) { // tofix: there seems to be an issue with converting audio using SDL_ConvertAudio in the callback vs preconverted, can cause pops and static depending on sample rate and channels if (Convert(cvt, datastream, readfromstream, &dataconverted)) { tomix = dataconverted; lengthloaded = cvt.len_cvt; } else { break; } } else { tomix = datastream; lengthloaded = readfromstream; } bool effectbufferloaded = false; if (rate != 1 && format.format == AUDIO_S16SYS) { int in_len = (int)((double)lengthloaded / samplesize); int out_len = samples; if (!channel.resampler) { channel.resampler = speex_resampler_init(format.channels, format.freq, format.freq, 0, 0); } if (readfromstream == toread) { // use buffer lengths for conversion ratio so that it fits exactly speex_resampler_set_rate(channel.resampler, in_len, samples - samplesloaded); } else { // reached end of stream so we cant use buffer length as resampling ratio speex_resampler_set_rate(channel.resampler, format.freq, (int)(format.freq * (1 / rate))); } speex_resampler_process_interleaved_int(channel.resampler, (const spx_int16_t*)tomix, (spx_uint32_t*)&in_len, (spx_int16_t*)effectbuffer, (spx_uint32_t*)&out_len); effectbufferloaded = true; tomix = effectbuffer; lengthloaded = (out_len * samplesize); } if (channel.pan != 0.5f && format.channels == 2) { if (!effectbufferloaded) { memcpy(effectbuffer, tomix, lengthloaded); effectbufferloaded = true; tomix = effectbuffer; } switch (format.format) { case AUDIO_S16SYS: EffectPanS16(channel, (sint16*)effectbuffer, lengthloaded / samplesize); break; case AUDIO_U8: EffectPanU8(channel, (uint8*)effectbuffer, lengthloaded / samplesize); break; } } int mixlength = lengthloaded; if (loaded + mixlength > length) { mixlength = length - loaded; } float volumeadjust = volume; volumeadjust *= (gConfigSound.master_volume / 100.0f); switch (channel.group) { case MIXER_GROUP_SOUND: volumeadjust *= (gConfigSound.sound_volume / 100.0f); // Cap sound volume on title screen so music is more audible if (RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) & SCREEN_FLAGS_TITLE_DEMO) { volumeadjust = Math::Min(volumeadjust, 0.75f); } break; case MIXER_GROUP_RIDE_MUSIC: volumeadjust *= (gConfigSound.ride_music_volume / 100.0f); break; } int startvolume = (int)(channel.oldvolume * volumeadjust); int endvolume = (int)(channel.volume * volumeadjust); if (channel.stopping) { endvolume = 0; } int mixvolume = (int)(channel.volume * volumeadjust); if (startvolume != endvolume) { // fade between volume levels to smooth out sound and minimize clicks from sudden volume changes if (!effectbufferloaded) { memcpy(effectbuffer, tomix, lengthloaded); effectbufferloaded = true; tomix = effectbuffer; } mixvolume = SDL_MIX_MAXVOLUME; // set to max since we are adjusting the volume ourselves int fadelength = mixlength / format.BytesPerSample(); switch (format.format) { case AUDIO_S16SYS: EffectFadeS16((sint16*)effectbuffer, fadelength, startvolume, endvolume); break; case AUDIO_U8: EffectFadeU8((uint8*)effectbuffer, fadelength, startvolume, endvolume); break; } } SDL_MixAudioFormat(&data[loaded], tomix, format.format, mixlength, mixvolume); if (dataconverted) { delete[] dataconverted; } channel.offset += readfromstream; } loaded += lengthloaded; if (channel.loop != 0 && channel.offset >= channel.source->Length()) { if (channel.loop != -1) { channel.loop--; } channel.offset = 0; } } while(loaded < length && channel.loop != 0 && !channel.stopping); channel.oldvolume = channel.volume; channel.oldvolume_l = channel.volume_l; channel.oldvolume_r = channel.volume_r; if (channel.loop == 0 && channel.offset >= channel.source->Length()) { channel.done = true; } } }
void Mixer::MixChannel(Channel& channel, uint8* data, int length) { if (channel.stream) { if (!channel.resampler) { channel.resampler = speex_resampler_init(format.channels, format.freq, format.freq, 0, 0); } AudioFormat channelformat = *channel.stream->Format(); int loaded = 0; SDL_AudioCVT cvt; cvt.len_ratio = 1; do { int samplesize = format.channels * format.BytesPerSample(); int samples = length / samplesize; int samplesloaded = loaded / samplesize; int samplestoread = (int)ceil((samples - samplesloaded) * channel.rate); int lengthloaded = 0; if (channel.offset < channel.stream->Length()) { bool mustconvert = false; if (MustConvert(*channel.stream)) { if (SDL_BuildAudioCVT(&cvt, channelformat.format, channelformat.channels, channelformat.freq, Mixer::format.format, Mixer::format.channels, Mixer::format.freq) == -1) { break; } mustconvert = true; } const uint8* datastream = 0; int readfromstream = (channel.stream->GetSome(channel.offset, &datastream, (int)(((samplestoread) * samplesize) / cvt.len_ratio)) / channelformat.BytesPerSample()) * channelformat.BytesPerSample(); if (readfromstream == 0) { break; } int volume = channel.volume; uint8* dataconverted = 0; const uint8* tomix = 0; if (mustconvert) { if (Convert(cvt, datastream, readfromstream, &dataconverted)) { tomix = dataconverted; lengthloaded = (cvt.len_cvt / samplesize) * samplesize; } else { break; } } else { tomix = datastream; lengthloaded = readfromstream; } bool effectbufferloaded = false; if (channel.rate != 1 && format.format == AUDIO_S16SYS) { int in_len = (int)(ceil((double)lengthloaded / samplesize)); int out_len = samples + 20; // needs some extra, otherwise resampler sometimes doesn't process all the input samples speex_resampler_set_rate(channel.resampler, format.freq, (int)(format.freq * (1 / channel.rate))); speex_resampler_process_interleaved_int(channel.resampler, (const spx_int16_t*)tomix, (spx_uint32_t*)&in_len, (spx_int16_t*)effectbuffer, (spx_uint32_t*)&out_len); effectbufferloaded = true; tomix = effectbuffer; lengthloaded = (out_len * samplesize); } if (channel.pan != 0.5f && format.channels == 2) { if (!effectbufferloaded) { memcpy(effectbuffer, tomix, lengthloaded); effectbufferloaded = true; tomix = effectbuffer; } switch (format.format) { case AUDIO_S16SYS: EffectPanS16(channel, (sint16*)effectbuffer, lengthloaded / samplesize); break; case AUDIO_U8: EffectPanU8(channel, (uint8*)effectbuffer, lengthloaded / samplesize); break; } } int mixlength = lengthloaded; if (loaded + mixlength > length) { mixlength = length - loaded; } SDL_MixAudioFormat(&data[loaded], tomix, format.format, mixlength, volume); if (dataconverted) { delete[] dataconverted; } channel.offset += readfromstream; } loaded += lengthloaded; if (channel.loop != 0 && channel.offset >= channel.stream->Length()) { if (channel.loop != -1) { channel.loop--; } channel.offset = 0; } } while(loaded < length && channel.loop != 0); } }