summaryrefslogtreecommitdiff
path: root/src/loris/Oscillator.C
diff options
context:
space:
mode:
Diffstat (limited to 'src/loris/Oscillator.C')
-rw-r--r--src/loris/Oscillator.C305
1 files changed, 305 insertions, 0 deletions
diff --git a/src/loris/Oscillator.C b/src/loris/Oscillator.C
new file mode 100644
index 0000000..1ffc5c5
--- /dev/null
+++ b/src/loris/Oscillator.C
@@ -0,0 +1,305 @@
+/*
+ * This is the Loris C++ Class Library, implementing analysis,
+ * manipulation, and synthesis of digitized sounds using the Reassigned
+ * Bandwidth-Enhanced Additive Sound Model.
+ *
+ * Loris is Copyright (c) 1999-2010 by Kelly Fitz and Lippold Haken
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY, without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * Oscillator.C
+ *
+ * Implementation of class Loris::Oscillator, a Bandwidth-Enhanced Oscillator.
+ *
+ * Kelly Fitz, 31 Aug 1999
+ * loris@cerlsoundgroup.org
+ *
+ * http://www.cerlsoundgroup.org/Loris/
+ *
+ */
+
+#if HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include "Oscillator.h"
+
+#include "Filter.h"
+#include "Partial.h"
+#include "Notifier.h"
+
+#include <cmath>
+#include <vector>
+
+#if defined(HAVE_M_PI) && (HAVE_M_PI)
+ const double Pi = M_PI;
+#else
+ const double Pi = 3.14159265358979324;
+#endif
+const double TwoPi = 2*Pi;
+
+// begin namespace
+namespace Loris {
+
+
+// ---------------------------------------------------------------------------
+// Oscillator construction
+// ---------------------------------------------------------------------------
+// Initialize stochastic modulators and state variables.
+//
+Oscillator::Oscillator( void ) :
+ m_modulator( 1.0 /* seed */ ),
+ m_filter( prototype_filter() ),
+ m_instfrequency( 0 ),
+ m_instamplitude( 0 ),
+ m_instbandwidth( 0 ),
+ m_determphase( 0 )
+{
+}
+
+// ---------------------------------------------------------------------------
+// resetEnvelopes
+// ---------------------------------------------------------------------------
+// Reset the instantaneous envelope parameters
+// (frequency, amplitude, bandwidth, and phase).
+// The sample rate is needed to convert the
+// Breakpoint frequency (Hz) to radians per sample.
+//
+void
+Oscillator::resetEnvelopes( const Breakpoint & bp, double srate )
+{
+ // Remember that the oscillator only knows about
+ // radian frequency! Convert!
+ m_instfrequency = bp.frequency() * TwoPi / srate;
+ m_instamplitude = bp.amplitude();
+ m_instbandwidth = bp.bandwidth();
+ m_determphase = bp.phase();
+
+ // clamp bandwidth:
+ if ( m_instbandwidth > 1. )
+ {
+ debugger << "clamping bandwidth at 1." << endl;
+ m_instbandwidth = 1.;
+ }
+ else if ( m_instbandwidth < 0. )
+ {
+ debugger << "clamping bandwidth at 0." << endl;
+ m_instbandwidth = 0.;
+ }
+
+ // don't alias:
+ if ( m_instfrequency > Pi )
+ {
+ debugger << "fading out aliasing Partial" << endl;
+ m_instamplitude = 0.;
+ }
+
+ // Reset the fitler state too.
+ m_filter.clear();
+
+}
+
+// ---------------------------------------------------------------------------
+// m2pi
+// ---------------------------------------------------------------------------
+// O'Donnell's phase wrapping function.
+//
+static inline double m2pi( double x )
+{
+ using namespace std; // floor should be in std
+ #define ROUND(x) (floor(.5 + (x)))
+ return x + ( TwoPi * ROUND(-x/TwoPi) );
+}
+
+// ---------------------------------------------------------------------------
+// setPhase
+// ---------------------------------------------------------------------------
+// Reset the phase of the Oscillator to the specified
+// value, and clear the accumulated phase modulation. (?)
+// Or not.
+// This is done when the amplitude of a Partial goes to
+// zero, so that onsets are preserved in distilled
+// and collated Partials.
+//
+void
+Oscillator::setPhase( double ph )
+{
+ m_determphase = m2pi(ph);
+}
+
+// ---------------------------------------------------------------------------
+// oscillate
+// ---------------------------------------------------------------------------
+// Accumulate bandwidth-enhanced sinusoidal samples modulating the
+// oscillator state from its current values of radian frequency,
+// amplitude, and bandwidth to the specified target values, into
+// the specified half-open range of doubles.
+//
+// The caller must ensure that the range is valid. Target parameters
+// are bounds-checked.
+//
+void
+Oscillator::oscillate( double * begin, double * end,
+ const Breakpoint & bp, double srate )
+{
+ double targetFreq = bp.frequency() * TwoPi / srate; // radians per sample
+ double targetAmp = bp.amplitude();
+ double targetBw = bp.bandwidth();
+
+ // clamp bandwidth:
+ if ( targetBw > 1. )
+ {
+ debugger << "clamping bandwidth at 1." << endl;
+ targetBw = 1.;
+ }
+ else if ( targetBw < 0. )
+ {
+ debugger << "clamping bandwidth at 0." << endl;
+ targetBw = 0.;
+ }
+
+ // don't alias:
+ if ( targetFreq > Pi ) // radian Nyquist rate
+ {
+ debugger << "fading out Partial above Nyquist rate" << endl;
+ targetAmp = 0.;
+ }
+
+ // compute trajectories:
+ const double dTime = 1. / (end - begin);
+ const double dFreqOver2 = 0.5 * (targetFreq - m_instfrequency) * dTime;
+ // split frequency update in two steps, update phase using average
+ // frequency, after adding only half the frequency step
+
+ const double dAmp = (targetAmp - m_instamplitude) * dTime;
+ const double dBw = (targetBw - m_instbandwidth) * dTime;
+
+ // Use temporary local variables for speed.
+ // Probably not worth it when I am computing square roots
+ // and cosines...
+ double ph = m_determphase;
+ double f = m_instfrequency;
+ double a = m_instamplitude;
+ double bw = m_instbandwidth;
+
+ // Also use a more efficient sample loop when the bandwidth is zero.
+ if ( 0 < bw || 0 < dBw )
+ {
+ double am, nz;
+ for ( double * putItHere = begin; putItHere != end; ++putItHere )
+ {
+ // use math functions in namespace std:
+ using namespace std;
+
+ // compute amplitude modulation due to bandwidth:
+ //
+ // This will give the right amplitude modulation when scaled
+ // by the Partial amplitude:
+ //
+ // carrier amp: sqrt( 1. - bandwidth ) * amp
+ // modulation index: sqrt( 2. * bandwidth ) * amp
+ //
+ nz = m_filter.apply( m_modulator.sample() );
+ am = sqrt( 1. - bw ) + ( nz * sqrt( 2. * bw ) );
+
+ // compute a sample and add it into the buffer:
+ *putItHere += am * a * cos( ph );
+
+ // update the instantaneous oscillator state:
+ f += dFreqOver2;
+ ph += f; // frequency is radians per sample
+ f += dFreqOver2;
+ a += dAmp;
+ bw += dBw;
+ if (bw < 0.)
+ {
+ bw = 0.;
+ }
+ } // end of sample computation loop
+ }
+ else
+ {
+ for ( double * putItHere = begin; putItHere != end; ++putItHere )
+ {
+ // use math functions in namespace std:
+ using namespace std;
+
+ // no modulation when there is no bandwidth
+
+ // compute a sample and add it into the buffer:
+ *putItHere += a * cos( ph );
+
+ // update the instantaneous oscillator state:
+ f += dFreqOver2;
+ ph += f; // frequency is radians per sample
+ f += dFreqOver2;
+ a += dAmp;
+ } // end of sample computation loop
+
+ }
+
+
+ // copy out of the local variables?
+ // no need because we are assigning to the target
+ // values below:
+ /*
+ m_instfrequency = f;
+ m_instamplitude = a;
+ m_instbandwidth = bw;
+ */
+
+ // wrap phase to prevent eventual loss of precision at
+ // high oscillation frequencies:
+ // (Doesn't really matter much exactly how we wrap it,
+ // as long as it brings the phase nearer to zero.)
+ m_determphase = m2pi( ph );
+
+ // set the state variables to their target values,
+ // just in case they didn't arrive exactly (overshooting
+ // amplitude or, especially, bandwidth, could be bad, and
+ // it does happen):
+ m_instfrequency = targetFreq;
+ m_instamplitude = targetAmp;
+ m_instbandwidth = targetBw;
+}
+
+// ---------------------------------------------------------------------------
+// protoype filter (static member)
+// ---------------------------------------------------------------------------
+// Static local function for obtaining a prototype Filter
+// to use in Oscillator construction. Eventually, allow
+// external (client) specification of the Filter prototype.
+//
+const Filter &
+Oscillator::prototype_filter( void )
+{
+ // Chebychev order 3, cutoff 500, ripple -1.
+ //
+ // Coefficients obtained from http://www.cs.york.ac.uk/~fisher/mkfilter/
+ // Digital filter designed by mkfilter/mkshape/gencode A.J. Fisher
+ //
+ static const double Gain = 4.663939184e+04;
+ static const double ExtraScaling = 6.;
+ static const double MaCoefs[] = { 1., 3., 3., 1. };
+ static const double ArCoefs[] = { 1., -2.9258684252, 2.8580608586, -0.9320209046 };
+
+ static const Filter proto( MaCoefs, MaCoefs + 4, ArCoefs, ArCoefs + 4, ExtraScaling/Gain );
+ return proto;
+}
+
+
+
+} // end of namespace Loris