void BeatCounterAudioProcessor::processBlock(AudioSampleBuffer &buffer, MidiBuffer &midiMessages) { TeragonPluginBase::processBlock(buffer, midiMessages); for(int i = 0; i < buffer.getNumSamples(); ++i) { float currentSample = *buffer.getSampleData(0, i); double currentSampleAmplitude; if(filterEnabled) { // This relies on the sample rate which may not be available during initialization if(filterConstant == 0.0) { filterConstant = calculateFilterConstant(getSampleRate(), filterFrequency->getValue()); } // Basic lowpass filter (feedback) filterOutput += (currentSample - filterOutput) / filterConstant; currentSampleAmplitude = fabs(filterOutput); } else { currentSampleAmplitude = fabs(currentSample); } // Find highest peak in the current period if(currentSampleAmplitude > highestAmplitudeInPeriod) { highestAmplitudeInPeriod = currentSampleAmplitude; // Is it also the highest value since we started? if(currentSampleAmplitude > highestAmplitude) { highestAmplitude = currentSampleAmplitude; } } // Downsample by skipping samples if(--samplesToSkip <= 0) { // Beat amplitude trigger has been detected if(highestAmplitudeInPeriod >= (highestAmplitude * tolerance->getValue() / 100.0) && highestAmplitudeInPeriod > kSilenceThreshold) { // First sample inside of a beat? if(!currentlyInsideBeat && numSamplesSinceLastBeat > cooldownPeriodInSamples) { currentlyInsideBeat = true; double bpm = (getSampleRate() * 60.0f) / ((beatLengthRunningAverage + numSamplesSinceLastBeat) / 2); // Check for half-beat patterns. For instance, a song which has a kick drum // at around 70 BPM but an actual tempo of 140 BPM (hello, dubstep!). double doubledBpm = bpm * 2.0; if(doubledBpm > minimumAllowedBpm && doubledBpm < maximumAllowedBpm) { bpm = doubledBpm; } beatLengthRunningAverage += numSamplesSinceLastBeat; beatLengthRunningAverage /= 2; numSamplesSinceLastBeat = 0; // Check to see that this tempo is within the limits allowed if(bpm > minimumAllowedBpm && bpm < maximumAllowedBpm) { bpmHistory.push_back(bpm); parameters.set("Beat Triggered", 1.0f); parameters.set("Current BPM", bpm); // Do total BPM and Reset? if(numSamplesProcessed > period->getValue() * getSampleRate()) { // Take advantage of this trigger point to do a tempo check and adjust the minimum // and maximum BPM ranges accordingly. if(useHostTempo->getValue()) { minimumAllowedBpm = getHostTempo() - kHostTempoLinkToleranceInBpm; maximumAllowedBpm = getHostTempo() + kHostTempoLinkToleranceInBpm; cooldownPeriodInSamples = (unsigned long)(getSampleRate() * (60.0 / maximumAllowedBpm)); } runningBpm = 0.0; for(unsigned int historyIndex = 0; historyIndex < bpmHistory.size(); ++historyIndex) { runningBpm += bpmHistory.at(historyIndex); } runningBpm /= (double)bpmHistory.size(); bpmHistory.clear(); numSamplesProcessed = 0; parameters.set("Running BPM", runningBpm); } } else { // Outside of bpm threshold, ignore } } else { // Not the first beat mark currentlyInsideBeat = false; } } else { // Were we just in a beat? if(currentlyInsideBeat) { currentlyInsideBeat = false; } } samplesToSkip = kDownsampleFactor; highestAmplitudeInPeriod = 0.0; } ++numSamplesProcessed; ++numSamplesSinceLastBeat; } }
void BeatCounter::processBlock(AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { for(int i = 0; i < buffer.getNumSamples(); ++i) { float* currentSample = buffer.getSampleData(0, i); double currentSampleAmplitude = 0.0f; if(this->isAutofilterEnabled) { // Basic lowpass filter (feedback) this->autofilterOutput += (*currentSample - this->autofilterOutput) / this->autofilterConstant; currentSampleAmplitude = fabs(this->autofilterOutput); } else { currentSampleAmplitude = fabs(*currentSample); } // Find highest peak in downsampled area ("bar") if(*currentSample > m_bar_high_point) { m_bar_high_point = *currentSample; // Find highest averaging value for testing period if(*currentSample > m_high_point) { m_high_point = *currentSample; } } // Process one "bar" if(--m_skip_count <= 0) { // Calculate average point m_bar_samp_avg /= m_downsample_rate; // Beat amplitude/frequency has been detected if(m_bar_samp_avg >= (m_bar_high_avg * this->tolerance / 100.0) && m_bar_high_point >= (m_high_point * this->tolerance / 100.0) && m_bar_high_point > kSilenceThreshold) { // First bar in a beat? if(!m_beat_state && m_beat_samples > m_dupe_interval) { m_beat_state = true; double bpm = (getSampleRate() * 60.0f) / ((m_last_avg + m_beat_samples) / 2); // Check for half-beat patterns double hbpm = bpm * 2.0; if(hbpm > m_min_bpm && hbpm < m_max_bpm) { bpm = hbpm; } // See if we're inside the threshhold if(bpm > m_min_bpm && bpm < m_max_bpm) { this->currentBpm = bpm; m_last_avg += m_beat_samples; m_last_avg /= 2; m_beat_samples = 0; bpmHistory.push_back(bpm); // Do total BPM and Reset? if(m_num_samples_processed > (this->periodSizeInSamples * getSampleRate())) { // Take advantage of this trigger point to do a tempo check if(this->linkWithHostTempo) { m_min_bpm = getHostTempo() - kHostTempoLinkToleranceInBpm; m_max_bpm = getHostTempo() + kHostTempoLinkToleranceInBpm; m_dupe_interval = (long)(getSampleRate() * (float)(60.0f / (float)m_max_bpm)); } this->runningBpm = 0.0; for(unsigned int bpmHistoryIndex = 0; bpmHistoryIndex < bpmHistory.size(); ++bpmHistoryIndex) { this->runningBpm += bpmHistory.at(bpmHistoryIndex); } bpmHistory.clear(); m_num_samples_processed = 0; } } else { // Outside of bpm threshhold // TODO: Unset BPM Display in GUI m_last_avg += m_beat_samples; m_last_avg /= 2; m_beat_samples = 0; } } else { // Not the first beat mark m_beat_state = false; } } else { // Were we just in a beat? if(m_beat_state) { m_beat_state = false; } } m_skip_count = m_downsample_rate; m_bar_high_point = 0.0; m_bar_samp_avg = 0.0; } else { m_bar_samp_avg += *currentSample; } ++m_num_samples_processed; ++m_beat_samples; } }