Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions LASS/src/Envelope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,32 @@ m_value_type Envelope::getValue(m_value_type x, m_value_type totalLength)
current -= generatedSegmentLengths_->at(x_Index);
}

// We know that this segment interpolates between xy point
// x_Index and x_Index+1
xy_point left, right;
left = getPoint(x_Index);
right = getPoint(x_Index + 1);

// Figure out which sample we want from the interpolator.
// This is done by taking the percentage we have to iterate thru
// the interpolator, and then multiplying that by the number of samples
int sample = (int) (round(((x - current) / generatedSegmentLengths_->at(x_Index)) * 100.0));

interpolation_type segType = getSegmentInterpolationType(x_Index);

// Fast path for linear segments. Spinning up a 100-sample interpolator
// iterator and stepping it just to read one value is equivalent to the
// closed form below: the LinearInterpolatorIterator advances by
// delta = (right.y - left.y) / 100 each step and saturates once it runs
// out of steps (step 99), so clamp to reproduce that behavior exactly.
if (segType != EXPONENTIAL && segType != CUBIC_SPLINE) {
if (sample > 99) sample = 99;
return left.y + sample * (right.y - left.y) / 100.0;
}

//Spawn an interpolator of the proper type
Interpolator *interp;
switch (getSegmentInterpolationType(x_Index)) {
switch (segType) {
case EXPONENTIAL:
interp = new ExponentialInterpolator();
break;
Expand All @@ -252,21 +275,10 @@ m_value_type Envelope::getValue(m_value_type x, m_value_type totalLength)
// set interpolator duration to one second to make it easy
interp->setDuration(1.0);

// We know that this segment interpolates between xy point
// x_Index and x_Index+1
xy_point left, right;
left = getPoint(x_Index);
right = getPoint(x_Index + 1);

// Now tell the interpolator what values we are going between
interp->addEntry(0, left.y);
interp->addEntry(1, right.y);

// Figure out which sample we want from the interpolator.
// This is done by taking the percentage we have to iterate thru
// the interpolator, and then multiplying that by the number of samples
int sample = (int) (round(((x - current) / generatedSegmentLengths_->at(x_Index)) * 100.0));

// Create a value iterator to get values from the interpolator
Iterator < m_value_type > tempIterator = interp->valueIterator();

Expand Down
19 changes: 14 additions & 5 deletions LASS/src/Loudness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ void Loudness::calculate(Sound& snd, m_rate_type rate)
vector< Iterator<m_value_type> > freqIter;
// finally, grab the maximum waveshape value from each partial
vector<m_value_type> maxWaveShape;
// RELATIVE_AMPLITUDE is a static param (constant over time), so cache it
// here instead of re-looking it up for every partial on every sample below
vector<m_value_type> relAmp;
// go:
for (int i=0; i<numPartials; i++)
{
Expand All @@ -64,6 +67,7 @@ void Loudness::calculate(Sound& snd, m_rate_type rate)
part.getParam(FREQUENCY).setSamplingRate(rate);
freqIter.push_back( part.getParam(FREQUENCY).valueIterator() );
maxWaveShape.push_back( part.getParam(WAVE_SHAPE).getMaxValue() );
relAmp.push_back( part.getParam(RELATIVE_AMPLITUDE) );
}

// create a vector of critical band objects (one for each valid band)
Expand All @@ -73,7 +77,13 @@ void Loudness::calculate(Sound& snd, m_rate_type rate)

// calculate the number of samples to do loudness on:
m_sample_count_type numSamples = (m_sample_count_type) ((m_time_type)rate * (m_time_type)duration);


// LOUDNESS is a static param (constant over time); read it once
m_value_type loudness = snd.getParam(LOUDNESS);

// fully overwritten each sample, so allocate once and reuse
vector<m_value_type> bandGamma(NUM_BANDS);

// iterate over time:
for (int s=0; s<numSamples; s++)
{
Expand All @@ -91,7 +101,7 @@ void Loudness::calculate(Sound& snd, m_rate_type rate)
m_value_type waveShape = maxWaveShape[p];

// scale the amplitude by the relative amplitude:
m_value_type thisAmp = waveShape * snd.get(p).getParam(RELATIVE_AMPLITUDE);
m_value_type thisAmp = waveShape * relAmp[p];

// is this the loudest partial?
if (thisAmp > maxAmp) maxAmp = thisAmp;
Expand All @@ -104,7 +114,6 @@ void Loudness::calculate(Sound& snd, m_rate_type rate)
}

// calculate the band gamma for each band:
vector<m_value_type> bandGamma(NUM_BANDS);
for (int i=0; i<NUM_BANDS; i++)
bandGamma[i] = CBands[i].getBandGamma(maxAmp);

Expand All @@ -117,7 +126,7 @@ void Loudness::calculate(Sound& snd, m_rate_type rate)
m_value_type gammaTotal = 0.0;
for (int i=0; i<NUM_BANDS; i++)
if (i != maxGamma) gammaTotal += bandGamma[i] * BANDS[i][F_FACTOR];
m_value_type numerator = snd.getParam(LOUDNESS) / (bandGamma[(int)maxGamma] + gammaTotal);
m_value_type numerator = loudness / (bandGamma[(int)maxGamma] + gammaTotal);

// for each band:
for (int b=0; b<NUM_BANDS; b++)
Expand All @@ -133,7 +142,7 @@ void Loudness::calculate(Sound& snd, m_rate_type rate)
int partial_id = CBands[b].partials_[p].ID_;

// scale the factor by the relative amplitude again:
scaleFactor *= snd.get(partial_id).getParam(RELATIVE_AMPLITUDE);
scaleFactor *= relAmp[partial_id];

//add the scaling factor for this moment in time.
scalingFactors[partial_id].addEntry(relativeTime, scaleFactor);
Expand Down
2 changes: 2 additions & 0 deletions LASS/src/MultiPan.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ class MultiPan : public Spatializer
**/
MultiTrack* spatialize_Track(Track& t, int numTracks);

bool isPlaceholder() const { return false; }

/**
* \deprecated
**/
Expand Down
4 changes: 3 additions & 1 deletion LASS/src/Pan.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ class Pan : public Spatializer
**/
MultiTrack* spatialize_Track(Track& t, int numTracks);

/**
bool isPlaceholder() const { return false; }

/**
* \deprecated
**/
void xml_print( ofstream& xmlOutput );
Expand Down
29 changes: 18 additions & 11 deletions LASS/src/Partial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -374,16 +374,24 @@ MultiTrack* Partial::render(int numChannels,
_track = &tmp;
}

/* ZIYUAN CHEN, July 2023: On the default behavior of spatialization (Partial side)
* If a non-placeholder subclass of Spatializer (Pan/MultiPan) is set in the sound,
* "spatializer_" in the partials will be placeholders that merely averages the
* sound evenly accross all tracks.
* But unlike Sound::render(), the placeholders are NOT IGNORED since they are essential
* for transforming Track to MultiTrack, whose channels uniformly hold scaled versions
* of the original Track.
* Compare Sound::render().
*/
MultiTrack* returnTrack = spatializer_->spatialize_Track(*_track, numChannels);
/* ZIYUAN CHEN, July 2023 (revised by Jacob McGrath, June 2026) */
MultiTrack* returnTrack;
if (spatializer_->isPlaceholder())
{
// The placeholder spatializer would expand into `numChannels` identical
// copies if done here, so instead we return a single-track (mono)
// `MultiTrack` and let `Sound::render()` perform one placeholder expansion after
// all partials are composited (scaled by `1/numChannels`).
returnTrack = new MultiTrack();
returnTrack->add(_track); // transfer ownership of the Track
}
else
{
// Non-placeholder subclasses (e.g., `Pan`/`MultiPan`) spatialize each partial
// differently, so we must perform the expansion per-partial.
returnTrack = spatializer_->spatialize_Track(*_track, numChannels);
delete _track; // spatialize_Track copies its input; free the original
}

//cout << "Partial::render - frequency after detune:" << getParam(FREQ_ENV).getMaxValue() << endl;
// cout<< "--------------------------------------------"<< endl;
Expand All @@ -396,7 +404,6 @@ MultiTrack* Partial::render(int numChannels,
delete freqtrans_amp_env;
delete freqtrans_rate_env;


return returnTrack;
}

Expand Down
28 changes: 18 additions & 10 deletions LASS/src/Reverb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,6 @@ m_sample_type Reverb::do_reverb(m_sample_type x_t, float x_value)
m_sample_type Reverb::do_reverb(m_sample_type x_t, float x_value, Envelope *percentReverbinput)
{
m_sample_type y;
Envelope* temp = new Envelope(*percentReverbinput);
delete percentReverb;
percentReverb = temp;

// run the sample through various comb filters (for effeciency
// reasons, I hard coded this (instead of looping from 0 to
Expand All @@ -286,8 +283,8 @@ m_sample_type Reverb::do_reverb(m_sample_type x_t, float x_value, Envelope *perc
// after adding up the results, run it through an allpass filter
y = apfilter->do_filter(y);
// Mix it with the input sound
float durationofEnv = percentReverb->getDuration();
float EnvelopeValueAtx = percentReverb->getValue(x_value,durationofEnv);
float durationofEnv = percentReverbinput->getDuration();
float EnvelopeValueAtx = percentReverbinput->getValue(x_value,durationofEnv);
y = (EnvelopeValueAtx*y) + ((1 - EnvelopeValueAtx)*x_t);
return y;
}
Expand Down Expand Up @@ -403,9 +400,22 @@ SoundSample *Reverb::do_reverb_SoundSample(SoundSample *inWave, Envelope *percen
float* inData = &(*inWave)[0];
float* outData = &(*outWave)[0];

// Accumulate the 6 comb filter outputs.
vector<float> combSum(N, 0.0f);
vector<float> filterBuf(N);
// Reusable per-thread scratch buffers. Reverb is invoked once per partial,
// once per sound, and once for the whole score, so allocating and freeing
// four full-length buffers on every call was a significant source of churn.
// thread_local keeps each render thread's scratch alive (grown to the largest
// call it has seen) and is race-free regardless of whether a Reverb instance
// is shared across the worker threads. resize() reuses existing capacity.
thread_local vector<float> combSum, filterBuf, envVals, diff;
combSum.resize(N);
filterBuf.resize(N);
envVals.resize(N);
diff.resize(N);

// Accumulate the 6 comb filter outputs. combSum accumulates, so unlike the
// other scratch buffers (each fully overwritten before use) it must be
// re-zeroed on every call.
vDSP_vclr(combSum.data(), 1, (vDSP_Length)N);
for (int f = 0; f < REVERB_NUM_COMB_FILTERS; f++) {
lpcfilter[f]->do_filter_buffer(inData, filterBuf.data(), N);
vDSP_vadd(combSum.data(), 1, filterBuf.data(), 1,
Expand All @@ -424,12 +434,10 @@ SoundSample *Reverb::do_reverb_SoundSample(SoundSample *inWave, Envelope *percen
// = env[i] * (apOut[i] - in[i]) + in[i]
float duration = percentReverb->getDuration();
float invN = 1.0f / (float)N;
vector<float> envVals(N);
for (long i = 0; i < N; i++)
envVals[i] = percentReverb->getValue((float)i * invN, duration);

// diff[i] = apOut[i] - in[i] (vDSP_vsub: C = B - A)
vector<float> diff(N);
vDSP_vsub(inData, 1, outData, 1, diff.data(), 1, (vDSP_Length)N);

// outData[i] = envVals[i] * diff[i] + in[i]
Expand Down
66 changes: 32 additions & 34 deletions LASS/src/Sound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,47 +311,45 @@ MultiTrack* Sound::render(
cout << "\t Applying Reverb..." << endl;
MultiTrack &reverbedTrack = reverbObj->do_reverb_MultiTrack(*composite);
delete composite;
composite = &reverbedTrack;
}

//------------------
// spatialize the sound into a MultiTrack object
//------------------
//------------------
// spatialize the sound into a MultiTrack object
//------------------

/* ZIYUAN CHEN, July 2023: On the default behavior of spatialization (Sound side)
* If a non-placeholder subclass of Spatializer (Pan/MultiPan) is set in the partials,
* "spatializer_" in the sound will be a placeholder and should be IGNORED.
* Otherwise, (indirectly) calling Spatializer::spatialize_Track() will cause each
* track to be averaged across all channels (default placeholder behavior) and
* OVERWRITE any spatialization performed by the partials!
/* Spatialization is applied at exactly one level -- this sound or its
* partials, never both (the other level holds a placeholder).
* If the partials carried the real Pan/MultiPan they already produced
* numChannels tracks; re-running a sound-level spatializer would average
* them together and destroy that per-partial spatialization, so we skip
* it (guarded by spa_modified_ below).
* If the partials used placeholders they returned single (mono) tracks, and
* the deferred fan-out below promotes the composite to numChannels using
* this sound's spatializer -- a real Pan/MultiPan if one was set, else the
* placeholder that averages evenly across channels.
* Compare Partial::render().
*/
cout << "\t Spatializing..." << endl;

if (!spa_modified_)
return &reverbedTrack;

MultiTrack* mt = spatializer_->spatialize_MultiTrack(reverbedTrack, numChannels, sampleCount, samplingRate);

// delete the temporary track object that held the unspatialized reverbed sound
delete &reverbedTrack;

return mt;
}
else
{
//------------------
// spatialize the sound into a MultiTrack object
//------------------

/* ZIYUAN CHEN, July 2023 - See above */
cout << "\t Spatializing..." << endl;
// When the partials used placeholder spatializers, Partial::render() returned
// single-track (mono) MultiTracks, so "composite" has fewer than numChannels
// tracks. Perform the deferred composite spatialization here.
if (composite->size() < numChannels) {
cout << "\t Spatializing..." << endl;
MultiTrack* mt = spatializer_->spatialize_MultiTrack(*composite, numChannels, sampleCount, samplingRate);
delete composite;
return mt;
}

if (!spa_modified_)
return composite;
// Otherwise the partials already produced numChannels tracks (real per-partial
// spatialization), so a sound-level spatializer would only overwrite them.
if (!spa_modified_)
return composite;

MultiTrack* mt = spatializer_->spatialize_MultiTrack(*composite, numChannels, sampleCount, samplingRate);
delete composite;
return mt;
}
cout << "\t Spatializing..." << endl;
MultiTrack* mt = spatializer_->spatialize_MultiTrack(*composite, numChannels, sampleCount, samplingRate);
delete composite;
return mt;
}

//----------------------------------------------------------------------------//
Expand Down
37 changes: 22 additions & 15 deletions LASS/src/Spatializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,29 +60,36 @@ class Spatializer
**/
virtual MultiTrack* spatialize_Track(Track& t, int numTracks);

/** ZIYUAN CHEN, July 2023
* This will take a MultiTrack object, and spatialize it
* to a MultiTrack object with numTracks tracks.
* CASE 1: If spatialization is applied [by sound], this method
* will superimpose numTracks spatialized copies of identical
* scaled tracks generated by the placeholder spatialize_Track
* method when composing "composite."
* CASE 2: If spatialization is applied [by partials], this method
* will effectively do nothing since the partials are already
* spatialized when composing "composite." This logic is NOT
* implemented in the function (it's tracked by spa_modified_).
* This is a shared method and is NOT supposed to be overridden
* by inheriting classes.
* \param t The MultiTrack to spatialize
* \param numTracks The number of MultiTracks *in both input and output*
/**
* \brief Spatialize a MultiTrack into one with numTracks channels
* \details Each input track is fanned out via spatialize_Track and the
* results are superimposed onto the output.
* (When the partials carried the real (e.g., `Pan`/`MultiPan`) spatializer
* they already produced `numTracks` channels and `Sound::render()` skips
* this call, since running it would overwrite that per-partial spatialization.)
* \note This is a shared method and is NOT supposed to be overridden by
* inheriting classes; only `spatialize_Track` is virtual.
* \param t The MultiTrack to spatialize (typically a single mono track)
* \param numTracks The number of channels in the output
* \param sampleCount The number of samples to process
* \param samplingRate The sampling rate
* \return a MultiTrack
* \author Ziyuan Chen
**/
virtual MultiTrack* spatialize_MultiTrack(MultiTrack& t, int numTracks,
m_sample_count_type sampleCount,
m_rate_type samplingRate = DEFAULT_SAMPLING_RATE);

/**
* Reports whether this is the default placeholder spatializer (even
* averaging across channels) as opposed to a `Pan`/`MultiPan`.
* `Partial::render()` uses this to skip the per-partial channel expansion
* when it would only produce identical scaled copies, deferring a
* single expansion to `Sound::render()` and saving `numChannels-1`
* buffers per partial.
**/
virtual bool isPlaceholder() const { return true; }

/**
* This function creates an exact duplicate of this Spatializer.
**/
Expand Down
Loading