Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
da1cb2f
skeleton of voiceallocator, to troubleshoot multi control in management
tremblap Feb 27, 2023
4944184
added a better monitoring of 3 x 3 input
tremblap Feb 27, 2023
51376a4
clang'd
tremblap Feb 27, 2023
7ca651f
Merge branch 'main' into feature/voice-allocator
tremblap Aug 9, 2023
734c48b
allocate fixed arrays for the output to see if that helps...
tremblap Aug 14, 2023
23f1a72
Merge branch 'main' into feature/voice-allocator
tremblap Aug 14, 2023
b1af337
fix for dynamic mem alloc but breaks stats - pushed at @aharker's req…
tremblap Aug 16, 2023
e90830b
Merge remote-tracking branch 'origin/main' into feature/voice-allocator
tremblap Aug 17, 2023
26635b3
new const needs
tremblap Aug 17, 2023
e2ece72
Changes for how clients determine if the output follows the input size
AlexHarker Aug 17, 2023
d5b4a7c
remove duplicate
tremblap Aug 17, 2023
d6c2c20
VoiceAllocator.hpp -> VoiceAllocatorClient.hpp
fearn-e Aug 20, 2023
77c04d4
names & boilerplate
fearn-e Aug 20, 2023
8954b29
partialTracking new getActiveVoices function
fearn-e Aug 20, 2023
246eb25
parameter list for voiceallocator
fearn-e Aug 20, 2023
d6813d6
functional allocator but still bad track IDs
fearn-e Aug 20, 2023
6a9ae43
allocator algo somewhat implemented, compiles but weird behaviour
fearn-e Aug 21, 2023
0a45601
past me apparently hoping a non-looped IF can clear a queue
fearn-e Aug 21, 2023
be7af8e
proper conversion from amp to dB + convert back to amp
fearn-e Aug 21, 2023
61d6cb5
removing mInputSize
fearn-e Aug 21, 2023
4713d3c
comments
fearn-e Aug 21, 2023
af2c896
allows existing voices to change their freq/mag
fearn-e Aug 21, 2023
d12ae68
fully removed mInputSize
fearn-e Aug 21, 2023
51cde4d
fixed bug with allocator loop for removing voices ending too early
fearn-e Aug 21, 2023
dca9460
output proper sorted voiceID instead of partialTracking ID
fearn-e Aug 21, 2023
15f6b41
voice stealing comment
fearn-e Aug 21, 2023
ab1ccf0
states & stealing, imperfect
fearn-e Aug 21, 2023
d938027
proper init for queues/deques
fearn-e Aug 21, 2023
2757d7a
add pruning
fearn-e Aug 21, 2023
f0b1d4a
readability
fearn-e Aug 21, 2023
6360dd1
init() call for clear messagess
fearn-e Aug 21, 2023
af0938c
replaced stealing with sorting
fearn-e Aug 21, 2023
c6c6351
clearer layout of function calls in process + resize outgoingVoices
fearn-e Aug 21, 2023
02c26a5
temp removal of trackMethod param, switching to hung causes issues
fearn-e Aug 23, 2023
57a5051
consistency of an enumparam with other flucoma objects
fearn-e Aug 23, 2023
6a308c6
Merge remote-tracking branch 'origin/main' into feature/voice-allocator
fearn-e Aug 23, 2023
f7ddbf5
removing random comments
fearn-e Aug 23, 2023
03fcf68
Merge branch 'main' into feature/voice-allocator
tremblap Sep 1, 2023
1a536f4
algo-client file code split
fearn-e Sep 11, 2023
e1d5950
Merge branch 'feature/voice-allocator' of https://github.com/fearn-e/…
tremblap Sep 17, 2023
09221ee
Merge branch 'main' into feature/voice-allocator
tremblap Apr 3, 2024
633ff8f
implement reset/init
tremblap Apr 4, 2024
c0c14df
instantiation initialisation was needed
tremblap Apr 6, 2024
c684d83
cleaned the order of initialisation
tremblap Apr 7, 2024
618fd10
first working NRT adaptor
tremblap Jul 17, 2024
c81e050
Merge branch 'production' into feature/voice-allocator
tremblap Jul 17, 2024
e15fd18
forgot the cmake
tremblap Jul 17, 2024
17a49b2
added task evolution
tremblap Jul 17, 2024
a2d385e
Merge branch 'main' into feature/voice-allocator
tremblap Jul 17, 2024
536215d
clang_formatted
tremblap Aug 27, 2024
ebe3d6c
moved
tremblap Jun 18, 2025
ff2e2a7
Merge branch 'main' into feature/voice-allocator
tremblap Jun 18, 2025
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
3 changes: 3 additions & 0 deletions FlucomaClients.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function(get_core_client_class name var)
set(${var} ${client_info} PARENT_SCOPE)
endfunction()
#

add_client(AmpFeature flucoma/clients/rt/AmpFeatureClient.hpp CLASS RTAmpFeatureClient )
add_client(AmpGate flucoma/clients/rt/AmpGateClient.hpp CLASS RTAmpGateClient )
add_client(AmpSlice flucoma/clients/rt/AmpSliceClient.hpp CLASS RTAmpSliceClient )
Expand Down Expand Up @@ -139,6 +140,8 @@ add_client(SpectralShape flucoma/clients/rt/SpectralShapeClient.hpp CLASS RTSpec
add_kr_in_client(Stats flucoma/clients/rt/RunningStatsClient.hpp CLASS RunningStatsClient )
add_client(TransientSlice flucoma/clients/rt/TransientSliceClient.hpp CLASS RTTransientSliceClient )
add_client(Transients flucoma/clients/rt/TransientClient.hpp CLASS RTTransientClient )
add_kr_in_client(VoiceAllocator flucoma/clients/rt/VoiceAllocatorClient.hpp CLASS VoiceAllocatorClient )


#lib manipulation client group
add_client(DataSet flucoma/clients/nrt/DataSetClient.hpp CLASS NRTThreadedDataSetClient GROUP MANIPULATION)
Expand Down
163 changes: 163 additions & 0 deletions include/flucoma/algorithms/public/VoiceAllocator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
Part of the Fluid Corpus Manipulation Project (http://www.flucoma.org/)
Copyright University of Huddersfield.
Licensed under the BSD-3 License.
See license.md file in the project root for full license information.
This project has received funding from the European Research Council (ERC)
under the European Union’s Horizon 2020 research and innovation programme
(grant agreement No 725899).
*/

#pragma once

#include "../util/PartialTracking.hpp"

namespace fluid {
namespace algorithm {

class VoiceAllocator
{
template <typename T>
using vector = rt::vector<T>;

public:
VoiceAllocator(index nVoices, Allocator& alloc)
: mTracking(alloc), mVoices{nVoices}, mFreeVoices(alloc),
mActiveVoices(alloc), mActiveVoiceData(0, alloc)
{}

void init(index nVoices, Allocator& alloc)
{
mVoices = nVoices;
while (!mActiveVoices.empty()) { mActiveVoices.pop_back(); }
while (!mFreeVoices.empty()) { mFreeVoices.pop(); }
for (index i = 0; i < nVoices; ++i) { mFreeVoices.push(i); }
mActiveVoiceData.resize(nVoices);
for (VoicePeak each : mActiveVoiceData) { each = {0, 0, 0}; }
mTracking.init();
mInitialized = true;
}

void processFrame(vector<SinePeak> incomingVoices,
vector<VoicePeak>& outgoingVoices, index minTrackLen,
double birthLowTreshold, double birthHighTreshold,
index trackMethod, double trackMagRange,
double trackFreqRange, double trackProb, index sortMethod,
Allocator& alloc)
{
assert(mInitialized);

double maxAmp = -144;
for (const SinePeak& voice : incomingVoices)
{
if (voice.logMag > maxAmp) { maxAmp = voice.logMag; }
}

mTracking.processFrame(incomingVoices, maxAmp, minTrackLen,
birthLowTreshold, birthHighTreshold, trackMethod,
trackMagRange, trackFreqRange, trackProb, alloc);

outgoingVoices = mTracking.getActiveVoices(alloc);
outgoingVoices = sortVoices(outgoingVoices, sortMethod);
if (outgoingVoices.size() > mVoices) outgoingVoices.resize(mVoices);
outgoingVoices = assignVoices(outgoingVoices, alloc);

mTracking.prune();
}

void reset() { mInitialized = false; }

bool initialized() const { return mInitialized; }

private:
vector<VoicePeak> sortVoices(vector<VoicePeak>& incomingVoices,
index sortingMethod)
{
switch (sortingMethod)
{
case 0: // lowest
std::sort(incomingVoices.begin(), incomingVoices.end(),
[](const VoicePeak& voice1, const VoicePeak& voice2) {
return voice1.freq < voice2.freq;
});
break;
case 1: // loudest
std::sort(incomingVoices.begin(), incomingVoices.end(),
[](const VoicePeak& voice1, const VoicePeak& voice2) {
return voice1.logMag > voice2.logMag;
});
break;
}
return incomingVoices;
}

vector<VoicePeak> assignVoices(vector<VoicePeak>& incomingVoices,
Allocator& alloc)
{
// move released to free
for (index existing = 0; existing < mActiveVoiceData.size(); ++existing)
{
if (mActiveVoiceData[existing].state ==
algorithm::VoiceState::kReleaseState)
mActiveVoiceData[existing].state = algorithm::VoiceState::kFreeState;
}

// handle existing voices - killing or sustaining
for (index existing = 0; existing < mActiveVoices.size(); ++existing)
{
bool killVoice = true;
for (index incoming = 0; incoming < incomingVoices.size(); ++incoming)
{
// remove incoming voice events & allows corresponding voice to live if
// it already exists
if (mActiveVoiceData[mActiveVoices[existing]].voiceID ==
incomingVoices[incoming].voiceID)
{
killVoice = false;
mActiveVoiceData[mActiveVoices[existing]] =
incomingVoices[incoming]; // update freq/mag
mActiveVoiceData[mActiveVoices[existing]].state =
algorithm::VoiceState::kSustainState;
incomingVoices.erase(incomingVoices.begin() + incoming);
break;
}
}
if (killVoice) // voice off
{
mActiveVoiceData[mActiveVoices[existing]].state =
algorithm::VoiceState::kReleaseState;
mFreeVoices.push(mActiveVoices[existing]);
mActiveVoices.erase(mActiveVoices.begin() + existing);
--existing;
}
}

// handle new voice allocation
for (index incoming = 0; incoming < incomingVoices.size(); ++incoming)
{
if (!mFreeVoices.empty()) // voice on
{
index newVoiceIndex = mFreeVoices.front();
mFreeVoices.pop();
mActiveVoices.push_back(newVoiceIndex);
algorithm::VoiceState prevState = mActiveVoiceData[newVoiceIndex].state;
mActiveVoiceData[newVoiceIndex] = incomingVoices[incoming];
if (prevState == algorithm::VoiceState::kReleaseState) // mark as stolen
mActiveVoiceData[newVoiceIndex].state =
algorithm::VoiceState::kStolenState;
}
}

return mActiveVoiceData;
}

PartialTracking mTracking;
index mVoices;
rt::queue<index> mFreeVoices;
rt::deque<index> mActiveVoices;
vector<VoicePeak> mActiveVoiceData;

bool mInitialized{false};
};
} // namespace algorithm
} // namespace fluid
45 changes: 42 additions & 3 deletions include/flucoma/algorithms/util/PartialTracking.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,38 @@ Capability through Linear Programming". Proceedings of DAFx-2018.
namespace fluid {
namespace algorithm {

enum class VoiceState {
kFreeState,
kAttackState,
kSustainState,
kReleaseState,
kStolenState
};

struct SinePeak
{
double freq;
double logMag;
bool assigned;
};

struct SineTrack
struct VoicePeak
{
double freq;
double logMag;
index voiceID;
VoiceState state;
};

struct SineTrack
{
SineTrack(Allocator& alloc) : peaks(alloc) {}

SineTrack(rt::vector<SinePeak>&& p, index s, index e, bool a, bool ass,
index t)
: peaks{p}, startFrame{s}, endFrame{e}, active{a}, assigned{ass}, trackId{
t}
: peaks{p}, startFrame{s}, endFrame{e}, active{a}, assigned{ass},
trackId{t}

{}

rt::vector<SinePeak> peaks;
Expand Down Expand Up @@ -134,6 +150,29 @@ class PartialTracking
return sinePeaks;
}

// todo - refactor this function with the one above
vector<VoicePeak> getActiveVoices(Allocator& alloc)
{
vector<VoicePeak> voicePeaks(0, alloc);
index latencyFrame = mCurrentFrame - mMinTrackLength;
if (latencyFrame < 0) return voicePeaks;
for (auto&& track : mTracks)
{
if (track.startFrame > latencyFrame) continue;
if (track.endFrame >= 0 && track.endFrame <= latencyFrame) continue;
if (track.endFrame >= 0 &&
track.endFrame - track.startFrame < mMinTrackLength)
continue;
voicePeaks.push_back(
{track.peaks[asUnsigned(latencyFrame - track.startFrame)].freq,
pow(10,
track.peaks[asUnsigned(latencyFrame - track.startFrame)].logMag /
20),
track.trackId, VoiceState::kAttackState});
}
return voicePeaks;
}

private:
void updateVariances()
{
Expand Down
Loading