// for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER#include <crtdbg.h>#endif #include <stdio.h>#include <stdlib.h>#include <stdint.h>#include <stdbool.h>#include <math.h>#include <SDL2/SDL.h>#ifdef _WIN32#include <io.h>#else#include <unistd.h>#endif#include <fcntl.h>#include <sys/types.h>#include <sys/stat.h>#include <limits.h>#include "pt2_audio.h"#include "pt2_helpers.h"#include "pt2_config.h"#include "pt2_textout.h"#include "pt2_scopes.h"#include "pt2_visuals_sync.h"#include "pt2_downsample2x.h"#include "pt2_replayer.h"#include "pt2_paula.h" // cumulative mid/side normalization factor (1/sqrt(2))*(1/sqrt(2))#define STEREO_NORM_FACTOR 0.5 #define INITIAL_DITHER_SEED 0x12345000 static uint8_t panningMode;static int32_t randSeed = INITIAL_DITHER_SEED, stereoSeparation = 100;static double *dMixBufferL, *dMixBufferR;static double dPrngStateL, dPrngStateR, dSideFactor;static SDL_AudioDeviceID dev; audio_t audio; // globalized void setAmigaFilterModel(uint8_t model){ if (audio.amigaModel == model) return; // same state as before! const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); audio.amigaModel = model; const int32_t paulaMixFrequency = audio.oversamplingFlag ? audio.outputRate*2 : audio.outputRate; paulaSetup(paulaMixFrequency, audio.amigaModel); if (audioWasntLocked) unlockAudio();} void toggleAmigaFilterModel(void){ const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); audio.amigaModel ^= 1; const int32_t paulaMixFrequency = audio.oversamplingFlag ? audio.outputRate*2 : audio.outputRate; paulaSetup(paulaMixFrequency, audio.amigaModel); if (audioWasntLocked) unlockAudio(); if (audio.amigaModel == MODEL_A500) displayMsg("AUDIO: AMIGA 500"); else displayMsg("AUDIO: AMIGA 1200");} void setLEDFilter(bool state){ if (audio.ledFilterEnabled == state) return; // same state as before! const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); audio.ledFilterEnabled = state; paulaWriteByte(0xBFE001, (uint8_t)audio.ledFilterEnabled << 1); if (audioWasntLocked) unlockAudio();} void toggleLEDFilter(void){ const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); audio.ledFilterEnabled ^= 1; paulaWriteByte(0xBFE001, (uint8_t)audio.ledFilterEnabled << 1); if (audioWasntLocked) unlockAudio();} void lockAudio(void){ if (dev != 0) SDL_LockAudioDevice(dev); audio.locked = true; audio.resetSyncTickTimeFlag = true; resetChSyncQueue();} void unlockAudio(void){ if (dev != 0) SDL_UnlockAudioDevice(dev); audio.resetSyncTickTimeFlag = true; resetChSyncQueue(); audio.locked = false;} void resetAudioDithering(void){ randSeed = INITIAL_DITHER_SEED; dPrngStateL = 0.0; dPrngStateR = 0.0;} static inline int32_t random32(void){ // LCG random 32-bit generator (quite good and fast) randSeed *= 134775813; randSeed++; return randSeed;} #define NORM_FACTOR 2.0 /* nominally correct, but can clip from high-pass filter overshoot */ static inline void processMixedSamplesAmigaPanning(int32_t i, int16_t *out){ int32_t smp32; double dPrng; double dL = dMixBufferL[i]; double dR = dMixBufferR[i]; // normalize w/ phase-inversion (A500/A1200 has a phase-inverted audio signal) dL *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); dR *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); // left channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dL = (dL + dPrng) - dPrngStateL; dPrngStateL = dPrng; smp32 = (int32_t)dL; CLAMP16(smp32); out[0] = (int16_t)smp32; // right channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dR = (dR + dPrng) - dPrngStateR; dPrngStateR = dPrng; smp32 = (int32_t)dR; CLAMP16(smp32); out[1] = (int16_t)smp32;} static inline void processMixedSamples(int32_t i, int16_t *out){ int32_t smp32; double dPrng; double dL = dMixBufferL[i]; double dR = dMixBufferR[i]; // apply stereo separation const double dOldL = dL; const double dOldR = dR; double dMid = (dOldL + dOldR) * STEREO_NORM_FACTOR; double dSide = (dOldL - dOldR) * dSideFactor; dL = dMid + dSide; dR = dMid - dSide; // normalize w/ phase-inversion (A500/A1200 has a phase-inverted audio signal) dL *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); dR *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); // left channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dL = (dL + dPrng) - dPrngStateL; dPrngStateL = dPrng; smp32 = (int32_t)dL; CLAMP16(smp32); out[0] = (int16_t)smp32; // right channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dR = (dR + dPrng) - dPrngStateR; dPrngStateR = dPrng; smp32 = (int32_t)dR; CLAMP16(smp32); out[1] = (int16_t)smp32;} static inline void processMixedSamplesAmigaPanning_2x(int32_t i, int16_t *out) // 2x oversampling{ int32_t smp32; double dPrng, dL, dR; // 2x downsampling (decimation) const uint32_t offset1 = (i << 1) + 0; const uint32_t offset2 = (i << 1) + 1; dL = decimate2x_L(dMixBufferL[offset1], dMixBufferL[offset2]); dR = decimate2x_R(dMixBufferR[offset1], dMixBufferR[offset2]); // normalize w/ phase-inversion (A500/A1200 has a phase-inverted audio signal) dL *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); dR *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); // left channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dL = (dL + dPrng) - dPrngStateL; dPrngStateL = dPrng; smp32 = (int32_t)dL; CLAMP16(smp32); out[0] = (int16_t)smp32; // right channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dR = (dR + dPrng) - dPrngStateR; dPrngStateR = dPrng; smp32 = (int32_t)dR; CLAMP16(smp32); out[1] = (int16_t)smp32;} static inline void processMixedSamples_2x(int32_t i, int16_t *out) // 2x oversampling{ int32_t smp32; double dPrng, dL, dR; // 2x downsampling (decimation) const uint32_t offset1 = (i << 1) + 0; const uint32_t offset2 = (i << 1) + 1; dL = decimate2x_L(dMixBufferL[offset1], dMixBufferL[offset2]); dR = decimate2x_R(dMixBufferR[offset1], dMixBufferR[offset2]); // apply stereo separation const double dOldL = dL; const double dOldR = dR; double dMid = (dOldL + dOldR) * STEREO_NORM_FACTOR; double dSide = (dOldL - dOldR) * dSideFactor; dL = dMid + dSide; dR = dMid - dSide; // normalize w/ phase-inversion (A500/A1200 has a phase-inverted audio signal) dL *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); dR *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); // left channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dL = (dL + dPrng) - dPrngStateL; dPrngStateL = dPrng; smp32 = (int32_t)dL; CLAMP16(smp32); out[0] = (int16_t)smp32; // right channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dR = (dR + dPrng) - dPrngStateR; dPrngStateR = dPrng; smp32 = (int32_t)dR; CLAMP16(smp32); out[1] = (int16_t)smp32;} void outputAudio(int16_t *target, int32_t numSamples){ if (audio.oversamplingFlag) // 2x oversampling { paulaGenerateSamples(dMixBufferL, dMixBufferR, numSamples*2); // downsample, normalize and dither int16_t out[2]; int16_t *outStream = target; if (stereoSeparation == 100) { for (int32_t i = 0; i < numSamples; i++) { processMixedSamplesAmigaPanning_2x(i, out); *outStream++ = out[0]; *outStream++ = out[1]; } } else { for (int32_t i = 0; i < numSamples; i++) { processMixedSamples_2x(i, out); *outStream++ = out[0]; *outStream++ = out[1]; } } } else { paulaGenerateSamples(dMixBufferL, dMixBufferR, numSamples); // normalize and dither int16_t out[2]; int16_t *outStream = target; if (stereoSeparation == 100) { for (int32_t i = 0; i < numSamples; i++) { processMixedSamplesAmigaPanning(i, out); *outStream++ = out[0]; *outStream++ = out[1]; } } else { for (int32_t i = 0; i < numSamples; i++) { processMixedSamples(i, out); *outStream++ = out[0]; *outStream++ = out[1]; } } }} static void SDLCALL audioCallback(void *userdata, Uint8 *stream, int len){ if (editor.mod2WavOngoing || editor.pat2SmpOngoing) // send silence to sound output device { memset(stream, 0, len); return; } int16_t *streamOut = (int16_t *)stream; uint32_t samplesLeft = (uint32_t)len / 4; while (samplesLeft > 0) { if (audio.tickSampleCounter == 0) // new replayer tick { if (editor.songPlaying) { intMusic(); // PT replayer ticker (also sets audio.samplesPerTickInt and audio.samplesPerTickFrac) fillVisualsSyncBuffer(); } audio.tickSampleCounter = audio.samplesPerTickInt; audio.tickSampleCounterFrac += audio.samplesPerTickFrac; if (audio.tickSampleCounterFrac >= BPM_FRAC_SCALE) { audio.tickSampleCounterFrac &= BPM_FRAC_MASK; audio.tickSampleCounter++; } } uint32_t samplesToMix = samplesLeft; if (samplesToMix > audio.tickSampleCounter) samplesToMix = audio.tickSampleCounter; outputAudio(streamOut, samplesToMix); streamOut += samplesToMix * 2; // *2 for stereo audio.tickSampleCounter -= samplesToMix; samplesLeft -= samplesToMix; } USED(userdata);} void audioSetStereoSeparation(uint8_t percentage) // 0..100 (percentage){ assert(percentage <= 100); stereoSeparation = percentage; dSideFactor = (percentage / 100.0) * STEREO_NORM_FACTOR;} void generateBpmTable(double dAudioFreq, bool vblankTimingFlag){ const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); for (int32_t bpm = MIN_BPM; bpm <= MAX_BPM; bpm++) { const int32_t i = bpm - MIN_BPM; // index for tables double dBpmHz; if (vblankTimingFlag) dBpmHz = AMIGA_PAL_VBLANK_HZ; else dBpmHz = ciaBpm2Hz(bpm); const double dSamplesPerTick = dAudioFreq / dBpmHz; double dSamplesPerTickInt; double dSamplesPerTickFrac = modf(dSamplesPerTick, &dSamplesPerTickInt); audio.samplesPerTickIntTab[i] = (uint32_t)dSamplesPerTickInt; audio.samplesPerTickFracTab[i] = (uint64_t)((dSamplesPerTickFrac * BPM_FRAC_SCALE) + 0.5); // rounded } audio.tickSampleCounter = 0; audio.tickSampleCounterFrac = 0; if (audioWasntLocked) unlockAudio();} static void generateTickLengthTable(bool vblankTimingFlag){ for (int32_t bpm = MIN_BPM; bpm <= MAX_BPM; bpm++) { const int32_t i = bpm - MIN_BPM; // index for tables double dHz; if (vblankTimingFlag) dHz = AMIGA_PAL_VBLANK_HZ; else dHz = ciaBpm2Hz(bpm); // BPM -> Hz -> tick length for performance counter (syncing visuals to audio) const double dTickTime = (double)hpcFreq.freq64 / dHz; double dTimeInt; double dTimeFrac = modf(dTickTime, &dTimeInt); audio.tickTimeIntTab[i] = (uint32_t)dTimeInt; audio.tickTimeFracTab[i] = (uint64_t)((dTimeFrac * TICK_TIME_FRAC_SCALE) + 0.5); // rounded }} void updateReplayerTimingMode(void){ const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); const bool vblankTimingMode = (editor.timingMode == TEMPO_MODE_VBLANK); generateBpmTable(audio.outputRate, vblankTimingMode); generateTickLengthTable(vblankTimingMode); if (audioWasntLocked) unlockAudio();} bool setupAudio(void){ SDL_AudioSpec want, have; want.freq = config.soundFrequency; want.samples = (uint16_t)config.soundBufferSize; want.format = AUDIO_S16; want.channels = 2; want.callback = audioCallback; want.userdata = NULL; dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); if (dev == 0) { showErrorMsgBox("Couldn't open audio device:\n\"%s\"\n\nDo you have an audio device enabled and plugged in?", SDL_GetError()); return false; } // lower than this is not safe for the BLEP synthesis in the mixer const int32_t minFreq = (int32_t)(PAULA_PAL_CLK / 113.0 / 2.0)+1; // /2 because we do 2x oversampling if (have.freq < minFreq) { showErrorMsgBox("Unable to open audio: An audio rate below %dHz can't be used!", minFreq); return false; } if (have.format != AUDIO_S16) { showErrorMsgBox("Couldn't open audio device:\nThis program only supports 16-bit audio streams. Sorry!"); return false; } audio.outputRate = have.freq; audio.audioBufferSize = have.samples; audio.oversamplingFlag = (audio.outputRate < 96000); // we do 2x oversampling if the audio output rate is below 96kHzLines 1–500 of 683. Load more
// for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER #include <crtdbg.h> #endif #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <math.h> #include <SDL2/SDL.h> #ifdef _WIN32 #include <io.h> #else #include <unistd.h> #endif #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <limits.h> #include "pt2_audio.h" #include "pt2_helpers.h" #include "pt2_config.h" #include "pt2_textout.h" #include "pt2_scopes.h" #include "pt2_visuals_sync.h" #include "pt2_downsample2x.h" #include "pt2_replayer.h" #include "pt2_paula.h" // cumulative mid/side normalization factor (1/sqrt(2))*(1/sqrt(2)) #define STEREO_NORM_FACTOR 0.5 #define INITIAL_DITHER_SEED 0x12345000 static uint8_t panningMode; static int32_t randSeed = INITIAL_DITHER_SEED, stereoSeparation = 100; static double *dMixBufferL, *dMixBufferR; static double dPrngStateL, dPrngStateR, dSideFactor; static SDL_AudioDeviceID dev; audio_t audio; // globalized void setAmigaFilterModel(uint8_t model) { if (audio.amigaModel == model) return; // same state as before! const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); audio.amigaModel = model; const int32_t paulaMixFrequency = audio.oversamplingFlag ? audio.outputRate*2 : audio.outputRate; paulaSetup(paulaMixFrequency, audio.amigaModel); if (audioWasntLocked) unlockAudio(); } void toggleAmigaFilterModel(void) { const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); audio.amigaModel ^= 1; const int32_t paulaMixFrequency = audio.oversamplingFlag ? audio.outputRate*2 : audio.outputRate; paulaSetup(paulaMixFrequency, audio.amigaModel); if (audioWasntLocked) unlockAudio(); if (audio.amigaModel == MODEL_A500) displayMsg("AUDIO: AMIGA 500"); else displayMsg("AUDIO: AMIGA 1200"); } void setLEDFilter(bool state) { if (audio.ledFilterEnabled == state) return; // same state as before! const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); audio.ledFilterEnabled = state; paulaWriteByte(0xBFE001, (uint8_t)audio.ledFilterEnabled << 1); if (audioWasntLocked) unlockAudio(); } void toggleLEDFilter(void) { const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); audio.ledFilterEnabled ^= 1; paulaWriteByte(0xBFE001, (uint8_t)audio.ledFilterEnabled << 1); if (audioWasntLocked) unlockAudio(); } void lockAudio(void) { if (dev != 0) SDL_LockAudioDevice(dev); audio.locked = true; audio.resetSyncTickTimeFlag = true; resetChSyncQueue(); } void unlockAudio(void) { if (dev != 0) SDL_UnlockAudioDevice(dev); audio.resetSyncTickTimeFlag = true; resetChSyncQueue(); audio.locked = false; } void resetAudioDithering(void) { randSeed = INITIAL_DITHER_SEED; dPrngStateL = 0.0; dPrngStateR = 0.0; } static inline int32_t random32(void) { // LCG random 32-bit generator (quite good and fast) randSeed *= 134775813; randSeed++; return randSeed; } #define NORM_FACTOR 2.0 /* nominally correct, but can clip from high-pass filter overshoot */ static inline void processMixedSamplesAmigaPanning(int32_t i, int16_t *out) { int32_t smp32; double dPrng; double dL = dMixBufferL[i]; double dR = dMixBufferR[i]; // normalize w/ phase-inversion (A500/A1200 has a phase-inverted audio signal) dL *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); dR *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); // left channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dL = (dL + dPrng) - dPrngStateL; dPrngStateL = dPrng; smp32 = (int32_t)dL; CLAMP16(smp32); out[0] = (int16_t)smp32; // right channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dR = (dR + dPrng) - dPrngStateR; dPrngStateR = dPrng; smp32 = (int32_t)dR; CLAMP16(smp32); out[1] = (int16_t)smp32; } static inline void processMixedSamples(int32_t i, int16_t *out) { int32_t smp32; double dPrng; double dL = dMixBufferL[i]; double dR = dMixBufferR[i]; // apply stereo separation const double dOldL = dL; const double dOldR = dR; double dMid = (dOldL + dOldR) * STEREO_NORM_FACTOR; double dSide = (dOldL - dOldR) * dSideFactor; dL = dMid + dSide; dR = dMid - dSide; // normalize w/ phase-inversion (A500/A1200 has a phase-inverted audio signal) dL *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); dR *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); // left channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dL = (dL + dPrng) - dPrngStateL; dPrngStateL = dPrng; smp32 = (int32_t)dL; CLAMP16(smp32); out[0] = (int16_t)smp32; // right channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dR = (dR + dPrng) - dPrngStateR; dPrngStateR = dPrng; smp32 = (int32_t)dR; CLAMP16(smp32); out[1] = (int16_t)smp32; } static inline void processMixedSamplesAmigaPanning_2x(int32_t i, int16_t *out) // 2x oversampling { int32_t smp32; double dPrng, dL, dR; // 2x downsampling (decimation) const uint32_t offset1 = (i << 1) + 0; const uint32_t offset2 = (i << 1) + 1; dL = decimate2x_L(dMixBufferL[offset1], dMixBufferL[offset2]); dR = decimate2x_R(dMixBufferR[offset1], dMixBufferR[offset2]); // normalize w/ phase-inversion (A500/A1200 has a phase-inverted audio signal) dL *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); dR *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); // left channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dL = (dL + dPrng) - dPrngStateL; dPrngStateL = dPrng; smp32 = (int32_t)dL; CLAMP16(smp32); out[0] = (int16_t)smp32; // right channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dR = (dR + dPrng) - dPrngStateR; dPrngStateR = dPrng; smp32 = (int32_t)dR; CLAMP16(smp32); out[1] = (int16_t)smp32; } static inline void processMixedSamples_2x(int32_t i, int16_t *out) // 2x oversampling { int32_t smp32; double dPrng, dL, dR; // 2x downsampling (decimation) const uint32_t offset1 = (i << 1) + 0; const uint32_t offset2 = (i << 1) + 1; dL = decimate2x_L(dMixBufferL[offset1], dMixBufferL[offset2]); dR = decimate2x_R(dMixBufferR[offset1], dMixBufferR[offset2]); // apply stereo separation const double dOldL = dL; const double dOldR = dR; double dMid = (dOldL + dOldR) * STEREO_NORM_FACTOR; double dSide = (dOldL - dOldR) * dSideFactor; dL = dMid + dSide; dR = dMid - dSide; // normalize w/ phase-inversion (A500/A1200 has a phase-inverted audio signal) dL *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); dR *= NORM_FACTOR * (-INT16_MAX / (double)PAULA_VOICES); // left channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dL = (dL + dPrng) - dPrngStateL; dPrngStateL = dPrng; smp32 = (int32_t)dL; CLAMP16(smp32); out[0] = (int16_t)smp32; // right channel - 1-bit triangular dithering (high-pass filtered) dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5 dR = (dR + dPrng) - dPrngStateR; dPrngStateR = dPrng; smp32 = (int32_t)dR; CLAMP16(smp32); out[1] = (int16_t)smp32; } void outputAudio(int16_t *target, int32_t numSamples) { if (audio.oversamplingFlag) // 2x oversampling { paulaGenerateSamples(dMixBufferL, dMixBufferR, numSamples*2); // downsample, normalize and dither int16_t out[2]; int16_t *outStream = target; if (stereoSeparation == 100) { for (int32_t i = 0; i < numSamples; i++) { processMixedSamplesAmigaPanning_2x(i, out); *outStream++ = out[0]; *outStream++ = out[1]; } } else { for (int32_t i = 0; i < numSamples; i++) { processMixedSamples_2x(i, out); *outStream++ = out[0]; *outStream++ = out[1]; } } } else { paulaGenerateSamples(dMixBufferL, dMixBufferR, numSamples); // normalize and dither int16_t out[2]; int16_t *outStream = target; if (stereoSeparation == 100) { for (int32_t i = 0; i < numSamples; i++) { processMixedSamplesAmigaPanning(i, out); *outStream++ = out[0]; *outStream++ = out[1]; } } else { for (int32_t i = 0; i < numSamples; i++) { processMixedSamples(i, out); *outStream++ = out[0]; *outStream++ = out[1]; } } } } static void SDLCALL audioCallback(void *userdata, Uint8 *stream, int len) { if (editor.mod2WavOngoing || editor.pat2SmpOngoing) // send silence to sound output device { memset(stream, 0, len); return; } int16_t *streamOut = (int16_t *)stream; uint32_t samplesLeft = (uint32_t)len / 4; while (samplesLeft > 0) { if (audio.tickSampleCounter == 0) // new replayer tick { if (editor.songPlaying) { intMusic(); // PT replayer ticker (also sets audio.samplesPerTickInt and audio.samplesPerTickFrac) fillVisualsSyncBuffer(); } audio.tickSampleCounter = audio.samplesPerTickInt; audio.tickSampleCounterFrac += audio.samplesPerTickFrac; if (audio.tickSampleCounterFrac >= BPM_FRAC_SCALE) { audio.tickSampleCounterFrac &= BPM_FRAC_MASK; audio.tickSampleCounter++; } } uint32_t samplesToMix = samplesLeft; if (samplesToMix > audio.tickSampleCounter) samplesToMix = audio.tickSampleCounter; outputAudio(streamOut, samplesToMix); streamOut += samplesToMix * 2; // *2 for stereo audio.tickSampleCounter -= samplesToMix; samplesLeft -= samplesToMix; } USED(userdata); } void audioSetStereoSeparation(uint8_t percentage) // 0..100 (percentage) { assert(percentage <= 100); stereoSeparation = percentage; dSideFactor = (percentage / 100.0) * STEREO_NORM_FACTOR; } void generateBpmTable(double dAudioFreq, bool vblankTimingFlag) { const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); for (int32_t bpm = MIN_BPM; bpm <= MAX_BPM; bpm++) { const int32_t i = bpm - MIN_BPM; // index for tables double dBpmHz; if (vblankTimingFlag) dBpmHz = AMIGA_PAL_VBLANK_HZ; else dBpmHz = ciaBpm2Hz(bpm); const double dSamplesPerTick = dAudioFreq / dBpmHz; double dSamplesPerTickInt; double dSamplesPerTickFrac = modf(dSamplesPerTick, &dSamplesPerTickInt); audio.samplesPerTickIntTab[i] = (uint32_t)dSamplesPerTickInt; audio.samplesPerTickFracTab[i] = (uint64_t)((dSamplesPerTickFrac * BPM_FRAC_SCALE) + 0.5); // rounded } audio.tickSampleCounter = 0; audio.tickSampleCounterFrac = 0; if (audioWasntLocked) unlockAudio(); } static void generateTickLengthTable(bool vblankTimingFlag) { for (int32_t bpm = MIN_BPM; bpm <= MAX_BPM; bpm++) { const int32_t i = bpm - MIN_BPM; // index for tables double dHz; if (vblankTimingFlag) dHz = AMIGA_PAL_VBLANK_HZ; else dHz = ciaBpm2Hz(bpm); // BPM -> Hz -> tick length for performance counter (syncing visuals to audio) const double dTickTime = (double)hpcFreq.freq64 / dHz; double dTimeInt; double dTimeFrac = modf(dTickTime, &dTimeInt); audio.tickTimeIntTab[i] = (uint32_t)dTimeInt; audio.tickTimeFracTab[i] = (uint64_t)((dTimeFrac * TICK_TIME_FRAC_SCALE) + 0.5); // rounded } } void updateReplayerTimingMode(void) { const bool audioWasntLocked = !audio.locked; if (audioWasntLocked) lockAudio(); const bool vblankTimingMode = (editor.timingMode == TEMPO_MODE_VBLANK); generateBpmTable(audio.outputRate, vblankTimingMode); generateTickLengthTable(vblankTimingMode); if (audioWasntLocked) unlockAudio(); } bool setupAudio(void) { SDL_AudioSpec want, have; want.freq = config.soundFrequency; want.samples = (uint16_t)config.soundBufferSize; want.format = AUDIO_S16; want.channels = 2; want.callback = audioCallback; want.userdata = NULL; dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); if (dev == 0) { showErrorMsgBox("Couldn't open audio device:\n\"%s\"\n\nDo you have an audio device enabled and plugged in?", SDL_GetError()); return false; } // lower than this is not safe for the BLEP synthesis in the mixer const int32_t minFreq = (int32_t)(PAULA_PAL_CLK / 113.0 / 2.0)+1; // /2 because we do 2x oversampling if (have.freq < minFreq) { showErrorMsgBox("Unable to open audio: An audio rate below %dHz can't be used!", minFreq); return false; } if (have.format != AUDIO_S16) { showErrorMsgBox("Couldn't open audio device:\nThis program only supports 16-bit audio streams. Sorry!"); return false; } audio.outputRate = have.freq; audio.audioBufferSize = have.samples; audio.oversamplingFlag = (audio.outputRate < 96000); // we do 2x oversampling if the audio output rate is below 96kHz