quantumVERB  1.0.0
A FOSS convolution reverb plugin
TimeStretch.cpp
1 /*
2  ==============================================================================
3 
4  TimeStretch.cpp
5 
6  ==============================================================================
7 */
8 
9 #include "TimeStretch.h"
10 #include "Logger.h"
11 
12 #include <algorithm>
13 #include <thread>
14 
15 namespace reverb
16 {
17 
18  //==============================================================================
19  /**
20  * @brief Constructs a TimeStretch object associated with an AudioProcessor
21  *
22  * Creates a TimeStretch object with a handle to a SoundTouch instance
23  *
24  * @param [in] processor Pointer to main processor
25  *
26  * @throws std::runtime_error
27  */
28  TimeStretch::TimeStretch(juce::AudioProcessor * processor)
29  : Task(processor)
30  {
31  soundtouch.reset(new soundtouch::SoundTouch());
32 
33  if (!soundtouch)
34  {
35  logger.dualPrint(Logger::Level::Error, "Failed to create SoundTouch handle");
36  }
37  }
38 
39  /**
40  * @brief Updates parameters from processor parameter tree
41  *
42  * @param [in] params Processor parameter tree
43  * @param [in] blockId ID of block whose paramters should be checked
44  */
45  void TimeStretch::updateParams(const juce::AudioProcessorValueTreeState& params,
46  const juce::String& blockId)
47  {
48  float _irLengthS = getParam(params, blockId);
49 
50  if (_irLengthS != irLengthS)
51  {
52  irLengthS = _irLengthS;
53  mustExec = true;
54  }
55  }
56 
57  //==============================================================================
58  /**
59  * @brief Apply time stretching algorithm to input IR buffer to change sample rate
60  *
61  * Stretch or compress input buffer by a factor proportional to original and desired
62  * sample rates.
63  *
64  * NOTE: prepareIR() method should be called before to manage buffer size. This
65  * will copy the original IR and resize the given buffer to the appropriate
66  * size based on sample rate and desired length.
67  *
68  * @param [in,out] ir Audio sample buffer to process
69  */
70  AudioBlock TimeStretch::exec(AudioBlock ir)
71  {
72  if (!soundtouch)
73  {
74  logger.dualPrint(Logger::Level::Error, "No SoundTouch instance exists, cannot apply time stretch to IR");
75  return ir;
76  }
77 
78  soundtouch->setChannels(1);
79  soundtouch->setSampleRate((unsigned)sampleRate);
80 
81  // Calculate tempo change & expected number of samples in output buffer
82  int newNumSamples = getOutputNumSamples();
83  double sampleRateRatio = (double)irOrig.getNumSamples() / (double)newNumSamples;
84 
85  // Use SoundTouch processor to calculate time stretch
86  soundtouch->clear();
87  soundtouch->setTempo(sampleRateRatio);
88 
89  soundtouch->putSamples(irOrig.getReadPointer(0), irOrig.getNumSamples());
90 
91  // Wait for processing to complete
92  unsigned curSample = 0;
93  unsigned nbSamplesReceived = 0;
94 
95  do
96  {
97  curSample += nbSamplesReceived;
98 
99  // Write processed samples to output buffer
100  auto curWritePtr = &ir.getChannelPointer(0)[curSample];
101 
102  nbSamplesReceived = soundtouch->receiveSamples(curWritePtr,
103  (unsigned)ir.getNumSamples() - curSample);
104  }
105  while (nbSamplesReceived != 0);
106 
107  // Get last remaining samples from SoundTouch pipeline, if any
108  soundtouch->flush();
109  do
110  {
111  auto curWritePtr = &ir.getChannelPointer(0)[curSample];
112 
113  nbSamplesReceived = soundtouch->receiveSamples(curWritePtr,
114  (unsigned)ir.getNumSamples() - curSample);
115 
116  curSample += nbSamplesReceived;
117  }
118  while (nbSamplesReceived != 0);
119 
120  jassert(curSample == ir.getNumSamples());
121 
122  // Reset mustExec flag
123  mustExec = false;
124 
125  return ir;
126  }
127 
128  //==============================================================================
129  /**
130  * @brief Copies given IR to internal representation and resizes it before processing
131  *
132  * @param [in,out] ir IR to copy and resize
133  */
134  void TimeStretch::prepareIR(juce::AudioSampleBuffer& ir)
135  {
136  irOrig.makeCopyOf(ir);
137  ir.setSize(ir.getNumChannels(), getOutputNumSamples());
138  }
139 
140  /**
141  * @brief Returns expected number of samples after processing
142  *
143  * @param [in] inputNumSamples Number of samples in input buffer
144  */
145  int TimeStretch::getOutputNumSamples()
146  {
147  return (int)std::ceil(irLengthS * sampleRate);
148  }
149 
150 }
float getParam(const juce::AudioProcessorValueTreeState &params, const juce::String &blockId) const
Internal method used to get (and check) a parameter&#39;s value.
Definition: Task.h:111
void prepareIR(juce::AudioSampleBuffer &ir)
Copies given IR to internal representation and resizes it before processing.
TimeStretch(juce::AudioProcessor *processor)
Constructs a TimeStretch object associated with an AudioProcessor.
Definition: TimeStretch.cpp:28
int getOutputNumSamples()
Returns expected number of samples after processing.
virtual AudioBlock exec(AudioBlock ir) override
Apply time stretching algorithm to input IR buffer to change sample rate.
Definition: TimeStretch.cpp:70
virtual void updateParams(const juce::AudioProcessorValueTreeState &params, const juce::String &blockID) override
Updates parameters from processor parameter tree.
Definition: TimeStretch.cpp:45