Entropy Piano Tuner  1.1.3 (documentation not yet complete)
An open-source experimental software for piano tuning by entropy minimization
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
audiorecorderadapter.cpp
Go to the documentation of this file.
1 /*****************************************************************************
2  * Copyright 2015 Haye Hinrichsen, Christoph Wick
3  *
4  * This file is part of Entropy Piano Tuner.
5  *
6  * Entropy Piano Tuner is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * Entropy Piano Tuner is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * Entropy Piano Tuner. If not, see http://www.gnu.org/licenses/.
18  *****************************************************************************/
19 
20 //=============================================================================
21 // Adapter for audio input
22 //=============================================================================
23 
24 // Test sine waves with: play -n synth sin 440 vol 0.1
25 // monitor system input with xoscope
26 
27 #include "audiorecorderadapter.h"
28 
29 #include "../../messages/messagerecorderenergychanged.h"
30 #include "../../messages/messagemodechanged.h"
31 #include "../../messages/messagehandler.h"
32 #include "../../math/mathtools.h"
33 #include "../../system/log.h"
34 
35 //-----------------------------------------------------------------------------
36 // Various constants
37 //-----------------------------------------------------------------------------
38 
41 
44 
46 const double AudioRecorderAdapter::ATTACKRATE = 0.97;
47 
49 const double AudioRecorderAdapter::DECAYRATE = 0.7;
50 
52 const double AudioRecorderAdapter::LEVEL_RETRIGGER = 0.3;
53 
55 const double AudioRecorderAdapter::LEVEL_TRIGGER = 0.48;
56 
58 const double AudioRecorderAdapter::LEVEL_CUTOFF = 0.9;
59 
61 const double AudioRecorderAdapter::DB_OFF = 2;
62 
63 
64 //-----------------------------------------------------------------------------
65 // Constructor
66 //-----------------------------------------------------------------------------
67 
69  mMuted(false), // input device is not muted
70  mGain(1), // Gain factor amplifying the PCM signal
71  mCounter(0), // Counter counting incoming PCM values
72  mCounterThreshold(0), // Counter threshold for a single packet
73  mPacketM1(0), // First intensity moment of a single packet
74  mPacketM2(0), // Second intensity moment of a single packet
75  mSlidingLevel(0), // Sliding VU level of the signal
76  mStopLevel(0.1), // Level at which recording stops
77  mRecording(false), // Flag for recording
78  mRestartable(true), // Flag if recorder is restartable (retrigger)
79  mWaiting(false), // Wait for analysis to be completed
80  mStandby(false), // Flag for standby mode
81  mPacketCounter(0), // Counter for the number of packages
82  mIntensityHistogram(), // Histogram of intensities for level control
83  mCurrentPacket(0), // Local audio buffer
84  mStroboscope(this)
85 {
86  setSamplingRate(44100); // Set default sampling rate
87 }
88 
89 
90 //---------------------------------------------l--------------------------------
91 // Reset input level control
92 //-----------------------------------------------------------------------------
93 
103 
105 {
107  mGain = 1;
108  mIntensityHistogram.clear();
109  mPacketCounter = 0;
110 }
111 
112 
113 //-----------------------------------------------------------------------------
114 // Mutes the input device
115 //-----------------------------------------------------------------------------
116 
124 
126 {
127  mMuted = muted;
128 }
129 
130 
131 //-----------------------------------------------------------------------------
132 // Set sampling rate
133 //-----------------------------------------------------------------------------
134 
142 
144 {
148 }
149 
150 
151 //-----------------------------------------------------------------------------
152 // Read all data from the buffer
153 //-----------------------------------------------------------------------------
154 
162 
164 {
165  std::lock_guard<std::mutex> lock(mCurrentPacketMutex); // lock the function
166  packet = mCurrentPacket.getOrderedData(); // copy the content
167  mCurrentPacket.clear(); // clear audio buffer
168 }
169 
170 
171 //-----------------------------------------------------------------------------
172 // Convert signal intensity to a VU level and vice versa
173 //-----------------------------------------------------------------------------
174 
180 
182 { return pow(intensity*mGain*mGain,0.25); }
183 
184 
191 
193 { return pow(level,4.0)/mGain/mGain; }
194 
195 
196 //-----------------------------------------------------------------------------
197 // Push raw PCM data from the implementation
198 //-----------------------------------------------------------------------------
199 
213 
215 {
216  if (data.size()==0) return;
217 
218  // Forward packet to the stroboscope
219  mStroboscope.pushRawData (data);
220 
221  std::lock_guard<std::mutex> lock(mCurrentPacketMutex);
222 
223  // loop over all PCM values of the incoming data vector
224  for (auto &pcmvalue : data)
225  {
226  mCurrentPacket.push_back(pcmvalue);
227  mPacketM1 += pcmvalue;
228  mPacketM2 += pcmvalue*pcmvalue;
229 
230  if (++mCounter > mCounterThreshold) // if packet is complete
231  {
232  // Compute the intensity (variance) of the packet and reset counter
233  double intensity = (mPacketM2-mPacketM1*mPacketM1/mCounter)/mCounter;
234  mPacketM1 = mPacketM2 = mCounter = 0;
235 
236  // Convert intensity to the corresponding VU level
237  double level = MathTools::restrictToInterval(convertIntensityToLevel(intensity),0,1);
238 
239  // Compute a sliding level with fast attack and slow deay
240  if (level > mSlidingLevel)
242  else mSlidingLevel -= (mSlidingLevel-level)*DECAYRATE;
243 
244  // If muted then the shown level is zero
245  double shownLevel = (mMuted ? 0 : mSlidingLevel);
246 
247  // Send shown (muted) level to the GUI VU meter
248  MessageHandler::send<MessageRecorderEnergyChanged>(MessageRecorderEnergyChanged::LevelType::LEVEL_INPUT, shownLevel);
249 
250  // Switch recording process on and off according to the shown (muted) level
251  controlRecordingState (shownLevel);
252 
253  // Control the input level shown at the VU meter automaticall
254  if (not mMuted) automaticControl (intensity,level);
255  }
256  }
257 }
258 
259 
260 //-----------------------------------------------------------------------------
261 // Automatic noise estimation and threshold control
262 //-----------------------------------------------------------------------------
263 
276 
277 void AudioRecorderAdapter::automaticControl (double intensity, double level)
278 {
279  if (intensity == 0) return;
280 
281  // Limit the maximal raw pcm intensity.
282  // A sine wave with maximal amplitude +/- 1 has the integrated intensity 1/2.
283  // Therefore, we reduce the hardware input level whenever this value
284  // is surpassed. This down-regulation is irreversible.
285  if (intensity > 0.45) setDeviceInputGain(getDeviceInputGain() * 0.9);
286 
287  // The level (passed as parameter) is a power law funtion of the intensity
288  // scaled with the parameter mGain. The parameter mGain controls the overall
289  // input amplification. It can move in both directions.
290 
291  // if level exceeds predefined cutoff turn gain down
292  if (level > LEVEL_CUTOFF) mGain *= 0.9;
293 
294  // compute intensity histogram in decibel
295  double dB = 4.34294 * log(intensity);
297 
298  // after 10 packets if the histogram has enough entries
299  if ((++mPacketCounter)%10==0 and mIntensityHistogram.size()>5)
300  {
301  // determine left and right boundary in a soft way
302  double norm=0, MIN=0, MAX=0;
303  for (auto &e : mIntensityHistogram)
304  {
305  norm += e.second;
306  MIN += pow(fabs(static_cast<double>(e.first)),10) * e.second;
307  MAX += exp(e.first) * e.second;
308  }
309  if (norm==0) return;
310  double dBoff = -pow(MIN/norm,1.0/10) + DB_OFF;
311  double dBmax = log(MAX/norm);
312 
313  // slowly adjust the gain according to the upper edge
314  double gain_target = exp(-dBmax/4.34294/2.0);
315  mGain += 0.02*(gain_target-mGain);
316 
317  // slowly adjust the noise cutoff according to the lower edge
318  double newStopLevel = convertIntensityToLevel(exp (dBoff/4.34294));
319  if (newStopLevel < LEVEL_RETRIGGER and fabs(newStopLevel-mStopLevel)>0.01)
320  {
321  // notify that stop level changed (reposition off level in mainwindow)
322  mStopLevel += 0.1*(newStopLevel-mStopLevel);
323  MessageHandler::send<MessageRecorderEnergyChanged>(
325  }
326 
327  // After 10 packages decrease the whole histogram by a factor.
328  // This ensures that new data gradually overwrites older data.
329  if (mPacketCounter >= 10) for (auto &e: mIntensityHistogram) e.second /= 1.018;
330  }
331 }
332 
333 
334 //-----------------------------------------------------------------------------
335 // Control the beginning and the end of recording
336 //-----------------------------------------------------------------------------
337 
350 
352 {
353  // check for recording end
354  // ------------------------------------------------------------------------
355 
356  if (mRecording and level < mStopLevel)
357  {
358  mRecording = false;
359  mRestartable = true;
361  LogI("Recording stopped");
362  }
363 
364  // check for recording start (only if not in standby mode)
365  // ------------------------------------------------------------------------
366 
367  if (mStandby or mWaiting) return;
368 
369  // If the level falls below LEVEL_RETRIGGER, the status of
370  // the adapter is declared as restartable:
372 
373  // If the adapter is restartable or crosses the trigger recording starts
374  if (level>LEVEL_TRIGGER and mRestartable)
375  {
376  if (mRecording) LogI("Recording retriggered")
377  else LogI("Recording started");
378  mRecording = true;
379  mRestartable = false;
380  mWaiting = true;
382  }
383 }
384 
385 
386 //-----------------------------------------------------------------------------
387 // Cut away silence at the beginning of the buffer
388 //-----------------------------------------------------------------------------
389 
398 
400 {
401  // determine the maximum amplitude in the packet
402  double maxamplitude = 0;
403  for (auto &y : packet) if (fabs(y)>maxamplitude) maxamplitude = fabs(y);
404  double trigger = std::min(0.2,maxamplitude*maxamplitude/100);
405 
406  int w = getSamplingRate() / 40; // section width of 0.025 sec
407  int sections = packet.size() / w; // number of sections
408  if (sections < 2) return; // required: at least two sections
409  size_t entries_to_delete = 0; // number of sections to be deleted
410  for (int sec = 0; sec < sections; ++sec) // scan sectors
411  {
412  double energy = 0;
413  for (int i = sec*w; i < (sec+1)*w; ++i) energy += packet[i] * packet[i];
414  if (energy / w < trigger) entries_to_delete += w;
415  else break;
416  }
417  // Remove the first silent sectors:
418  EptAssert(entries_to_delete <= packet.size(),"inconsistent numbers of elements");
419  if (entries_to_delete > 0)
420  packet.erase(packet.begin(), packet.begin() + entries_to_delete);
421 }
double restrictToInterval(double x, double xmin, double xmax)
Restrict floating point value to an interval.
Definition: mathtools.cpp:196
AudioRecorderAdapter()
Constructor.
std::vector< PCMDataType > PacketType
Type definition of a PCM packet (vector of PCM values).
Definition: audiobase.h:51
double mSlidingLevel
Sliding VU level of the signal.
static const double ATTACKRATE
Attack rate at which the sliding level goes up (1=instantly).
std::vector< data_type > getOrderedData() const
Copy entire data in a time-ordered form.
double convertIntensityToLevel(double intensity)
Convert an intensity (variance) of the signal to a VU level.
void setMuted(bool muted)
Set and reset the muting flag.
void pushRawData(const AudioBase::PacketType &data)
Stroboscope::pushRawData.
Definition: stroboscope.cpp:59
CircularBuffer< PCMDataType > mCurrentPacket
Local audio buffer.
bool mMuted
Is the input device muted.
static const double LEVEL_TRIGGER
Level above which the recorder starts to operate.
void pushRawData(const PacketType &data)
The implementation calls this function when new data is available.
void controlRecordingState(double level)
Control the beginning and the end of recording.
static const int BUFFER_SIZE_IN_SECONDS
Capacity of the local circular audio buffer in seconds.
std::mutex mCurrentPacketMutex
Buffer access mutexbo.
static void send(Args &&...args)
short function for creating and sending a message
The input level changed (actual signal)
int roundToInteger(T x)
Round a floating point number to an integer.
Definition: mathtools.h:43
Stroboscope mStroboscope
Instance of stroboscope.
bool mStandby
Standby flag.
bool mRecording
Flag true if recording is on.
virtual void setDeviceInputGain(double volume)=0
bool mWaiting
Wait for the data analysis to be completed.
int mCounterThreshold
Counter threshold for updating energy.
#define LogI(...)
Definition: log.h:50
double convertLevelToIntensity(double level)
Convert a VU level to the corresponding intensity. This function is the inverse of convertIntensityTo...
std::map< int, double > mIntensityHistogram
Histogram of intensities.
int mPacketCounter
Counter for the number of packages.
static const int UPDATE_IN_MILLISECONDS
Update interval in milliseconds, defining the packet size.
void readAll(PacketType &packet)
Read all data from the internal buffer.
void resetInputLevelControl()
Reset input level control.
double mStopLevel
Level at which recording stops.
double mGain
Recording amplification factor.
int getSamplingRate() const
Get the actual sampling rate.
Definition: audiobase.cpp:78
void resize(std::size_t maximum_size)
Resize the buffer, shrink oldest data if necessary.
static const double LEVEL_CUTOFF
Level above which the input mGain is automatically reduced.
void push_back(const data_type &data)
Append a new data element to the buffer.
void setSamplingRate(int rate) override
void automaticControl(double intensity, double level)
Automatic noise estimation and threshold adjustment.
static const double DB_OFF
dB shift for off mark (high value = shorter recording)
#define EptAssert(a, b)
Definition: eptexception.h:47
recording ended
Definition: message.h:52
double mPacketM2
Second intensity moment of a single packet.
static const double LEVEL_RETRIGGER
Level below which retriggering (restart) is allowed.
double mPacketM1
First intensity moment of a single packet.
virtual double getDeviceInputGain() const =0
bool mRestartable
Flag true if start/retriggering possible.
int mCounter
Counts counting incoming PCM values.
virtual void setSamplingRate(int rate)
Allow the implementation to change the sampling rate during operation.
Definition: audiobase.cpp:96
void cutSilence(PacketType &packet)
Cut trailing silence.
void clear()
Clear the buffer, keeping its maximal size.
keystroke recognized and recording started
Definition: message.h:51
static const double DECAYRATE
Decay rate at which the sliding level goes down.