Just Intonation  Version 1.3.0 (18)
Explore scale-independent dynamically adapting tuning in just intonation
soundgenerator.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 // SoundGenerator - manages the tones that are being played
22 //=============================================================================
23 
24 #include "soundgenerator.h"
25 #include "system/systemtools.h"
26 
27 #include <QDebug>
28 
29 #define STATUS if (mVerbosity >= 4) qDebug() << "SoundGenerator:"
30 #define MESSAGE if (mVerbosity >= 3) qDebug() << "SoundGenerator:"
31 #define WARNING if (mVerbosity >= 2) qWarning()<<"SoundGenerator: WARNING:"
32 #define ERROR if (mVerbosity >= 1) qCritical() << "SoundGenerator: ERROR:"
33 
34 //-----------------------------------------------------------------------------
35 // Constructor
36 //-----------------------------------------------------------------------------
37 
39  : mQueue()
40  , mQueueMutex()
41  , mTones()
42  , mToneMutex()
43  , mIncrements(128,0x1000)
44  , mIncrementMutex()
45  , mSampler()
46  , pVoice(nullptr)
47  , pApplication(nullptr)
48  , mSustainPedeal(false)
49  , mSostenutoPedal(false)
50 {}
51 
52 
53 //-----------------------------------------------------------------------------
54 // Initialize and start thread
55 //-----------------------------------------------------------------------------
56 
63 
64 void SoundGenerator::init(Application *application, Voice *voice)
65 {
66  pApplication = application;
67  pVoice = voice; // Store the reference of the Voice in pVoice
68  mSampler.init(this);
72 }
73 
74 
75 
76 
78 {
79  mQueueMutex.lock();
80  mQueue.clear();
81  mQueueMutex.unlock();
82  mToneMutex.lock();
83  mTones.clear();
84  mToneMutex.unlock();
86 }
87 
88 
89 
90 //-----------------------------------------------------------------------------
91 // Register a single request to play a note in the queue
92 //-----------------------------------------------------------------------------
93 
101 
103 {
104  if (not isActive()) return;
105  mQueueMutex.lock();
106  mQueue.enqueue(request);
107  mQueueMutex.unlock();
109 }
110 
112 {
113  mToneMutex.lock();
114  QList<Tone> copy = mTones;
115  mToneMutex.unlock();
116  mQueueMutex.lock();
117  for (Tone &tone : copy)
118  {
119  Request request;
120  request.command = Request::NOTE_OFF;
121  request.key = tone.mKey;
122  mQueue.enqueue(request);
123  }
124  mQueueMutex.unlock();
127 }
128 
129 
130 //-----------------------------------------------------------------------------
131 // Thread worker function
132 //-----------------------------------------------------------------------------
133 
137 
139 {
140  if (isInterruptionRequested() or not isActive()) return;
141  while (queueFilled())
142  {
143  mQueueMutex.lock();
144  Request request = mQueue.dequeue();
145  mQueueMutex.unlock();
146  handleRequest(request);
147  }
148  mToneMutex.lock();
150  mToneMutex.unlock();
151 }
152 
153 
154 //-----------------------------------------------------------------------------
155 // Handle a queued request
156 //-----------------------------------------------------------------------------
157 
159 {
160  // available data:
161  // request.channel; // still ignored here
162  // request.command; // enum Command in request
163  // request.data;
164  // request.intensity;
165  // request.note
166  if (not pVoice) return;
167 
168  switch(request.command)
169  {
170  case Request::NOTE_ON:
171  {
172  int key = request.key;
173  double volume1 = 0.6, volume2 = 0;
174  if (pVoice->isVolumeDynamic())
175  {
176  if (pVoice->getNumberOfScales()==1) volume1 = pow(request.intensity,0.75);
177  else
178  {
179  volume2 = pow(request.intensity,0.5);
180  volume1 = 2*pow(request.intensity,2.5);
181  }
182  }
183 
184  if (volume1 > 0 and pVoice->getNumberOfScales() > 0)
185  {
186  Wave &sample = pVoice->getScale(0).getWave(key);
187  if (sample.waveFormExists())
188  {
189  Tone::Type type = (key > pVoice->getHighestDampedKey() ?
190  Tone::Type::SUSTAIN_NONENDING : Tone::Type::SUSTAIN_ENDING);
191  Tone T(key,0,sample,volume1,type);
192  mToneMutex.lock();
193  mTones.push_back(T);
194  mToneMutex.unlock();
195  }
196  }
197  if (volume2 > 0 and pVoice->getNumberOfScales() > 1)
198  {
199  Wave &sample = pVoice->getScale(1).getWave(key);
200  if (sample.waveFormExists())
201  {
202  Tone::Type type = (key > pVoice->getHighestDampedKey() ?
203  Tone::Type::SUSTAIN_NONENDING : Tone::Type::SUSTAIN_ENDING);
204  Tone T(key,1,sample,volume2,type);
205  mToneMutex.lock();
206  mTones.push_back(T);
207  mToneMutex.unlock();
208  }
209  }
210  break;
211  }
212  case Request::NOTE_OFF:
213  {
214  int key = request.key;
215  mToneMutex.lock();
216  for (auto &tone : mTones)
217  if (tone.mKey == key
218  and tone.mState == State::PLAYING
219  and tone.mType == Tone::Type::SUSTAIN_ENDING) // no release above key 92 hard coded
220  tone.mState = State::RELEASE_REQUESTED;
221  mToneMutex.unlock();
222  break;
223  }
225  mToneMutex.lock();
226  for (auto &tone : mTones)
227  if (tone.mState == State::PLAYING)
228  tone.mState = State::RELEASE_REQUESTED;
229  mToneMutex.unlock();
230  break;
231 
233  break;
235  break;
236  case Request::UNDEFINED:
237  default:
238  LOGWARNING << "Sound::run: Request undefined";
239  }
240 }
241 
242 
243 //-----------------------------------------------------------------------------
244 // Manage envelope
245 //-----------------------------------------------------------------------------
246 
248 {
249  double const releaseVolume = 0.005;
250  double const terminationVolume = 0.0001;
251 
252  bool exit = false;
253 
254  // Check all tones for being below cutoff volume
255  // Tone mutex is locked when calling the function
256  for (auto &tone : mTones)
257  {
258  switch(tone.mState)
259  {
260  case State::PLAYING:
261  case State::OFF_PLAYING:
262  if (tone.mVolume < releaseVolume)
263  {
264  LOGSTATUS << "Released key" << tone.mKey << "below cutoff volume";
265  tone.mState = State::RELEASED;
266  }
267  break;
268  case State::RELEASE_REQUESTED:
269  {
270  int key = tone.mKey;
271 
272  // Play at least 1/10 seconds
273  if (tone.mTime < getIncrement(key)*mSampler.getSampleRate()/10) break;
274 
275  // Do not release if sustain pedal is pressed
276  if (mSustainPedeal and tone.mVolume > releaseVolume) break;
277 
278  LOGSTATUS << "Retriggering off key" << key;
279  tone.mState = State::RELEASED;
280  int scale = tone.mScaleIndex;
281  Wave &sample = pVoice->getScale(scale).getWave(key);
282  if (not sample.releaseExists()) break;
283 
284  quint32 arrayindex = (tone.mTime>>11) & 0x1FFFFEU;
285  double env = tone.getSample().getEnvelope(arrayindex);
286  double vol=0;
287 
288  // TODO volume control both here and in importer
289  // hard-coded for Hofstallstrasse
290  switch(tone.mScaleIndex)
291  {
292  case 0:
293  {
294  vol = pow(env/500.0*tone.mVolume,0.5);
295  if (key<30) vol *= pow(key/30.0,3);
296  }
297  break;
298  case 1:
299  {
300  vol = pow(env/80.0*tone.mVolume,0.35);
301  }
302  break;
303  default: {}
304  }
305 
306  if (vol>5) vol=5;
307 
308  Tone T(key,scale,sample,vol,Tone::Type::RELEASETONE);
309  T.mState = State::OFF_PLAYING;
310  mTones.push_back(T);
311  exit = true;
312  }
313  break;
314  case State::RELEASED:
315  {
316  if (tone.mVolume < terminationVolume)
317  {
318  LOGSTATUS << "Terminated key" << tone.mKey << "below infinitesimal volume";
319  tone.mState = State::TERMINATED;
320  }
321  }
322  break;
323  default:
324  break;
325  }
326  if (exit) break;
327  if (not mQueue.empty()) return;
328  }
329 
330 
331  // remove the terminated entries from the mTones vector
332  for (auto it = mTones.begin(); it != mTones.end(); /* no increment */)
333  {
334  if (it->mState == State::TERMINATED)
335  {
336  LOGSTATUS << "Remove key" << it->mKey << "from mTones";
337  it = mTones.erase(it);
338  if (not mQueue.empty()) return;
339  }
340  else ++it;
341  }
342 }
343 
344 
345 
346 
348 {
349  QMutexLocker locker (&mQueueMutex);
350  return not mQueue.empty();
351 }
352 
354 {
355  QMutexLocker locker(&mIncrementMutex);
356  return mIncrements[key & 0x7F];
357 }
358 
359 void SoundGenerator::setIncrement(int key, quint32 increment)
360 {
361  QMutexLocker locker(&mIncrementMutex);
362  mIncrements[key&0x7F] = increment;
363 }
364 
366 {
367  QMutexLocker locker(&mIncrementMutex);
368  mIncrements.resize(128);
369  mIncrements.fill(0x1000);
370 }
371 
372 
373 
374 //-----------------------------------------------------------------------------
375 // Register a tuning correction
376 //-----------------------------------------------------------------------------
377 
378 void SoundGenerator::receiveTuningCorrections(QMap<int,double> corrections)
379 {
380  // fast function approximating 0x1000*2^(cent/1200)
381  if (not pVoice) return;
382  auto f = [] (double cent) { return static_cast<int>( 0.5 + 4096. + 2.36594*cent + 0.000683311*cent*cent ); };
383 
384  for (auto it = corrections.begin(); it!=corrections.end(); it++)
385  {
386  if (it.key()<0 or it.key()>127) continue;
387  setIncrement(it.key(),f(it.value() - pVoice->getRecordedPitch(it.key())));
388  }
389 }
390 
392 {
393  LOGSTATUS << "Sustain pedal =" << pressed;
394  mSustainPedeal = pressed;
395 }
396 
398 {
399  LOGSTATUS << "Sostenuto pedal =" << pressed;
400  mSostenutoPedal = pressed;
401 }
402 
404 {
405  LOGSTATUS << "Sostenuto pedal =" << pressed;
406  mSoftPedal = pressed;
407 }
408 
409 
410 
void setTimerInterval(const int msec, const int firstMsec=0)
Set timer interval for the periodically called worker.
Definition: threadbase.cpp:117
double intensity
Volume.
Definition: request.h:51
bool isActive() const
Return true if thread is running and not suspended.
Definition: threadbase.cpp:136
bool waveFormExists()
Definition: wave.h:74
Wave & getWave(int &key)
Get the wave corresponding to a given key.
Definition: scale.cpp:57
Class holding the sampled sound for one key.
Definition: wave.h:49
virtual void suspend()
Mark the thread as suspended.
Definition: threadbase.cpp:84
Structure containing the data for a play-note request.
Definition: request.h:36
Voice - a set of scales, keeping the acoustic data of an instrument.
Definition: voice.h:39
Adiabatically drive the overall pitch down.
Definition: request.h:44
void receiveTuningCorrections(QMap< int, double > corrections)
void registerRequest(Request &request)
Register a single request to play a note in the queue.
void handleRequest(Request &request)
double getEnvelope(int index)
Definition: wave.h:71
QString f(Eigen::MatrixXd mat)
Definition: tunerdebug.h:29
Class describing a tone that is currently being played.
Definition: tone.h:45
Type
Definition: tone.h:48
Scale & getScale(int i)
Definition: voice.h:59
bool init() override
Virtual initialization function (no functionality here)
Stop all notes.
Definition: request.h:42
int key
Key index according to MIDI norm (0..127)
Definition: request.h:50
void init(SoundGenerator *soundgenerator)
Initialization of the sampler.
Definition: sampler.cpp:56
QVector< quint32 > mIncrements
State mState
Phase in which the tone is playing.
Definition: tone.h:78
Command command
command
Definition: request.h:48
void setIncrement(int key, quint32 increment)
void registerAllNotesOff()
QMutex mIncrementMutex
bool releaseExists()
Definition: wave.h:75
void timeout() override final
Private thread worker function.
Application * pApplication
#define LOGSTATUS
Definition: log.h:42
void setSustainPedal(bool pressed)
virtual bool exit()
Virtual exit function (no functionality here)
Definition: threadbase.h:71
void setSoftPedal(bool pressed)
void suspend() override final
Mark the thread as suspended.
Main application.
Definition: application.h:60
bool isInterruptionRequested() const
Return true if the thread was requested to interrupt or terminate.
Definition: threadbase.cpp:128
void forceImmediateProcessing()
QList< Tone > mTones
Play a note with parameters listed below.
Definition: request.h:40
quint32 getIncrement(int key)
QQueue< Request > mQueue
Adiabatically drive the overall pitch up.
Definition: request.h:43
Stop playing the note.
Definition: request.h:41
#define LOGWARNING
Definition: log.h:44
void setSostenutoPedal(bool pressed)