Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

339 lines
9.6KB

  1. /*************************************************************************************
  2. * Original code copyright (C) 2012 Steve Folta
  3. * Converted to Juce module (C) 2016 Leo Olivers
  4. * Forked from https://github.com/stevefolta/SFZero
  5. * For license info please see the LICENSE file distributed with this source code
  6. *************************************************************************************/
  7. #include "SFZDebug.h"
  8. #include "SFZRegion.h"
  9. #include "SFZSample.h"
  10. #include "SFZSound.h"
  11. #include "SFZVoice.h"
  12. #include "water/midi/MidiMessage.h"
  13. #include <cmath>
  14. namespace sfzero
  15. {
  16. static const float globalGain = -1.0;
  17. Voice::Voice()
  18. : region_(nullptr), trigger_(0), curMidiNote_(0), curPitchWheel_(0), pitchRatio_(0), noteGainLeft_(0), noteGainRight_(0),
  19. sourceSamplePosition_(0), sampleEnd_(0), loopStart_(0), loopEnd_(0), numLoops_(0), curVelocity_(0)
  20. {
  21. ampeg_.setExponentialDecay(true);
  22. }
  23. Voice::~Voice() {}
  24. bool Voice::canPlaySound(water::SynthesiserSound *sound) { return dynamic_cast<Sound *>(sound) != nullptr; }
  25. void Voice::startNote(int midiNoteNumber, float floatVelocity, water::SynthesiserSound *soundIn,
  26. int currentPitchWheelPosition)
  27. {
  28. Sound *sound = dynamic_cast<Sound *>(soundIn);
  29. if (sound == nullptr)
  30. {
  31. killNote();
  32. return;
  33. }
  34. int velocity = static_cast<int>(floatVelocity * 127.0);
  35. curVelocity_ = velocity;
  36. if (region_ == nullptr)
  37. {
  38. region_ = sound->getRegionFor(midiNoteNumber, velocity);
  39. }
  40. if ((region_ == nullptr) || (region_->sample == nullptr) || (region_->sample->getBuffer() == nullptr))
  41. {
  42. killNote();
  43. return;
  44. }
  45. if (region_->negative_end)
  46. {
  47. killNote();
  48. return;
  49. }
  50. // Pitch.
  51. curMidiNote_ = midiNoteNumber;
  52. curPitchWheel_ = currentPitchWheelPosition;
  53. calcPitchRatio();
  54. // Gain.
  55. double noteGainDB = globalGain + region_->volume;
  56. // Thanks to <http:://www.drealm.info/sfz/plj-sfz.xhtml> for explaining the
  57. // velocity curve in a way that I could understand, although they mean
  58. // "log10" when they say "log".
  59. double velocityGainDB = -20.0 * log10((127.0 * 127.0) / (velocity * velocity));
  60. velocityGainDB *= region_->amp_veltrack / 100.0;
  61. noteGainDB += velocityGainDB;
  62. noteGainLeft_ = noteGainRight_ = decibelsToGain(noteGainDB);
  63. // The SFZ spec is silent about the pan curve, but a 3dB pan law seems
  64. // common. This sqrt() curve matches what Dimension LE does; Alchemy Free
  65. // seems closer to sin(adjustedPan * pi/2).
  66. double adjustedPan = (region_->pan + 100.0) / 200.0;
  67. noteGainLeft_ *= static_cast<float>(sqrt(1.0 - adjustedPan));
  68. noteGainRight_ *= static_cast<float>(sqrt(adjustedPan));
  69. ampeg_.startNote(&region_->ampeg, floatVelocity, getSampleRate(), &region_->ampeg_veltrack);
  70. // Offset/end.
  71. sourceSamplePosition_ = static_cast<double>(region_->offset);
  72. sampleEnd_ = region_->sample->getSampleLength();
  73. if ((region_->end > 0) && (region_->end < sampleEnd_))
  74. {
  75. sampleEnd_ = region_->end + 1;
  76. }
  77. // Loop.
  78. loopStart_ = loopEnd_ = 0;
  79. Region::LoopMode loopMode = region_->loop_mode;
  80. if (loopMode == Region::sample_loop)
  81. {
  82. if (region_->sample->getLoopStart() < region_->sample->getLoopEnd())
  83. {
  84. loopMode = Region::loop_continuous;
  85. }
  86. else
  87. {
  88. loopMode = Region::no_loop;
  89. }
  90. }
  91. if ((loopMode != Region::no_loop) && (loopMode != Region::one_shot))
  92. {
  93. if (region_->loop_start < region_->loop_end)
  94. {
  95. loopStart_ = region_->loop_start;
  96. loopEnd_ = region_->loop_end;
  97. }
  98. else
  99. {
  100. loopStart_ = region_->sample->getLoopStart();
  101. loopEnd_ = region_->sample->getLoopEnd();
  102. }
  103. }
  104. numLoops_ = 0;
  105. }
  106. void Voice::stopNote(float /*velocity*/, bool allowTailOff)
  107. {
  108. if (!allowTailOff || (region_ == nullptr))
  109. {
  110. killNote();
  111. return;
  112. }
  113. if (region_->loop_mode != Region::one_shot)
  114. {
  115. ampeg_.noteOff();
  116. }
  117. if (region_->loop_mode == Region::loop_sustain)
  118. {
  119. // Continue playing, but stop looping.
  120. loopEnd_ = loopStart_;
  121. }
  122. }
  123. void Voice::stopNoteForGroup()
  124. {
  125. if (region_->off_mode == Region::fast)
  126. {
  127. ampeg_.fastRelease();
  128. }
  129. else
  130. {
  131. ampeg_.noteOff();
  132. }
  133. }
  134. void Voice::stopNoteQuick() { ampeg_.fastRelease(); }
  135. void Voice::pitchWheelMoved(int newValue)
  136. {
  137. if (region_ == nullptr)
  138. {
  139. return;
  140. }
  141. curPitchWheel_ = newValue;
  142. calcPitchRatio();
  143. }
  144. void Voice::controllerMoved(int /*controllerNumber*/, int /*newValue*/) { /***/}
  145. void Voice::renderNextBlock(water::AudioSampleBuffer &outputBuffer, int startSample, int numSamples)
  146. {
  147. if (region_ == nullptr)
  148. {
  149. return;
  150. }
  151. water::AudioSampleBuffer *buffer = region_->sample->getBuffer();
  152. const float *inL = buffer->getReadPointer(0, 0);
  153. const float *inR = buffer->getNumChannels() > 1 ? buffer->getReadPointer(1, 0) : nullptr;
  154. float *outL = outputBuffer.getWritePointer(0, startSample);
  155. float *outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer(1, startSample) : nullptr;
  156. int bufferNumSamples = buffer->getNumSamples(); // leoo
  157. // Cache some values, to give them at least some chance of ending up in
  158. // registers.
  159. double sourceSamplePosition = this->sourceSamplePosition_;
  160. float ampegGain = ampeg_.getLevel();
  161. float ampegSlope = ampeg_.getSlope();
  162. int samplesUntilNextAmpSegment = ampeg_.getSamplesUntilNextSegment();
  163. bool ampSegmentIsExponential = ampeg_.getSegmentIsExponential();
  164. float loopStart = static_cast<float>(this->loopStart_);
  165. float loopEnd = static_cast<float>(this->loopEnd_);
  166. float sampleEnd = static_cast<float>(this->sampleEnd_);
  167. while (--numSamples >= 0)
  168. {
  169. int pos = static_cast<int>(sourceSamplePosition);
  170. jassert(pos >= 0 && pos < bufferNumSamples); // leoo
  171. float alpha = static_cast<float>(sourceSamplePosition - pos);
  172. float invAlpha = 1.0f - alpha;
  173. int nextPos = pos + 1;
  174. if ((loopStart < loopEnd) && (nextPos > loopEnd))
  175. {
  176. nextPos = static_cast<int>(loopStart);
  177. }
  178. // Simple linear interpolation with buffer overrun check
  179. float nextL = nextPos < bufferNumSamples ? inL[nextPos] : inL[pos];
  180. float nextR = inR ? (nextPos < bufferNumSamples ? inR[nextPos] : inR[pos]) : nextL;
  181. float l = (inL[pos] * invAlpha + nextL * alpha);
  182. float r = inR ? (inR[pos] * invAlpha + nextR * alpha) : l;
  183. //// Simple linear interpolation, old version (possible buffer overrun with non-loop??)
  184. // float l = (inL[pos] * invAlpha + inL[nextPos] * alpha);
  185. // float r = inR ? (inR[pos] * invAlpha + inR[nextPos] * alpha) : l;
  186. float gainLeft = noteGainLeft_ * ampegGain;
  187. float gainRight = noteGainRight_ * ampegGain;
  188. l *= gainLeft;
  189. r *= gainRight;
  190. // Shouldn't we dither here?
  191. if (outR)
  192. {
  193. *outL++ += l;
  194. *outR++ += r;
  195. }
  196. else
  197. {
  198. *outL++ += (l + r) * 0.5f;
  199. }
  200. // Next sample.
  201. sourceSamplePosition += pitchRatio_;
  202. if ((loopStart < loopEnd) && (sourceSamplePosition > loopEnd))
  203. {
  204. sourceSamplePosition = loopStart;
  205. numLoops_ += 1;
  206. }
  207. // Update EG.
  208. if (ampSegmentIsExponential)
  209. {
  210. ampegGain *= ampegSlope;
  211. }
  212. else
  213. {
  214. ampegGain += ampegSlope;
  215. }
  216. if (--samplesUntilNextAmpSegment < 0)
  217. {
  218. ampeg_.setLevel(ampegGain);
  219. ampeg_.nextSegment();
  220. ampegGain = ampeg_.getLevel();
  221. ampegSlope = ampeg_.getSlope();
  222. samplesUntilNextAmpSegment = ampeg_.getSamplesUntilNextSegment();
  223. ampSegmentIsExponential = ampeg_.getSegmentIsExponential();
  224. }
  225. if ((sourceSamplePosition >= sampleEnd) || ampeg_.isDone())
  226. {
  227. killNote();
  228. break;
  229. }
  230. }
  231. this->sourceSamplePosition_ = sourceSamplePosition;
  232. ampeg_.setLevel(ampegGain);
  233. ampeg_.setSamplesUntilNextSegment(samplesUntilNextAmpSegment);
  234. }
  235. bool Voice::isPlayingNoteDown() { return region_ && region_->trigger != Region::release; }
  236. bool Voice::isPlayingOneShot() { return region_ && region_->loop_mode == Region::one_shot; }
  237. int Voice::getGroup() { return region_ ? region_->group : 0; }
  238. water::int64 Voice::getOffBy() { return region_ ? region_->off_by : 0; }
  239. void Voice::setRegion(Region *nextRegion) { region_ = nextRegion; }
  240. water::String Voice::infoString()
  241. {
  242. const char *egSegmentNames[] = {"delay", "attack", "hold", "decay", "sustain", "release", "done"};
  243. const static int numEGSegments(sizeof(egSegmentNames) / sizeof(egSegmentNames[0]));
  244. const char *egSegmentName = "-Invalid-";
  245. int egSegmentIndex = ampeg_.segmentIndex();
  246. if ((egSegmentIndex >= 0) && (egSegmentIndex < numEGSegments))
  247. {
  248. egSegmentName = egSegmentNames[egSegmentIndex];
  249. }
  250. water::String info;
  251. info << "note: " << curMidiNote_ << ", vel: " << curVelocity_ << ", pan: " << region_->pan << ", eg: " << egSegmentName
  252. << ", loops: " << numLoops_;
  253. return info;
  254. }
  255. void Voice::calcPitchRatio()
  256. {
  257. double note = curMidiNote_;
  258. note += region_->transpose;
  259. note += region_->tune / 100.0;
  260. double adjustedPitch = region_->pitch_keycenter + (note - region_->pitch_keycenter) * (region_->pitch_keytrack / 100.0);
  261. if (curPitchWheel_ != 8192)
  262. {
  263. double wheel = ((2.0 * curPitchWheel_ / 16383.0) - 1.0);
  264. if (wheel > 0)
  265. {
  266. adjustedPitch += wheel * region_->bend_up / 100.0;
  267. }
  268. else
  269. {
  270. adjustedPitch += wheel * region_->bend_down / -100.0;
  271. }
  272. }
  273. double targetFreq = fractionalMidiNoteInHz(adjustedPitch);
  274. double naturalFreq = water::MidiMessage::getMidiNoteInHertz(region_->pitch_keycenter);
  275. pitchRatio_ = (targetFreq * region_->sample->getSampleRate()) / (naturalFreq * getSampleRate());
  276. }
  277. void Voice::killNote()
  278. {
  279. region_ = nullptr;
  280. clearCurrentNote();
  281. }
  282. double Voice::fractionalMidiNoteInHz(double note, double freqOfA)
  283. {
  284. // Like MidiMessage::getMidiNoteInHertz(), but with a float note.
  285. note -= 69;
  286. // Now 0 = A
  287. return freqOfA * pow(2.0, note / 12.0);
  288. }
  289. }