| 175 | // ============================================================================= |
| 176 | |
| 177 | void LfoManager::generateLfoOutput(double sampleRate, juce::AudioPlayHead* playHead, int numSamples) |
| 178 | { |
| 179 | // This is the original 'process' function, now repurposed as a private helper. |
| 180 | // Its sole responsibility is to generate the raw LFO signals. |
| 181 | |
| 182 | // 1. Get Transport State from Host |
| 183 | juce::Optional<juce::AudioPlayHead::PositionInfo> positionInfo; |
| 184 | isPlaying = false; |
| 185 | double currentBpm = 120.0; |
| 186 | |
| 187 | if (playHead) |
| 188 | { |
| 189 | positionInfo = playHead->getPosition(); |
| 190 | if (positionInfo) |
| 191 | { |
| 192 | isPlaying = positionInfo->getIsPlaying(); |
| 193 | if (auto bpm = positionInfo->getBpm()) |
| 194 | currentBpm = *bpm; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | wasPlaying = isPlaying; |
| 199 | |
| 200 | // 3. Process each LFO |
| 201 | lfoOutputBuffer.setSize(4, numSamples, false, false, true); // Ensure buffer is correct size |
| 202 | lfoOutputBuffer.clear(); |
| 203 | |
| 204 | for (int i = 0; i < 4; ++i) |
| 205 | { |
| 206 | // Shape update logic (unchanged) |
| 207 | bool needsUpdate = true; |
| 208 | if (shapeUpdateFlags[i].compare_exchange_strong(needsUpdate, false)) |
| 209 | { |
| 210 | lfoEngines[i].updateShape(lfoData[i]); |
| 211 | } |
| 212 | |
| 213 | auto* syncParam = treeState.getRawParameterValue(ParameterIDAndName::getIDString(LFO_SYNC_MODE_ID, i)); |
| 214 | const bool isInSyncMode = syncParam != nullptr && syncParam->load() > 0.5f; |
| 215 | float phaseDelta = 0.0f; |
| 216 | |
| 217 | // 1. Calculate phaseDelta for advancing phase within the block (or for free-running when stopped) |
| 218 | if (isInSyncMode) |
| 219 | { |
| 220 | auto* rateSyncParam = treeState.getRawParameterValue(ParameterIDAndName::getIDString(LFO_RATE_SYNC_ID, i)); |
| 221 | const int rateIndex = static_cast<int>(rateSyncParam->load()); |
| 222 | const float beatMultiplier = mapRateSyncIndexToBeatMultiplier(rateIndex); |
| 223 | const float beatsPerCycle = beatMultiplier * 4.0f; |
| 224 | if (beatsPerCycle > 0.0 && currentBpm > 0.0) |
| 225 | { |
| 226 | const float samplesPerCycle = (beatsPerCycle / currentBpm) * 60.0f * (float) sampleRate; |
| 227 | if (samplesPerCycle > 0) |
| 228 | phaseDelta = 1.0f / samplesPerCycle; |
| 229 | } |
| 230 | } |
| 231 | else // Free (Hz) mode |
| 232 | { |
| 233 | auto* rateHzParam = treeState.getRawParameterValue(ParameterIDAndName::getIDString(LFO_RATE_HZ_ID, i)); |
| 234 | const float freqInHz = rateHzParam->load(); |
nothing calls this directly
no test coverage detected