-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
678 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
============================================================================== | ||
This file is part of the GIN library. | ||
Copyright (c) 2019 - Roland Rabien. | ||
============================================================================== | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
/* | ||
============================================================================== | ||
This file is part of the GIN library. | ||
Copyright (c) 2019 - Roland Rabien. | ||
============================================================================== | ||
*/ | ||
|
||
#pragma once | ||
|
||
//============================================================================== | ||
/** MSEG LFO | ||
*/ | ||
class MSEG | ||
{ | ||
public: | ||
//============================================================================== | ||
struct Parameters | ||
{ | ||
float frequency = 0, phase = 0, offset = 0, depth = 0, delay = 0, fade = 0; | ||
}; | ||
|
||
struct Point | ||
{ | ||
float time = 0.0f; | ||
float value = 0.0f; | ||
float curve = 0.0f; | ||
}; | ||
|
||
struct Data | ||
{ | ||
juce::Array<Point> points; | ||
int numPoints = 0; | ||
int maxPoints = 50; | ||
|
||
void toValueTree (juce::ValueTree v) | ||
{ | ||
v.removeAllChildren (nullptr); | ||
|
||
for (auto i = 0; i < numPoints; i++) | ||
{ | ||
juce::ValueTree c (juce::Identifier ("PT")); | ||
|
||
c.setProperty ("t", points[i].time, nullptr); | ||
c.setProperty ("v", points[i].value, nullptr); | ||
c.setProperty ("c", points[i].curve, nullptr); | ||
|
||
v.addChild (c, -1, nullptr); | ||
} | ||
} | ||
|
||
void fromValueTree (const juce::ValueTree& v) | ||
{ | ||
if (! v.isValid()) | ||
{ | ||
reset(); | ||
return; | ||
} | ||
|
||
numPoints = v.getNumChildren(); | ||
|
||
int i = 0; | ||
for (auto c : v) | ||
{ | ||
auto& p = points.getReference (i); | ||
p.time = c.getProperty ("t"); | ||
p.value = c.getProperty ("v"); | ||
p.curve = c.getProperty ("c"); | ||
i++; | ||
} | ||
} | ||
|
||
void reset() | ||
{ | ||
points.set (0, { 0.0f, 0.0f, 5.5f }); | ||
points.set (1, { 0.5f, 1.0f, 5.5f }); | ||
points.set (2, { 1.0f, 0.0f, 5.5f }); | ||
numPoints = 3; | ||
} | ||
}; | ||
|
||
//============================================================================== | ||
MSEG (Data& d) | ||
: data (d) | ||
{ | ||
data.points.resize (data.maxPoints); | ||
|
||
jassert (data.maxPoints >= 3); | ||
|
||
if (data.numPoints < 2) | ||
data.reset(); | ||
} | ||
|
||
int getMaxPoints() { return data.maxPoints; } | ||
juce::Array<Point>& getPoints() { return data.points; } | ||
|
||
//============================================================================== | ||
void setSampleRate (double sr) { sampleRate = sr; } | ||
void setParameters (Parameters p) { parameters = p; } | ||
void reset() | ||
{ | ||
output = 0.0f; | ||
phase = 0.0f; | ||
curPhase = 0.0f; | ||
curFade = 1.0f; | ||
fadeDelta = 0.0f; | ||
delaySteps = 0; | ||
} | ||
|
||
void setPoints (const juce::Array<Point>& p) | ||
{ | ||
data.numPoints = p.size(); | ||
for (auto i = 0; i < data.numPoints; i++) | ||
data.points[i] = p[i]; | ||
} | ||
|
||
void noteOn (float phase_ = -1.0f) | ||
{ | ||
if (parameters.fade <= 0) | ||
curFade = 1.0f; | ||
else | ||
curFade = 0.0f; | ||
|
||
curPhase = 0.0f; | ||
phase = phase_ < 0.0f ? 0.0f : phase_; | ||
fadeDelta = float (1.0f / (sampleRate * parameters.fade)); | ||
delaySteps = juce::roundToInt (sampleRate * parameters.delay); | ||
|
||
float maxPhase = 1.0f; | ||
float newCurPhase = std::fmod (phase + parameters.phase, maxPhase); | ||
if (newCurPhase < 0) newCurPhase += maxPhase; | ||
|
||
updateCurrentValue(); | ||
} | ||
|
||
float process (int numSamples) | ||
{ | ||
jassert (sampleRate > 0); | ||
|
||
float step = 0.0f; | ||
if (parameters.frequency > 0.0001f) | ||
step = float (parameters.frequency / sampleRate); | ||
|
||
for (int i = 0; i < numSamples; i++) | ||
{ | ||
if (delaySteps > 0) | ||
{ | ||
delaySteps--; | ||
} | ||
else | ||
{ | ||
curFade = juce::jlimit (0.0f, 1.0f, curFade + fadeDelta); | ||
|
||
float maxPhase = 1.0f; | ||
|
||
phase += step; | ||
while (phase >= maxPhase) | ||
phase -= maxPhase; | ||
|
||
float newCurPhase = std::fmod (phase + parameters.phase, maxPhase); | ||
if (newCurPhase < 0) newCurPhase += maxPhase; | ||
|
||
curPhase = newCurPhase; | ||
} | ||
} | ||
|
||
return updateCurrentValue(); | ||
} | ||
|
||
float getOutput() | ||
{ | ||
return curFade * output * parameters.depth + parameters.offset; | ||
} | ||
|
||
float getCurrentPhase() | ||
{ | ||
return phase; | ||
} | ||
|
||
// Get value at phase, not taking into account phase offset, fade, delay, depth, etc | ||
float getValueAt (float atPhase) | ||
{ | ||
for (auto i = 0; i < data.numPoints - 1; i++) | ||
{ | ||
const auto& p1 = data.points.getReference (i + 0); | ||
const auto& p2 = data.points.getReference (i + 1); | ||
|
||
if (atPhase >= p1.time && atPhase < p2.time) | ||
{ | ||
auto dy = p2.value - p1.value; | ||
auto dx = p2.time - p1.time; | ||
auto fr = (dx > 0.0f) ? ((atPhase - p1.time) / dx) : 1.0f; | ||
|
||
return dy * curve (fr, p1.curve) + p1.value; | ||
} | ||
} | ||
return 0; | ||
} | ||
|
||
private: | ||
Data& data; | ||
|
||
float updateCurrentValue() | ||
{ | ||
if (delaySteps == 0) | ||
{ | ||
for (auto i = 0; i < data.numPoints - 1; i++) | ||
{ | ||
const auto& p1 = data.points.getReference (i + 0); | ||
const auto& p2 = data.points.getReference (i + 1); | ||
|
||
if (curPhase >= p1.time && curPhase < p2.time) | ||
{ | ||
auto dy = p2.value - p1.value; | ||
auto dx = p2.time - p1.time; | ||
auto fr = (dx > 0.0f) ? ((curPhase - p1.time) / dx) : 1.0f; | ||
|
||
output = dy * curve (fr, p1.curve) + p1.value; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return curFade * output * parameters.depth + parameters.offset; | ||
} | ||
|
||
inline float curve (float fraction, float curve) | ||
{ | ||
if (curve < 0.0f) | ||
return ((1.0f + -curve) * (fraction) / (1.0f + -curve * (fraction))); | ||
|
||
return ((1.0f + curve) * ((fraction) - 1) / (1.0f + curve * (1.0f - (fraction))) + 1); | ||
} | ||
|
||
Parameters parameters; | ||
|
||
double sampleRate = 0.0; | ||
float phase = 0.0f, curPhase = 0.0f, output = 0.0f, fadeDelta = 0.0f, curFade = 1.0f; | ||
int delaySteps = 0; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.