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
soundgenerator.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 // Sound generator driving the synthesizer
22 //=============================================================================
23 
24 #include "soundgenerator.h"
25 
26 #include <algorithm>
27 
28 #include "../../system/log.h"
29 #include "../../system/eptexception.h"
30 #include "../../system/simplethreadhandler.h"
31 #include "../../messages/messagechangetuningcurve.h"
32 #include "../../messages/messagekeyselectionchanged.h"
33 #include "../../messages/messagerecorderenergychanged.h"
34 #include "../../messages/messagepreliminarykey.h"
35 #include "../../messages/messagehandler.h"
36 #include "../../messages/messagemidievent.h"
37 #include "../../messages/messagemodechanged.h"
38 #include "../../messages/messageprojectfile.h"
39 #include "../../messages/messagefinalkey.h"
40 #include "../../piano/piano.h"
41 #include "../../piano/key.h"
42 #include "../../math/mathtools.h"
43 #include "../../settings.h"
44 
45 //-----------------------------------------------------------------------------
46 // Constructor
47 //-----------------------------------------------------------------------------
48 
53 
55  mPiano(nullptr),
56  mOperationMode(OperationMode::MODE_IDLE),
57  mNumberOfKeys(0),
58  mKeyNumberOfA4(0),
59  mSelectedKey(-1),
60  mResonatingKey(-1),
61  mResonatingVolume(0)
62 {
63  audioAdapter->setWriter(&mSynthesizer);
64 }
65 
66 
67 //-----------------------------------------------------------------------------
68 // Message listener, handling all messages related to sound generation
69 //-----------------------------------------------------------------------------
70 
78 
80 {
81  EptAssert(m, "Message has to exist!");
82 
83  switch (m->getType())
84  {
85  // REFERENCE SOUND IN TUNING MODE
86  // When the key selection changes, the reference sound is stopped.
87  // In the tuning mode a new reference sound is started.
88  // This is important when the selection changes during tuning.
90  {
91  // keep track of selected key
92  auto message(std::static_pointer_cast<MessageKeySelectionChanged>(m));
93  mSelectedKey = message->getKeyNumber();
97  }
98  break;
99  // START REFERENCE SOUND IN TUNING MODE AT THE BEGINNING OF RECORDING
101  {
103  {
106  }
107  }
108  break;
109  // CHANGE REFERENCE SOUND IN TUNING MODE WHEN KEY SELECTION CHANGES
111  {
113  {
114  auto message(std::static_pointer_cast<MessagePreliminaryKey>(m));
115  int recognizedkey = message->getKeyNumber();
116  if (mResonatingKey != recognizedkey and recognizedkey >= 0)
117  playResonatingReferenceSound(recognizedkey);
118  }
119  }
120  break;
121  // STOP REFERENCE SOUND AT THE END OF RECORDING
123  {
124  // if reference tone with constant volume stop it here.
125  if (Settings::getSingleton().getSoundGeneratorMode() == SGM_REFERENCE_TONE
128  }
129  break;
130  // REFERENCE SOUND VOLUME ADJUSTMENT IN TUNING MODE
132  {
134  {
135  auto message(std::static_pointer_cast<MessageRecorderEnergyChanged>(m));
136  if (message->getLevelType()==MessageRecorderEnergyChanged::LevelType::LEVEL_INPUT)
138  changeVolumeOfResonatingReferenceSound(message->getLevel());
139  }
140  }
141  break;
142  // MODE CHANGES STOP THE REFERENCE SOUND AND TRIGGER RECALCULATION
144  {
145  // Keep track of mode changes
146  auto message(std::static_pointer_cast<MessageModeChanged>(m));
147  mOperationMode = message->getMode();
149  }
150  break;
151  // IF A NEW FILE IS OPENED OR IF PIANO SETTINGS ARE CHANGED
152  // ALL SOUNDS HAVE TO BE CALCULATED ONCE AGAIN IN THE CORRESPONDING MODE
154  {
155  // Get a pointer to the piano and various of its properties.
156  auto mpf(std::static_pointer_cast<MessageProjectFile>(m));
157  mPiano = &(mpf->getPiano());
163  }
164  break;
165  // HANDLE MIDI KEYPRESSES AND RELATED EVENTS
167  {
168  auto message(std::static_pointer_cast<MessageMidiEvent>(m));
169  MidiAdapter::Data data = message->getData();
170  switch (data.event)
171  {
173  {
174  handleMidiKeypress(data); // see following function
175  break;
176  }
178  {
179  int key = data.byte1-69+mKeyNumberOfA4;
181  break;
182  }
184  {
185  // Funny feature that allows you to switch
186  // between the operating modes by MIDI pedal
187  // If it is not a pedal break (damper=67):
188  if (data.byte1 < 64 or data.byte1 > 67) break;
189  // If pedal pressed go to recording mode.
190  if (data.byte2 > 0)
191  MessageHandler::send<MessageModeChanged>(MODE_RECORDING);
192  else // else go back to calculation mode
193  MessageHandler::send<MessageModeChanged>(MODE_CALCULATION);
194  }
195  default:
196  break;
197  }
198  }
199  break;
200  // RECALCULATE AND PLAY ECHO SOUND IN THE RECORDING MODE
202  {
203  // echo synthesized sound of the recorded key in recording mode.
205  {
206  auto message(std::static_pointer_cast<MessageFinalKey>(m));
207  int keynumber = message->getKeyNumber();
208  auto spectrum = message->getFinalKey()->getPeaks();
209  // replay only if the selected key was recognized:
210  if (keynumber == mSelectedKey and spectrum.size() > 0)
211  {
212  preCalculateSoundOfKey (keynumber,spectrum);
213  mSynthesizer.playSound(keynumber,1,0.2,Envelope(5,5,0,30,false),true,false);
214  }
215  }
216  }
217  break;
218  // RECALCULTE WAVEFORM DURING CALCULATION MODE WHEN FREUQUENCY CHANGES
220  {
222  {
223  auto message(std::static_pointer_cast<MessageChangeTuningCurve>(m));
224  preCalculateSoundOfKey(message->getKeyNumber());
225  }
226  }
227  break;
228  default:
229  break;
230  }
231 }
232 
233 
234 //-----------------------------------------------------------------------------
235 // Handle MIDI keypress
236 //-----------------------------------------------------------------------------
237 
248 
250 {
251  int key = data.byte1-69+mKeyNumberOfA4; // extract key number starting with 0
252  if (key<0 or key>=mNumberOfKeys) return;
253  double volume = pow(static_cast<double>(data.byte2) / 128, 2); // keystroke volume
254  // The following formula mimics the decay time of a piano string
255  const double decay = (key <= 12 ? 1.0/6 : 1.0/210*pow(key,1.43));
256  const double release = (key - mKeyNumberOfA4 >= 22 ? decay : 30);
257  Envelope envelope (40,decay,0,release,true);
258 
259  switch(mOperationMode)
260  {
261  case MODE_IDLE:
262  {
263  // In this mode play sine waves with respect to the selected concert pitch
264  double frequency = mPiano->getEqualTempFrequency(key,0,mPiano->getConcertPitch());
265  mSynthesizer.playSound(key,frequency,volume,Envelope(40,5,0.6,10));
266  }
267  break;
268  case MODE_RECORDING:
269  {
270  // In this mode select key and play the original sound in the original pitch
271  MessageHandler::send<MessageKeySelectionChanged>(key, &(mPiano->getKey(key)));
272  mSynthesizer.playSound(key,1,0.1*volume,envelope);
273  }
274  break;
275  case MODE_CALCULATION:
276  {
277  // In this mode select key and play the original sound in the original pitch
278  MessageHandler::send<MessageKeySelectionChanged>(key, &(mPiano->getKey(key)));
279  double recorded = mPiano->getKey(key).getRecordedFrequency();
280  if (recorded > 0)
281  {
282  double frequencyshift = mPiano->getKey(key).getComputedFrequency() *
283  mPiano->getConcertPitch() / 440.0 / recorded;
284  mSynthesizer.playSound(key,frequencyshift,0.1*volume,envelope,true);
285  }
286  }
287  break;
288  case MODE_TUNING:
289  {
290  // In this mode play the computed sound in selected concert pitch
291  double recorded = mPiano->getKey(key).getRecordedFrequency();
292  if (recorded > 0)
293  {
294  double frequencyshift = mPiano->getKey(key).getComputedFrequency() *
295  mPiano->getConcertPitch() / 440.0 / recorded;
296  mSynthesizer.playSound(key,frequencyshift,0.1*volume,envelope);
297  }
298  }
299  break;
300  default:
301  break;
302  }
303 }
304 
305 
306 //-----------------------------------------------------------------------------
307 // Play a resonating reference sound in the tuning mode
308 //-----------------------------------------------------------------------------
309 
324 
326 {
327  if (mResonatingKey >= 0 and keynumber != mResonatingKey)
329  if (keynumber < 0 or keynumber >= mNumberOfKeys) return;
330  if (mSynthesizer.isPlaying(keynumber)) return;
331  auto &key = mPiano->getKey(keynumber);
332  double frequency = key.getComputedFrequency()*mPiano->getConcertPitch()/440.0;
333  if (frequency>0)
334  {
335  mResonatingKey=keynumber;
336  mResonatingVolume = 1;
337  switch (Settings::getSingleton().getSoundGeneratorMode())
338  {
339  case SGM_REFERENCE_TONE:
340  {
341  playResonatingSineWave(keynumber,frequency, 0.5);
342  }
343  break;
344  case SGM_SYNTHESIZE_KEY:
345  {
346  const double volume = 0.2;
347  Envelope env(30,50,mResonatingVolume,10,false);
348  double frequencyshift = frequency /
349  mPiano->getKey(keynumber).getRecordedFrequency();
350  mSynthesizer.playSound(keynumber,frequencyshift,volume,env,false,false);
351  }
352  break;
353  default:
354  break;
355  }
356  }
357 
358 }
359 
360 
361 //-----------------------------------------------------------------------------
362 // Stop the resonating reference sound
363 //-----------------------------------------------------------------------------
364 
368 
370 {
371  if (mResonatingKey>=0)
372  {
374  mResonatingKey = -1;
375  mResonatingVolume = 0;
376  }
377 }
378 
379 
380 //-----------------------------------------------------------------------------
381 // Change the volume of the resonating reference sound
382 //-----------------------------------------------------------------------------
383 
394 
396 {
397  if (mResonatingKey < 0 or mResonatingKey >= mNumberOfKeys) return;
398  if (not mSynthesizer.isPlaying(mResonatingKey)) { mResonatingKey=-1; return; }
399  double truncatedlevel = std::min(0.8,level);
400  double volume = pow(truncatedlevel,2.0);
401  if (volume > mResonatingVolume) mResonatingVolume = volume;
402  else mResonatingVolume *= 0.87;
404 }
405 
406 
407 //-----------------------------------------------------------------------------
408 // Play a permanent sine wave as a resonating background sound for tuning
409 //-----------------------------------------------------------------------------
410 
417 
418 void SoundGenerator::playResonatingSineWave (int keynumber, double frequency, double volume)
419 {
420 
421  auto &key = mPiano->getKey(keynumber);
422  auto &peaks = key.getPeaks();
423  if (peaks.size()==0 or frequency==0 or volume==0) return;
424  if (keynumber < mKeyNumberOfA4 and peaks.size()<4) return;
425  auto it = peaks.begin();
426  double factor = frequency / it->first;
427  if (keynumber < mKeyNumberOfA4-24) it++;
428  if (keynumber < mKeyNumberOfA4-30) it++;
429  if (keynumber < mKeyNumberOfA4-36) it++;
430  double frequ = it->first*factor;
431  mSynthesizer.playSound(keynumber,frequ,0.5,Envelope(30,5,1,30),false,false);
432 }
433 
434 
435 //-----------------------------------------------------------------------------
436 // Calculate the sound of a given key in advance
437 //-----------------------------------------------------------------------------
438 
444 
445 void SoundGenerator::preCalculateSoundOfKey (const int keynumber)
446 {
447  mSynthesizer.preCalculateWaveform(keynumber, mPiano->getKey(keynumber).getPeaks());
448 }
449 
450 
451 //-----------------------------------------------------------------------------
452 // Calculate the sound of a given key in advance
453 //-----------------------------------------------------------------------------
454 
461 
463 {
464  mSynthesizer.preCalculateWaveform(keynumber,spectrum);
465 }
466 
467 
468 //-----------------------------------------------------------------------------
469 // Calculate the sound of all keys in advance
470 //-----------------------------------------------------------------------------
471 
475 
477 {
478  for (int keynumber = 0; keynumber < mNumberOfKeys; keynumber++)
479  preCalculateSoundOfKey(keynumber);
480 }
double getRecordedFrequency() const
Get recorded frequency.
Definition: key.cpp:119
void preCalculateSoundOfAllKeys()
Calculate the sound of all keys in advance.
std::shared_ptr< Message > MessagePtr
Global type of a shared message pointer.
Definition: message.h:98
void handleMessage(MessagePtr m) overridefinal
Sound generator message listener and dispatcher.
OperationMode
Operation mode of the tuner.
Definition: prerequisites.h:66
void stopResonatingReferenceSound()
Stop the resonating reference sound.
int mResonatingKey
Keynumber of the resonating sound.
bool isPlaying(const int id) const
Check whether a sound with given id is still playing.
double mResonatingVolume
Volume of the resonating sound.
static Settings & getSingleton()
Get a pointer to the singleton instance.
Definition: settings.cpp:46
MidiEvent event
Midi event, encoded by the enumeration MidiEvent.
Definition: midiadapter.h:90
void preCalculateSoundOfKey(const int keynumber)
Calculate the sound of a given key in advance, using the spectrum of the given key.
Synthesizer mSynthesizer
Instance of the synthesizer.
const PeakListType & getPeaks() const
Get a read-only reference to mPeaks.
Definition: key.cpp:259
const Piano * mPiano
Pointer to the piano.
Message that a change was made with the current project file.
Definition: message.h:68
SoundGenerator(AudioPlayerAdapter *audioAdapter)
Constructor, initializes the member variables.
Message that the operation mode has changed.
Definition: message.h:65
Midi event when a key is pressed.
Definition: midiadapter.h:71
The input level changed (actual signal)
OperationMode mOperationMode
Copy of the operation mode.
void playSound(const int id, const double frequency, const double volume, const Envelope &env, const bool waitforcomputation=false, const bool stereo=true)
Function which plays a single note (sound)
double getEqualTempFrequency(int keynumber, double cents=0, double A4=0) const
Function returning the equal temperament.
Definition: piano.cpp:123
Midi event for changing voice.
Definition: midiadapter.h:73
Mode where the entropy optimization is carried out.
Definition: prerequisites.h:70
void playResonatingSineWave(int keynumber, double frequency, double volume)
Play a permanent sine wave as a resonating background sound for tuning.
Mode for recording the piano keys.
Definition: prerequisites.h:69
Message that a key has been selected.
Definition: message.h:63
const Keyboard & getKeyboard() const
Definition: piano.h:83
recording input level has changed
Definition: message.h:69
double getComputedFrequency() const
Get computed frequency.
Definition: key.cpp:174
int byte1
Data byte, usually representing the MIDI key index.
Definition: midiadapter.h:91
int mSelectedKey
Copy of selected key.
number of the key recognized during recording
Definition: message.h:67
void releaseSound(const int id)
Terminate a sound.
int mNumberOfKeys
Copy of the number of keys.
bool isSoundGeneratorVolumeDynamic() const
Get flag for dynamic volume adaption in the tuning mode.
Definition: settings.h:67
Message that the tuning curve has been adapted.
Definition: message.h:60
Midi event when a key is released.
Definition: midiadapter.h:72
Mode for manually tuning the piano.
Definition: prerequisites.h:71
void setNumberOfKeys(int numberOfKeys)
Tell the synthesizer to change the total number of keys.
final key information after recording
Definition: message.h:55
void playResonatingReferenceSound(int keynumber)
Play a resonating reference sound in the tuning mode.
Produce a sound which imitates the string.
void handleMidiKeypress(MidiAdapter::Data &data)
Handle MIDI keypress.
int getKeyNumberOfA4() const
Definition: keyboard.h:73
void setWriter(PCMWriterInterface *interface)
Connect the audio adapter to a PCM writer interface.
Produce a simple sine wave as reference.
const double & getConcertPitch() const
Definition: piano.h:80
#define EptAssert(a, b)
Definition: eptexception.h:47
new event from MIDI keyboard received
Definition: message.h:64
void preCalculateWaveform(const int id, const Spectrum &spectrum)
Pre-calculate the PCM waveform of a sound.
recording ended
Definition: message.h:52
void ModifySustainLevel(const int id, const double level)
Change the level (sustain level) of a constantly playing sound.
std::map< double, double > Spectrum
Definition: synthesizer.h:113
int byte2
Data byte, usually representing the keystroke intensity.
Definition: midiadapter.h:92
Do nothing.
Definition: prerequisites.h:68
int getNumberOfKeys() const
Definition: keyboard.h:72
Abstract adapter class for PCM-based audio output drivers.
Structure describing the envelope (dynamics) of a sound.
Definition: synthesizer.h:46
int mKeyNumberOfA4
Copy of A-key position.
void changeVolumeOfResonatingReferenceSound(double level)
Change the volume of the resonating reference sound.
keystroke recognized and recording started
Definition: message.h:51
Structure of the data associated with a MIDI event.
Definition: midiadapter.h:88
const Key & getKey(int i) const
Definition: piano.h:86