Closed JG1VPP closed 2 years ago
以下のような感じでしょうか?
if strings.Contains(直前周期の文字列, 解析で出てきた文字列){
fmt.Println(直前周期の文字列)
}
if 直前有音 && 今無音{
fmt.Println(解析で出てきた文字列)
}
直前周期の文字列 = 解析で出てきた文字列
解析が安定しない場合(フェージングや、現状のようにいろんな周波数のピークを取れるようにするため、閾値を0.2にしていることから、他のイシューで挙げた動画のように文字列の前に全く関係ないものがあり、そこら辺は不安定です)には何も表示されずに使えないプラグインになってしまいそうですが大丈夫でしょうか?
閾値Thre
は周波数の選択で使用されるだけなので、全く関係のない文字が現れることとは関係がないと思います。解析が安定しないというのは、具体的にはどういう状況でしょうか?
解析が不安定な件 前回のイシューで建てたように前に音がないとJA1ZLOがOA1ZLOになったりするのはその通りなんですけど、そこのところになにかあると検知しているんだと思うんですが、JA1ZLOの前にRだったりAだったりがついてしまい、ある程度しない(1が解析されるくらいまで)その文字は一定にならないという感じです(ノイズなんで)
以下の動画の二局目のJR8の前に何かついていたりします。(JA1ZLOでもタイミングによってはつきます)
そもそも未確定文字の非表示について 現状のCWプラグインでは7MHzでは対応できないくらい遅いというのがZLOの部員の意見でした
さすがに7Mとかこの間だと厳しいかも、特にパイル時とか次の呼びかけが始まりそう
それを踏まえると確定するまで非表示は表示が遅れてしまうので、あまりよろしくないのかなと思いました (例えば二文字一気に解析された場合は二文字の表示が遅れることになるので致命的)
もちろん
super checkみたいに補助として使うならいい感じ 1局消化したあとQRZ?の代わりに裏の局を呼び出せるのでQSOs/hr上がりそうだね
という評価はもらいましたし、21MHzとかならいけそうという話ではありました。
コード VPPさんが想定されているのは上の方法ということでしょうか?
今、問題にしているのは、0.5秒周期で解析している都合により、特に最後に読み取った文字は信頼できないので、解析結果が固まるまでは非表示にすべきだということです。
var prev_text string
var latest_text string
var shown_text string
for i := len(prev_text); i >= 1; i-- {
if strings.HasPrefix(latest_text, prev_text[:i]) {
shown_text = prev_text[:i]
break
}
}
JA1ZLOの前にRだったりAだったりがついてしまい、ある程度しない(1が解析されるくらいまで)その文字は一定にならないという感じです(ノイズなんで)
仮に何かノイズを拾っているとして、そのノイズの正体を明らかにして、対策を講じるべきです。
現状のCWプラグインでは7MHzでは対応できないくらい遅いというのがZLOの部員の意見でした
遅い、というのと、安定しない、というのは、別の現象を指していて、その対策は矛盾しないように思います。まずは原因を考察しましょう。対応できないくらい遅いとか言われたら、自分なら悔しくて徹底的に対策しますが。
現役部員には、自由にIssueを立ててもらってください。OSSなのにクローズドな開発をするのはもったいないです。
解析が固まるまで表示しない そのように書いています 上のコードだと、最後の確定のタイミング(有音から無音に切り替わるタイミング)では最後の文字がチェックされないため、最後の文字が表示されないという問題があるので、最後の確定のタイミング(有音から無音に切り替わるタイミング)では何もせずに返すようにしないといけないですね
ノイズ的なもの 調査します
遅いという件 考えます… CWスキーマがどういう感じなのかが気になりますね(偉大なる巨人の肩に乗りたい) issueを立てましょうと推奨します
動画を見ると、0.5秒では説明が付かない遅延がありますが、実際その通りですか?
実際その通りですか?
どういう意味でしょうか? 音はpowerpointもプラグインも同じラインで入れていて、かつ、動画は実時間でやっていますので、この時間の感覚です。
https://github.com/nextzlog/todo/issues/120#issuecomment-1290683762 リンク先の動画が確定した文字列とか考えずに表示する場合です。時間の感覚としてどうなんでしょうか?
recordtime = 0.6 //リングバッファの時間[s]
リングバッファの時間(全くもってリングバッファは使っていないがコメントアウトがその名残に)が0.6秒になっていますね…
それはあるかもしれないです
つまり、Read
関数自体の遅延は無視できるという理解で合っていますか?
一応は、0.5秒でコンパイルし直したのが以下です
若干早くなりましたね
Read
関数自体よりも待機時間の方が律速な感じですね(雰囲気は)
例えば、現状でも半分の0.25秒くらいまで音が入っていて有音とされた場合は、次の0.5秒まで合わせて0.75秒(0.6秒でやっていた時は0.9秒)間は確定されないので、その辺がワンテンポ遅れるとかボトルネック的なものになっていてもおかしくはないと思います。
そもそも、所詮1次元のクラスタリングに50回もイテレーションを回すのはやり過ぎで、10回でも多すぎる気がします。また、Read
関数の遅延時間を測定してみて、どの程度まで周期を短くできるか検討しましょう。
例えば、0.1秒周期でも問題ないのであれば、0.1秒周期にしましょう。それで、UXが改善する筈です。
無音判定は、例えば、12WPMの場合、短点1個の時間は0.1秒で、文字の区切りは0.3秒なので、閾値にも依りますが、最長でも0.6秒待てば文章が終了したかがわかります。つまり、無音が6回続けば待機状態に戻れる訳ですよね。
現状は以下の通りなので
if peak <= mean*m.Squelch {
m.storage = signal
m.counter = 0
return text, true
} else {
return text, false
}
をいい感じに書き換える必要がありますね
あと周期を短くすれば、m.storage = signal
で入れる量が短くなり余計なものを入れなくなるので、誤解析も防げそうな感じがしますね
こんな感じですかね(zylo/morse側ですけど) 無音であっても一定回数ないであればfalseを返してほしいです(実際に確定していないという意味で)
m.cnt_silent (無音判定が有音から何回繰り返されたか)
m.cnt_wait (待機状態に戻るための無音回数)
switch {
case peak <= mean*m.Squelch && m.cnt_silent => m.cnt_wait :
m.storage = signal
m.counter = 0
return text, true
case peak <= mean*m.Squelch && m.cnt_silent < m.cnt_wait :
m.cnt_silent += 1
return text, false
default :
m.cnt_silent = 0
return text, false
}
時間計測について
now := time.Now()
decode_result, mute_bool := monitor.Read(signal)
duration := time.Since(now).Milliseconds()
以上のようなコードを入れて、その結果を3列目に表示するようにしました
実験環境など ・intel core5 第5世代(zLog利用者に合わせるため) ・周期は0.5秒です ・イタレーションは10回です
結果 最初は100ms弱で最後の方は500msに近くになってくるので、周期が0.5秒というのは案外、限界だったりしそうですね… イタレーションは50でも10でも時間はあんまり変わらなかったです…
decoder = morse.Decoder{
Thre: 0.2,
Bias: 20,
Iter: 50,
STFT: &stft.STFT{
FrameShift: int(float64(rate_sound) / float64(100)),
FrameLen: 4096,
Window: window.CreateHanning(4096),
},
}
を人間が打てないCWを解析できる能力を失いますが、以下のようにshiftを下げ、バッファーを0.3秒にしました
decoder = morse.Decoder{
Thre: 0.2,
Bias: 20,
Iter: 20,
STFT: &stft.STFT{
FrameShift: int(float64(rate_sound) / float64(50)),
FrameLen: 4096,
Window: window.CreateHanning(4096),
},
}
ちょっと早くなりましたかね? 無音のところの処理を書いていないので、たまによくないタイミングで確定されます
7MHzでも使えるかという質問をZLO部員(正確には私と同期で老害なので、表立ってZLOの場で活動したくないため、このissueには遠くから回答したいとのこと)したところ
だいぶ早くなったすね これなら問題なさそう
という回答を得ました もうCWerがいらない可能性が見えてきたかもしれません
無音の判定アルゴリズムを以下のように修正しました:
Thre
以上を占める周波数を選ぶ使用例です:
package main
import (
"fmt"
"github.com/faiface/beep/speaker"
"github.com/mjibson/go-dsp/wav"
"github.com/r9y9/gossp/stft"
"github.com/r9y9/gossp/window"
"os"
"strings"
"zylo/morse"
)
const rate = 8000
func main() {
file, _ := os.Open("pileup001.wav")
wave, _ := wav.New(file)
data, _ := wave.ReadFloats(wave.Samples)
signal := make([]float64, wave.Samples)
for i, val := range data {
signal[i] = float64(val)
}
decoder := morse.Decoder{
Thre: 0.03,
Iter: 10,
Bias: 2,
STFT: &stft.STFT{
FrameShift: rate / 100,
FrameLen: 4096,
Window: window.CreateHanning(4096),
},
}
speaker.Init(rate, 256)
monitor := morse.Monitor{
MaxHold: rate * 60,
Decoder: decoder,
}
chunk := len(signal) / 5
for n := 0; n < 5; n++ {
finish := true
sig := signal[n*chunk : (n+1)*chunk]
result := monitor.Read(sig)
for _, code := range result {
if !strings.HasSuffix(CodeToText(code), " ") {
finish = false
}
}
if finish {
for _, code := range result {
fmt.Println(code)
fmt.Println(CodeToText(code))
}
}
}
}
計算時間のボトルネックはFFTだと思います。FFTの計算量はnlognで、周波数解像度を妥協してウィンドウ幅を小さくすれば、処理時間が改善するのではないでしょうか?
https://gist.github.com/jucky154/53f1906d2959d5ff0cea541fd3db28e0 と書き換えて、
decoder := morse.Decoder{
Thre: 0.03,
Iter: 10,
Bias: 2,
STFT: &stft.STFT{
FrameShift: rate / 50,
FrameLen: 2048,
Window: window.CreateHanning(2048),
},
}
で実行したところJA1ZLO程度の短さなら80ms程度で終わっているようですね。(もっと長いR UR 599 100110 H BKとか来た場合にどうなるかを検討する必要はありそうです) そのため、0.2秒周期くらいにしても良さそうな気がしますね
→そのようにする場合は内部の無音判定は何回か無音が来たら無音とするみたいな感じへの修正をお願いします。
その他の問題点としては、動画のようにfinishがうまくいっていません
finish := true
morse_texts := make([]string, 0)
for i, code := range decode_result {
morse_texts = append(morse_texts, morse.CodeToText(code))
if !strings.HasSuffix(morse_texts[i], " ") {
finish = false
}
}
と書いて、上とほぼ同じように対応していると思うのですが、おそらくdecoder内部では解析中(無音と判断しているのであればJA1ZLOと続けて書かれない)と判断しているのにもかかわらず、finishがtrueで帰ってきてしまい、動画のように無意味な改行や、finish時には点検なしで表示するところ関係で不安定な文字列を表示してしまっています。
finish
の件はさておき、想定としては、閾値の調整により、無音判定が正確になる筈です。0.03よりも大きな値にすると余計な文字は消えますか?
finish
の判定は、上にあるように、末尾に空白文字が付与されるかで決まるので、デバッグ方法としては、空白文字を可視化するか、CodeToText
関数を使わずに、末尾に;
が挿入されているかを確認しましょう。
恐らく、読み取れずにCodeToText
関数でモールス信号の符号語をそのまま出力している場合に、末尾に空白文字が追加されてしまうために、finish
がtrue
になっているのでしょう。対策としては、CodeToText
関数を使わずに判定するか、CodeToText
関数を修正するかですが、もともとCodeToText
関数の想定用途ではないですし、と言って、;
で終わる文字列なら読み取り完了、とするのも不親切ですよね。
下の三つは別の音のせいでこうなってしまったんですが、それ以外を見ると、普通に;
はないですね…
それはcorrect_string
関数を通した値を表示しているからではないですか?
通さないように書き換えた結果が上です。 (本当に表示部は何もしていないcodeが表示されています)
Monitor.Read
関数の内部では、末尾に;
の3文字がある場合に送信終了というように判定しているので、finish
の挙動と一致しなければおかしいです。
https://gist.github.com/jucky154/29e03126a97feebef27dde4a07f84b58 がコードで、変更した部分はここですね…
for i := 0; i < int(math.Min(float64(len(decode_result)), float64(2))); i++ {
latest_text := decode_result[i]
switch i + 1 {
case 1:
cwitems.morseresult1 = latest_text
case 2:
cwitems.morseresult2 = latest_text
case 3:
cwitems.morseresult3 = latest_text
}
}
/*
for i := 0; i < int(math.Min(float64(len(decode_result)), float64(2))); i++ {
latest_text := morse_texts[i]
switch i + 1 {
case 1:
cwitems.morseresult1 = correct_string(prev_texts[i], latest_text, finish)
case 2:
cwitems.morseresult2 = correct_string(prev_texts[i], latest_text, finish)
case 3:
cwitems.morseresult3 = correct_string(prev_texts[i], latest_text, finish)
}
prev_texts[i] = latest_text
}
*/
finish := true
morse_texts := make([]string, 0)
for i, code := range decode_result {
morse_texts = append(morse_texts, morse.CodeToText(code))
if !strings.HasSuffix(morse_texts[i], " ") {
finish = false
}
}
この書き方が良くないとかですかね?
append
すると最後の無意味な空白文字が消えるとか、morse_texts[i]
ではなくmorse_texts[i+1]
呼ぶべき?
code
の末尾に空白文字がないですか?それをstrings.Split(code, " ")
した際に、最後が空文字列となり、それに対応してCodeToText
関数が空白文字を挿入した可能性があります。
調査しました
テキトーに文字列の最後に/
を入れてみました
一番右の列に比較用に_/
という空白のない場合を作ってみました
すると、スペースがあるぽいので、VPPさんの想定通りと思われます
strings.TrimSpace
をすればいいということでしょうか?
当面は;
の3文字がsuffixになっているかで判定してください。
流石に酷い仕様なので、Message.Finish()
という関数を用意しました。というか、Monitor.Read
関数からstring
ではなくMessage
を返すように変更しました。ここには、周波数の情報も含まれていますが、正確にはFFTのインデックスなので、サンプリング周波数を窓長で割った係数を掛けるとHz単位になります。
それに合わせてプラグインも書き換えました https://gist.github.com/jucky154/8c24cd3e843a8382dddb8efe889c090f
無音なのに停止しなくなりました わざと閾値を0.5に設定して、実行したのが以下の動画なんですけど、無音なのに正しく無音判定がなされず、実行時間がただひたすらに伸びていることがわかります(一番右の列はread関数の実行時間)つまり、適切に無音判定が行われず、音ありと判断され、ひたすらにsignalが連結されて行っています
「ノイズのみ」ということはFinishが一つ前:trueで現在 : trueということなんですけど…
無音判定、というより文の終わりの判定ですが、誤って真偽が逆転していたので、修正しました
適切に動きました 動画のようになりました 無音判定が早い都合でコールサインが分かれてしまいましたが、結構よさげですね
対策として、文の区切りの空白の判定基準を引き上げました
適切に動きました 動画のようになりました 無音判定が早い都合でコールサインが分かれてしまいましたが、結構よさげですね
問題意識
モールス信号解読プラグインでは、刻々とデコード結果が変化するが、却ってわかりにくい。
解決方法
無音を検知するまでは、直前の周期で読み取った文字列と一致する部分のみを表示する。