Just Intonation  Version 1.3.0 (18)
Explore scale-independent dynamically adapting tuning in just intonation
tuneralgorithm.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 // Main Tuning Algorithm
22 //=============================================================================
23 
24 #include "tuneralgorithm.h"
25 #include "tuner.h"
26 #include "tunerdebug.h"
27 
28 #include <QGenericMatrix>
29 
30 //-----------------------------------------------------------------------------
31 // Constructor
32 //-----------------------------------------------------------------------------
36 
38  : mSelectionIndex(128,128) // JI interval variant selection
39  , mProgression(0) // pitch progression variable
40 {
41  // Reset just intonation interval sizes
42  mSelectionIndex.setZero();
43 }
44 
45 
46 //-----------------------------------------------------------------------------
47 // Tune in a statically defined unequal temperament
48 //-----------------------------------------------------------------------------
66 
68  const QVector<double> pitches,
69  const int referenceKey,
70  const int wolfsIntervalShift)
71 {
72  for (TunerKeyData &key : keys) if (key.intensity > 0)
73  {
74  int distance = (key.key + 240 - referenceKey + wolfsIntervalShift)%12;
75  int A4dist = (69 + 240 - referenceKey + wolfsIntervalShift)%12;
76  key.pitch = pitches[distance]-pitches[A4dist];
77  }
78  mProgression = 0;
79 }
80 
81 
82 //-----------------------------------------------------------------------------
83 // Main Tuning Algorithm: Tune dynamically
84 //-----------------------------------------------------------------------------
106 
107 
109  const QVector<double> intervals,
110  const QVector<double> weights,
111  bool optimizedJI)
112 {
113  using namespace Eigen;
114 
115  // In order to limit the complexity of the tuning process, we first
116  // determine the 8 most intense playing keys and the most 4 relevant
117  // memorized keys.
118 
119  const int Nmax=8;
120 
121  QMultiMap<double,int> playMap,memoMap;
122  for (auto &key : keyData)
123  {
124  if (key.intensity>0) playMap.insert(-key.intensity,key.key);
125  if (key.memory>0) memoMap.insert(-key.memory,key.key);
126  }
127 
128  QVector<int> playingKeys = playMap.values().mid(0,Nmax).toVector();
129  const int N = playingKeys.size();
130  if (N==0) return 0;
131  QVector<int> memorizedKeys = memoMap.values().mid(0,Nmax-N).toVector();
132  const int M = N + memorizedKeys.size();
133  // Sorting the keys is not necessary, but convenient for debugging:
134  qSort(playingKeys); qSort(memorizedKeys);
135  QVector<int> allKeys(playingKeys); allKeys << memorizedKeys;
136 
137  //qDebug() << "N =" << N << playingKeys << " M-N =" << M-N << memorizedKeys;
138 
139  // Copy pitch, intensity and memory level
140  VectorXd pitch(M), significance(M); bool newKeys = false;
141  for (int i=0; i<M; ++i)
142  {
143  TunerKeyData& entry = keyData[allKeys[i]];
144  if (entry.newlyPressed) newKeys = true;
145  pitch(i) = entry.pitch;
146  significance(i) = entry.intensity + entry.memory;
147  }
148 
149  // Setting up various auxiliary matrices
150  MatrixXi variants(M,M), semitones(M,M), direction(M,M);
151  MatrixXd interval(M,M), weight(M,M);
152  for (int i=0; i<M; ++i) for (int j=0; j<M; ++j)
153  {
154  semitones(i,j) = abs(allKeys[j] - allKeys[i]);
155  direction(i,j) = (allKeys[j] >= allKeys[i] ? 1:-1);
156  variants(i,j) = cJustIntonation[semitones(i,j)%12].size();
157  int variant = mSelectionIndex(allKeys[i],allKeys[j]);
158  interval(i,j) = direction(i,j) *
159  (optimizedJI ? cJustIntonation[semitones(i,j)%12][variant]
160  : intervals[semitones(i,j)%12]);
161  if (j>=N) interval(i,j) -= pitch(j);
162  if (i>=N) interval(i,j) += pitch(i);
163  weight(i,j) = (i==j or (i>=N and j>=N) ? 0 : 1) *
164  (i>=N or j>=N ? memoryGlobalWeight : 1) *
165  weights[semitones(i,j)%12] *
166  std::pow(octaveWeightFactor, (semitones(i,j)-1)/12) *
167  sqrt(significance(i) * significance(j));
168  }
169 
170  // Construct the matrix A and its inverse AI
171  MatrixXd A = -weight.block(0,0,N,N) + MatrixXd::Identity(N,N)*epsilon
172  + MatrixXd(VectorXd(weight.array().rowwise().sum()).head(N).asDiagonal());
173  MatrixXd AI = A.inverse();
174 
175  // ######################## BINARY SEARCH ############################
176 
177  if (optimizedJI and newKeys)
178  {
179 // qDebug() << "--------------------------------------";
180 // for (int i=0; i<M; ++i)
181 // {
182 // QString line;
183 // for (int j=0; j<M; ++j) line += QString::number(weight(i,j)) + " ";
184 // qDebug() << line;
185 // }
186 // qDebug() << "--------------------------------------";
187  VectorXd U(N); U.setZero();
188  MatrixXi m(M,M), mmin(M,M); m.setZero(); mmin.setZero();
189  for (int i=0; i<M; i++) for (int j=0; j<M; j++)
190  {
191  if (keyData[allKeys[i]].newlyPressed or keyData[allKeys[j]].newlyPressed) mmin(j,i)=mmin(i,j)=0;
192  else m(i,j) = mSelectionIndex(allKeys[i],allKeys[j]);
193  }
194  bool searching = true;
195  double Pmin = 1e100;
196  while (searching)
197  {
198  MatrixXd interval(M,M);
199  for (int i=0; i<M; ++i) for (int j=0; j<M; ++j)
200  {
201  interval(i,j) = direction(i,j) * cJustIntonation[semitones(i,j)%12][m(i,j)];
202  if (j>=N) interval(i,j) -= pitch(j);
203  if (i>=N) interval(i,j) += pitch(i);
204  }
205  VectorXd B = (interval * weight).diagonal().head(N) - epsilon*pitch.head(N);
206  VectorXd V = -AI * B;
207  double C = (interval.array() * interval.array() * weight.array()).sum() / 4;
208  double P = V.dot(A*V)/2 + B.dot(V) + C;
209  if (P<Pmin-1E-7) { Pmin=P; mmin=m; U=V; }
210 
211  //increment
212  searching = false;
213  for (int i=0; i<M; ++i) for (int j=0; j<M; ++j) if (i<j) if (keyData[allKeys[i]].newlyPressed or keyData[allKeys[j]].newlyPressed) if (variants(i,j)>1 and not searching)
214  {
215  m(j,i) = m(i,j) = (m(i,j)+1) % variants(i,j);
216  if (m(i,j)>0) searching = true;
217  }
218  }
219 // qDebug() << "var" << f(variants);
220 // qDebug() << "mmin" << f(mmin);
221 // qDebug() << "U" << f(U);
222  for (int i=0; i<M; i++) for (int j=0; j<M; j++)
223  mSelectionIndex(allKeys[i],allKeys[j]) = mmin(i,j);
224  for (int i=0; i<N; ++i) keyData[playingKeys[i]].newlyPressed = false;
225  }
226 
227  // Tune the keys micrononally:
228  VectorXd B = (interval * weight).diagonal().head(N) - epsilon*pitch.head(N);
229  VectorXd U = - AI * B;
230  for (int i=0; i<N; i++) keyData[playingKeys[i]].pitch = U(i);
231 
232  // Compute potential as a measure of how much the chord is tempered
233  double C = (interval.array() * interval.array() * weight.array()).sum() / 4;
234  const double V = U.dot(A*U)/2 + B.dot(U) + C;
235  return V;
236 }
void tuneStaticUT(KeyData &keys, const QVector< double > pitches, const int referenceKey, const int wolfsIntervalShift)
Tune statically in a given unequal temperament (UT)
const double octaveWeightFactor
const double epsilon
TunerAlgorithm()
Constructor of the TunerAlgorithm.
double pitch
Definition: tunerkeydata.h:43
double memory
Definition: tunerkeydata.h:39
QVector< TunerKeyData > KeyData
Definition: tunerkeydata.h:60
const double memoryGlobalWeight
bool newlyPressed
Definition: tunerkeydata.h:36
Eigen::MatrixXi mSelectionIndex
const QVector< QVector< double > > cJustIntonation
Structure holding the tuner&#39;s data of a single key.
Definition: tunerkeydata.h:32
double intensity
Definition: tunerkeydata.h:38
double tuneDynamically(KeyData &keyData, const QVector< double > intervals, const QVector< double > weights, bool optimizedJI)
TunerAlgorithm::solve.