diff --git a/src/engine/zone.cpp b/src/engine/zone.cpp index afdf6d23..1d1584af 100644 --- a/src/engine/zone.cpp +++ b/src/engine/zone.cpp @@ -36,6 +36,7 @@ #include "sst/basic-blocks/mechanics/block-ops.h" #include "group_and_zone_impl.h" +#include "dsp/sample_analytics.h" namespace scxt::engine { @@ -154,6 +155,38 @@ void Zone::removeVoice(voice::Voice *v) assert(false); } +void Zone::setNormalizedSampleLevel(const bool usePeak, const int associatedSampleID) +{ + const auto startSample = (associatedSampleID < 0) ? 0 : associatedSampleID; + const auto endSample = (associatedSampleID < 0) ? maxVariantsPerZone : associatedSampleID; + + for (auto i = startSample; i < endSample; ++i) + { + if (variantData.variants[i].active && samplePointers[i]) + { + auto normVal = usePeak ? dsp::sample_analytics::computePeak(samplePointers[i]) + : dsp::sample_analytics::computeRMS(samplePointers[i]); + // convert linear measure into db + // To undo this, std::pow(amp / 10.f, 10.f) + variantData.variants[i].normalizationAmplitude = 10.f * std::log10(1.f / normVal); + } + } +} + +void Zone::clearNormalizedSampleLevel(const int associatedSampleID) +{ + const auto startSample = (associatedSampleID < 0) ? 0 : associatedSampleID; + const auto endSample = (associatedSampleID < 0) ? maxVariantsPerZone : associatedSampleID; + + for (auto i = startSample; i < endSample; ++i) + { + if (variantData.variants[i].active && samplePointers[i]) + { + variantData.variants[i].normalizationAmplitude = 0.f; + } + } +} + engine::Engine *Zone::getEngine() { if (parentGroup && parentGroup->parentPart && parentGroup->parentPart->parentPatch) diff --git a/src/engine/zone.h b/src/engine/zone.h index 63ff6676..47f860ee 100644 --- a/src/engine/zone.h +++ b/src/engine/zone.h @@ -119,12 +119,18 @@ struct Zone : MoveableOnly, HasGroupZoneProcessors, SampleRateSuppor int loopCountWhenCounted{0}; int64_t loopFade{0}; + float normalizationAmplitude{0.f}; // db + // per-sample pitch and amplitude + float pitchOffset{0.f}; // semitones + float amplitude{0.f}; // db bool operator==(const SingleVariant &other) const { return active == other.active && sampleID == other.sampleID && startSample == other.startSample && endSample == other.endSample && - startLoop == other.startLoop && endLoop == other.endLoop; + startLoop == other.startLoop && endLoop == other.endLoop && + normalizationAmplitude == other.normalizationAmplitude && + pitchOffset == other.pitchOffset && amplitude == other.amplitude; } }; @@ -196,6 +202,8 @@ struct Zone : MoveableOnly, HasGroupZoneProcessors, SampleRateSuppor return attachToSample(manager, variation); } + void setNormalizedSampleLevel(bool usePeak = false, int associatedSampleID = -1); + void clearNormalizedSampleLevel(int associatedSampleID = -1); struct ZoneMappingData { diff --git a/src/json/engine_traits.h b/src/json/engine_traits.h index b51ee33c..a2a04254 100644 --- a/src/json/engine_traits.h +++ b/src/json/engine_traits.h @@ -319,7 +319,10 @@ SC_STREAMDEF(scxt::engine::Zone::SingleVariant, SC_FROM({ {"loopMode", s.loopMode}, {"loopDirection", s.loopDirection}, {"loopCountWhenCounted", s.loopCountWhenCounted}, - {"loopFade", s.loopFade}}; + {"loopFade", s.loopFade}, + {"normalizationAmplitude", s.normalizationAmplitude}, + {"pitchOffset", s.pitchOffset}, + {"amplitude", s.amplitude}}; } else { @@ -345,6 +348,9 @@ SC_STREAMDEF(scxt::engine::Zone::SingleVariant, SC_FROM({ s.loopDirection); findOrSet(v, "loopFade", 0, s.loopFade); findOrSet(v, "loopCountWhenCounted", 0, s.loopCountWhenCounted); + findOrSet(v, "normalizationAmplitude", 0.f, s.normalizationAmplitude); + findOrSet(v, "pitchOffset", 0.f, s.pitchOffset); + findOrSet(v, "amplitude", 0.f, s.amplitude); } else { diff --git a/src/messaging/client/client_serial.h b/src/messaging/client/client_serial.h index 70ca2431..a8ddfab6 100644 --- a/src/messaging/client/client_serial.h +++ b/src/messaging/client/client_serial.h @@ -89,6 +89,8 @@ enum ClientToSerializationMessagesIds c2s_update_zone_mapping_int16_t, c2s_update_lead_zone_single_variant, c2s_update_zone_variants_int16_t, + c2s_normalize_zone_samples, + c2s_clear_normalize_zone_samples, c2s_update_zone_output_float_value, c2s_update_zone_output_int16_t_value, diff --git a/src/messaging/client/zone_messages.h b/src/messaging/client/zone_messages.h index 23f8cde8..72f4f763 100644 --- a/src/messaging/client/zone_messages.h +++ b/src/messaging/client/zone_messages.h @@ -113,6 +113,46 @@ CLIENT_TO_SERIAL(UpdateLeadZoneSingleVariant, c2s_update_lead_zone_single_varian updateLeadZoneSingleVariantPayload_t, doUpdateLeadZoneSingleVariant(payload, engine, cont)); +using associatedSampleZoneNormalizePayload_t = std::tuple; +inline void doAssociatedSampleZoneNormalize(const associatedSampleZoneNormalizePayload_t &payload, + const engine::Engine &engine, MessageController &cont) +{ + const auto &samples = payload; + auto sz = engine.getSelectionManager()->currentLeadZone(engine); + if (sz.has_value()) + { + auto [ps, gs, zs] = *sz; + cont.scheduleAudioThreadCallback([p = ps, g = gs, z = zs, sampv = samples](auto &eng) { + auto &[idx, use_peak] = sampv; + eng.getPatch()->getPart(p)->getGroup(g)->getZone(z)->setNormalizedSampleLevel(use_peak, + idx); + }); + } +} +CLIENT_TO_SERIAL(AssociatedSampleZoneNomalizeRequest, c2s_normalize_zone_samples, + associatedSampleZoneNormalizePayload_t, + doAssociatedSampleZoneNormalize(payload, engine, cont)); + +using associatedSampleZoneNormalizeClearPayload_t = size_t; +inline void +doAssociatedSampleZoneNormalizeClear(const associatedSampleZoneNormalizeClearPayload_t &payload, + const engine::Engine &engine, MessageController &cont) +{ + const auto &samples = payload; + auto sz = engine.getSelectionManager()->currentLeadZone(engine); + if (sz.has_value()) + { + auto [ps, gs, zs] = *sz; + cont.scheduleAudioThreadCallback([p = ps, g = gs, z = zs, sampv = samples](auto &eng) { + auto &idx = sampv; + eng.getPatch()->getPart(p)->getGroup(g)->getZone(z)->clearNormalizedSampleLevel(idx); + }); + } +} +CLIENT_TO_SERIAL(AssociatedSampleZoneNomalizeClearRequest, c2s_clear_normalize_zone_samples, + associatedSampleZoneNormalizeClearPayload_t, + doAssociatedSampleZoneNormalizeClear(payload, engine, cont)); + CLIENT_TO_SERIAL_CONSTRAINED(UpdateZoneVariantsInt16TValue, c2s_update_zone_variants_int16_t, detail::diffMsg_t, engine::Zone::Variants, detail::updateZoneMemberValue(&engine::Zone::variantData, payload, diff --git a/src/voice/voice.cpp b/src/voice/voice.cpp index 073e6f3c..8859026d 100644 --- a/src/voice/voice.cpp +++ b/src/voice/voice.cpp @@ -271,6 +271,14 @@ template bool Voice::processWithOS() { halfRate.process_block_D2(output[0], output[1], blockSize << 1); } + + auto scale_db = zone->variantData.variants[sampleIndex].normalizationAmplitude; + // we are in dB so 0 change is 0 dB + if (scale_db != 0.f) + { + const float linear_scale = dsp::dbTable.dbToLinear(scale_db); + sst::basic_blocks::mechanics::scale_by(linear_scale, output[0], output[1]); + } } else memset(output, 0, sizeof(output));