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
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
21 changes: 16 additions & 5 deletions LASS/src/Reverb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,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 +437,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