電子回路

2024年12月31日 (火)

ウラギンシジミの日向ぼっこ

きょうは2024年の大晦日です。
しかもきょうは今月2度目の新月で、同月2度目の満月を指す「ブルームーン」ほど知られていませんが、「ブラックムーン」と呼ばれることもあるそうです。次に大晦日がブラックムーンとなるのは19年後の2043年。遠い未来です。

きょうは日中13度ほどまで気温が上がってポカポカ陽気でした。昼過ぎに高円寺天祖神社に年越しの大祓の人形を預けに行って、帰り道で蝶が飛んでいたので見ていたら、陽の当たる路面に止まって日光浴を始めました。近づいてよく見るとウラギンシジミのメスでした(^-^)

ウラギンシジミは、シジミとはいうものの他のシジミチョウとは異なる特徴を持った蝶で、シジミチョウ科ではなくウラギンシジミ科に分類するべきだという議論が、ぼくが知る限り50年くらい前から続いています。ウラギンシジミは、子供の頃、秋が深まって、なったまま熟れた柿の汁を吸いに集ってくるところを捕まえた記憶があるので、晩秋に発生する蝶だと思い込んでいましたが、実際は5月から10月くらいに発生する蝶で、越冬した成虫が春に見られることもあってほぼ一年中生息している蝶のようです。

2024年もよい年でした(^-^)
ことしはSSDAC基板を応用してレコード再生用のRIAAイコライザを実装し、とくに12月になってからRIAAイコライザをIIRとFIRで実装して聴き比べができたのが印象的でした。アナログレコードはまた人気が出てきているようで新譜も出ているようですね。また世界的に見て日本は音楽売上げに占めるCDの割合がとても高く、これは音質の点から喜ばしいことだと思います。

ことしお会いできたみなさまへ。
ことしも大変お世話になりました。また来年もよろしくお願いします。

よいお年をお迎えください。

20241231uragin
写真1.きょう2024年大晦日に目撃したウラギンシジミ(メス)


20190207uragin
写真2.2019年2月7日に目撃した越冬中のウラギンシジミ

| | | コメント (0)

2024年12月17日 (火)

RIAAフォノイコライザを FIR デジタルフィルタで実装する ④ 実践&聴き比べ編

【参考記事】
RIAAフォノイコライザをIIRデジタルフィルタで実装する ①準備編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ②係数計算編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ③シミュレーション編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ④実装・評価編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ⑤Octaveによる位相検証

RIAAフォノイコライザを FIR デジタルフィルタで実装する ① 予告編
RIAAフォノイコライザを FIR デジタルフィルタで実装する ② 係数計算編
RIAAフォノイコライザを FIR デジタルフィルタで実装する ③ シミュレーション編

前回までで、実際にダイレクトリッピングしたwavファイルを入力して、FIRおよびIIRフィルタでRIAAイコライジング処理をする具体的な方法を説明しました。

今回は私がもっている何枚かのアナログレコードからパソコンでダイレクトリッピングしたサンプルソースを紹介し、実際に聞き比べた結果をお知らせしたいと思います。

まずはダイレクトリッピングする際に使用した機材です。図1がそのブロック図、図2は使用したフラットアンプの回路です。

Rippingblockdiagram
図1.ダイレクトリッピングブロック図


Flatampsch
図2.フラットアンプ回路(電源は±12V)


実際のリッピング作業では、SoundForgePro11を使ってファイルフォーマットは96kHz24bitのwavで行い、今回の検証用に、同じSound ForgePro11を使って48kHz16bitにダウンサンプリングしました。
ダウンサンプリングした理由は、今回3種類のフィルタタイプによる比較を行うにあたって、どちらかというとロースペックにした方が差が出やすいのではないかと思ったからです。

今回試聴した曲を何曲か紹介します。以下はすべてダイレクトリッピングしてダウンサンプリングした48kHz16bitフォーマットですので、実際にOctaveでRIAAイコライザを通して聴くことができます。

かっこ内はアーティスト名と収録DISKです。

Minor Swing(Stephane Grappelli/Live in Sanfransisco)

Whisper Not(阿川泰子/Journey)

Autumn Leaves(Bill Evans/PORTRAIT IN JAZZ)

夏服のイブ(松田聖子/Seiko Town)

ロックンルージュ(松田聖子/Seiko Town)

Take Five(Dave Brubeck/TAKE FIVE(Single))

Mambo Mongo(Belmonte & His Afro Latin7/OLE)

NIGHT IN TUNISIA(HOT SALSA/Hot Salsa meets Swedish Jazz)


さて、実際に試聴してみた印象ですが、思ったほど違いはありませんでした。というかほぼ同じです。
ただ、ほんのわずかにですが、FIRの直線位相タイプは女性ボーカルのサ行やパーカッションのハイハット、トランペットの音、バイオリンの弦がこすれる音や、音が細く掠れる場面などが少し物足りないというか、リアリティに欠けるように聞こえる印象がしました。
FIRの非直線位相とIIRはほぼ違いがなく、FIRの直線位相に比べるとリアルな音、という印象です。
ピアノの音や、大編成の演奏の音はほとんど差を感じませんでした。

もっともこれはブラインドテストではなく、自分でどのEQでかけているかわかっていますから、先入観でそう感じた可能性は多いにあります。
機会があれば、少し広めの会場でたくさんの人に聴いてもらって比較をしてみたいと思います。

みなさんもぜひ試してみてくださいね(^-^)


| | | コメント (0)

2024年12月16日 (月)

RIAAフォノイコライザを FIR デジタルフィルタで実装する ③ シミュレーション編

【参考記事】
RIAAフォノイコライザをIIRデジタルフィルタで実装する ①準備編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ②係数計算編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ③シミュレーション編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ④実装・評価編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ⑤Octaveによる位相検証

RIAAフォノイコライザを FIR デジタルフィルタで実装する ① 予告編
RIAAフォノイコライザを FIR デジタルフィルタで実装する ② 係数計算編

この記事はアナログレコード再生の際に使用するRIAAフォノイコライザをFIRフィルタで実装し、シミュレーションと実際の音出しを行って評価を行うことを目的とするものです。


前回までで、今回の目標である2種類のFIRフィルタの係数が計算できました。
2種類のFIRフィルタとはすなわち、直線位相タイプと非直線位相タイプを指します。
RIAA特性はアナログフィルタの時定数で定義されており、当然ながら位相も含めて録音時の逆特性で再生することが求められるため、RIAAイコライザをデジタルフィルタで再現する場合もアナログRIAAイコライザと等価な特性を目指すべきです。

しかしながらFIRデジタルフィルタでは、アナログでは実現できない直線位相フィルタが構成できるため、FIRフィルタで直線位相のRIAAイコライザを構成したらどのような音になるのか?アナログの特性に近似の、非直線位相のFIRフィルタや、IIRフィルタで構成した場合とで聴感上の違いがあるのか?という検証を行うことが今回の目的です。

それではさっそく始めましょう。

前回、直線位相FIRと非直線位相FIRの2種類のRIAAの係数を計算し、それぞれ8192タップの係数のファイルを得ました。
今回はこれらの係数ファイルを使って、特性のグラフ表示と、実際に音楽信号(wavファイル)を処理した出力(wavファイル)を得ることを目標とします。
今回もOctaveを使って進めます。

まずはFIRフィルタの係数ファイルと入力のwavファイルを与えると、結果をwavファイルで出力するプログラムです。

%%%%%ここから

pkg load signal;

% FIRフィルタの係数を読み込み
coeff = load('coeff_nonlin6db_48000.txt');                                 %  ①使用する係数ファイル

% 音声ファイルの読み込み
inputFile = "alice4816_direct_15s.wav";                                    %  ②処理するwavファイル
[~,name,ext] = fileparts(inputFile);
inputFileName = [name,ext];
[input_signal, fs] = audioread(inputFile);                                  % 入力信号とサンプリング周波数を取得

% フィルタリング
filtered_signal = filter(coeff, 1, input_signal);                           % フィルタ適用

% 出力ファイルを保存
outputFileName = sprintf('FIR_RIAA_%s',inputFileName);
audiowrite(outputFileName, filtered_signal, fs);

% 周波数特性を計算
[h, w] = freqz(coeff, 1, 4096, fs);                                          % 周波数特性を計算

% ゲイン(dB)と位相(degree)を計算
gain = 20 * log10(abs(h));                                                   % ゲイン(dB)
phase = angle(h) * (180 / pi);                                              % 位相(degree)

% プロット
figure;

% ゲイン特性
subplot(2, 1, 1);
semilogx(w,gain);
grid;
title(sprintf('FIR RIAA EQ %dHz Sampling',fs));
xlabel('Frequency (Hz)');
ylabel('Gain (dB)');
xlim([10, 30000]);
ylim([-20, 30]);                  % 必要に応じて調整

% 位相特性
subplot(2, 1, 2);
semilogx(w, phase);
grid;
xlabel('Frequency (Hz)');
ylabel('Phase (Deg)');
xlim([10, 30000]);

%%%%%ここまで

使い方は簡単で、まず上のコードをOctaveのエディタにコピペして、適当な名前でOctaveのワークスペースのディレクトリに保存します。
次に、使用する係数ファイルを①のところに記述します。前回のファイルはcoeff_lin6db_48000.txtとcoeff_nonlin6db_48000.txtの2種類でしたので、都度書き換えて使います。
次に、実際に処理したいwavファイルを②のところに指定します。
これらのファイルはすべてOctaveのワークスペースに配置します。
処理したいwavファイルが他の場所にある場合は、フルパスで指定します。形式は"c:/AAA/bbb/CCC.wav"のような書式で、バックスラッシュではなくスラッシュなので注意が必要です。フルパスを打ち込むのが面倒な場合は、Octaveのコマンドウィンドウを表示して、そこに目的のファイルをドラッグ&ドロップすればフルパスが表示されるので、それをコピペすると便利です。

あとは実行ボタンを押せば、特性が表示され、"FIR_RIAA_(入力ファイル名)"でRIAA処理されたファイルが出力されます。
今回は48kHzサンプリングでレコードからダイレクト録音したソースを使って検証しており、処理時間はだいたいwavファイルの再生時間と同じくらいかかります。つまり5分の音楽ファイルなら、処理に5分ほどかかりました。特性は図1のように表示されます。
実行ボタンを押したらすぐにコマンドウィンドウでエラーが出ていないか確認してください。エラーが出ているといくら待っても何も起こりません(^-^;

Fir_riaa_nonlinear_towav

図1.表示された非直線位相FIRの特性


直線位相タイプの係数も入れてみましょう。
リストの①のところにcoeff_lin6db_48000.txtと入力して実行します。図2に表示された特性を示します。


Fir_riaa_linear_towav
図2.表示された直線位相FIRの特性

ここまでで、処理されたwavファイルも出力されたと思います。出力wavファイルは同名の場合は上書きされてしまいますので、都度どこかに待避させてください。


次に、IIRフィルタで同じ処理を行ってみましょう。プログラムは次のとおりです。

%%%%%ここから

pkg load signal;

% フィルタ係数を定義
%{
%96kHz プリワーピングなし +6dB
#ゲインはb0のみで調整
b0 = 0.26;
b1 = [1 0];
b2 = [1 -0.96777105 0];
a1 = [1 0];
a2 = [1 -1.866859545 0.867284263];
%}

%
%48kHz プリワーピングなし +6dB
#ゲインはb0のみで調整
b0 = 0.5;
b1 = [1 0];
b2 = [1 -0.936564324 0];
a1 = [1 0];
a2 = [1 -1.749567588 0.751160265];
%}

% IIRフィルタ係数の計算
b = b0 * conv(b1, b2);
a = conv(a1, a2);

% 音声ファイルの読み込み
inputFile = "alice4816_direct_15s.wav";                  %③
[~,name,ext] = fileparts(inputFile);
inputFileName = [name,ext];
inputFileName
[input_signal, fs] = audioread(inputFile);

% フィルタリング
filtered_signal = filter(b, a, input_signal);

% 出力ファイルを保存
outputFileName = sprintf('IIR_RIAA_%s',inputFileName);
audiowrite(outputFileName, filtered_signal, fs);

% 周波数特性を計算
[h, w] = freqz(b, a, 4096, 'whole', fs);

% ゲイン(dB)と位相(degree)を計算
gain = 20 * log10(abs(h)); % ゲイン (dB)
phase = angle(h) * (180 / pi); % 位相 (degree)

% プロット
figure;

%ゲイン特性
subplot(2,1,1);
semilogx(w,gain);
grid;
title(sprintf('IIR RIAA EQ %dHz Sampling',fs));
xlabel('Frequency (Hz)');
ylabel('Gain (dB)');
xlim([10 30000]);

%位相特性
subplot(2,1,2);
semilogx(w,phase);
grid;
xlabel('Frequency (Hz)');
ylabel('Phase (Deg)');
xlim([10 30000]);
ylim([-100, 0]); % 必要に応じて調整

%%%%%ここまで

このコードでは、96kHzサンプリングと48kHzサンプリングの2種類のフィルタ係数が書かれていて、96kHzはコメントアウトされ、48kHzサンプリングが使えるようになっています。IIRフィルタでの係数計算については
RIAAフォノイコライザをIIRデジタルフィルタで実装する ②係数計算編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ③シミュレーション編
を参照してください。

処理したいwavファイルを③のところに指定して実行すれば、同じように"IIR_RIAA_(入力ファイル名)"でRIAA処理されたファイルが出力されます。
このIIRフィルタの処理は時間がほとんどかからず、即座に結果が出力されます。

図3に表示された特性を示します。


Iir_riaa_towav_20241214210201


図3.表示されたIIRフィルタによるRIAAの特性


これから何曲か処理して、聴き比べをしてみようと思います。はたして聴感的に差があるのか……!?

| | | コメント (0)

2024年12月14日 (土)

RIAAフォノイコライザを FIR デジタルフィルタで実装する ② 係数計算編

【参考記事】
RIAAフォノイコライザをIIRデジタルフィルタで実装する ①準備編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ②係数計算編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ③シミュレーション編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ④実装・評価編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ⑤Octaveによる位相検証

RIAAフォノイコライザを FIR デジタルフィルタで実装する ① 予告編


今回はフリーで使える数値解析用プログラミング言語Octaveを使って、アナログレコード再生用のRIAAイコライザのシミュレーションを行うために、まずはRIAAイコライザ回路をFIRフィルタで構成するためのフィルタ係数を求めます。

最終的な目標は、
・IIRフィルタでRIAAイコライザ回路(アナログ等価=非直線位相)
・FIRフィルタでRIAAイコライザ回路(アナログ等価=非直線位相)
・FIRフィルタでRIAAイコライザ回路(直線位相)
という3種類のシミュレーションと試聴を行い、それぞれの特性と実際の音の違いを検証することです。

ではさっそく、FIRフィルタでRIAAイコライザ回路を構成する作業を説明します。
デジタルフィルタとはなにか?などについては上に書いた過去記事やWikiなどを参照してください。

FIRフィルタは、フィルタ係数を対称形にすることで直線位相フィルタを構成できることが大きな特徴で、今回は直線位相と非直線位相(アナログに近似)の2種類のFIRフィルタ設計でRIAAイコライザ回路を構成します。

任意の周波数特性に対し、逆フーリエ変換を行うと、その周波数特性のインパルスを求めることができ、そのインパルスを係数とするFIRフィルタを構成すれば、もとの周波数特性をもつフィルタができます。

この作業を行う際に、元の周波数特性に複素共役対称性を持たせる(=負の周波数領域を追加して、対称性を持たせる)ことで、逆フーリエ変換の結果が対称性を持ち、直線位相のFIRフィルタを構成することができます。

逆に、元の周波数特性に複素共役対称性を適用せず、正の周波数領域のみで逆フーリエ変換を行った場合は、非直線位相(=アナログで構成するフィルタに近似)となります。

それでは実際の作業に入りましょう。作業環境はWindows11にGNU Octave 8.3.0です。

まずはFIRフィルタの特徴である、直線位相特性をもつRIAAイコライザのフィルタ係数生成です。つぎのコードをOctaveのエディタにコピペして実行すれば、RIAAイコライザを構成するFIRフィルタの係数ファイルが生成されます。

%%%%%ここから

pkg load signal;

% サンプリング周波数とフィルタ設定
fs = 48000;               % サンプリング周波数 (Hz)
N = 8192;                % FIR フィルタのタップ数
f = linspace(0, fs/2, N/2+1);       % 周波数範囲(片側スペクトル)
current_dir=pwd;

% RIAA伝達関数の定義
T1 = 3.18e-3; % 時定数1
T2 = 318e-6; % 時定数2
T3 = 75e-6; % 時定数3
s = 2 * pi * 1i * f; % s = jω
Hs = (1 + s*T2) ./ ((1 + s*T1) .* (1 + s*T3));

% 時間領域のインパルス応答を逆フーリエ変換で取得
Hf_full = [Hs, conj(flip(Hs(2:end-1)))];       % 直線位相特性を得るため複素共役対称性を持たせる
h = real(ifft(Hf_full,N));               % インパルス応答を取得
h = circshift(h, -N/2);                % ゼロ位相シフト
h_6db = 20*h;                    %+6dB@1kHzに調整

% FIRフィルタの周波数特性
[Hfir, w] = freqz(h, 1, N, fs);

% gainとphase
gain = 20*log10(20*abs(Hfir));     % ゲイン特性 (dB)
phase = angle(Hfir) * (180/pi);     % 位相特性 (degree)

% FIRフィルタの係数を保存
save_path=[pwd,sprintf("/coeff_lin6db_%d.txt",fs)]
save("-ascii", save_path, "h_6db");
#save(coeff_file, 'h_fir', '-ascii');

% ゲイン特性と位相特性のプロット
figure;
subplot(2,1,1);
semilogx(w, gain);
grid on;
xlim([10 24000]);
ylim([-30 30]);
xlabel('Frequency (Hz)');
ylabel('Gain (dB)');
title(sprintf('Linear FIR RIAA Response %dHzSampling',fs));

subplot(2,1,2);
semilogx(w, phase);
grid on;
xlim([10 24000]);
xlabel('Frequency (Hz)');
ylabel('Phase (Deg)');

% FIRフィルタ係数をファイルに保存
coeff_file = 'current_dir/coeff.txt';
disp(['FIR filter coefficients saved to ', coeff_file]);

%%%%%ここまで

サンプリング周波数fsとタップ数Nは任意の数値を入れられますが、今回はfs=48000,N=8192としています。
Nは2^nを選びます。
FIRフィルタの低域特性は分解能fs/Nで決まり、この場合は48000/8192=5.86 (Hz)
となるので、周波数帯域は5.86Hz~24kHzとなります。
ゲインは1kHzで+6dBになるように調整しています。

このコードの中で注目すべき箇所は、
Hf_full = [Hs, conj(flip(Hs(2:end-1)))];       % 直線位相特性を得るため複素共役対称性を持たせる
h = real(ifft(Hf_full,N));               % インパルス応答を取得
の部分です。直線位相特性を得るために、求める周波数特性に複素共役対称性をもたせて、周波数特性を正負対称にしています。
そして、ifft関数を使って、この周波数特性のインパルスを得ています。

生成されたFIRフィルタの係数はcoeff_lin6db_48000.txtというファイルとして保存されます。これには8192個の係数が書かれています。
係数ファイルが生成されると同時に、この係数を使った場合のFIRフィルタの特性のグラフが表示されます(図1)。

Riaa_linear_gp

図1.FIRフィルタによるRIAAイコライザ(直線位相)のGain-Phase特性


次に、同じFIRですがこんどはアナログ回路に近い非直線位相のFIRフィルタ係数を生成します。

%%%%%ここから

pkg load signal;

% サンプリング周波数とフィルタ設定
fs = 48000; % サンプリング周波数 (Hz)
N = 8192; % FIR フィルタのタップ数
f = linspace(0, fs/2, N/2+1); % 周波数範囲(片側スペクトル)
current_dir=pwd;

% RIAA伝達関数の定義
T1 = 3.18e-3; % 時定数1
T2 = 318e-6; % 時定数2
T3 = 75e-6; % 時定数3
s = 2 * pi * 1i * f; % s = jω
Hs = (1 + s*T2) ./ ((1 + s*T1) .* (1 + s*T3)); %周波数特性

% 非直線位相を持つフィルタ設計のため、複素共役対称性を適用しない
%Hf_full = Hs; % 複素共役対称性をもたせず、そのまま使用

% 時間領域のインパルス応答を逆フーリエ変換で取得
h = real(ifft(Hs, N)); % 時間領域の係数
h_6db = 36*h; %+6dB@1kHzに調整

% FIRフィルタの周波数特性
[H, f_resp] = freqz(h, 1, N, fs); % 周波数特性の計算

% gainとphase
gain = 20*log10(36*abs(H)); % ゲイン特性 (dB)
phase = angle(H) * (180/pi); % 位相特性 (degree)

% FIRフィルタの係数を保存
save_path=[pwd,sprintf("/coeff_nonlin6db_%d.txt",fs)]
save("-ascii", save_path, "h_6db");

% ゲイン特性と位相特性のプロット
figure;
subplot(2, 1, 1);
semilogx(f_resp, gain, 'b');
xlabel('Frequency (Hz)');
ylabel('Gain (dB)');
title(sprintf('non-Linear FIR RIAA Response %dHzSampling',fs));
xlim([10 24000]);
ylim([-30 30]);
grid on;

subplot(2, 1, 2);
semilogx(f_resp, phase, 'r');
xlabel('Frequency (Hz)');
ylabel('Phase (Deg)');
xlim([10 24000]);
ylim([-100 0]);
grid on;

%%%%%ここまで

使い方は上と同じですが、周波数特性に複素共役対称性を適用せず、正の周波数特性のみを使用し、非直線位相特性になっています。
得られる係数ファイルはcoeff_nonlin6db_48000.txt、特性図は図2のとおりです。

Riaa_nonlinear_gp_20241214180201

図2.FIRフィルタによるRIAAイコライザ(非直線位相)のGain-Phase特性


これでFIRの係数が計算できたので、次回はその係数をFIRフィルタに実装して、再度Gain-Phase特性を確認し、実際に音楽を再生します。

| | | コメント (0)

2024年12月10日 (火)

RIAAフォノイコライザを FIR デジタルフィルタで実装する ① 予告編

この記事は次の参考記事の続編です。
今回はRIAAフォノイコライザをFIRフィルタで実装するシミュレーションを行いました。

【参考記事】
RIAAフォノイコライザをIIRデジタルフィルタで実装する ①準備編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ②係数計算編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ③シミュレーション編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ④実装・評価編
RIAAフォノイコライザをIIRデジタルフィルタで実装する ⑤Octaveによる位相検証

デジタルRIAAフォノイコライザ・サブ基板(基板頒布あり)
SSDAC128 and デジタルRIAAイコライザ デュアル基板(基板頒布あり)

上の参考記事で書きましたが、アナログレコードはRIAAカーブという周波数特性で録音されているので、正しい音で再生するにはRIAAイコライザを通す必要があります。従来はこれをアナログ回路で行っていましたが、IIRデジタルフィルタで構成することも可能で、IIRフィルタではゲイン特性、位相特性ともにアナログフィルタと等価な特性を持たせることができます。

ところで、デジタルフィルタにはIIRの他にFIRというタイプのものがあって、特徴は直線位相特性をもつことです。つまり全帯域において一律にサンプリング時間の遅れのみ発生し、アナログフィルタのように周波数による位相回りが起こらないという特徴があります。

※すみません、最初に記事を書いた時点で間違った思い込みがありました。
正しくは、「係数が対称なFIRフィルタは直線位相になる」であって、係数が対称でなければ直線位相にはなりません。
また直線位相特性が実現できることはFIRフィルタの重要な性質で、IIRでは実現できません。

過去記事ですでに述べたとおりRIAA規格によるレコード録音再生では、ゲイン特性に伴い位相特性も再現する必要があるため、当然ながら位相特性も含めてアナログ回路によるRIAAカーブと等価な特性を再現できるIIRフィルタで構成するのが妥当と考えられます。
ただ、正しい再生ができるかどうかとは別に、FIRフィルタでRIAAカーブを再現するとどのようになるのか、というのは興味があります。

FIRフィルタの係数はどのように求められるかというと、周波数特性を逆フーリエ変換したものがそのインパルス関数となるため、それをFIRフィルタの係数に使うことで実装できます。
ところが、検証に使っていたプログラミング言語”Octave”の逆フーリエ変換"ifft"の使い方がわからず、保留にしていました。

今回別の課題があって、Linux上でsoxやらbrutefirやらで遊んでいたときに、ふと、
「ひょっとしてChatGPT先生に訊いたら、FIRでRIAAイコライザを設計してくれるかも……!?」
と思い、実際にChatGPT先生に訊いてみたら、教えてもらえました\(^o^)/
なんと、Pythonのnumpyに逆フーリエ変換があって、それが使えるのです!

そういうわけで、ChatGPTに教えてもらったPythonのコードを手直ししたものを以下に示します。


############ 以下 コード

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import freqz

# サンプリング周波数
fs = 44100
nyquist = fs / 2

# RIAAカーブの時間定数
t1 = 3180e-6 # 3180 µs
t2 = 318e-6 # 318 µs
t3 = 75e-6 # 75 µs

# 周波数特性の計算
frequencies = np.linspace(10, nyquist, 4096) # 10Hzから始める
s = 2j * np.pi * frequencies # sはフーリエ演算子、jは虚数
riaa = (1 + t2 * s) / ((1 + t1 * s) * (1 + t3 * s)) #RIAAの伝達特性

# 振幅特性(対数スケールでのプロット用)
gain = 20 * np.log10(np.abs(riaa))
gain -= gain[np.argmin(np.abs(frequencies - 1000))] # 1kHzで0dBに正規化

# 位相特性(ラジアンを度に変換)
phase = np.angle(riaa, deg=True)

# FIRフィルタ設計(逆FFT)
N = 8192 # フィルタのタップ数
impulse_response = np.fft.ifft(riaa, n=N) #逆FFTを求める
fir_coeffs = np.real(impulse_response[:N])

# FIR係数を保存
np.savetxt("riaa_coefficients.dat", fir_coeffs)

# FIRフィルタの周波数応答を確認
w, h = freqz(fir_coeffs, worN=4096, fs=fs)
fir_gain = 20 * np.log10(np.abs(h))
fir_gain -= fir_gain[np.argmin(np.abs(w - 1000))] # 1kHzで0dBに正規化
fir_phase = np.angle(h, deg=True)

# グラフ描画
fig, ax1 = plt.subplots(figsize=(12, 8))

# 振幅特性(ゲイン)のプロット
ax1.set_title("RIAA EQ Frequency and Phase Response")
ax1.semilogx(frequencies, gain, label="Ideal RIAA Gain", linewidth=1.5)
ax1.semilogx(w, fir_gain, label="FIR Filter Gain", linewidth=1.5)
ax1.set_xlabel("Frequency (Hz) [Log Scale]")
ax1.set_ylabel("Gain (dB)")
ax1.set_xlim(10, 30000) # 横軸を10Hzから30kHzに設定
ax1.set_ylim(-30, 30) # 縦軸を30dBから-30dBに設定
ax1.grid(which="both", linestyle="--", linewidth=0.5)
ax1.axvline(1000, color="gray", linestyle="--", linewidth=0.8, label="1 kHz Reference")
ax1.legend(loc="upper left")

# 位相特性のプロット
ax2 = ax1.twinx() # 第二軸(位相特性用)
ax2.semilogx(frequencies, phase, label="Ideal RIAA Phase", color="tab:orange", linestyle="--", linewidth=1.5)
ax2.semilogx(w, fir_phase, label="FIR Filter Phase", color="tab:red", linestyle="--", linewidth=1.5)
ax2.set_ylabel("Phase (degrees)")
ax2.set_ylim(-180, 180) # 位相特性の範囲を-180度から180度に設定
ax2.legend(loc="lower right")

plt.show()


#############ここまで


この中でキモとなるのは、

s = 2j * np.pi * frequencies#フーリエ演算子s
riaa = (1 + t2 * s) / ((1 + t1 * s) * (1 + t3 * s))#RIAAの伝達関数

の、RIAA特性の伝達関数の部分と、

# FIRフィルタ設計(逆FFT)
N = 16384 # フィルタのタップ数
impulse_response = np.fft.ifft(riaa, n=N) #逆FFTを求める
fir_coeffs = np.real(impulse_response[:N])

# FIR係数を保存
np.savetxt("riaa_coefficients.dat", fir_coeffs)

ここの、逆FFTを行ってインパルス応答を求めて、FIR係数として保存している部分です。
このFIR係数を使えば実際のFIRフィルタに実装できます。

このプログラムを実行した結果は次のとおり。

20241210_fir_riaa_gp

図1.FIRで実装したRIAAイコライザの特性

図1は、青いプロットがRIAA特性の理論値、橙のプロットがFIRフィルタのシミュレーション結果です。
位相特性も含めてなかなかきれいに出ていますね。
低域で誤差が大きく出ているのは、サンプリング周波数(Fs)とタップ数(N)によって決まる分解能によるものです。この例では分解能Δfは

Δf=Fs/N=96000/8192=11.72(Hz)

となります。

そういうわけで、音が出せる状態になったらまた報告します(^-^)

| | | コメント (0)

2024年10月 3日 (木)

SSSRC( スーパーサンプリング・サンプリングレートコンバータ ) ソフトウェア公開

SSDACのPCアプリによる実装について、これまで次の記事で説明してきました。

SSDACをパソコン上のPythonで実装する ①

SSDACをパソコン上のPythonで実装する ②

これらの記事では、SSDACの目的や原理について説明し、
新たな発見として、スーパーサンプリングによってアップサンプリングしたwavデータは、一般的な市販品のオーバーサンプリングDACで再生しても新たにプリエコー、ポストエコーが発生しない、ということがわかりました。

ソフトウェアでスーパーサンプリングを行うことは、スプライン関数によって補間データを追加してファイルを生成していることから、スプライン関数補間によるサンプリングレートコンバートを行っているということになりますから、入力がCDフォーマットの44.1kHz16bitフォーマットの出力ファイルは、たとえば88.2kHz24bit(2倍スーパーサンプリング)、176.4kHz24bit(4倍スーパーサンプリング)という具合になります。

前回の記事では、88.2kHzサンプリング音源でプリエコー、ポストエコーが発生したとしても、その周波数は44.1kHzなので、聴感上の悪影響はほとんどないであろう事を予想していましたが、実際の検証では、スーパーサンプリングでアップサンプリングしたデジタル信号は、その後オーバーサンプリングDACで処理しても、プリエコー、ポストエコーが発生しない、という意外な結果を得ました。

また、前回までPC上のプログラミング言語Pythonを使ってスーパーサンプリングを実装し検証しましたが、Pythonはインタープリタ言語(逐次翻訳型言語)であることから非常に処理が遅く、15秒の音楽ファイルを処理するのに数時間かかるという具合で、およそ実用的ではありませんでした。

そこで今回は、wavファイルをスーパーサンプリング処理して同じくwavファイルで出力する、サンプリングレートコンバータ、名付けて、
スーパーサンプリング・サンプリングレートコンバータ(SSSRC)をC#コンパイラで実装しましたので、次の通り公開します。

今回作成したSSSRCソフトの仕様は次の通りです。

入力フォーマット : 44.1kHz 16bit (CDフォーマット)
出力フォーマット : 88.2kHz、176.4kHz、352.8kHz、705.6kHz(いずれも24bit)


●ダウンロードと使い方
このソフトはwindows10またはwindows11上で動作します。

ダウンロード - sssrc.zip

SSSRC.zipをダウンロードして任意の場所に解凍したら、SSSRC.exeをダブルクリックして実行します。
このとき、.NETのランタイムがインストールされていないと、次のようなエラーが出ることがあります。

Sssrcnet1
図1..NETランタイムがない場合のエラー

この場合は”Download it now”をクリックして、.NETランタイムをインストールしてから、あらためてSSSRC.exeを実行してください。


●使い方
図2はSSSRCの起動画面です。

Sssrc
図2.SSSRC起動画面


使い方は簡単で、希望するスーパーサンプリング倍率(Super Sampling Ratio)を選んで、変換したいwavファイル(44.1kHz16bitのみ)を、この画面上にドラッグ&ドロップすると、元のwavファイルと同じ場所に”SS_OUTPUT”ホルダが生成され、その中にアップサンプリングされたファイルが生成されます。複数ファイルを一括してドラッグ&ドロップすることができます。
変換時間は、私の古いパソコン(第5世代Corei7(2core)、メモリー16GB、windows11)で、4分(約40MB)のwavファイルを88.2kHzに変換するのに35秒、705.6kHzに変換するのに1分30秒かかりました。

実際にこのソフトで処理した信号の波形を見てみましょう。代表例として、1kHzの方形波と正弦波を4倍スーパーサンプリングでアップサンプリングした場合の波形を図3、図4に示します。

1korg
図3.元波形(44.1kHz16bitサンプリング)


1k4x
図4.4倍アップサンプリング波形(176.4kHz24bitサンプリング)

以上のように、スプライン関数による補間によりアップサンプリングしたファイルが出力されます。

ここで注意したいのは、出力ファイルの容量です。アップサンプリング倍率に加えて、ビット分解能が16bit→24bitと1.5倍となることから、
出力ファイルはアップサンプリング倍率x1.5倍の容量になります。
たとえば長さ4分で約40MBのwavファイルは、2倍アップサンプリングでは3倍の120MBに、16倍アップサンプリングでは24倍の960MBになりますので、ストレージを圧迫します。要注意です。

今後、今回のSSSRCで元ファイルをアップサンプリングして通常のDACで再生したものと、元ファイルをそのまま通常のDACで再生したもの、また、元ファイルをハードウェアのSSDACで再生したものの違いについて聴き比べと検証を行う予定です。


| | | コメント (0)

2024年9月27日 (金)

SSDACをパソコン上のPythonで実装する ②

【今回の結論】
・WAVファイル再生時の前処理として、スーパーサンプリングを応用したアップサンプリング処理をしておけば、通常のオーバーサンプリングDACによる再生においても、プリエコー、ポストエコーによる音質への悪影響をSSDACと同程度に軽減できる。


前回の記事では、Windows上でPythonを使ってSSDACを実装し、補間の様子をグラフで確認しました。
今回はPythonで実装したSSDACで、実際のWAVファイルを処理して、結果を同じくWAVファイルで取り出し、効果を確認、検証します。

なお今回も前回に引き続き、PythonによるSSDACの実装コードを公開します。今回は任意WAVファイルに対して、任意のスーパーサンプリング倍率処理を施したWAVファイルを出力するもので、名称はSSSRC(スーパーサンプリング・サンプリングレートコンバータ)です。

SSDAC(スーパーサンプリングD/Aコンバータ)の目的は何かというと、過渡的な信号を再生する際に、従来から行われているオーバーサンプリングで原理的に発生するプリエコー、ポストエコーというノイズを抑えることです。

図1に、一般的なDACデバイスを使用して過渡的な信号を再生した場合の典型的なプリエコー、ポストエコー発生の様子を、図2にSSDACを使用してプリエコー、ポストエコーが抑えられた波形を示します。

Ess_20240926181001
図1.ESS9018K2Mによるポストエコー、プリエコー


Tek0053_20240926181001
図2.スプライン関数補間により、プリエコー、ポストエコーが抑えられる


図1に示すように、従来のオーバーサンプリングデバイスではプリエコー、ポストエコーが発生し、その周波数はサンプリング周波数の半分です。たとえばCD音源の場合はサンプリング周波数が44.1kHzなので、プリエコー、ポストエコーの周波数は22.05kHzです。ヒトの可聴周波数帯域は20kHz上限といわれていますから、22.05kHzのノイズが直接聞こえることはないと思われますが、CDには20kHzまでの信号が記録されていて、たとえば記録上限の20kHzの音が再生されたとき、これが22.05kHzのプリエコー、ポストエコーと干渉して、その差分すなわち2.05kHzのビートが発生する可能性があります。このようにプリエコー、ポストエコーが発生すると、音源に含まれるすべての周波数帯域との間でビートが発生することになり、これが聴感に影響を与えます。
このようなことが起こらないように、プリエコー、ポストエコーの発生を抑えつつデータ補間を行う事を目的に考案されたのがSSDACで、同じ過渡的な信号をSSDACで再生すると、図2に示すようにプリエコー、ポストエコーが抑えられます。

ところで、上に述べたプリエコー、ポストエコーは、22.05kHzという、再生周波数上限の20kHzに近い周波数で発生することによって聴感に悪影響を与えますが、プリエコー、ポストエコーの周波数が十分高ければ、具体的にはたとえば44.1kHz、あるいはそれよりも高い周波数であれば、もし発生しても聴感上の影響はかなり小さくなるだろうと思われます。それはつまり、音源のサンプリング周波数が88.2kHzまたは96kHz以上であれば、プリエコー、ポストエコーの周波数は44.1kHzまたは48kHz以上となり、聴感への悪影響は些少であるということです。

そうすると、44.1kHzサンプリングのCD音源信号を、88.2kHzサンプリングに変換してから通常のDACで再生すれば、プリエコー、ポストエコーの影響は無視できるのではないかと考えられます。
それならばSRC(サンプリングレートコンバータ)であらかじめサンプリングレートを88.2kHzに変換しておけばよいのではないかということになりますが、これでは意味がないのです。なぜなら、SRCの原理はオーバーサンプリングと同じで、インターポーレーション後にFIRフィルタ処理を行うため、SRCの行程でプリエコー、ポストエコーが付加されてしまうからです。

そこで、2倍のスーパーサンプリング、つまりスプライン関数でデータ間1点の補間を行えば、プリエコー、ポストエコーの発生を抑えた2倍アップサンプリングが可能で、この2倍アップサンプリングデータを通常のDACで再生すれば、プリエコー、ポストエコーの影響が些少な再生が可能であると考えられます。

以上のような考察に基づいて、以下の検証を行います。

【使用音源】
a. 1kHz方形波をCDフォーマット(44.1kHz16bit)で録音したWAVファイル→元音源
b. 元音源を2倍スーパーサンプリングによってアップサンプリングしたもの→2XSS音源(88.2kHz32bit)

【評価内容】
①元音源をソフトウェア(TASCAM Hi-Res Editor)によって352.8kHz32bitにアップサンプリング
②2XSS音源をソフトウェア(TASCAM Hi-Res Editor)によって352.8kHz32bitにアップサンプリング
③上記①、②の再生波形において、プリエコー、ポストエコーの発生を比較

【評価結果】
まずは図3に元音源(1kHz方形波44.1kHz16bit sampling)の波形を示します。
ここでは補間の様子がわかりやすいように、すべての波形はデータの散布図として表示します。波形の表示にはaudacity2.4.2を使用しました。

Sq1k_org
図3.元波形(1kHz方形波 44.1kHz16bit)

この元波形を、TASCAM Hi-Res Editorを使用して352.8kHz32bitにアップサンプリングしたものを図4に示します。

Sq1k_352
図4.352.8kHz32bitにアップサンプリング


図4では、プリエコーポストエコーが発生しているのがわかります。これは一般的なDACデバイスによってオーバーサンプリング再生を行った場合と等価な結果です。


次に、元波形に対して今回作成したpythonによるSSSRCを使って2倍のアップサンプリングをした波形を図5に示します。

Sq1k_ss2x
図5.SSSRCによる2倍アップサンプリング波形

スーパーサンプリングによる補間によって、立ち上がり前後でオーバーシュートが発生する典型的な波形になっています。これは図2で示したSSDACによる波形と等価です。
これは元の波形のフォーマットを44.1kHz16bitから88.2kHz32bitにアップサンプリングしたものであると考えることができます。

次にここで得られた88.2kHz32bitサンプリングの信号を、TASCAM Hi-Res Editorを使って352.8kHz32bitサンプリングにアップサンプリングした波形を図6に示します。

Sq1k_ss2x_352
図6.SSSRCで2倍アップサンプリングしたものを、352.8kHzにアップサンプリングしたもの

これは興味深い結果で、44.1kHzのプリエコー、ポストエコーが新たに付加されることを予想していましたが、予想に反して新たなプリエコー、ポストエコーは付加されず、SSSRCによる2倍アップサンプリングの波形(エンベロープ)がそのまま出てきました。

つまり、SSSRCで前処理として2倍アップサンプリングしておくことによって、一般的なオーバーサンプリングDACデバイスにる再生においてSSDACと等価な結果が得られるのではないか?ということです。

もちろん前処理は2倍に限らず任意のアップサンプリング倍率を選ぶことができますが、ソフトによる処理では倍率が高くなるほど処理時間が増加し、生成されるファイルが大きくなります。
今回のSSSRCでは、5秒のWAVファイルを2倍アップサンプリングするのに1分40秒かかりました(^-^;


【SSSRC(スーパーサンプリング・サンプリングレートコンバータ)ソースコード】
今回もPythonによりコードを作成したので公開します。
コードの内容は前回発表したSSDACの実装とほとんど同じで、今回は補間してできあがったデータをWAVファイルとして出力する部分を追加しました。

ダウンロード - 20240924python_ssdac101.py

興味のある方はダウンロードしてご覧ください。
以下は要点です。

19行目
data_dir = 'C:\\Local_Files\\Python'
これは加工する元WAVファイルの格納ディレクトリです。

20行目
fname = '1k_1s.wav'
加工するターゲットファイルです。

26行目
ss_ratio=2 #スーパーサンプリング比
スーパーサンプリング倍率を設定します。2^nの数値を設定します。上限はありませんが、大きくするほど仕上がりのファイル容量は大きくなります。

75行目
wavfile.write(fileName, samplerate*ss_ratio, (y_all*47662).astype(np.int32))
演算したデータをWAVファイルに書き込みます。ファイル名は演算開始時刻にしており、できあがったWAVファイルの生成時刻との比較により演算に要した時間がわかります。
y_allは16bitフルスケールの整数からfloatで計算しているため、これを32bit整数に拡張してファイル出力します。スーパーサンプリングによる補間によって、信号ピークは最大1.375倍になるので、65536/1.375=47662を乗じて、フルスケールになるように調整します。

78行以降
グラフ表示部分をコメントアウトしています。

※できあがりのWAVファイルは、このソースファイルと同じディレクトリに生成されます。


【今後の予定】
今回のPC上のPythonによる実装では、多大な処理時間がかかり実用的ではないため、C#などのコンパイラによる実装で処理時間を短縮し、より実用的なものにしたいと思います。


そういうわけで、ソフト処理によってより簡単にSSDACの効果を体感できるということがわかりましたので、より多くの方々にSSDACの音を楽しんでいただきたいと思います(^-^)

【おまけ】
今回公開したPythonのファイルはWindowsパソコン上で実行できます。
もっとも簡単な方法は、Anaconda3をインストールして、同梱のSpyderを起動、pyファイルを読み込んで実行ボタンを押せば実行できます。
Anaconda3はこちらからメールアドレスを入力すればダウンロードできます。
実行結果にグラフが含まれる場合は、Spyderの右上窓のPlotsタブに表示されます。
コマンドラインでのPython実行は右下窓で実行できます。
Pythonが初めての方もぜひ遊び感覚でいじってみてくださいね(^-^)

関連記事
SSSRC( スーパーサンプリング・サンプリングレートコンバータ ) ソフトウェア公開



| | | コメント (0)

2024年9月 5日 (木)

aliexpressで購入したCP2112欠陥基板

SiliconLabsのCP2112というデバイスがある。これを使うとマイコンの介在なしに、パソコンのUSBポートから、C#などのコードを使って直接I2CデバイスやGPIOの制御ができる。このデバイスとUSBコネクタとピンヘッダが搭載された基板が販売されている。

サンハヤト

Amazon

AliExpress

この中でサンハヤト製がいちばん信頼が置けそうだが少しお高めだ。アマゾンで売っているものはサンハヤトの半額以下で、お試しに購入するにはよさそうだが、アリエクではさらにその半額に近い値段で、しかもUSB-Cのものがあるので、アリエクのUSB-Cのものを3つ購入した。

テスト用のアプリは、本家SiliconLabsのサイトから、
Legacy: CP2112 Software Development Kit for Windows XP and Vista
をダウンロードして使う。XP and Vistaと書いてあるが、windows11でも使えるようだ。
また、上のサンハヤトのサイトからは、C#で書かれたサンプルプログラムが入手可能だ。

さっそくSiliconLabsのテストアプリをつかって、とりあえずGPIOのON/OFF動作を見てみたが、どうも様子がおかしい。
動いているようではあるのだが、ハイレベルが2Vちょっと、ローレベルがマイナスになったりしていて、しかもすごいノイズが乗る。
電源のラインをみると、これも2Vくらいしかなく、どうも様子がおかしいので、回路を追ってみたところ、なんとGNDパターンがGNDにつながっていない(^-^;
写真1のように、GNDをジャンパ線で接続したら正常に動作するようになった。写真2は基板裏面の様子。

Cp2112_a
写真1.GNDジャンパ接続の様子

Cp2112_b
写真2.基板裏面

このデバイスは、I2CのほかにGPIOが8本使えるが、この基板のシルクを見るとGPIOはIO5~IO7の3本しか出ていないように見える。
回路を追ってみると、IO0とIO1は基板上のLEDに接続されていて、外部には引き出されていない。IO0とIO1はそれぞれTxToggleとRxToggleという機能が使えるようになっており、これは通信時のデータアクセスランプとして使う機能のようだ。IO2~IO4はどうなっているかというと、それぞれMAK、INT、RSTのシルクの端子に接続されていた。つまりIO2~IO7は外部に引き出して使うことができる。デバイスのRST端子は4.7kでプルアップされていて、外部には出されていない。

そういうわけで、どうやら無事に使えるらしいことがわかった。
アリエクはまあ、こういうことが時々あって、やれやれ、という感じだ……(^-^;

| | | コメント (0)

2024年7月15日 (月)

SSDACをパソコン上のPythonで実装する ①

この記事では、これまで紹介してきたSSDACを実際にパソコン上のPythonで実装し、デジタルデータをスプライン関数で補間するという作業をご理解いただきたいと思います。

今回は、まずは簡単な信号を録音したCDと同じ44.1kHz16bitのWAVファイルを読み込んで、任意のスーパーサンプリング倍率でスプライン補間した場合の波形をシミュレーションします。

最終的にはPythonで実装したソフトで、CDのデータをスプライン関数補間によってSRC(サンプルレートコンバート)したファイルを出力し、疑似ハイレゾファイルとして再生できるようにしたいと思います。

なおこの記事を書くにあたっては、スプライン関数を導出するための公式を使っています。スプライン関数の公式の詳細な説明は次の文献をご参照ください。

トランジスタ技術2018年10月号記事

電気学会論文 ECT-17-058-3

電子情報通信学会論文  ICD2017-28


1.なぜスプライン関数で補間したいのか
オーディオDACでは通常、オーバーサンプリングという技術でデータ補間を行っています。これはごくおおざっぱに言うと、上限周波数20kHzに対してサンプリング周波数が44.1kHzでは粗いので、補間してなめらかにしてから再生するということです。また、このときオーバーサンプリングによって量子化ノイズやエイリアシングノイズの周波数を高域に追いやることで、ポスト・ローパスフィルタの設計が楽にできるというという大きな利点があります。
通常、オーバーサンプリングは、データに対して補間点に0値の点を挿入(インターポーレーション)して、FIRフィルタを通すことで実現していますが、ここで使われているFIRフィルタには、過渡的な再生信号に対してプリエコー、ポストエコーと呼ばれるノイズが発生するという、宿命的な欠点があります。図1に示すように、ポストエコー、プリエコーはサンプリング周波数の半分、たとえばCDだと44.1/2=22.05kHzで発生します。
それでは、プリエコー、ポストエコーの発生を抑えつつ補間する方法はないか?ということで考案されたのが、スプライン関数による補間です。図2に示すように、スプライン関数による補間では、プリエコー、ポストエコーが大幅に抑えられているのがわかります。
これこそが、SSDAC最大の利点なのです。

Ess
図1.ESS9018K2Mによるポストエコー、プリエコー

Tek0053
図2.スプライン関数補間により、プリエコー、ポストエコーが抑えられる


2.なぜこれまでスプライン関数補間によるDACがなかったのか
飛び飛びの点をなめらかにつなぐために、たとえばEXCELでは包絡線の機能があり、これはスプライン関数によるものです。離散的な点をスプライン関数でつなぐ、という発想はごく当然なものです。それではなぜこれまでデジタルオーディオで応用されなかったのでしょうか。

スプライン関数を正確に計算するためには、開始点と終止点が確定していることが必要条件です。たとえば音楽再生で考えると、スプライン関数を計算するためには、ある曲が始まってから終了するまでのすべてのデータを使って演算する必要があるのです。そうすると、データを一曲分先読みしておいて、スプライン関数をすべて計算してから再生するということになり、再生ボタンを押してから再生開始まで、スプライン関数の演算時間を待たなければなりません。短い曲ならいいかもしれませんが、長い曲ほど演算に時間がかかります。また早送りなどをして途中から再生したい場合などには、全体を通して再生する場合とスプライン関数の演算結果が違ってしまうなどの不都合が生じます。
SSDACでは、これらの問題点について数学的なアプローチで解決することに成功しました。

3.どのように解決したのか
いま、CDからデジタル信号が入力しているとして、これは44.1kHz16bitのサンプリングなので1/44100≒22.68μsごとに-32768~32767までのいずれかの値を持つデータが1個(ステレオだと2個)送られてきますから、これをアナログの値に変換するのがD/Aコンバータの仕事です。-32768~32767の値を持ったデータは、D/Aコンバータの仕様によって、たとえば0V~2Vとか、±2.5Vとかの範囲の電圧に変換されて出力されます。

今現在D/A変換している生データをD0として、ひとつ未来の生データをD+1、n個未来の生データをD+n、ひとつ過去の生データをD-1、n個過去の生データをD-nというように、現在の生データD0を中心に、データに番号を振っておきます。

スプライン関数でデータを補間するとは、具体的には現在の生データD0から1個未来の生データD+1までの間を、

Shiki1・・・・・①

という3次関数でなめらかにつなぐということです。この式のd0は現在の生データD0です。
このときxの変域は

Heniki

であり、式さえ求まってしまえば、あとはxを細かくステップすればするほどなめらかな補間ができます。
この式でx=0のときはy0=d0=D0、つまり現在の生データ、x=1のときは1個未来の生データD+1となります。

それでは、スプライン関数の公式を使って、式1を決定するやり方を見ていきましょう。式①を決定するとは、各係数a,b,cを決定することです。

まず式①で、x^2の項の係数bを求める公式は、


Bj・・・・・②

djは入力された生データです。e,fは次式で与えられます。

En・・・・・③

Fn  ・・・・・④

ここで、αは、

Alpha

という定数です(これは発明者の小林芳直氏によってIS(Infinite Spline)定数と名付けられました)。

上記式③、④および定数αが、SSDACでスプライン関数の計算が可能になった最大のキモなので、以下に簡単に説明します。

スプライン関数の計算を実装可能にしたのは、このαという定数の発見と、それに伴い計算の打ち切り条件が算出されたことです。
これらは上述のトランジスタ技術2018年10月号にて詳しく解説していますので、ここではごくおおざっぱに説明します。
まず、現在の生データとひとつ未来の生データの間を補間するスプライン関数に与える前後の生データの影響は、1つ未来または1つ過去に遠ざかるにつれて、αを乗じた分だけ影響度が低下してゆく、ということが発見されました。つまり、式③、式④にて示されるように、ひとつ過去また未来の生データの影響度はα倍、2つ過去または未来の生データの影響度はα^2倍・・・n個未来の過去または未来の生データの影響度はα^n倍となっています。αはおよそ-0.26ですから、2乗、3乗、4乗……と乗じるごとにどんどん小さくなっていきます。
さて、式③、式④では未来または過去に13個離れた生データまでで打ち切られています。これはどうしてかというと、これらの式はデータ精度を24bit出力として得る場合のものなのですが、13個より遠い生データが現在のスプライン関数の演算結果に与える影響は24bit精度以下になるので14個目以降の生データを打ち切ることができる、ということを示しています。
どこで打ち切っていいのか、ということは、希望する出力のbit精度によって違い、次のようになります。

16bit・・・・・未来過去それぞれ9個の生データまでで、10個以降は打ち切り
24bit・・・・・未来過去それぞれ13個の生データまでで、14個以降は打ち切り
32bit・・・・・未来過去それぞれ17個の生データまでで、18個以降は打ち切り

つまり、得たい出力ビット数に応じて、ビット数/2+1までの未来過去までを演算すれば良い、ということがわかったのです。

ここまででスプライン関数のx^2の係数bが計算できたので、残りのaとcを、次の公式を使って求めます。

Aj・・・・・⑤

Cj・・・・・⑥

ここで、djは現在の生データ、dj+1は1つ未来の生データ、bjは先に計算した現在のスプライン関数の係数b、bj+1は1つ未来のスプライン関数の係数bです。

これで、現在の生データから1つ未来の生データまでを補間するスプライン関数が求まりました。

4.PC上のPythonで実装する
それでは以上に述べたスプライン関数によるデータ補間を、実際にPythonで実装してみましょう。
ここでは、CDと同じ44.1kHz16bitサンプリングのWAVファイルのデータを、スプライン関数によって補間し、補間前後の波形をグラフで表示して比較する、ということを行います。
まず筆者の作業環境ですが、windows11にAnaconda3をインストールするとJupyter Notebookが自動的にインストールされるので、これを使っています。

Jupyter Notebookを起動すると、ブラウザ上にjupyterのHomeが起動するので、ここで作業を行います。画面右上のNewのプルダウンからNotebookを選ぶと、新しくNotebookが開きますので、ここのコード入力窓にコードを入力して最終行末尾にカーソルを持って行ってshift+Enterすれば実行されます。
Pythonを使い慣れた人はanacondaに付随しているSpyderを使う方が便利かもしれません。
次のコードの下にソースコードのダウンロードリンクを張っておきます。

以下に、今回Pythonで作成したSSDACのコードを示します。

##############ここから#################################
from os.path import dirname, join as pjoin #文字列接続メソッドjoinのインスタンス定義
from scipy.io import wavfile
import scipy.io
import numpy as np

import matplotlib.pyplot as plt
from scipy.io.wavfile import read

data_dir = 'C:\\MyData\\Python'
fname = '1k_5ms.wav'
wav_fname = pjoin(data_dir, fname)

alpha=-2+3**0.5 #IS定数
ss_ratio=4 #スーパーサンプリング比

samplerate, data = wavfile.read(filename=wav_fname)

#24bit精度でスプライン関数を計算するため、データの先頭と末尾に(24/2)+1=13個の計算用ダミーデータを追加する。
padding_array = np.array([[0, 0], [0, 0], [0, 0], [0, 0],[0, 0], [0, 0], [0, 0], [0, 0],[0, 0], [0, 0], [0, 0], [0, 0],[0,0]])

data=np.vstack((padding_array,data)) #data先頭にダミーデータ13個追加
data=np.vstack((data,padding_array)) #data末尾にダミーデータ13個追加

#ファイル全体分のインパルスe,fの空の配列(L,R)を用意する
e = np.zeros(shape=(len(data),2))
f = np.zeros(shape=(len(data),2))

#ファイル全体分のスプライン関数のaj,bj,cjの空の配列(L,R)を用意する
aj = np.zeros(shape=(len(data),2))
bj = np.zeros(shape=(len(data),2))
cj = np.zeros(shape=(len(data),2))


#仕上がりデータ
y_all=np.empty((1,2)) #ここで空の配列を作る

#ファイル全体分のインパルスe,fを計算する
for i in range(13,len(data)-13):
      for j in range(0,14):
            e[i]+=(alpha**j)*data[i-j]
            f[i]+=(alpha**j)*data[i+j]

#先頭のe(-1)=0
e[12]=0
#末尾のf(+1)=0
f[len(data)-13]=0

#ファイル全体分のスプライン関数の係数aj,bj,cjを計算する
for i in range(13,len(data)-13):
      bj[i]=-3*(3**0.5-1)*data[i]+3*(2*(3**0.5)-3)*(f[i+1]+e[i-1])

for i in range(13,len(data)-13):
      aj[i]=(bj[i+1]-bj[i])/3
      cj[i]=data[i+1]-data[i]-2*bj[i]/3-bj[i+1]/3

for i in range(13,len(data)-26):
      for j in range(0,ss_ratio):
            y_all=np.vstack((y_all,aj[i]*(j/ss_ratio)**3+bj[i]*(j/ss_ratio)**2+cj[i]*(j/ss_ratio)+data[i]))

#plt.plot(y_all)

x=np.arange(0,len(data),1)
plt.xlim(24,55)
#(24,55)@1k_5ms
#(24,35)@10k_500us
plt.step(x,data[x])
plt.title(fname+" NOS Zoom in")
plt.show()

x=np.arange(0,len(y_all),1)
plt.xlim(220,350)
#(110,170)@1k_5ms_ss2
#(220,350)@1k_5ms_ss4
#(900,1400)@1k_5ms_ss16
#(0,17)@10k_500us_ss2
#(0,34)@10k_500us_ss4
#(0,80)@10k_500us_ss16
plt.step(x,y_all[x])
plt.title(fname+" SS"+str(ss_ratio)+"x"+" Zoom in")
plt.show()

x=np.arange(0,len(y_all),1)
plt.xlim(0,len(y_all))
plt.step(x,y_all[x])
plt.title(fname+" SS"+str(ss_ratio)+"x"+" All over")
plt.show()
########################ここまで########################

ダウンロード - 20240716python_ssdac000.py


今回、コードで使用するための44.1kHz16bitサンプリングの短いWAVファイルを用意しました。これはefu氏によるWaveGeneによって生成したもので、Lチャンネルに方形波、Rチャンネルに正弦波を録音してあります。

ダウンロード - 1k_5ms.wav

ダウンロード - 10k_500us.wav


まず、コードの9,10行目で、WAVファイルのディレクトリとファイル名を設定しています。

data_dir = 'C:\\MyData\\Python'
fname = '1k_5ms.wav'

ここはご利用の環境に応じて適宜変更してください。

次に14行目でスーパーサンプリング比を設定しています。これは現在の生データから次の生データまでの間を何倍のデータで補間するかを設定します。現在頒布しているSSDAC基板では、SSDAC128なら128倍、PCM1704やI2Sデバイスを使ったSSDACは16倍になっています。

ss_ratio=4 #スーパーサンプリング比

66行目以降はグラフ表示の部分で、ここでは入力信号の周波数とスーパーサンプリング比によって適切なズーム倍率で表示されるように、plt.xlimの値を変更する必要があります。これは注釈としてコード中に記載してあるので、適宜変更します。
なお、グラフ表示にあたっては、実際のD/Aコンバータと同じく0次ホールドでグラフを描画しているため、ステップ表示になります。

その他の注意点としては、上で述べたとおり、24bit精度でスプライン関数を計算するために、現在のデータの前後13データが必要になるため、ファイルから読み込んだデータの先頭と末尾に13個の0データを付け加えています(18~22行目)。

それではさっそく実行してみましょう。
まずは1k_5ms.wavを使って、4倍スーパーサンプリングの波形を見てみます。ファイルのディレクトリだけ注意して、あとは上のコードのままでいけるはずです。次のグラフが表示されれば成功です。

1k_5ms_nos
図3.1kHzの方形波および正弦波のNOS(元)データ

1k_5ms_4xss
図4.1kHzの方形波および正弦波の4倍スーパーサンプリングデータ(ズーム)


1k_5ms_4xss_all
図5.1kHzの方形波および正弦波の4倍スーパーサンプリングデータ(全体)


図3が元のデータ、いわゆるNOSでのグラフで、図4が4倍スーパーサンプリング処理をしたものです。
図4ではデータが4倍スーパーサンプリングされて、ギザギザ(量子化ノイズ)が細かくなっている様子がわかります。

次に、同じ1kHzの信号を16倍でスーパーサンプリングした波形を見てみます。
プログラムは、14行目
ss_ratio=16 #スーパーサンプリング比
と75行目
plt.xlim(900,1400)
のように変更します。

1k_5ms_nos
図6.1kHzの方形波および正弦波のNOS(元)データ


1k_5ms_16xss
図7.1kHzの方形波および正弦波の16倍スーパーサンプリングデータ(ズーム)


1k_5ms_16xss_all
図8.1kHzの方形波および正弦波の16倍スーパーサンプリングデータ(全体)


図7を見ると、ほとんど段差が見られないほどなめらかになっています。

次に10kHzの信号ではどうでしょうか。10行目を

fname = '10k_500us.wav'

14行目を

ss_ratio=4 #スーパーサンプリング比

とし、67行目を

plt.xlim(24,35)

75行目を

plt.xlim(0,34)

に変更して実行します。

10k_500us_nos
図9.10kHzの方形波および正弦波のNOS(元)データ


10k_500us_4xss
図10.10kHzの方形波および正弦波の4倍スーパーサンプリングデータ(ズーム)


図9の元データでは、正弦波の面影はなくなってしまっていますが、図10の4倍スーパーサンプリングではかろうじて正弦波が復元されているように見えます。ここでは全体データは図10とほぼ同じくなるため省略します。

次に同じ10kHzの信号を16倍スーパーサンプリングします。14行目を

ss_ratio=16 #スーパーサンプリング比

に変更し、75行目を

plt.xlim(0,80)

とします。

10k_500us_16xss
図11.10kHzの方形波および正弦波の16倍スーパーサンプリングデータ(ズーム)


元のデータは図9と同様です。
16倍スーパーサンプリングでは図10と比べてかなりなめらかになりました。全体波形は図11とほぼ同じなため省略します。


スプライン関数によって信号補間を行うSSDACの動作がご理解いただけたでしょうか。
このプログラムにより、今回ご用意した1kHz、10kHzの正弦波、方形波の他にも任意のWAVファイルを処理することができ、スーパーサンプリング倍率も任意に設定可能です。ぜひいろいろな波形、いろいろな条件で試していただきたいと思います。

さて、この記事の続編として、今回と同じくPythonで記述したソフトによってスーパーサンプリングした信号をファイルとして取り出し、擬似的にハイレゾ化する検証をする予定です。つまりスーパーサンプリングによるSRC(サンプリングレートコンバーター)です。

通常、アップサンプリングしたからといって音質が良くなるわけではなく、条件によってはFIRフィルタの使用によってプリエコー、ポストエコーが付加されるなど、百害あって一利無し、と考えられますが、スーパーサンプリングによってアップサンプリングし、それを市販のDACで再生した場合、発生するプリエコー、ポストエコーの周波数はスーパーサンプリングの倍率分高くなりますので、聴感上の悪影響が減少すると考えられます。

関連記事
SSDACをパソコン上のPythonで実装する ②

SSSRC( スーパーサンプリング・サンプリングレートコンバータ ) ソフトウェア公開





| | | コメント (0)

2024年6月 6日 (木)

トランジスタ技術7月号 記事掲載のお知らせ

Toragi202407

6月10日発売のトランジスタ技術7月号別冊付録 「使えるはんだ付け 小型&チップ部品対応」 に私が書いたインタビュー記事が掲載されます(^-^)
これはトラ技ジュニア2018年5月号「宇宙に届け! 日本のはんだ付け技術 」の再掲です。

2018年に、NECスペーステクノロジー社の宇宙機器専門はんだ付け職人、斎藤克摩氏にご協力いただいて、宇宙機器のはんだ付け技術について取材し、記事を書かせていただきました。

NECスペーステクノロジー社はJR南武線西府駅からすぐの、NEC府中事業所内にある、人工衛星やロケットなどの宇宙機器を専門に扱う会社で、開発から販売まですべてのサービスを行っている会社です。


20240606trgbst

記事はこんな感じです。

取材に行ったNECの事業所がある西府駅前には御嶽塚古墳があって、いきなり興味をそそられます。御嶽塚古墳の建設は6世紀前半と言われています。

取材に行った2018年からもう6年経って、ことしはH3ロケットの打ち上げが成功しましたね。トランジスタ技術本紙は「月面探査に学ぶ自律走行ロボット」を特集しています。

ぜひぜひお読みくださいね!!


| | | コメント (0)

より以前の記事一覧