/* // Created by bruno on 16.2.2025. */ #include "audio.h" AudioData audioData; uint16_t getAvailableChannel() { for (uint16_t i = 0; i < NUM_SYNTH_VOICES; i++) { if (audioData.synthVoices[i].volume == 0) { return i; } } return -1; } // Helper: compute left/right gains from a pan value in [–1..+1] // pan = –1.0 → full left (L=1, R=0) // pan = +1.0 → full right (L=0, R=1) // pan = 0.0 → center (L=R=1/sqrt(2) or just 0.707 to avoid clipping) static void compute_stereo_gains(float pan, float *outL, float *outR) { // Simple linear panning (no constant‐power law). // If you prefer constant‐power, you could do: // float angle = (pan + 1.0f) * (M_PI / 4.0f); // *outL = cosf(angle); // *outR = sinf(angle); // // Here we’ll just do linear: pan = fmaxf(-1.0f, fminf(+1.0f, pan)); if (pan <= 0.0f) { *outL = 1.0f; *outR = 1.0f + pan; // pan is negative, so R < 1 } else { *outL = 1.0f - pan; // pan is positive, so L < 1 *outR = 1.0f; } // Optionally, scale down both so we never exceed 1.0f / sqrt(2) // e.g. *outL *= 0.7071f; *outR *= 0.7071f; } // This callback now writes stereo frames: interleaved L/R floats. void audio_callback(void *userdata, Uint8 *stream, int len) { AudioData *audio = (AudioData *) userdata; // 'len' is total bytes; each sample‐frame is 2 floats (L+R), i.e. 2 * sizeof(float). int frames = len / (2 * sizeof(float)); // Zero out the entire output buffer (silence) // We’ll accumulate into it. // Each float is 4 bytes, so total floats = 2 * frames. float *outBuf = (float *) stream; for (int i = 0; i < 2 * frames; ++i) { outBuf[i] = 0.0f; } // Precompute the listener center float listenerCx = audio->playerRect->x + audio->playerRect->w * 0.5f; // For each synth voice, mix into the stereo buffer for (int v = 0; v < NUM_SYNTH_VOICES; v++) { SynthVoice *voice = &audio->synthVoices[v]; if (voice->volume == 0 || voice->frequency == 0) { continue; // skip silent or inactive voices } // Compute source center X float sourceCx = voice->sourceRect.x + voice->sourceRect.w * 0.5f; float dx = sourceCx - listenerCx; // Normalize for pan. If |dx| >= maxPanDistance → full left or full right. float pan = dx / audio->maxPanDistance; if (pan < -1.0f) pan = -1.0f; if (pan > +1.0f) pan = +1.0f; float gainL, gainR; compute_stereo_gains(pan, &gainL, &gainR); // Optional: You could also attenuate overall volume with distance // float dist = fabsf(dx); // float distanceAtten = 1.0f - fminf(dist / audio->maxPanDistance, 1.0f); // float finalVolume = (voice->volume / 255.0f) * distanceAtten; // But for now, we’ll just use voice->volume for amplitude. float amp = (voice->volume / 255.0f); // Phase increment per sample‐frame: // (freq * 256) / SAMPLE_RATE tells how many phase steps per mono-sample. // Because we’re writing stereo, we still advance phase once per frame. uint8_t phaseInc = (uint8_t)((voice->frequency * 256) / SAMPLE_RATE); // Mix into each frame for (int i = 0; i < frames; i++) { float t = (float) voice->phase / 255.0f * 2.0f - 1.0f; float sample; switch (voice->waveform) { default: case WAVE_SINE: sample = sinf(voice->phase * 2.0f * M_PI / 256.0f); break; case WAVE_SQUARE: sample = (t >= 0.0f) ? 1.0f : -1.0f; break; case WAVE_SAWTOOTH: sample = t; break; case WAVE_TRIANGLE: sample = (t < 0.0f) ? -t : t; break; case WAVE_NOISE: sample = ((float) rand() / RAND_MAX) * 2.0f - 1.0f; break; } voice->phase += phaseInc; // Interleaved index: left = 2*i, right = 2*i + 1 int idxL = 2 * i; int idxR = 2 * i + 1; // Accumulate into buffer outBuf[idxL] += sample * amp * gainL; outBuf[idxR] += sample * amp * gainR; } } // Note: We did not normalize by active voices here, because each voice already // uses its own volume. If you still want an automatic “divide by N active voices”, // you would need to track active voices per‐frame, which is relatively expensive. // In practice, you manage the volume per voice so clipping doesn’t occur. }