Just Intonation  Version 1.3.1 (19)
Explore key-independent dynamically adapting tuning in just intonation
tuner.cpp
Go to the documentation of this file.
1 /*****************************************************************************
2  * Copyright 2016-2017 Karolin Stange, Christoph Wick, and Haye Hinrichsen
3  *
4  * This file is part of JustIntonation.
5  *
6  * JustIntonation 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  * JustIntonation 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
14  * for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with JustIntonation. If not, see http://www.gnu.org/licenses/.
18  *****************************************************************************/
19 
20 //=============================================================================
21 // Tuner: Module for adaptive tuning
22 //=============================================================================
23 
24 #include "tuner.h"
25 #include <math.h>
26 
27 //-----------------------------------------------------------------------------
28 // Constructor
29 //-----------------------------------------------------------------------------
34 
36  , mTunerAlgorithm()
37  , mGlobalTargetPitchInCents(0)
38  , mTuningMode (1)
39  , mStaticReferenceKey(0)
40  , mWolfsIntervalShift(0)
41  , mTuningEnabled(true)
42  , mLastMessage("")
43  , mIntervalSizes(12,0)
44  , mIntervalWeights(12,1)
45  , mKeyDataVector()
46  , pElapsedTimer(nullptr)
47  , mStaticTuningModeEnabled(false)
48  , mPitchAutoCorrectionParameter(0.5)
49  , mDelayParameter(0)
50  , mMemoryOffFactor(0)
51  , mWaitingTimeMsec(0)
52 
53 {
54  setThreadName("Tuner");
55  setModuleName("Tuner");
56  setMemoryLength(3);
57 }
58 
59 
60 //-----------------------------------------------------------------------------
61 // Init
62 //-----------------------------------------------------------------------------
67 
69 {
71  setTimerInterval(updateIntervalMsec); // Initialize envelope timer
72  mKeyDataVector.resize(128);
73  for (int n=0; n<128; n++) // Reset all keys
74  {
75  mKeyDataVector[n].clear();
76  mKeyDataVector[n].key = n;
77  }
78  emitPitchCorrections(); // Emit zero pitch corrections
79  signalAveragePitchDrift(0); // Pitch drift zero
80  return true;
81 }
82 
83 
84 //-----------------------------------------------------------------------------
85 // Public slot: Start the tuner
86 //-----------------------------------------------------------------------------
93 
95 {
96  if (not ThreadBase::start()) return false;
97  signalReadyForReceivingParameters(); // Ask the GUI to set the tuning parameters
98  return true;
99 }
100 
101 
102 //-----------------------------------------------------------------------------
103 // Public slot: Set frequency of A4
104 //-----------------------------------------------------------------------------
109 
111 {
112  if (f<=0) return;
113  double cents = 1200 * log(f/440.0) / log(2.0);
114  // Do not accept pitch changes of more than two semitones
115  if (cents > 200 or cents < -200)
116  {
117  LOGWARNING << "Unreasonable global pitch =" << cents;
118  return;
119  }
120  LOGMESSAGE << "Setting global ptich to" << cents;
122 }
123 
124 
125 //-----------------------------------------------------------------------------
126 // Public slot: Set tuning mode
127 //-----------------------------------------------------------------------------
133 
134 void Tuner::setTuningMode (int mode, int wolfsShift)
135 {
136  mTuningMode = mode;
137  mWolfsIntervalShift = wolfsShift;
138 
139  // If the tuning mode changes all sounding keys are declared
140  // as being newly pressed. This ensures renewed tuning of those keys
141  for (int i=0; i<128; ++i) if (mKeyDataVector[i].intensity>0)
142  mKeyDataVector[i].newlyPressed=true;
143  switch(mode)
144  {
145  case 0:
146  LOGMESSAGE << "Tuning mode: ET: Equal temperament";
147  break;
148  case 1:
149  LOGMESSAGE << "Tuning mode: JI: Dynamically tuned optimized just intonation";
150  break;
151  case 2:
152  LOGMESSAGE << "Tuning mode: UT: Custom unequal temperament given by table";
153  break;
154  default:
155  LOGERROR << "Undefined value for tuning mode";
156  }
157  LOGSTATUS << "Wolfs interval shift =" << mWolfsIntervalShift;
158 }
159 
160 
161 //-----------------------------------------------------------------------------
162 // Enable or disable tuning process
163 //-----------------------------------------------------------------------------
174 
175 void Tuner::enableTuning (bool enable)
176 {
177  LOGMESSAGE << (enable ? "enable" : "disable") << " tunging algorithm";
178  mTuningEnabled = enable;
179  for (int i=0; i<128; ++i) if (mKeyDataVector[i].intensity>0)
180  mKeyDataVector[i].newlyPressed=true;
181 }
182 
183 
184 //-----------------------------------------------------------------------------
185 // Public slot: Set parameter for pitch progression compensation
186 //-----------------------------------------------------------------------------
200 
202 {
203  if (lambda<0 or lambda>1)
204  {
205  LOGWARNING << "Unreasonable correction parameter =" << lambda;
206  LOGWARNING << "Value should be between 0 and 1.";
207  return;
208  }
209  LOGMESSAGE << "Setting pitch progression parameter lambda =" << lambda;
211 }
212 
213 
214 
215 //-----------------------------------------------------------------------------
216 // Public slot: Set delay parameter
217 //-----------------------------------------------------------------------------
225 
226 void Tuner::setDelayParameter (double delay)
227 {
228  if (delay<0 or delay>10)
229  {
230  LOGWARNING << "Unreasonable delay parameter =" << delay;
231  LOGWARNING << "Value should be between 0 and 10.";
232  return;
233  }
234  LOGMESSAGE << "Setting delay parameter: delay=" << delay;
235  mDelayParameter = delay;
236 }
237 
238 
239 //-----------------------------------------------------------------------------
240 // Public slot: Set memory parameter
241 //-----------------------------------------------------------------------------
246 
247 void Tuner::setMemoryLength (double seconds)
248 {
249  if (seconds<0 or seconds>10)
250  {
251  LOGWARNING << "Unreasonable memory duration parameter =" << seconds;
252  LOGWARNING << "Value should be between 0 and 10.";
253  return;
254  }
255  LOGMESSAGE << "Setting memory duration parameter: delay=" << seconds;
256  mMemoryOffFactor = exp(-0.005*updateIntervalMsec/seconds);
257 }
258 
259 
260 //-----------------------------------------------------------------------------
261 // Main input slot: Receive Midi event
262 //-----------------------------------------------------------------------------
270 
271 void Tuner::receiveMidiEvent(QMidiMessage event)
272 {
273  if (not isActive()) return;
274 
275  // get key number and intensity from the Midi message
276  int key = event.byte1() & 0x7F;
277  double intensity = static_cast<double>(event.byte2())/127.0;
278 
279  switch (event.byte0() & 0xF0)
280  {
281  case 0x90: // Press key: Note on
282  if (intensity>0)
283  {
284  // Reset data of the newly pressed key
285  mKeyDataVector[key].pressed = true;
286  mKeyDataVector[key].newlyPressed = true;
287  mKeyDataVector[key].emitted = false;
288  mKeyDataVector[key].intensity = intensity;
289  mKeyDataVector[key].memory = 0;
290  mKeyDataVector[key].timeStamp = getNow();
291  // Reset time variable to enforce immediate tuning
293  }
294  else mKeyDataVector[key].pressed = false;
295  break;
296 
297  case 0x80: // Release key: Note off
298  mKeyDataVector[key].pressed = false;
299  break;
300 
301  case 0xB0: // Control command:
302  {
303  switch(event.byte1())
304  {
305  case 67: // Pressing the left pedal will turn off tuning
306  enableTuning(event.byte2()==0);
307  break;
308 
309  case 123: // Command to switch all notes off
310  for (int k=0; k<128; ++k) mKeyDataVector[k].pressed = false;
311  break;
312  default:
313  break;
314  }
315  }
316  break;
317  default:
318  break;
319  }
320 }
321 
322 
323 //-----------------------------------------------------------------------------
324 // Slot: Set interval size
325 //-----------------------------------------------------------------------------
338 
339 void Tuner::setIntervalSize (int semitones, double cents)
340 {
341  if (semitones<1 or semitones>12)
342  { LOGERROR << "Set cents: Number of halftones" << semitones << "must be between 1 and 12"; }
343  else if (cents < -50 or cents > 50)
344  { LOGERROR << "Set cents: Value" << cents << "out of allowed range (-50..50)"; }
345  else mIntervalSizes[semitones%12] = cents;
346 }
347 
348 
349 //-----------------------------------------------------------------------------
350 // Slot: Set interval weight
351 //-----------------------------------------------------------------------------
368 
369 void Tuner::setIntervalWeight (int semitones, double weight)
370 {
371  if (semitones < 1 or semitones > 12)
372  { LOGERROR << "Set weights: Number of halftones" << semitones << "must be between 1 and 12"; }
373  else if (weight < 0 or weight > 1)
374  { LOGERROR << "Set weights: Value" << weight << "out of allowed range (0..1)"; }
375  else mIntervalWeights[semitones%12] = (weight==0 ? 0.000000001 : weight);
376 }
377 
378 
379 //-----------------------------------------------------------------------------
380 // Emit the newly calculated pitch corrections
381 //-----------------------------------------------------------------------------
388 
390 {
391  const double centQuantization = 0.05;
392  QMap<int,double> changes;
393  qint64 now = getNow();
394  for (KeyData &keydata : mKeyDataVector) if (keydata.intensity > 0)
395  {
396  // Handle delayed tuning if necessary
397  bool tune = now - keydata.timeStamp >= 1000 * mDelayParameter;
398  double pitch = mGlobalTargetPitchInCents + (tune ? keydata.pitch : 0);
399 
400  if (not keydata.emitted or
401  fabs(pitch-keydata.previousPitch) > centQuantization)
402  changes[keydata.key] = keydata.previousPitch = pitch;
403  keydata.emitted = true;
404  }
405  if (changes.size()>0)
406  {
407  emit signalTuningCorrections(changes);
408 
409  // Report the emitted signal:
410  QString message = "| ";
411  int firstkey=0; double lastpitch=0;
412  for (KeyData &key : mKeyDataVector) if (key.pressed)
413  {
414  firstkey=key.key; lastpitch=key.pitch; break;
415  }
416  for (KeyData &key : mKeyDataVector) if (key.pressed and key.key > firstkey)
417  {
418  message += QString::number(((int)(10*(key.pitch-lastpitch)))/10.0) + " | ";
419  lastpitch = key.pitch;
420  }
421  if (message != mLastMessage)
422  {
423  emit signalIntervalString(message);
424  mLastMessage = message;
425  }
426  if (getVerbosity() >= 4) if (message.size() > 0) { LOGSTATUS << message; }
427  LOGSTATUS << "INTERVALS:" << message;
428  }
429 }
430 
431 
432 //-----------------------------------------------------------------------------
433 // Get current elapsed tuner runtime in milliseconds
434 //-----------------------------------------------------------------------------
435 
440 
442 {
443  if (pElapsedTimer) return pElapsedTimer->elapsed();
444  else return 0;
445 }
446 
447 
448 //-----------------------------------------------------------------------------
449 // Public slot: Enable or disable static tuning mode
450 //-----------------------------------------------------------------------------
456 
457 void Tuner::setStaticTuningMode (bool enable, int reference)
458 {
459  LOGMESSAGE << "Setting static tuning =" << enable
460  << "with reference key" << reference;
461  mStaticTuningModeEnabled = enable;
462  mStaticReferenceKey = reference%128;
463 }
464 
465 
466 //-----------------------------------------------------------------------------
467 // Set the essential parameters of the envelope
468 //-----------------------------------------------------------------------------
489 
490 void Tuner::setEnvelopeParameters (double secondsBass,
491  double secondsTreble,
492  double secondsRelease)
493 {
494  LOGMESSAGE << "Envelope: Bass =" << secondsBass << " Treble ="
495  << secondsTreble << " Release =" << secondsRelease;
496  auto factor = [this] (double seconds) {
497  return (seconds==0 ? 0 : exp(-0.001*updateIntervalMsec/seconds)); };
498  for (int key=0; key<128; key++)
499  {
500  double sedondsSustain = secondsBass +
501  (108.0-key)/88.0*(secondsTreble-secondsBass);
502  mKeyDataVector[key].sustainDampingFactor = factor(sedondsSustain);
503  mKeyDataVector[key].releaseDampingFactor = factor(secondsRelease);
504  }
505 }
506 
507 
508 
509 //-----------------------------------------------------------------------------
510 // Initialization of the starting thread
511 //-----------------------------------------------------------------------------
515 
517 {
518  pElapsedTimer = new QElapsedTimer;
519  QTimer::singleShot(1,this,&Tuner::tune);
520 }
521 
522 
523 //-----------------------------------------------------------------------------
524 // Cleanup of the terminating thread
525 //-----------------------------------------------------------------------------
529 
531 {
532  if (pElapsedTimer) delete pElapsedTimer;
533  pElapsedTimer = nullptr;
534 }
535 
536 //-----------------------------------------------------------------------------
537 // Update procedure triggered by the timer
538 //-----------------------------------------------------------------------------
544 
546 {
547  // Update the key data to emulate the envelope and memory
548  for (KeyData &key : mKeyDataVector) if (key.memory > 0 or key.intensity > 0)
549  {
550  if (key.pressed)
551  {
552  key.memory += (1-memoryOnFactor)*(key.intensity-key.memory);
553  key.intensity *= key.sustainDampingFactor;
554 
555  // If volume decreases below cutoff turn the key off
556  if (key.intensity < cutoffIntensity or
557  getNow()-key.timeStamp > 1000*noteTimeoutSeconds)
558  key.pressed = false;
559  }
560  else // if the key has been released
561  {
562  key.memory += (1-mMemoryOffFactor)*(key.intensity-key.memory);
563  key.intensity *= key.releaseDampingFactor;
564  if (key.intensity < cutoffIntensity) key.intensity = 0;
565  if (key.memory < cutoffMemory) key.memory = 0;
566  }
567  }
568 
569  // Pitch drift compensation mechanism
571 }
572 
573 
574 //-----------------------------------------------------------------------------
575 // Tune the pitches
576 //-----------------------------------------------------------------------------
583 
585 {
586  if (isInterruptionRequested()) return;
587  if ((--mWaitingTimeMsec) > 0) // wait until mWaitingTimeMsec == 0
588  { QTimer::singleShot(1,this,&Tuner::tune); return; }
589 
590  if (not mTuningEnabled or mTuningMode==0)
591  for (auto &key : mKeyDataVector) key.pitch = 0;
592  else if (mStaticTuningModeEnabled)
595  else
596  {
597  double tension = mTunerAlgorithm.tuneDynamically (mKeyDataVector,
599  emit signalTension(exp(-0.01*std::abs(tension)));
600  }
602 
603  mWaitingTimeMsec = tuningIntervalMsec; // Trigger next tuning event
604  QTimer::singleShot(1,this,&Tuner::tune);
605 }
606 
607 
608 //-----------------------------------------------------------------------------
609 // Handle pitch drift
610 //-----------------------------------------------------------------------------
619 
621 {
623  {
625  return;
626  }
627  // Determine arithmetic mean of pitch weighted by intensity
628  double avsum = 0, norm = 0;
629  for (KeyData &key : mKeyDataVector) if (key.intensity > 0)
630  { avsum += key.pitch * key.intensity; norm += key.intensity; }
631  if (norm > 0)
632  {
633  double averagePitch = avsum/norm;
634  if (not mStaticTuningModeEnabled)
635  {
636  const double dP = - averagePitch * pow(mPitchAutoCorrectionParameter,2.5);
637  for (KeyData &key : mKeyDataVector) key.pitch += dP;
638  averagePitch += dP;
639  }
640  if (std::abs(averagePitch) < 1E-6) averagePitch=0;
641  signalAveragePitchDrift(averagePitch);
642  }
643 }
644 
645 
646 //-----------------------------------------------------------------------------
647 // Reset pitch progression to a given value
648 //-----------------------------------------------------------------------------
655 
657 {
658  LOGMESSAGE << "Resetting global pitch correction";
659  double av=0,sum=0;
660  for (KeyData &e : mKeyDataVector)
661  {
662  sum += e.intensity;
663  av += e.intensity * e.pitch;
664  }
665  for (KeyData &e : mKeyDataVector)
666  {
667  if (e.intensity > 0 and sum > 0) e.pitch -= av/sum;
668  else e.pitch=0;
669  }
672 }
void setTimerInterval(const int msec, const int firstMsec=0)
Set timer interval for the periodically called worker.
Definition: threadbase.cpp:117
void signalIntervalString(QVariant str)
Signal emitting the tuned interval sizes in a human-readable form.
bool isActive() const
Return true if thread is running and not suspended.
Definition: threadbase.cpp:136
bool start() override final
Public slot: Start the tuner, starting the tuner thread.
Definition: tuner.cpp:94
Universal base class for threaded modules.
Definition: threadbase.h:60
void setIntervalSize(int semitones, double cents)
Public Slot: Set interval size This function allows the user to specify the temperament. To this end one specifies the interval sizes relative to the equal temperament in cents. The complete definition of the temperament requires to set all interval sizes, meaning that this slot has to be called 11 times.
Definition: tuner.cpp:339
void setModuleName(const QString &name)
Specify the name of the class-specific module.
Definition: log.cpp:82
const double cutoffMemory
Definition: tuner.h:111
double mPitchAutoCorrectionParameter
Pitch correction parameters.
Definition: tuner.h:129
void tuneStatically(KeyDataVector &keys, const QVector< double > pitches, const int referenceKey, const int wolfsIntervalShift)
Tune statically in a given unequal temperament (UT)
const uint tuningIntervalMsec
Update interval tuning.
Definition: tuner.h:105
QVector< double > mIntervalWeights
List of 12 interval weights.
Definition: tuner.h:125
int getVerbosity()
Get verbosity level.
Definition: threadbase.h:80
TunerAlgorithm mTunerAlgorithm
The tuning algorithm.
Definition: tuner.h:117
void enableTuning(bool enable)
Public Slot: Enable or disable adaptive tuning.
Definition: tuner.cpp:175
#define LOGMESSAGE
Definition: log.h:43
double mMemoryOffFactor
Update factor for memory decay.
Definition: tuner.h:131
Structure holding the tuner&#39;s data of a single key.
Definition: tunerkeydata.h:32
void setStaticTuningMode(bool enable, int reference=0)
Slot: Enable or disable static tuning mode.
Definition: tuner.cpp:457
void signalAveragePitchDrift(double progression)
Signal that periodically transmits the average pitch progression. In the GUI this progression is indi...
bool init() override final
Initialize the Tuner.
Definition: tuner.cpp:68
void setMemoryLength(double seconds)
Public slot: Set memory parameter.
Definition: tuner.cpp:247
void resetPitchProgression()
Reset average pitch progression to a given value.
Definition: tuner.cpp:656
QString mLastMessage
remember last message sent
Definition: tuner.h:123
const uint noteTimeoutSeconds
Definition: tuner.h:109
void setTuningMode(int mode, int wolfsShift)
Public slot: Set tuning mode.
Definition: tuner.cpp:134
Tuner()
Constructor of the Tuner.
Definition: tuner.cpp:35
virtual void periodicallyCalledWorker() override final
Periodically called worker function: Update key data.
Definition: tuner.cpp:545
bool mTuningEnabled
Flag for temporary on/off.
Definition: tuner.h:122
double mDelayParameter
Delay time.
Definition: tuner.h:130
QString f(Eigen::MatrixXd mat)
Definition: tunerdebug.h:27
virtual bool init()
Virtual initialization function (no functionality here)
Definition: threadbase.h:70
qint8 mWaitingTimeMsec
Waiting time before tuning event.
Definition: tuner.h:132
void setDelayParameter(double delay)
Public Slot: Set delay parameter.
Definition: tuner.cpp:226
void signalTension(QVariant mu)
Signal emitting the network tension in the tuned chord, indicating the deviation from just intonation...
#define LOGERROR
Definition: log.h:45
int mStaticReferenceKey
Index of static UT reference key.
Definition: tuner.h:120
void handlePitchDrift()
Handle pitch drift.
Definition: tuner.cpp:620
void setIntervalWeight(int semitones, double weight)
Public Slot: Set interval weight The human perception of various intervals depends on their size...
Definition: tuner.cpp:369
QVector< double > mIntervalSizes
List of 12 interval sizes.
Definition: tuner.h:124
double mGlobalTargetPitchInCents
Global pitch against 440Hz.
Definition: tuner.h:118
qint64 getNow()
Private function: Get current elapsed tuner runtime in milliseconds.
Definition: tuner.cpp:441
void signalTuningCorrections(QMap< int, double > corrections)
Signal sending the tuning results to the microtonal sound device in the form of a map keyindex -> cen...
const uint updateIntervalMsec
Update interval envelope.
Definition: tuner.h:108
void receiveMidiEvent(QMidiMessage event)
Public slot: Receive Midi event (Main input of the tuner)
Definition: tuner.cpp:271
void setFrequencyOfA4(double f=440)
Public slot: Set the desired target frequency of A4 (concert pitch)
Definition: tuner.cpp:110
#define LOGSTATUS
Definition: log.h:42
QVector< KeyData > mKeyDataVector
Vector containing all key data.
Definition: tuner.h:126
const double memoryOnFactor
Definition: tuner.h:113
virtual bool start()
Start the thread.
Definition: threadbase.cpp:64
int mWolfsIntervalShift
Placement of the wolfs interval.
Definition: tuner.h:121
bool isInterruptionRequested() const
Return true if the thread was requested to interrupt or terminate.
Definition: threadbase.cpp:128
void setPitchProgressionCompensationParameter(double lambda)
Public slot: Set the parameter for pitch progression compensation.
Definition: tuner.cpp:201
void setThreadName(const QString name)
Set thread name (Linux only)
Definition: threadbase.cpp:121
void emitPitchCorrections()
Emit the pitch corrections This functions checks to what extent the calculated pitches differ from th...
Definition: tuner.cpp:389
void signalReadyForReceivingParameters()
Signal telling the GUI that the tuner is ready and that the GUI may send the user-defined parameters ...
QElapsedTimer * pElapsedTimer
Time elapsed since construction.
Definition: tuner.h:127
bool mStaticTuningModeEnabled
Flag: Tune statically.
Definition: tuner.h:128
virtual void finallyCalledWorker() override final
Cleanup of the terminating thread.
Definition: tuner.cpp:530
double tuneDynamically(KeyDataVector &keyData, const QVector< double > intervals, const QVector< double > weights, bool optimizedJI)
Main Tuninig Procedure: Tune dynamically.
const double cutoffIntensity
Definition: tuner.h:110
int mTuningMode
Actual tuning mode.
Definition: tuner.h:119
void setEnvelopeParameters(double secondsSustainBass, double secondsSustainTreble, double secondsRelease)
Set the essential parameters of the envelope.
Definition: tuner.cpp:490
virtual void initiallyCalledWorker() override final
Initialization of the starting thread.
Definition: tuner.cpp:516
void tune()
Tune the pitches of the pressed keys (call TunerAlgorithm)
Definition: tuner.cpp:584
#define LOGWARNING
Definition: log.h:44