commit ab03466e37606685b79d540362d3d217155dbc5a
parent 2d23ec63d4043c1f883cdd690b4dfc436c5192d6
Author: Matt Demanett <matt@demanett.net>
Date: Tue, 22 Sep 2020 23:55:42 -0400
RANALYZER: window the signals going into the FFT, rather than window the test signal directly. #116
Diffstat:
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/README-prerelease.md b/README-prerelease.md
@@ -963,12 +963,12 @@ RANALYZER is a frequency response analyzer: it passes a test signal to another m
By default, will produce a one-shot test signal, emitted at SEND, upon receipt of a trigger (manual or CV) at the TRIG inputs. The duration of the test signal is always 16384 samples (32768 if Rack's sample rate is 96K or higher) -- the duration in time will depend on Rack's current sample rate. The same number of samples is collected from RETURN, if it is patched. Both signals are converted to the frequency domain and displayed (test in green, response in magenta), along with an analysis trace (orange), which shows the difference between the response and the test signals, in the frequency domain, in decibels. The context-menu option "Display traces" controls which of the traces are displayed.
+A window function is optionally applied to the signals as they are converted to the frequency domain; this cleans up noise that otherwise shows up in the signal plots as an artifact of the conversion. It also slightly distorts the displayed signals. The default "taper" window minimizes the distortion; Hamming and Kaiser windows are also available. The window can be selected or disabled on the context menu. The window is not applied to the test signal before it is emitted at SEND.
+
The test signal is a swept sine wave (or "chirp", see <a href="#chirp">CHIRP</a>), with an exponential (if the EXP toggle is on) or linear sweep. The start and end frequencies for sine sweep are set by the FREQ1 and FREQ2 knobs, each with a range in hertz from 1 to a bit less than the Nyquist rate (half the sampling rate); if FREQ1 is less than FREQ2 the sweep is upwards in frequency, downwards otherwise.
Patching an input to TEST overrides the swept sine generator; the TEST input is used as the test signal.
-A window function is optionally applied to the test signal (whether the internally-generated one, or one being read from TEST) before it is emitted at SEND and before frequency domain conversion for the display. The default "taper" window type applies a brief fade in and fade out to the test signal; this cleans up noise that otherwise shows up in the displayed plots, as an artifact of frequency domain conversion. It also slightly distorts the test signal. The window can be disabled on the context window (or set to another type -- Hamming or Kaiser -- provided mostly as a curiosity).
-
If the LOOP toggle is enabled, the module continuously outputs, collects and displays the test, response and analysis signals. Regardless of whether the collection cycle is triggered or looped, a pulse is emitted at the TRIG output when the cycle begins, and at the EOC output when it ends.
The R. DELAY (response delay) control allows sample-accurate alignment of the test and response signals for analysis. When SEND is patched directly to the module to be analyzed, and that module's output is patched directly back to RETURN, and the analyzed module does not impose an internal sample delay, then the returned signal will be received by RANALYZER two samples later, relative to the test sample RANALYZER emits. More complicated patches between SEND and RETURN, or modules under test which have internal sample delays, can increase this, in which case R. DELAY may be set to whatever this actual sample delay is. In practice, getting this right will likely not be very important.
diff --git a/src/Ranalyzer.cpp b/src/Ranalyzer.cpp
@@ -34,7 +34,9 @@ void Ranalyzer::sampleRateChange() {
_core.setParams(1, AnalyzerCore::QUALITY_FIXED_16K, AnalyzerCore::WINDOW_NONE);
}
setWindow(_windowType);
- if (!_run && !_initialDelay) {
+ _run = false;
+ _flush = false;
+ if (!_initialDelay) {
_initialDelay = new Timer(_sampleRate, initialDelaySeconds);
}
}
@@ -179,17 +181,15 @@ void Ranalyzer::processAll(const ProcessArgs& args) {
else {
out = _chirp.next() * 5.0f;
}
- if (_window) {
- out *= _window->at(_cycleI);
- }
_inputBuffer.push(out);
if (_outBufferCount > 0) {
--_outBufferCount;
}
else {
- _core.stepChannelSample(0, _inputBuffer.value(_currentReturnSampleDelay - 1));
- _core.stepChannelSample(1, inputs[RETURN_INPUT].getVoltage());
+ float w = _window ? _window->at(_cycleI - _currentReturnSampleDelay) : 1.0f;
+ _core.stepChannelSample(0, w * _inputBuffer.value(_currentReturnSampleDelay - 1));
+ _core.stepChannelSample(1, w * inputs[RETURN_INPUT].getVoltage());
}
++_cycleI;
@@ -200,8 +200,9 @@ void Ranalyzer::processAll(const ProcessArgs& args) {
}
}
if (_flush) {
- _core.stepChannelSample(0, _inputBuffer.value((_run ? _currentReturnSampleDelay : _analysisBufferCount) - 1));
- _core.stepChannelSample(1, inputs[RETURN_INPUT].getVoltage());
+ float w = _window ? _window->at(_cycleN - _analysisBufferCount) : 1.0f;
+ _core.stepChannelSample(0, w * _inputBuffer.value((_run ? _currentReturnSampleDelay : _analysisBufferCount) - 1));
+ _core.stepChannelSample(1, w * inputs[RETURN_INPUT].getVoltage());
--_analysisBufferCount;
if (_analysisBufferCount < 1) {
_flush = false;