Just Intonation  Version 1.3.0 (18)
Explore scale-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: User interface for instant tuning
22 //=============================================================================
23 
24 #include "tuner.h"
25 #include <math.h>
26 
27 //-----------------------------------------------------------------------------
28 // Constructor
29 //-----------------------------------------------------------------------------
34 
36  , mGlobalTargetPitchInCents(0)
37  , mTuningMode (1)
38  , mStaticReferenceKey(0)
39  , mWolfsIntervalShift(0)
40  , mTuningEnabled(true)
41  , mLastMessage("")
42  , mIntervalSizes(12,0)
43  , mIntervalWeights(12,1)
44  , mKeyData()
45  , mTunerAlgorithm()
46  , pElapsedTimer(nullptr)
47  , mStaticTuningModeEnabled(false)
48  , mPitchAutoCorrectionParameter(0.5)
49  , mDelayParameter(0)
50  , mMemoryOffFactor(0)
51 
52 {
53  setThreadName("Tuner");
54  setModuleName("Tuner");
55 }
56 
57 
58 //-----------------------------------------------------------------------------
59 // Init
60 //-----------------------------------------------------------------------------
65 
67 {
70  mKeyData.resize(128);
71  for (int n=0; n<128; n++)
72  {
73  mKeyData[n].clear();
74  mKeyData[n].key = n;
75  }
78  return true;
79 }
80 
81 
82 //-----------------------------------------------------------------------------
83 // Public slot: Start the tuner
84 //-----------------------------------------------------------------------------
91 
93 {
94  if (not ThreadBase::start()) return false;
95  signalReadyForReceivingParameters(); // Ask the GUI to set the tuning parameters
96  return true;
97 }
98 
99 
100 //-----------------------------------------------------------------------------
101 // Public slot: Set frequency of A4
102 //-----------------------------------------------------------------------------
107 
109 {
110  if (f<=0) return;
111  double cents = 1200 * log(f/440.0) / log(2.0);
112  if (cents > 200 or cents < -200)
113  {
114  LOGWARNING << "Unreasonable global pitch =" << cents;
115  return;
116  }
117  LOGMESSAGE << "Setting global ptich to" << cents;
119 }
120 
121 
122 //-----------------------------------------------------------------------------
123 // Public slot: Set tuning mode
124 //-----------------------------------------------------------------------------
130 
131 void Tuner::setTuningMode (int mode, int wolfsShift)
132 {
133  mTuningMode = mode;
134  mWolfsIntervalShift = wolfsShift;
135  for (int i=0; i<128; ++i) if (mKeyData[i].intensity>0) mKeyData[i].newlyPressed=true;
136  switch(mode)
137  {
138  case 0:
139  LOGMESSAGE << "Tuning mode: ET: Equal temperament";
140  break;
141  case 1:
142  LOGMESSAGE << "Tuning mode: JI: Dynamically tuned optimized just intonation";
143  break;
144  case 2:
145  LOGMESSAGE << "Tuning mode: UT: Custom unequal temperament given by table";
146  break;
147  default:
148  LOGERROR << "Undefined value for tuning mode";
149  }
150  LOGSTATUS << "Wolfs interval shift =" << mWolfsIntervalShift;
151 }
152 
153 
154 //-----------------------------------------------------------------------------
155 // Enable or disable tuning process
156 //-----------------------------------------------------------------------------
167 
168 void Tuner::enableTuning (bool enable)
169 {
170  LOGMESSAGE << (enable ? "enable" : "disable") << " tunging algorithm";
171  mTuningEnabled = enable;
172  for (int i=0; i<128; ++i) if (mKeyData[i].intensity>0) mKeyData[i].newlyPressed=true;
173 }
174 
175 
176 //-----------------------------------------------------------------------------
177 // Public slot: Set parameter for pitch progression compensation
178 //-----------------------------------------------------------------------------
192 
194 {
195  if (lambda<0 or lambda>1)
196  {
197  LOGWARNING << "Unreasonable correction parameter =" << lambda;
198  LOGWARNING << "Value should be between 0 and 1.";
199  return;
200  }
201  LOGMESSAGE << "Setting pitch progression parameter lambda =" << lambda;
203 }
204 
205 
206 
207 //-----------------------------------------------------------------------------
208 // Public slot: Set delay parameter
209 //-----------------------------------------------------------------------------
217 
218 void Tuner::setDelayParameter (double delay)
219 {
220  if (delay<0 or delay>10)
221  {
222  LOGWARNING << "Unreasonable delay parameter =" << delay;
223  LOGWARNING << "Value should be between 0 and 10.";
224  return;
225  }
226  LOGMESSAGE << "Setting delay parameter: delay=" << delay;
227  mDelayParameter = delay;
228 }
229 
230 
231 //-----------------------------------------------------------------------------
232 // Public slot: Set memory parameter
233 //-----------------------------------------------------------------------------
238 
239 void Tuner::setMemoryLength (double seconds)
240 {
241  if (seconds<0 or seconds>10)
242  {
243  LOGWARNING << "Unreasonable memory duration parameter =" << seconds;
244  LOGWARNING << "Value should be between 0 and 10.";
245  return;
246  }
247  LOGMESSAGE << "Setting memory duration parameter: delay=" << seconds;
248  mMemoryOffFactor = exp(-0.005*updateIntervalMsec/seconds);
249 }
250 
251 
252 //-----------------------------------------------------------------------------
253 // Main input slot: Receive Midi event
254 //-----------------------------------------------------------------------------
262 
263 void Tuner::receiveMidiEvent(QMidiMessage event)
264 {
265  if (not isActive()) return;
266  int key = event.byte1() & 0x7F;
267  double intensity = static_cast<double>(event.byte2())/127.0;
268 
269  switch (event.byte0() & 0xF0)
270  {
271  // Press key: Note on
272  case 0x90:
273  if (intensity>0)
274  {
275  // Reset data of the newly pressed key
276  mKeyData[key].pressed = true;
277  mKeyData[key].newlyPressed = true;
278  mKeyData[key].emitted = false;
279  mKeyData[key].intensity = intensity;
280  mKeyData[key].memory = 0;
281  mKeyData[key].timeStamp = getNow();
282  }
283  else mKeyData[key].pressed = false;
284  break;
285 
286  // Release key: Note off
287  case 0x80:
288  mKeyData[key].pressed = false;
289  break;
290 
291  case 0xB0: // Control command
292  {
293  switch(event.byte1())
294  {
295  // Pressing the left pedal will turn off tuning
296  case 67:
297  enableTuning(event.byte2()==0);
298  break;
299 
300  // Command to switch all notes off
301  case 123:
302  for (int k=0; k<128; ++k) mKeyData[k].pressed = false;
303  break;
304  default:
305  break;
306  }
307  }
308  break;
309  default:
310  break;
311  }
312 }
313 
314 
315 //-----------------------------------------------------------------------------
316 // Slot: Set interval size
317 //-----------------------------------------------------------------------------
330 
331 void Tuner::setIntervalSize (int halftones, double cents)
332 {
333  if (halftones<1 or halftones>12)
334  { LOGERROR << "Set cents: Number of halftones" << halftones << "must be between 1 and 12"; }
335  else if (cents < -50 or cents > 50)
336  { LOGERROR << "Set cents: Value" << cents << "out of allowed range (-50..50)"; }
337  else mIntervalSizes[halftones%12] = cents;
338 }
339 
340 
341 //-----------------------------------------------------------------------------
342 // Slot: Set interval weight
343 //-----------------------------------------------------------------------------
360 
361 void Tuner::setIntervalWeight (int halftones, double weight)
362 {
363  if (halftones < 1 or halftones > 12)
364  { LOGERROR << "Set weights: Number of halftones" << halftones << "must be between 1 and 12"; }
365  else if (weight < 0 or weight > 1)
366  { LOGERROR << "Set weights: Value" << weight << "out of allowed range (0..1)"; }
367  else mIntervalWeights[halftones%12] = (weight==0 ? 0.000000001 : weight);
368 }
369 
370 
371 //-----------------------------------------------------------------------------
372 // Emit the newly calculated pitch corrections
373 //-----------------------------------------------------------------------------
380 
382 {
383  const double centQuantization = 0.05;
384  QMap<int,double> changes;
385  qint64 now = getNow();
386  for (TunerKeyData &keydata : mKeyData) if (keydata.intensity > 0)
387  {
388  // Handle delayed tuning if necessary
389  bool tune = now - keydata.timeStamp >= 1000 * mDelayParameter;
390  double pitch = mGlobalTargetPitchInCents + (tune ? keydata.pitch : 0);
391 
392  if (not keydata.emitted or
393  fabs(pitch-keydata.previousPitch) > centQuantization)
394  changes[keydata.key] = keydata.previousPitch = pitch;
395  keydata.emitted = true;
396  }
397  if (changes.size()>0)
398  {
399  emit signalTuningCorrections(changes);
400 
401  // Report the emitted signal:
402  QString message = "| ";
403  int firstkey=0; double lastpitch=0;
404  for (TunerKeyData &key : mKeyData) if (key.pressed)
405  {
406  firstkey=key.key; lastpitch=key.pitch; break;
407  }
408  for (TunerKeyData &key : mKeyData) if (key.pressed and key.key > firstkey)
409  {
410  message += QString::number(((int)(10*(key.pitch-lastpitch)))/10.0) + " | ";
411  lastpitch = key.pitch;
412  }
413  if (message != mLastMessage)
414  {
415  emit signalIntervalString(message);
416  mLastMessage = message;
417  }
418  if (getVerbosity() >= 4) if (message.size() > 0) { LOGSTATUS << message; }
419  LOGSTATUS << "INTERVALS:" << message;
420  }
421 }
422 
423 
424 //-----------------------------------------------------------------------------
425 // Get current elapsed tuner runtime in milliseconds
426 //-----------------------------------------------------------------------------
427 
432 
434 {
435  if (pElapsedTimer) return pElapsedTimer->elapsed();
436  else return 0;
437 }
438 
439 
440 //-----------------------------------------------------------------------------
441 // Public slot: Enable or disable static tuning mode
442 //-----------------------------------------------------------------------------
448 
449 void Tuner::setStaticTuningMode (bool enable, int reference)
450 {
451  LOGMESSAGE << "Setting static tuning =" << enable
452  << "with reference key" << reference;
453  mStaticTuningModeEnabled = enable;
454  mStaticReferenceKey = reference%128;
455 }
456 
457 
458 
459 //-----------------------------------------------------------------------------
460 // Set the essential parameters of the envelope
461 //-----------------------------------------------------------------------------
482 
483 void Tuner::setEnvelopeParameters (double secondsBass,
484  double secondsTreble,
485  double secondsRelease)
486 {
487  LOGMESSAGE << "Envelope: Bass =" << secondsBass << " Treble ="
488  << secondsTreble << " Release =" << secondsRelease;
489  auto factor = [this] (double seconds) {
490  return (seconds==0 ? 0 : exp(-0.001*updateIntervalMsec/seconds)); };
491  for (int key=0; key<128; key++)
492  {
493  double sedondsSustain = secondsBass +
494  (108.0-key)/88.0*(secondsTreble-secondsBass);
495  mKeyData[key].sustainDampingFactor = factor(sedondsSustain);
496  mKeyData[key].releaseDampingFactor = factor(secondsRelease);
497  }
498 }
499 
500 
501 
502 //-----------------------------------------------------------------------------
503 // Initialization of the starting thread
504 //-----------------------------------------------------------------------------
508 
510 {
511  pElapsedTimer = new QElapsedTimer;
512  QTimer::singleShot(1,this,&Tuner::tune);
513 }
514 
515 
516 //-----------------------------------------------------------------------------
517 // Cleanup of the terminating thread
518 //-----------------------------------------------------------------------------
522 
524 {
525  if (pElapsedTimer) delete pElapsedTimer;
526  pElapsedTimer = nullptr;
527 }
528 
529 //-----------------------------------------------------------------------------
530 // Update procedure triggered by the timer
531 //-----------------------------------------------------------------------------
537 
539 {
540  // Update the key data to emulate the envelope and memory
541  for (TunerKeyData &key : mKeyData) if (key.memory > 0 or key.intensity > 0)
542  {
543  if (key.pressed)
544  {
545  key.memory += (1-memoryOnFactor)*(key.intensity-key.memory);
546  key.intensity *= key.sustainDampingFactor;
547 
548  // If volume decreases below cutoff turn the key off
549  if (key.intensity < cutoffIntensity or
550  getNow()-key.timeStamp > 1000*noteTimeoutSeconds)
551  key.pressed = false;
552  }
553  else // if the key has been released
554  {
555  key.memory += (1-mMemoryOffFactor)*(key.intensity-key.memory);
556  key.intensity *= key.releaseDampingFactor;
557  if (key.intensity < cutoffIntensity)
558  key.intensity = 0;
559  if (key.memory < cutoffMemory)
560  key.memory = 0;
561  }
562  }
563 
564  // Pitch drift compensation mechanism
566 }
567 
568 
569 
570 //-----------------------------------------------------------------------------
571 // Tune the pitches
572 //-----------------------------------------------------------------------------
579 
581 {
582  if (isInterruptionRequested()) return;
583 
584  if (not mTuningEnabled or mTuningMode==0) for (auto &key : mKeyData) key.pitch = 0;
585  else if (mStaticTuningModeEnabled)
586  {
589  }
590  else
591  {
592  double tension = mTunerAlgorithm.tuneDynamically (mKeyData,
594  emit signalTension(exp(-0.01*std::abs(tension)));
595  }
597  QTimer::singleShot(20,this,&Tuner::tune); // wait 20ms
598 }
599 
600 
601 //-----------------------------------------------------------------------------
602 // Handle pitch progression
603 //-----------------------------------------------------------------------------
612 
614 {
615  // Determine arithmetic mean of pitch weighted by intensity
616  double avsum = 0, norm = 0;
617  for (TunerKeyData &key : mKeyData) if (key.intensity > 0)
618  { avsum += key.pitch * key.intensity; norm += key.intensity; }
619  if (norm > 0)
620  {
621  double averagePitch = avsum/norm;
622  if (not mStaticTuningModeEnabled)
623  {
624  const double dP = - averagePitch * pow(mPitchAutoCorrectionParameter,2.5);
625  for (TunerKeyData &key : mKeyData)
626  {
627  if (key.intensity > 0) key.pitch += dP;
628  else if (key.memory == 0) key.pitch = averagePitch;
629  }
630  averagePitch += dP;
631  }
632  if (std::abs(averagePitch) < 1E-6) averagePitch=0;
633  signalAveragePitchProgression(averagePitch);
634  }
635 }
636 
637 
638 //-----------------------------------------------------------------------------
639 // Reset pitch progression to a given value
640 //-----------------------------------------------------------------------------
647 
649 {
650  LOGMESSAGE << "Resetting global pitch correction";
651  double av=0,sum=0;
652  for (TunerKeyData &e : mKeyData)
653  {
654  sum += e.intensity;
655  av += e.intensity * e.pitch;
656  }
657  for (TunerKeyData &e : mKeyData)
658  {
659  if (e.intensity > 0 and sum > 0) e.pitch -= av/sum;
660  else e.pitch=0;
661  }
664 }
void setTimerInterval(const int msec, const int firstMsec=0)
Set timer interval for the periodically called worker.
Definition: threadbase.cpp:117
void tuneStaticUT(KeyData &keys, const QVector< double > pitches, const int referenceKey, const int wolfsIntervalShift)
Tune statically in a given unequal temperament (UT)
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:92
Universal base class for threaded modules.
Definition: threadbase.h:60
void setModuleName(const QString &name)
Specify the name of the class-specific module.
Definition: log.cpp:82
const double cutoffMemory
Definition: tuner.h:102
double mPitchAutoCorrectionParameter
Pitch correction parameters.
Definition: tuner.h:120
QVector< double > mIntervalWeights
List of 12 interval weights.
Definition: tuner.h:115
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:168
#define LOGMESSAGE
Definition: log.h:43
double mMemoryOffFactor
Update factor for memory loss.
Definition: tuner.h:122
void setStaticTuningMode(bool enable, int reference=0)
Slot: Enable or disable static tuning mode.
Definition: tuner.cpp:449
bool init() override final
Initialize the Tuner.
Definition: tuner.cpp:66
void setMemoryLength(double seconds)
Public slot: Set memory parameter.
Definition: tuner.cpp:239
void resetPitchProgression()
Reset average pitch progression to a given value.
Definition: tuner.cpp:648
QString mLastMessage
remember last message sent
Definition: tuner.h:113
const uint noteTimeoutSeconds
Definition: tuner.h:100
void setTuningMode(int mode, int wolfsShift)
Public slot: Set tuning mode.
Definition: tuner.cpp:131
Tuner()
Constructor of the Tuner.
Definition: tuner.cpp:35
virtual void periodicallyCalledWorker() override final
Periodically called worker function: Update key data.
Definition: tuner.cpp:538
void signalAveragePitchProgression(double progression)
Signal that periodically transmits the average pitch progression.
void handlePitchProgression()
Handle pitch progression.
Definition: tuner.cpp:613
bool mTuningEnabled
Flag for temporary on/off.
Definition: tuner.h:112
double mDelayParameter
Delay time.
Definition: tuner.h:121
QString f(Eigen::MatrixXd mat)
Definition: tunerdebug.h:29
virtual bool init()
Virtual initialization function (no functionality here)
Definition: threadbase.h:70
QVector< TunerKeyData > mKeyData
Vector containing all key data.
Definition: tuner.h:116
void setDelayParameter(double delay)
Public Slot: Set delay parameter.
Definition: tuner.cpp:218
void signalTension(QVariant mu)
Signal emitting the network tension in the tuned chord.
#define LOGERROR
Definition: log.h:45
int mStaticReferenceKey
Index of static UT reference key.
Definition: tuner.h:110
void setIntervalSize(int halftones, 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:331
QVector< double > mIntervalSizes
List of 12 interval sizes.
Definition: tuner.h:114
double mGlobalTargetPitchInCents
Global pitch against 440Hz.
Definition: tuner.h:108
qint64 getNow()
Private function: Get current elapsed tuner runtime in milliseconds.
Definition: tuner.cpp:433
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
Definition: tuner.h:99
void receiveMidiEvent(QMidiMessage event)
Public slot: Receive Midi event (Main input of the tuner)
Definition: tuner.cpp:263
void setFrequencyOfA4(double f=440)
Public slot: Set the desired target frequency of A4 (concert pitch)
Definition: tuner.cpp:108
#define LOGSTATUS
Definition: log.h:42
const double memoryOnFactor
Definition: tuner.h:104
virtual bool start()
Start the thread.
Definition: threadbase.cpp:64
int mWolfsIntervalShift
Placement of the wolfs interval.
Definition: tuner.h:111
void setIntervalWeight(int halftones, double weight)
Public Slot: Set interval weight The human perception of various intervals depends on their size...
Definition: tuner.cpp:361
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:193
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:381
void signalReadyForReceivingParameters()
Signal telling the GUI that the tuner is ready and that the GUI may send the user-defined parameters ...
Structure holding the tuner&#39;s data of a single key.
Definition: tunerkeydata.h:32
QElapsedTimer * pElapsedTimer
Time elapsed since construction.
Definition: tuner.h:118
bool mStaticTuningModeEnabled
Flag: Tune statically.
Definition: tuner.h:119
virtual void finallyCalledWorker() override final
Cleanup of the terminating thread.
Definition: tuner.cpp:523
const double cutoffIntensity
Definition: tuner.h:101
int mTuningMode
Actual tuning mode.
Definition: tuner.h:109
void setEnvelopeParameters(double secondsSustainBass, double secondsSustainTreble, double secondsRelease)
Set the essential parameters of the envelope.
Definition: tuner.cpp:483
virtual void initiallyCalledWorker() override final
Initialization of the starting thread.
Definition: tuner.cpp:509
void tune()
Tune the pitches of the pressed keys.
Definition: tuner.cpp:580
double tuneDynamically(KeyData &keyData, const QVector< double > intervals, const QVector< double > weights, bool optimizedJI)
TunerAlgorithm::solve.
#define LOGWARNING
Definition: log.h:44