diff --git a/sauSynKlick/SynKlick.cc b/sauSynKlick/SynKlick.cc index 0156ff9..d9de76c 100644 --- a/sauSynKlick/SynKlick.cc +++ b/sauSynKlick/SynKlick.cc @@ -36,6 +36,7 @@ const float SILENCE_GATE = 4096.0f * EPSILON;// signal below this level (-33dB) const float TRIGGER_FACTOR = 6.0f; // discontinuity detected by local change compared to average change const float CONFIRMATION_DEGREE = 0.66f; // degree of confirmation by multiple tracks for an event to be accepted const uint AVERAGE_SAMPLES = 50; // number of past samples to use for average delta +const uint POS_LEEWAY = 3; // events within this timespan (samples) still count as "synchronous" const bool VERBOSE = true; @@ -79,6 +80,9 @@ class EventScore { float weights_ = 0.0f; float score_ = 0.0f; + uint confs_ = 0; + bool isHere_ = false; + static uint minConfs; uint last_ = 0; float fac_ = 0.0f; @@ -94,22 +98,32 @@ class EventScore operator bool() { - return CONFIRMATION_DEGREE <= this->confirmationDegree(); + return CONFIRMATION_DEGREE <= this->confirmationDegree() + && confs_ >= minConfs + && isHere_; } void - decide(bool unclear, bool confirmed, float factor) + record (bool unclear, bool confirmed, bool noOffset, float factor) { fac_ = factor; last_ = unclear? 0 : confirmed? 2 : 1; // record for diagnostics if (unclear) return; + if (confirmed) ++confs_; + if (noOffset) isHere_ = true; float weight = confirmed? (factor/TRIGGER_FACTOR) - : (0.0 TRIGGER_FACTOR); - event.decide (unclear, trigger, changeFactor); + event.record (unclear, trigger, noOffset, changeFactor); } + else + event.recordNeutral(); } private: @@ -253,10 +279,10 @@ class Channel } float - delta() + delta(size_t pos) { - if (0 < workPos_) - return abs(buff_[workPos_] - buff_[workPos_-1]); + if (0 < pos) + return abs(buff_[pos] - buff_[pos-1]); else return 0; } @@ -266,10 +292,34 @@ class Channel { float old_ema = ema_; // avoid poisoning EMA used for detection - ema_ = (delta() + ema_*(AVERAGE_SAMPLES - 1)) / AVERAGE_SAMPLES; + ema_ = (delta(workPos_) + ema_*(AVERAGE_SAMPLES - 1)) / AVERAGE_SAMPLES; return old_ema; } + /** + * sometimes the actual jump appears slightly offset on neighbouring tracks. + * We allow for some leeway by moving the work position to maximise delta. + * @return if the triggering event is at the current position + */ + bool + fineTuneWorkPos (size_t currPos) + { + size_t maxPos = 0; + float largestDelta = 0.0f; + for (size_t j=currPos; j < currPos+POS_LEEWAY && j < numSamples_; ++j) + { + float thisDelta = delta(j); + if (thisDelta > largestDelta) + { + largestDelta = thisDelta; + maxPos = j; + } + } + + workPos_ = maxPos; + return (workPos_ == currPos); + } + float* accessCurrentContext() { @@ -343,6 +393,7 @@ class Patch int num_sources = 0; vector streams(0); bool Channel::hitEnd = false; +uint EventScore::minConfs = 1; void @@ -351,6 +402,7 @@ openStreams (char* name[], string targetDir) assert (num_sources); assert (0 == streams.size()); streams.resize(num_sources); + EventScore::adjustRequiredMinimalConfirmations (num_sources); assert (targetDir.length()); if (targetDir.back() != '/') diff --git a/sauSynKlick/SynKlick.txt b/sauSynKlick/SynKlick.txt index 64331f9..8161d0d 100644 --- a/sauSynKlick/SynKlick.txt +++ b/sauSynKlick/SynKlick.txt @@ -55,6 +55,19 @@ Dies erfordert, von einer gegebenen Position auch stets auf das _Vorgänger-_ un zu können, was besondere Vorsicht an den Puffergrenzen bedingt. Dies werde ich durch entsprechende Abstraktion und Trennung des Lese- vom Verarbeitungsvorgang lösen. +Im Detail wird die Entscheidungs-Logik einigermaßen komplex, da wir gezwungenermaßen auch einige gemischte +und nicht regelmäßige Fälle mit abdecken müssen. Dadurch gewinnt dieses Werkzeug aber auch eine größere +Einsatzbreite. + +- die Entscheidung, ob tatsächlich ein zu behandelndes Ereignis vorliegt, wird durch eine + gewichtete Abstimmung aller beteiligten Spuren getroffen. Es zählt die Stärke des Sprunges. +- die genaue Stelle des Sprunges wird mit einer geringen Toleranz ggfs noch um wenige Samples + vor- oder zurück-justiert, so daß auch _nur fast synchrone_ Defekte mit erfaßt werden. +- wenn sich vor oder hinter dem Sprung ein drop-Out befindet, wird dieser _vor_ Aufbringen + der eigentlichen Korrektur durch eine einfache, lineare Verbindung ersetzt, so daß die + durch den drop-Out bedingten, zusätzlichen Sprung-Stellen schon beseitigt sind. +- eine Spur, welche _definitiv keinen_ Defekt aufweist, wird grundsätzlich nicht repariert. + Arbeitsnotizen -------------- @@ -84,4 +97,25 @@ daß an der betreffenden Stelle ein Sprung in allen Wellenformen auftritt. ausgeknobelten Länge von 4.5ms (200 Samples). - das große Problem ist bloß: _woher bekomme ich das zusätzliche Material für die Kreuzblende?_ +.31.8.2014 +Nun habe ich einen ganzen Arbeitstag gebraucht, diese komplexere Entscheidungs-Logik zu implementieren, +zu testen und zu optimieren. Ich verwende nun ein gewichtetes Mittel, um die Stärke des erkannten Sprunges +in die Entscheidung einfließen zu lassen; häufig liegt nämlich auch auf den anderen Spuren ein Sprung knapp +unter der Trigger-Schwelle vor; das negativ-Votum solcher Spuren fällt nun geringer aus, als das Votum einer +Spur, die nun wirklich kontinuierlich ist. Damit komme ich an den ausgewählten Probe-Samples zu klaren +Entscheidungen. + +Als Kompensation habe ich zunächst das fehlende Signal ganz einfach durch _Rückwärts-Abspielen_ der Wellenform +gewonnen. Das funktioniert im _Idealfall_ verblüffend gut -- man hört nur noch ein klopfendes Geräusch. +Die Kompensation funktioniert allerdings oft nicht befriedigend, denn es treten zusätzliche Komplikationen auf: + +- bei manchen Sprung-Ereignissen, liegt die Diskontinuität um ein bis zwei Samples verschoben auf den + Spuren vor. Die Sprünge sind also nur _fast_ synchron. +- direkt anschließend an den Sprung tritt auch öfters tatsächlich eine kurze Strecke von Null-Werten auf. + Dies hatte mich wohl ursprünglich zur falschen Diagnose geführt, es handele sich um drop-Outs. + +Die Wirklichkeit ist eben _widerständig_ -- einfache Methoden funktionieren nur in einer konditionierten +Umgebung. Hier führen also die Verschiebungen dazu, daß Ereignisse nicht, oder doppelt erkannt werden. +Und die im Idealfall sehr gut funktionierende Reparatur mit dem gespiegelten Signal führt bei den Null-Strecken +dazu, daß ein weiterer Sprung (am anderen Ende des drop-Out) _rückwärts_ in das ursprünglich saubere Signal +verschleppt wird. Das heitßt, in diesem Fall verschlimmert die ``Korrektur'' den Schaden.