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