Entscheidungslogik noch mal verfeinert und Umgebung mit einbezogen

This commit is contained in:
Fischlurch 2014-09-05 00:51:58 +02:00
parent 139225fc5c
commit 109adce56c
2 changed files with 98 additions and 12 deletions

View file

@ -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<factor? TRIGGER_FACTOR/factor : -10);
: (0.0<factor? TRIGGER_FACTOR/factor : 10);
weights_ += weight;
score_ += weight * (confirmed? 1.0 : -1.0);
}
void
recordNeutral()
{
record (true, false, true, 0.0);
}
bool
possible()
{
@ -124,6 +138,17 @@ class EventScore
: "rej \t"
) + to_string(fac_) +'\t'+ to_string(confirmationDegree());
}
/**
* to avoid triggering by noise peaks, we require a confirmation
* by multiple channels; but the minimum fraction of confirming
* channels is lowered when processing a large number of channels.
*/
static void
adjustRequiredMinimalConfirmations (uint channelCount)
{
minConfs = 1 + uint(std::log2(channelCount));
}
};
@ -229,16 +254,17 @@ class Channel
visit (uint i, EventScore& event)
{
if (i + CONTEXT < numSamples_
&& CONTEXT <= i
&& workPos_ <= i)
&& CONTEXT <= i)
{
workPos_ = i;
bool noOffset = fineTuneWorkPos (i);
float averageDelta = updateMovingAverage();
float changeFactor = delta() / (averageDelta + EPSILON);
float changeFactor = delta(workPos_) / (averageDelta + EPSILON);
bool unclear = (sampleLevel() < SILENCE_GATE );
bool trigger = (changeFactor > 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<Channel> 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() != '/')

View file

@ -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.