UMEHOSHI ITA TOP PAGE    COMPUTER SHIEN LAB

[UMEHOSHI ITA]の制御で使っているIC「PIC32MX270F256B-I/SO」のフラッシュメモリには、テスト用プログラムが書き込まれいています。
これを利用したユーザー用のプログラムを「ウメ・エディットプログラム」と呼ぶことにします。
「ウメ・エディットプログラム」の開発では「umehoshiEditツール」が必要で、 その取得や最初の操作情報は、こちらを参照してください。
以下は「umehoshiEditツール」で録音で生成したumehoshiEdit.wavのファイルをpythonでグラフ化し、音の再生過程を示すものです。

[UMEHOSHI ITA]での録音と、pythonで再生

[UMEHOSHI ITA]での録音して、 umehoshiEdit.wavのファイルを作る。

umehoshiEdit.wavは、次のコードの実行で生成したものです。
実行時に、[Tools]メニューの[ADC to CSV]のチェックを入れるとファイルを作る機能があり、 これで作られたファイルです
なおADCを扱う基本プログラムは、こちらの別ページで紹介しています。

#include <xc.h> // ADC_CN8_8K.c
#include "common.h"

#define AdrStart	0x80005000
#define AdrStop	0x80006000

__attribute__((address( AdrStart  ))) void start (void);
void start()
{
	_RB15 = ! _RB15;// 動作確認用のD1 LEDの点灯を反転
	PR3=2499;	//サンプリング周波数を 8KHzに指定するパラメタ
	// (1/8000)/(1/40e6)/2-1=2499
	_set_adc_mode(1, 0);// CN8のAN0端子だけでサンプリングする。出力はバイナリモード 
	_set_adc_exe(63, 0);// 63ブロックのサンプリング(1/8000*1024*63=8.064秒)
	// 63は、上記関数を使う場合の最大値で、1の値でサンプリングバッファ1024ワード分を意味します。
	AD1CON1bits.ON = 1; //ADC モジュールを有効にする(b15)
	T3CONbits.ON = 1; // timer3を開始
}

__attribute__((address( AdrStop))) void stop (void);
void stop()
{
	T3CONbits.ON = 0; // すぐにtimer3を停止
	AD1CON1bits.ON = 0; //ADC モジュールを無効にする(b15)
	IFS0CLR = _IFS0_AD1IF_MASK; // Clear ADC interrupt flag  
	IFS0CLR = _IFS0_T3IF_MASK; // Clear the timer3 interrupt status flag
}

上記の実行で、16bit符号モノラルで8KHzビットレートにより、8.064秒間の録音したサンプリングファイル「umehoshiEdit.wav」を 「自身のミュージックのフォルダ」の中に生成します。

pythonで umehoshiEdit.wavのファイルを再生して、グラフ化する。

上記実行で作られた umehoshiEdit.wavのファイルが、カレントディレクトリに存在する前程で、再生後にグラフ化するPythonコード例を示します。

import numpy as np
import wave

WAV_FILE="umehoshiEdit.wav" # 再生、プロット対象

wavefile = wave.open(WAV_FILE, "r")
framerate = wavefile.getframerate() # フレームレート(1秒間のデータ出力数)
print("フレームレート:", framerate/1000, "KHz") 
nchannel=wavefile.getnchannels()
print("チャンネル数:", nchannel)
sampwidth=wavefile.getsampwidth()
print("サンプル幅数:", sampwidth)
nframes = wavefile.getnframes() # フレーム数(データの個数)
print("nframes:" , nframes) # 実行例: nframes: 80000(10秒)
buffer = wavefile.readframes(nframes) # ファイルからフレーム数の読み取りバイナリのデータを返す。
wavefile.close() # ファイルを閉じる 

print("----- 以上で、.wavファイルをbufferに読み込み、それをここより再生する -----")
import pyaudio # 録音や再生に使う ここより再生
audio = pyaudio.PyAudio()
format=audio.get_format_from_width(sampwidth) # ストリームを読み書きするときのデータ型
stream = audio.open(format=format,channels=nchannel,rate=framerate,output=True)

stream.write(buffer)  # ストリームへの書き込みで音が出力される。
print("上記再生音のデータバイト数:", len(buffer) )
stream.stop_stream()

print("----- 上記再生データをグラフにプロットする -----")
import matplotlib.pyplot as plt
xn= np.arange(0, len(buffer)/2 )
ys = np.frombuffer(buffer, dtype="int16")
# 上記 buffer のバイナリが、16bit 整数(リトルエンディアン)である前提で、numpy生成した
plt.plot( xn, ys ) # samplingデータysをプロット
plt.show()
上記実行結果例を示します。

C:\Users\work\Music>python ADC_CN8_8K.py
フレームレート: 8.0 KHz
チャンネル数: 1
サンプル幅数: 2
nframes: 80000
----- 以上で、.wavファイルをbufferに読み込み、それをここより再生する -----
上記再生音のデータバイト数: 129024
----- 上記再生データをグラフにプロットする -----

上記 wavefile.getnframes()# フレーム数(データの個数)で得られる80000と、
buffer = wavefile.readframes(nframes)で得られる129024が合わないことが分かった。
再生音のデータバイト数: 129024は16bitなので129024/2=64512と80000が一致していない。
これは、wavefile.getnframes() が実際にファイルに格納されるデータ個数と違う場合があると予想される。
wavefile.readframesを使うことで、引数が80000と多くても実際に存在するデータが取得されるようだ。
よって、次にwavefile.readframes(1024)で読む繰り返しして、再生に対して、リアルタイムプロットのが変化する手法を検討する。

pythonで umehoshiEdit.wavのファイルを1024フレームごと再生して、グラフをアニメーション化する。

umehoshiEdit.wav(符号無し16bit符号モノラル,8KHz)のファイルが、カレントディレクトリに存在する前程で、 そのファイルから1024フレームごとに、『読み取って音の再生と、そのグラフ表示』を繰り返すPythonコード例を、以下に示します。

import numpy as np
import wave

WAV_FILE="umehoshiEdit.wav" # 再生、プロット対象

import matplotlib.pyplot as plt

def plotting( xlist, ylist ): # xlist、ylistでプロットして1e-20秒待つ
    lines.set_data(xlist, ylist) # 描画データを更新する
    ax.set_xlim((xlist.min(), xlist.max()))
    plt.pause(1e-20) # ブロックするplt.show()の代わりに描画している。

wavefile = wave.open(WAV_FILE, "r")
framerate = wavefile.getframerate() # フレームレート(1秒間のデータ出力数)
print("フレームレート:", framerate/1000, "KHz") 
nchannel=wavefile.getnchannels()
print("チャンネル数:", nchannel)
sampwidth=wavefile.getsampwidth()
print("サンプル幅数:", sampwidth)
nframes = wavefile.getnframes() # フレーム数(データの個数)
print("nframes:" , nframes) # 実行例: nframes: 80000(10秒)

import pyaudio # 録音や再生に使う。再生オープン
audio = pyaudio.PyAudio()
format=audio.get_format_from_width(sampwidth) # ストリームを読み書きするときのデータ型
stream = audio.open(format=format,channels=nchannel,rate=framerate,output=True)

print("----- wavファイル1024ワードをbufferに読み込みそれを再生する繰り返し -----")
fig, ax = plt.subplots(1, 1) # FigureとAxes object取得
plt.get_current_fig_manager().window.wm_geometry("+100+50") # (100,50)位置に表示
lines, = ax.plot([0,0], [1024, 25000])  # matplotlib.lines.Line2D取得
xlist=np.arange(1024) # x軸プロット用の集合
while True:
    buffer = wavefile.readframes(1024) # 1024フレーム読み取る
    stream.write(buffer)  # ストリームへの書き込みで音が出力される。
    # print("上記再生音のデータバイト数:", len(buffer) )
    if len(buffer) != 1024*2: break
    ylist = np.frombuffer(buffer, dtype="int16")
    plotting( xlist, ylist ) # プロット描画
    xlist += 1024

wavefile.close() # ファイルを閉じる 
stream.stop_stream()

plt.show()

16bitのバイナリーで1024個のフレームごとに、『読み取り、再生、グラフ表示』を繰り返しています。
これは将来的に、連続的で録音で取得する情報を、無限に再生すること行う目標のために作ったコードです。
最終的には、USBで接続される[UMEHOSHI ITA]で録音して、そのUSB受信情報で『読み取り、再生、グラフ表示』させます。
上記のコードはその前提で、連続的で録音で取得する箇所を、ファイルからの呼び出しにした検証用のコードです。
なお、グラフ化に関しては、1024個のフレームごと表示が繰り返されるので、グラフのアニメーション表示になっており、 その1024フレーム1回の表示用として、plotting関数を定義して使っています。
この実行で得られるグラフのアニメーション表示例のイメージを下に示します。

録音時は、「イチ、ニィ、サン、シィ、ゴォ、ロク、シチ、ハチ」、「ニィ、ニィ、サン、シィ」と発音しました。

連続録音再生に挑戦 ステップ1(独自コーディックの模索)

UMEHOSHI ITA基板のPIC32MX270F256B-50I/SPのADC能力は逐次比較の10-bit(1MSPS)です。
録音には向かないけど、前述のサンプリング周波数を8KHzから16Hzに上げて連続録音・再生すること挑戦します。
しかし、USB CDC(Communication Device Class)を介して行うので、転送能力がかなり足りない現状です。
対策として、録音データを圧縮して送信する方法があり、挑戦した内容です。
これは、基板の1024ワード送信に合わせた可逆圧縮の独自コーディックを模索する内容です。

16Bit、16KHzのサンプリングで、1024ワードの.binファイルを作る

まず、目標のバイナリファイルを作る。テスト用として1KHzの正弦波のファイル(16bit16KHz_1KHz1024sin.bin)を作る。
これは、圧縮対象の実験用ファイルとして作成したサウンド生ファイルです。

# 16KHzで16Bitのサンプリングでリトルエンディアン符号無しの1000Hz正弦波データを1024ワード並ぶファイルを、16Bit_1KHz_1024_sin.binの名前で作る。
# このファイルのバイト数は、1024ワードなので、2048byteになる。全体の時間=(1/16KHz)*1024=(1/16e3)*1024=0.064秒
# 振幅は、0〜1024で変化する生成にする。
import math
import numpy as np
sample_rate = 16000 # サンプリングレート
frequency=1000
singen=lambda sec : math.sin(  math.pi * 2 * frequency * sec )*(2**15-1) + (2**15-1)
# singen=lambda sec : math.sin(  math.pi * 2 * frequency * sec )*(2**15-1)*(sec/0.065) + (2**15-1) # ★ 徐々に大きくする
ts = [ 1/sample_rate * t for t in range( 1024 )]
ys = [ singen( t ) for t in ts ] 
# ys = [ singen( t )+int(np.random.uniform(-5,5)) for t in ts ] # ★ ノイズを加える
bs=np.array(ys, np.uint16)
buffer=b''.join(bs) # バイナリに変換

with open("16bit16KHz_1KHz1024sin.bin","wb") as fw:
    fw.write( buffer )

# 確認用の表示
import matplotlib.pyplot as plt
plt.plot( ts, ys )
plt.show()

上記で生成されたファイルは、uint16が1024個のが並ぶ2048byteサイズで、 ADCの出力データを模擬したデータです。
そして、このファイルの圧縮を後で検討します。
なお、このファイルをWindow標準圧縮で.zipにすると、2048byteが237byteになりました。
圧縮元のファイルが正確な算術による正弦波なので、同じデータを多く含むからではと思いますが、この圧縮率はビックリ!

チョットな別のサンプルを作って確かめようとしたコードが、上記ソースの次のコメント部分です。
# singen=lambda sec : math.sin( math.pi * 2 * frequency * sec )*(2**15-1)*(sec/0.065) + (2**15-1) # ★ 徐々に大きくする
このコメント外して、このコードを有効にすると、Window標準圧縮で.zipに変換した場合のサイズが、2160byteになりました。
予想の通りで、同じデータをほとんど含まないファイルは、圧縮できないという結果です。

なお、次のコードを有効にして乱数で雑音(-5から+5)を加えてみました。
# ys = [ singen( t )+int(np.random.uniform(-5,5)) for t in ts ] # ★ ノイズを加える
この結果の.zipに変換したファイルサイズ例では、2048byteが2198byteになりました

この時の波形を以下に示します。(乱数を加えていますが、見た目は加えていない場合と変わりません。これが人間の識別というものですね)



16Bit、16KHzのサンプリングが1024ワード並ぶだけのbinから、.wavファイルを作って検証

上記で作成した、ADC出力の模擬ファイルの検証用の、.wavファイルに変換するプログラムです。
ADC出力イメージのバイナリイメージファイル(16bit16KHz_1KHz1024sin.bin)を読み取り、 その16bit符号無し 16KHz 1024 ワード(リトルエンディアン)のバイナリから、"16bit16KHz_1KHz1024sin.wav"を作り、 作成データの検証をする。
参考: https://docs.python.org/ja/3/library/wave.html

import wave
buffer=b''
fr=open("16bit16KHz_1KHz1024sin.bin","rb")
while True:   
    bs = fr.read(1) # 可能な限り読み取る
    if len(bs) != 1 : break
    #bs = bytes([bs[0] - 2**7]) # リストを指定して、bytesへ変換 
    buffer += bs

fr.close()
# 以上のデータ読み取りまでは、他の言語でもできるようにnumpyを使わなかった。

# buffer のbytesがuint16を、int16に変換する。
import numpy as np
ys2 = np.frombuffer(buffer, dtype="uint16")
ys2=ys2.astype(int)
ys2=ys2-(2**15-1)
ys2=ys2.astype(np.int16)
plt.plot( ts, ys2 )
plt.show()
buffer=b''.join(ys2) # バイナリに戻す

# int16が1024個並ぶbytesのbufferから、"16bit16KHz_1KHz1024sin.wav"を生成する。
import wave
''' barray の16bitリトルエンディアンバイト列から、未圧縮でpathのサウンドファイルを作る'''
wavefile = wave.open("16bit16KHz_1KHz1024sin.wav", 'wb') # waveファイルをバイナリ保存用として開く。
wavefile.setnchannels(1) # モノラル(単一の音信号)
wavefile.setsampwidth(2) # 2byteのデータ群と録音する指定
wavefile.setcomptype("NONE", "not compressed") # comptype, compnameの非圧縮指定
wavefile.setframerate(16000) # サンプリングレート
#wavefile.writeframes(buffer) # 上記で設定した属性で、ファイルに出力
wavefile.writeframesraw(buffer) # 上記で設定した属性で、ファイルに出力

print(f"-----{path}ファイルの情報-----") 
print('type: ', type(wavefile)) 
print('チャンネル数:', wavefile.getnchannels()) # モノラルなら1,ステレオなら2
print('サンプル幅:', wavefile.getsampwidth()) # バイト数 (1byte=8bit)
print('サンプリング周波数:', wavefile.getframerate()) # CDは44100Hz
print('フレーム数:', wavefile.getnframes()) # サンプリング周波数で割れば時間
params=wavefile.getparams()# 上記+αのパラメータクラスを返す
print('パラメータ',type(params) ,":", params) 

wavefile.close()

上記で出来上がった、wavファイルをAudacityPortable.exeで、.wavのしての確認を音を鳴らして検証した。

"16bit16KHz_1KHz1024sin.bin"バイナリ(2048byte)を、.wavに変換しているが、そのサイズは2092byteになった。
これをWindow標準圧縮で、.zipに変換すると280byteに圧縮された。
またmp3に変換すると2880 byteに増え、AACに変換すると1769byteに減った。
これはAudacityの[Export Audio]のメニュー操作で行って得た値で、正確な能力差でないかもしれない。


圧縮アルゴリズムの検討中 (この案のバージョンを UMECMPRE Ver 0.1とする)


ADCデータは基板内バッファが一杯になった時点で割り込みで一括送信する仕組みになっているので、 このバッファ一サイズ単位で圧縮する方法にします。

さて、音のように連続的なデータの場合、前に変換した値に対して次のタイミングのデータの変化量に注目すると、 その変化量の情報量が少ないことが多いのではと、予測できます。
そこで、タイミング的に前のデータからの変化量を送ることで行います。

いずれにしても、圧縮は情報量を少なくする考え方なので、これまでのように一つのデータを固定バイトで送る考え方でなく、 一つのデータを、必要に応じて変化するビット長に変換して送ることになります。
そのためには、個々のデータを送る時、ビット単位で送る仕組みが必要になり、同時にビット長が自由に変更できる仕様が必要です。
また、ハフマン符号化のように、まとまった情報ブロックを辞書に登録して短い情報量で情報ブロックに復元する仕様も欲しい所です。
これにより、情報量や、辞書情報への切り替え情報を決める必要があります。以下でその仕様を決めます。
まず、ルールで使うブロックの名前を次のように決めました。(ブロックはあるビット長のまとまりです)

ブロック名記号説明
「データブロック」DAB実際のデータが入るブロックで、同じビット長が続く状態で伝達します。 そして、直前の「データブロック」の変化量(+/-)を記憶させます。
格納データは符号あり情報としますが、ゼロや負のデータは1足した値を記憶させます。 これは全てのビットがゼロを「データゼロブロック」として利用するためです。
ビット長を変更する場合は、「データゼロブロック」で終端させ、 次の「切り替えブロック」で「データゼロブロック」の長さを変更します。
「基準ブロック」BAB「データブロック」が並ぶ前のどこかで、後に並ぶ最初の「データブロック」の基準値を 指定するブロック。これは後述する「切り替えブロック」の直後に存在する。(直後に存在しない場合もある。) このサイズは生データとする。(つまり、16bitのワードであれば16ビットになる)
「切り替えブロック」CHB上記の「データブロック」のサイズを変更する時に使うブロック。 この直後に「基準ブロック」の存在の有無を指定するビットがある。 また、これに続く「データブロック」のビット長を指定するブロックである。
「辞書ブロック」DIB上記の「データブロックや「切り替えブロックが複数並ぶ領域を参照するブロック。
「辞書参照ブロック」RFB「辞書ブロックの再生タイミング位置に存在して、辞書を参照したデータ復元や再生に使う添え字を 記憶・管理する。
「データゼロブロック」ZEB上記の「データブロック」から「切り替えブロック」や「辞書参照ブロック」への変更前に 存在するブロックで、直前の「データブロック」のビット長と同じで、全てのビットがゼロ
その他、特記事項を示す。

「辞書参照ブロック」は、「切り替えブロック」の仕様を次のように決めます。ともに「データゼロブロック」の後に出現するものです。
そこでこの情報の並びで先頭ビットが0を「辞書参照ブロック」、先頭ビットが1を「切り替えブロック」とします。
次にビット数を決めます。将来的に不確定要素があるので先頭の2ビットの並びが00であれば、8ビット長とします。
(これで先頭01並びのデータ長で将来、別のルールを設けこともできるでしょう。 00の8bit長では、残り6bitを辞書内の添え字として使えるので64個指定できることになります。これが少ないと感じる場合などは、 この並びで新しいルールを追加する場合の対応をしているということです。)

次に「切り替えブロック」のビット長ですが、少しでも短い方が良いので4bitに決定します。
先頭1ビット目は、「辞書参照ブロック」と区別で、必ず1です。
1000が「データブロック」を6ビット(-31〜31)に変更する「切り替えブロック」とします。
1001が「データブロック」を9ビット(-255〜255)に変更する「切り替えブロック」とします。
1010が「データブロック」を14ビット(-8191〜8191)に変更する「切り替えブロック」とします。
1011が「データブロック」を15ビット(-16383〜16383)に変更する「切り替えブロック」とします。
1100の場合、次のデータが「基準ブロック」の生データで、ビット長の変更無しとします。
2番目のビットが1の場合、次のデータが「基準ブロック」で、以下はビット長も変更する指定です。
1101「データブロック」を15ビット(-16383〜16383)に変更する「切り替えブロック」とします。
1110「データブロック」を16ビット(-32767〜32767)に変更する「切り替えブロック」とします。
1111「データブロック」を17ビット(-65535〜65535)に変更する「切り替えブロック」とします。


以上の仕様で色々実験検討した結果内容

# 連続データの可逆圧縮プログラム umecmpre.py  (解凍用:umedecmp.py)
import wave 

class Block:
    bit_range=[6,9,14,15,16,17] # 戻り値の候補
    ranges=[2**(v-1)-1 for v in bit_range] # 上記ビットの記憶可能な絶対値
    prevBitSize=25
    downcount = 0 # ビットサイズ連続する回数   
    def __init__(self, category='CHB', val=11):
        self.category=category
        self.nextBase = False
        if category=='DAB': self.value = val # データ設定値
        elif category=='BAB': self.value=val # 基準ブロック
        elif category=='CHB': self.value=val # データブロック長
        elif category=='ZEB': self.value = val # ゼロブロック   
    
    @classmethod
    def getBit(cls,datas, idx, baseValue, nextNumb):
        ''' datas[idx]〜datas[idx+nextNumb]で、必要なビット長を、
            bit_rangeの中から比較して求めて返す。(-1リターンはエラー)
            最初の値がbaseValueとして、
            それからの変化量で記憶することを前提に記憶可能か判定する。
        '''
        bit_rang_idx = 0 # bit_range内走査用添え字
        while True: # 
            prevValue = baseValue 
            iStart=idx
            iEnd=idx+nextNumb
            flagCanStore = True # 記憶可能フラグ
            if iEnd > len(datas) : iEnd = len(datas)
            for n in range(iStart,iEnd):
                diff = datas[n]-prevValue # 前のデータの差
                #print(f"n:{n}, diff:{diff}, bit_rang_idx:{bit_rang_idx},{cls.ranges[bit_rang_idx]}")
                if abs(diff) > cls.ranges[bit_rang_idx]: 
                    #print(f"break:{abs(diff)}>{cls.ranges[bit_rang_idx]}")
                    flagCanStore = False # 記憶ができないサイズ
                    break # 記憶不可
                prevValue = datas[n]
            #print(F"   n={n}, iEnd={iEnd}")
            if flagCanStore == False: # 記憶するビットサイズが足りない
                bit_rang_idx += 1
                if bit_rang_idx == len(cls.ranges): return -1 #エラー
                continue # ビットサイズを大きくして探す
            newBitSize = cls.bit_range[bit_rang_idx]
            if cls.downcount > 0 and newBitSize >= cls.prevBitSize:
                cls.downcount -= 1
                return cls.prevBitSize
            #
            cls.downcount = nextNumb-1
            if newBitSize > cls.prevBitSize:
                cls.downcount = 0 # 大きい方に変化した場合は0に変更
            cls.prevBitSize=newBitSize
            return newBitSize # 必要なビット長

''' 上記クラスの検証
blk = Block('CHB', 17)
ys=[ -y*511 for y in range(0, 10)]
for i in range(5):
    bit=Block.getBit(datas=ys, idx=i, baseValue=0, nextNumb=4)
    print(ys,"必要ビット:",bit)
exit() # 終了
''' #ここまでが簡易検証

import random
print(Block.ranges)
buffer=b''
fr=open("16bit16KHz_1KHz1024sin_0.bin","rb") # 圧縮対象
ys=[]
dic = {} # 共通データがどれだけ存在するかの検証用
while True:
    bs = fr.read(2) # 可能な限り読み取る
    if len(bs) != 2 : break
    #ys.append(bs[0]+bs[1]*256-random.randrange(0, 50))
    y=bs[0]+bs[1]*256
    ys.append(y)
    sy=str(y)
    if sy in dic:
        dic[sy] = dic[sy] + 1
    else:
        dic[sy] = 1

import matplotlib.pyplot as plt
plt.plot(ys)
plt.show() # データ確認用

for k,v in dic.items():
    if v > 1: print(f"{k}のデータ数{v}個")

adc_resolution=16 # サンプリング分解能
datas=ys # データ群のリスト
baseValue=0 # 比較対象となる現在の値
nextNumb=4 # 必要ビットを探索すする先行ワード数
now_bit_len=adc_resolution+1 # 現在のブロックビット数
blk = Block('CHB', now_bit_len) # 「切り替えブロック」
blk.nextBase=True # 次のブロックが「基準ブロック」であることを指定
blocks=[blk]
prevBlk=blk
total_bit = 4 # 圧縮データサイス検証用
for idx in range(len(datas)):
    if prevBlk.nextBase:
        baseValue=datas[idx]
        blk = Block('BAB', baseValue) # 「基準ブロック」
        blocks.append(blk)
        total_bit += adc_resolution # 検証用
        prevBlk=blk
        continue
    chg_bit_len=Block.getBit(datas, idx, baseValue, nextNumb)#ビット長取得
    #print(f"chg_bit_len:{chg_bit_len}")
    if(chg_bit_len == -1): raise "Error!"
    if now_bit_len!=chg_bit_len:
        total_bit += now_bit_len # 検証用
        now_bit_len=chg_bit_len
        blk = Block('CHB', now_bit_len) # 「切り替えブロック」
        print(f"({idx:5}) CHB bit={blk.value:2}")
        blocks.append(blk)
        total_bit += 4 # 検証用
    blk = Block('DAB', datas[idx]-baseValue) # 「データブロック」
    blk.now_bit_len = now_bit_len # pythonならではの情報埋め込み(デバック用)
    print(f"({idx:5}) DAB bit={now_bit_len:2} value={blk.value:5} data={baseValue+blk.value}")
    baseValue=datas[idx]
    blocks.append(blk)
    total_bit += now_bit_len # 検証用
    prevBlk=blk
#
print(f"元サイス:{len(datas)*2}byte, 圧縮サイス:{int(total_bit/8+0.5)}")

上記では、実際に圧縮はさせていませんが各ブロックに必要なビット数から圧縮後のサイズを計算できるようにした検討用プログラムです。
このコードで、前述の振幅一定の1KHz正弦波バイナリファイルを使った実行結果は次のようになりました。

元サイス:2048byte, 圧縮サイス:2451

圧縮サイスの方が増えてしまうという失敗例です。
理由を検討して、対策を試みます。まず、上記仕様で圧縮した場合の各ブロックが並ぶ例を示します。

[CHB],[BAB],[DAB],[DAB],[ZEB],[CHB],[DAB],[DAB],[DAB],[DAB],[ZEB],[CHB],[DAB],[DAB],[DAB],[DAB]・・
[CHB],[ZEB]の間にある[DAB]の並びは[CHB]で指定される同じサイスのデータブロックです。
ですから小さい[DAB]が多く続くほど、小さく圧縮できることになります。

実際に、振幅が徐々に大きくなる定の1KHz正弦波バイナリファイル(変化量が少ないデータを含む)を使った実行結果は次のようになりました。

元サイス:2048byte, 圧縮サイス:2009
少しだけ、圧縮が成功しています。[ZEB],[CHB],の出現数を減らすために、nextNumbの設定値を4から7に変更した結果が次の通りです。
元サイス:2048byte, 圧縮サイス:1829

以上より失敗の理由は、、[ZEB],[CHB],が沢山あるためです。
前述の仕様では、[DAB]のサイズを変更するためには直前の[DAB]のサイズと同じビット数の[ZEB]と4ビット固定の[CHB]が必要です。
ですから、[ZEB],[CHB],を頻繁に行うと、簡単にサイズが大きくなります。

前述の不具合対策をした圧縮アルゴリズム (UMECMPRE Ver 0.2)

前述の失敗の原因は、[DAB]のサイズ変更が多いと、制御の[ZEB],[CHB],が多くなり、余計なサイズが増えます。
よって、不必要なサイズ変更を少なくすることが大事です。
[DAB]のビットサイズ変更用に、「切り替えブロック」の固定4bitの存在は致し方がないとしても、「切り替えブロック」の前に[ZEB]が必要か?と いう点に着目して再検討しました。
元々はビットサイズが可変長なので、この切り替えを知らせるために、変更前の[DAB]のビットが全てゼロの[ZEB]を導入したのです。
(また、そのために[DAB]のデータパターンに全てゼロがビットの値を除いています。)

この[ZEB]の存在を無くす方法を考えて見ました。
その対応策は、[CHB]の中に、その後に続く『同じビット長の[DAB]の並ぶ個数を埋め込む手法です。
  (こうすると[ZEB]をなくせて、に全てのビットがゼロの[DAB]もデータとして使うことができます。)

しかにこれを実現するためには、[CHB]にビット長を記憶するの固定4bit長以外に、並べる個数を記憶する領域が必要となります。
これに必要な最大値は、バッファサイス1024なので10bitです。
この方法ですと、[CHB]のサイズがは、固定4bit+10の14bitも必要で、前と同じで大きなサイズになってしまいます。
そこで、並べる個数を記憶する領域をどうするか検討して、[CHB]の詳細仕様を次のようにしてみます。
[DAB]のビットサイズを指定する部分は、固定長のままですが3bitとします。
前述ソースコードのBlockクラスのbit_rangeがサイズの種類のリストでは6個で足りているからです。
そして並べる個数を記憶する領域を、5ビットにします。つまり、1〜32個の指定とします。

但し、[CHB]の3bit固定部の一部に特別な意味を持たせることにします。
●[CHB]が0b000である場合、現在の[DAB]のビット長を維持したまま、[DAB]が32個並ぶこと意味します。
 個数が32なので、個数をを指定する5ビット領域を無しとします。
 つまり、この特別な3bitの[CHB]だけで、同じビットサイズが33個並ぶ延長指定ができるということです。

●[CHB]が0b111である場合、後ろに並ぶ5ビットで先頭部にある辞書データの添え字(0〜31)を指定します。
 (先頭部にある辞書データは、最大分解能のデータが並ぶ領域とします)
 つまり、この[CHB]は、3+5=8bit固定長サイスです。
 これは、例外的に大きく変化する情報を、ビット長指定なしで表現できる指定です。
 よって比較的に大きなビット長で、重複して出現する情報を辞書データにいれて、8bitで指定できるように使うものです。
 複数の箇所で同じデータを指定できますが、この指定データ最大32個しか登録できません。
 またこの[CHB]の次のブロックは必ず[CHB]が続くものとします。
[CHB]が2続くと、合計16bitでデータを表現することになり全く圧縮にならない。
 それで、[CHB]を続ける指定は無しに、これを使った場合は後述に変化しないビット長の[DAB]が32個並ぶ意味にします。
 これで、[CHB]8bitで辞書データを参照できますが、16bitデータであれば2個の存在で4bit縮小できます。
(3個の存在で、16*3-16-8*3=8で8bit縮小、 4個の存在で、16*4-16-8*4=16で16bit縮小となります。)
 この能力は、以前の案(UMECMPRE Ver 0.1)の「辞書参照ブロック」に対応する仕様となる。

●[CHB]が0b001〜0b110である場合、この0から6の添え字で、Blockのrangesリスト内のビット数を指定します。
 そして、続く固定5ビット領域で続く[DAB]の個数を指定します。
 その個数の[DAB]の並びの直後に次の[CHB]が在ります。無ければデータの終わりを意味します。
 つまり、この[CHB]は、3+5=8bit固定長サイスになります。
先頭部の仕様と、その続きを次のように決めます。

「5bit:ranges[5]のビット数」←最大データの分解能ビット数を記憶するためのビット数」
「5bit:ranges[4]のビット数」
「5bit:ranges[3]のビット数」
「5bit:ranges[2]のビット数」
「5bit:ranges[1]のビット数」
「5bit:ranges[0]のビット数」
「最大データの分解能bit:先頭データ」←「最大データの分解能ビット数を記憶するためのビット数」Ver. 0.1の「基準ブロック」に相当するデータ
『5bit:「辞書参照ブロック」が続く個数(1から32個)』
 上記個数分だけ「最大データの分解能bit:辞書参照ブロック」・・・・・と並ぶ。
『ここで最初の[CHB]』
 [CHB]で示されるビット長で、指定個数分だけ[DAB]が並ぶ。
「[CHB]」
 [CHB]で示されるビット長で、指定個数分だけ[DAB]が並ぶ。
『場合により、[辞書参照のCHB][CHB]』
 [CHB]で示されるビット長で、指定個数分だけ[DAB]が並ぶ。
・・・と続く。