Descent3/sndlib/mixer.cpp
2024-09-10 03:00:25 +03:00

625 lines
20 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <memory.h>
#include "log.h"
#include "ssl_lib.h"
#include "mixer.h"
#include "pserror.h"
#define MIN_SOUND_MIX_VOLUME 0.0f
#define MAX_WRITE_AHEAD 0.04f // Seconds to write ahead of the play position (in seconds)
#define VOLUME_FIX_BITS 1024
static inline void opti_8m_mix(uint8_t *cur_sample_8bit, const int num_write, int &samples_played,
int16_t *mixer_buffer16, const float l_volume, const float r_volume);
static inline void opti_8s_mix(uint8_t *cur_sample_8bit, const int num_write, int &samples_played,
int16_t *mixer_buffer16, const float l_volume, const float r_volume);
static inline void opti_16m_mix(int16_t *cur_sample_16bit, const int num_write, int &samples_played,
int16_t *mixer_buffer16, const float l_volume, const float r_volume);
static inline void opti_16s_mix(int16_t *cur_sample_16bit, const int num_write, int &samples_played,
int16_t *mixer_buffer16, const float l_volume, const float r_volume);
software_mixer::software_mixer() {
m_init = false;
m_buffer = NULL;
}
software_mixer::~software_mixer() {
if (m_buffer) {
free(m_buffer);
}
}
bool software_mixer::Initialize(tMixerInit *mi) {
if (m_init) {
LOG_ERROR << "Mixer: Already Init";
Int3();
return false;
}
// Sam 6/29 - When using SDL, there's no primary buffer
// if(!mi->primary_buffer || !mi->ll_sound_ptr)
if (!mi->ll_sound_ptr) {
LOG_ERROR << "Mixer: Bad Value passed on init";
Int3();
return false;
}
m_init = true;
m_ll_sound_ptr = mi->ll_sound_ptr;
m_primary_buffer = mi->primary_buffer;
if (m_primary_buffer) {
m_BufferSize = m_primary_buffer->GetNumBufferBytes();
} else {
m_BufferSize = 0;
}
m_max_sounds_available = mi->max_sounds_available;
m_sound_cache = mi->sound_cache;
m_primary_alignment = mi->primary_alignment;
m_error_code = mi->p_error_code;
m_fpSetError = mi->fp_SetError;
m_fpErrorText = mi->fp_ErrorText;
if (m_BufferSize) {
m_buffer = (uint8_t *)malloc(m_BufferSize);
}
return true;
}
// A peroidic mixer that uses the primary buffer as a stream buffer
void software_mixer::DoFrame(void) {
if (!m_init)
return;
int amount = m_BufferSize;
StreamMixer((char *)m_buffer, amount);
m_primary_buffer->Write(m_buffer, amount);
}
// The actual mixer code that sum's the sounds on each channel and does all the actual
// mixing and effects (writes data to the locked primary buffer)
void software_mixer::StreamMixer(char *ptr, int len) {
int i;
int16_t *mixer_buffer16 = (int16_t *)ptr;
int current_slot = 0;
bool f_loop;
bool f_mono;
const int buff_len = len / m_primary_alignment;
// this code will assure that this function will not be called when sound system is in error mode.
if (*m_error_code != SSL_OK) {
LOG_ERROR.printf("MIX: Mixer in error code %d", *m_error_code);
return;
}
// ASSERT(len <= m_BufferSize);
ASSERT(ptr && len >= 0);
if ((len % m_primary_alignment) != 0) {
LOG_ERROR << "MIX:Len is not aligned!";
(*m_fpSetError)(SSL_ERROR_STREAMMIXER);
(*m_fpErrorText)("ASSERT((len % ll_sound_ptr->m_primary_alignment) == 0)\nLen:%d PrA:%d", len, m_primary_alignment);
return;
}
memset((char *)ptr, 0, len);
// Mix the sound slots
while (current_slot < (*m_max_sounds_available)) {
sound_buffer_info *cur_buf = &m_sound_cache[current_slot];
int num_samples = buff_len;
mixer_buffer16 = (int16_t *)ptr;
f_mono = true;
// Find slots with sounds in them
if ((cur_buf->m_status != SSF_UNUSED) && !(cur_buf->m_status & SSF_PAUSED)) {
float l_volume = cur_buf->play_info->left_volume;
float r_volume = cur_buf->play_info->right_volume;
int skip_interval = cur_buf->play_info->sample_skip_interval;
int samples_played = cur_buf->play_info->m_samples_played;
int16_t *sample_16bit;
uint8_t *sample_8bit;
int np_sample_length = 0;
int sample_length;
int loop_start;
int loop_end;
if (cur_buf->m_status & SSF_PLAY_STREAMING) {
switch (cur_buf->play_info->m_stream_format) {
case SIF_STREAMING_16_M:
sample_16bit = (int16_t *)cur_buf->play_info->m_stream_data;
sample_8bit = NULL;
np_sample_length = sample_length = cur_buf->play_info->m_stream_size / 2;
break;
case SIF_STREAMING_8_M:
sample_16bit = NULL;
sample_8bit = (uint8_t *)cur_buf->play_info->m_stream_data;
np_sample_length = sample_length = cur_buf->play_info->m_stream_size;
break;
case SIF_STREAMING_16_S:
sample_16bit = (int16_t *)cur_buf->play_info->m_stream_data;
sample_8bit = NULL;
np_sample_length = sample_length = cur_buf->play_info->m_stream_size / 4;
f_mono = false;
break;
case SIF_STREAMING_8_S:
sample_16bit = NULL;
sample_8bit = (uint8_t *)cur_buf->play_info->m_stream_data;
np_sample_length = sample_length = cur_buf->play_info->m_stream_size / 2;
f_mono = false;
break;
default:
Int3();
break;
}
loop_start = 0;
loop_end = np_sample_length - 1;
} else {
int sound_index = cur_buf->m_sound_index;
int sample_index = Sounds[sound_index].sample_index;
sound_file_info *snd_file = &SoundFiles[sample_index];
sample_16bit = snd_file->sample_16bit;
sample_8bit = snd_file->sample_8bit;
np_sample_length = snd_file->np_sample_length;
sample_length = snd_file->sample_length;
loop_start = Sounds[sound_index].loop_start;
loop_end = Sounds[sound_index].loop_end;
if (!sample_16bit && !sample_8bit) {
LOG_WARNING.printf("sound file %s didn't have data for samples.\n", snd_file->name);
}
}
// cleanly continue if this happens, and inform a logfile, if it does. error handling
// ASSERT(sample_16bit || sample_8bit);
if (!sample_16bit && !sample_8bit) {
sound_file_info *snd_file = &SoundFiles[Sounds[cur_buf->m_sound_index].sample_index];
LOG_WARNING.printf("MIX: No data for %s\n", snd_file->name);
(*m_fpSetError)(SSL_ERROR_SAMPLE_NODATA);
(*m_fpErrorText)("ASSERT(sample_16bit || sample_8bit)\nNo data found for sound file: %s", snd_file->name);
cur_buf->m_status = SSF_UNUSED;
goto error_bail;
}
ASSERT(np_sample_length <= sample_length);
ASSERT(samples_played >= 0 || samples_played <= sample_length);
int num_write;
error_trap:
// We have not looped -- yet
f_loop = false;
// Verify the volume levels are o.k.
ASSERT(l_volume >= 0.0 && l_volume <= 1.0);
ASSERT(r_volume >= 0.0 && r_volume <= 1.0);
looping: // Will go to this label to do the next iteration of a looping sample
if (cur_buf->m_status &
(SSF_PLAY_LOOPING | SSF_PLAY_STREAMING)) // Looping sample's process is broken up into linear pieces
{
if (f_loop) {
ASSERT(num_write >= 0);
num_samples -= num_write;
ASSERT(num_samples > 0);
mixer_buffer16 += num_write << 1; // update to the new start position
// (2x because of left and right channels)
samples_played = loop_start;
}
if (cur_buf->m_status & SSF_PLAY_LOOPING) // Looping sample's process is broken up into linear pieces
{
ASSERT(loop_end < sample_length);
if (loop_end < samples_played) {
if (loop_end != loop_start) {
while (loop_end < samples_played) {
samples_played -= loop_end - loop_start;
}
ASSERT(samples_played >= 0);
} else {
cur_buf->m_status &= ~SSF_PLAY_LOOPING;
cur_buf->m_status |= SSF_PLAY_NORMAL;
}
goto error_trap;
}
}
// The number of samples to write to the primary buffer
num_write = ((num_samples) < (loop_end - samples_played + 1)) ? (num_samples) : (loop_end - samples_played + 1);
ASSERT(num_write >= 0 && num_write <= num_samples);
if (num_write < num_samples)
f_loop = true;
else
f_loop = false;
if (num_write <= 0) {
num_write = 0;
goto stream_done;
}
} else {
// The number of samples to write to the primary buffer
num_write =
((num_samples) < (np_sample_length - samples_played)) ? (num_samples) : (np_sample_length - samples_played);
if (!(num_write > 0 && num_write <= num_samples)) {
num_write = 0;
goto done;
}
// Optimization for silent sounds
if (l_volume <= MIN_SOUND_MIX_VOLUME && r_volume <= MIN_SOUND_MIX_VOLUME) {
cur_buf->play_info->m_samples_played += num_write;
goto done;
}
}
if ((num_write < 0) || (num_write > num_samples)) // this was an assert
{
num_write = 0;
goto done;
}
// Mix at 16 bits per sample
if (skip_interval == 0) {
int16_t *cur_sample_16bit = sample_16bit;
uint8_t *cur_sample_8bit = sample_8bit;
if (f_mono) {
if (sample_8bit) {
cur_sample_8bit += samples_played;
opti_8m_mix(cur_sample_8bit, num_write, samples_played, mixer_buffer16, l_volume, r_volume);
} else {
cur_sample_16bit += samples_played;
opti_16m_mix(cur_sample_16bit, num_write, samples_played, mixer_buffer16, l_volume, r_volume);
}
} else {
if (sample_8bit) {
cur_sample_8bit += (samples_played << 1);
opti_8s_mix(cur_sample_8bit, num_write, samples_played, mixer_buffer16, l_volume, r_volume);
} else {
cur_sample_16bit += (samples_played << 1);
opti_16s_mix(cur_sample_16bit, num_write, samples_played, mixer_buffer16, l_volume, r_volume);
}
}
} else
// Account for lower-sampling rate
{
if (skip_interval == 1) {
for (i = 0; i < (num_write << 1); i += 2) {
int sample;
if (sample_16bit) {
if (samples_played & 0x0001) {
sample = ((int)sample_16bit[samples_played ^ 0x0001] + (int)sample_16bit[samples_played + 1]) >> 1;
} else
sample = sample_16bit[samples_played];
} else {
if (samples_played & 0x0001) {
// Notes: (<<7) is from a (<<8) - (>>1)
// Notes: (-256) is from (-128) + (-128)
sample = ((int)sample_8bit[samples_played ^ 0x0001] + (int)sample_8bit[samples_played + 1] - 256) << 7;
} else
sample = (((int)sample_8bit[samples_played]) - (int)128) << 8;
}
samples_played++;
ASSERT(i >= 0 && (i + 1 < num_samples * 2));
int l_sample = mixer_buffer16[i] + (sample * l_volume);
int r_sample = mixer_buffer16[i + 1] + (sample * r_volume);
if (l_sample < -32767)
l_sample = -32767;
if (l_sample > 32767)
l_sample = 32767;
if (r_sample < -32767)
r_sample = -32767;
if (r_sample > 32767)
r_sample = 32767;
mixer_buffer16[i] = (int16_t)l_sample;
mixer_buffer16[i + 1] = (int16_t)r_sample;
}
} else {
for (i = 0; i < (num_write << 1); i += 2) {
int sample;
const int mod_pos = samples_played % 4;
if (sample_16bit) {
switch (mod_pos) {
case 0:
sample = sample_16bit[samples_played];
break;
case 1:
sample = (sample_16bit[samples_played - 1] * 3 + sample_16bit[samples_played + 3]) >> 2;
break;
case 2:
sample = (sample_16bit[samples_played - 2] + sample_16bit[samples_played + 2]) >> 1;
break;
case 3:
sample = (sample_16bit[samples_played - 3] + sample_16bit[samples_played + 1] * 3) >> 2;
break;
}
} else {
switch (mod_pos) {
case 0:
sample = ((((int)sample_8bit[samples_played]) - 128) << 8);
break;
case 1:
sample = (((((int)sample_8bit[samples_played - 1]) - 128) << 8) * 3 +
((((int)sample_8bit[samples_played + 3]) - 128) << 8)) >>
2;
break;
case 2:
sample = (((((int)sample_8bit[samples_played - 2]) - 128) << 8) +
((((int)sample_8bit[samples_played + 2]) - 128) << 8)) >>
1;
break;
case 3:
sample = (((((int)sample_8bit[samples_played - 3]) - 128) << 8) +
((((int)sample_8bit[samples_played + 1]) - 128) << 8) * 3) >>
2;
break;
}
}
samples_played++;
ASSERT(i >= 0 && (i + 1 < num_samples * 2));
int l_sample = mixer_buffer16[i] + (sample * l_volume);
int r_sample = mixer_buffer16[i + 1] + (sample * r_volume);
if (l_sample < -32767)
l_sample = -32767;
if (l_sample > 32767)
l_sample = 32767;
if (r_sample < -32767)
r_sample = -32767;
if (r_sample > 32767)
r_sample = 32767;
mixer_buffer16[i] = (int16_t)l_sample;
mixer_buffer16[i + 1] = (int16_t)r_sample;
}
}
}
stream_done:
cur_buf->play_info->m_samples_played = samples_played;
if (cur_buf->m_status & SSF_PLAY_STREAMING) {
if (f_loop) {
if (cur_buf->play_info->m_stream_cback && cur_buf->play_info->m_stream_data) {
cur_buf->play_info->m_stream_data = (*cur_buf->play_info->m_stream_cback)(
cur_buf->play_info->user_data, cur_buf->play_info->m_stream_handle, &cur_buf->play_info->m_stream_size);
// cur_buf->s->current_position = (char*)cur_buf->play_info->m_stream_data;
// mprintf(0, "%x %d\n", cur_buf->play_info->m_stream_data, cur_buf->play_info->m_stream_size));
ASSERT(!(cur_buf->play_info->m_stream_data && cur_buf->play_info->m_stream_size <= 0));
// mprintf(0, "Data %X, length %d\n", cur_buf->play_info->m_stream_data, cur_buf->play_info->m_stream_size);
if (cur_buf->play_info->m_stream_data) {
switch (cur_buf->play_info->m_stream_format) {
case SIF_STREAMING_16_M:
sample_16bit = (int16_t *)cur_buf->play_info->m_stream_data;
loop_end = sample_length = np_sample_length = cur_buf->play_info->m_stream_size / 2;
break;
case SIF_STREAMING_8_M:
sample_8bit = (uint8_t *)cur_buf->play_info->m_stream_data;
loop_end = sample_length = np_sample_length = cur_buf->play_info->m_stream_size;
break;
case SIF_STREAMING_16_S:
sample_16bit = (int16_t *)cur_buf->play_info->m_stream_data;
loop_end = sample_length = np_sample_length = cur_buf->play_info->m_stream_size / 4;
break;
case SIF_STREAMING_8_S:
sample_8bit = (uint8_t *)cur_buf->play_info->m_stream_data;
loop_end = sample_length = np_sample_length = cur_buf->play_info->m_stream_size / 2;
break;
default:
Int3();
break;
}
loop_end -= 1;
} else {
LOG_WARNING << "SE: Data is NULL";
cur_buf->m_status &= ~SSF_PLAY_STREAMING;
f_loop = false;
}
} else {
LOG_WARNING << "SE: Callback/data is NULL";
cur_buf->m_status &= ~SSF_PLAY_STREAMING;
f_loop = false;
}
}
}
if (f_loop)
goto looping;
done:
if (cur_buf->play_info->m_samples_played >= (np_sample_length) &&
!(cur_buf->m_status & (SSF_PLAY_LOOPING | SSF_PLAY_STREAMING))) {
m_ll_sound_ptr->StopSound(cur_buf->m_unique_id);
}
}
error_bail:
current_slot++;
}
}
inline void opti_8m_mix(uint8_t *cur_sample_8bit, const int num_write, int &samples_played, int16_t *mixer_buffer16,
const float l_volume, const float r_volume) {
int i;
int16_t *mb = mixer_buffer16;
for (i = 0; i < (num_write << 1); i += 2) {
int16_t sample;
int l_sample;
int r_sample;
sample = (((int16_t)(*cur_sample_8bit)) - (int16_t)128) << 8;
cur_sample_8bit++;
l_sample = *mb + (sample * l_volume);
r_sample = *(mb + 1) + (sample * r_volume);
samples_played++;
if (l_sample < -32767)
l_sample = -32767;
if (l_sample > 32767)
l_sample = 32767;
if (r_sample < -32767)
r_sample = -32767;
if (r_sample > 32767)
r_sample = 32767;
*mb = (int16_t)l_sample;
mb++;
*mb = (int16_t)r_sample;
mb++;
}
}
inline void opti_8s_mix(uint8_t *cur_sample_8bit, const int num_write, int &samples_played, int16_t *mixer_buffer16,
const float l_volume, const float r_volume) {
int i;
int16_t *mb = mixer_buffer16;
for (i = 0; i < (num_write << 1); i += 2) {
int16_t lsample;
int16_t rsample;
int l_sample;
int r_sample;
lsample = (((int16_t)(*cur_sample_8bit)) - (int16_t)128) << 8;
cur_sample_8bit++;
rsample = (((int16_t)(*cur_sample_8bit)) - (int16_t)128) << 8;
cur_sample_8bit++;
l_sample = *mb + (lsample * l_volume);
r_sample = *(mb + 1) + (rsample * r_volume);
samples_played++;
if (l_sample < -32767)
l_sample = -32767;
if (l_sample > 32767)
l_sample = 32767;
if (r_sample < -32767)
r_sample = -32767;
if (r_sample > 32767)
r_sample = 32767;
*mb = (int16_t)l_sample;
mb++;
*mb = (int16_t)r_sample;
mb++;
}
}
inline void opti_16m_mix(int16_t *cur_sample_16bit, const int num_write, int &samples_played, int16_t *mixer_buffer16,
const float l_volume, const float r_volume) {
int i;
int16_t *mb = mixer_buffer16;
for (i = 0; i < (num_write << 1); i += 2) {
int16_t sample;
int l_sample;
int r_sample;
sample = *cur_sample_16bit;
cur_sample_16bit++;
l_sample = *mb + (sample * l_volume);
r_sample = *(mb + 1) + (sample * r_volume);
samples_played++;
if (l_sample < -32767)
l_sample = -32767;
if (l_sample > 32767)
l_sample = 32767;
if (r_sample < -32767)
r_sample = -32767;
if (r_sample > 32767)
r_sample = 32767;
*mb = (int16_t)l_sample;
mb++;
*mb = (int16_t)r_sample;
mb++;
}
}
inline void opti_16s_mix(int16_t *cur_sample_16bit, const int num_write, int &samples_played, int16_t *mixer_buffer16,
const float l_volume, const float r_volume) {
int i;
int16_t *mb = mixer_buffer16;
for (i = 0; i < (num_write << 1); i += 2) {
int16_t lsample;
int16_t rsample;
int l_sample;
int r_sample;
lsample = *cur_sample_16bit;
cur_sample_16bit++;
rsample = *cur_sample_16bit;
cur_sample_16bit++;
l_sample = *mb + (lsample * l_volume);
r_sample = *(mb + 1) + (rsample * r_volume);
samples_played++;
if (l_sample < -32767)
l_sample = -32767;
if (l_sample > 32767)
l_sample = 32767;
if (r_sample < -32767)
r_sample = -32767;
if (r_sample > 32767)
r_sample = 32767;
*mb = (int16_t)l_sample;
mb++;
*mb = (int16_t)r_sample;
*mb++;
}
}