Python:リングモジュレーターの実装①



今回はリングモジュレータを実装していく。

リングモジュレータ

入力と関係ない音程を出すエフェクター。マルチ・エフェクターに組み込まれていることもある。かなり前衛的な演奏ができる。(weblio引用

音としてはこんな感じ

元の音

エフェクト音

理論としては入力の音に対してある周期波形を乗算してめちゃくちゃな音程にするエフェクトです。
変調波形を正弦波として表すとこのような感じ

  • out[ n] = in[ n] \times Asin2\pi f_{0} t
  • t = n / fs

A:正弦波の振幅
f0:正弦波の周波数[Hz]
n:その時のサンプル
fs:サンプリング周波数[Hz]

エフェクトの波形

f:id:gsmcustomeffects:20180801023157p:plain

  • 上段:入力音
  • 中段:変調波形(200Hzの正弦波)
  • 下段:出力音

かけている正弦波の影響を受けて波形が変形している。

ソースコード

処理の流れはこのようになる

  1. 元データがステレオなのでwavファイルをLRにわける
  2. モノラル化する(LRを半分にして加算)
  3. 正弦波を作る
  4. エフェクトをかける
  5. wavに格納しなおす
import wave
import audio_func as af
import scipy
import struct
import numpy as np
from pylab import *

wf = wave.open("GS04.wav", "r")#wav 扱うならお決まりのやつ
num_data = scipy.fromstring(wf.readframes(wf.getnframes()),dtype = "int16") / 32768.0#正規化

if(wf.getnchannels() == 2):
    left = num_data[::2]#1 スライス
    right= num_data[1::2]
    in_data = left*0.5 + right*0.5#2ステレオモノラル化
    #1:スライスの説明
    #a[1,2,3,4,5]ていうリストがあったとして
    #a[::2]  -> 1,3,5
    #a[1::2] -> 2,4

    #2:ステレオ->モノラル化
    #もともとギターの録音で左右に同じ音ふってるのでLR分解して半分にして足せば同じようなもん

fs = wf.getframerate()
AM_WAVE = []#振幅変調波形のメモリ確保
f0 = 200      #正弦波の周波数[Hz]

#フレームの長さ分だけ正弦波を作る
for n in np.arange(wf.getnframes()):
    sine = 0.2 * np.sin(2 * np.pi * f0 * n / fs)
    AM_WAVE.append(sine*(1/0.2))

#リングモジュレータなので振幅変調する
out_data = in_data * AM_WAVE
subplots_adjust(hspace=0.5)
subplot(311)
plot(in_data[0:6000],label="input")
legend()
subplot(312)
plot(AM_WAVE[0:6000],"red")
subplot(313)
plot(out_data[0:6000],label="output")
legend()
show()
#正規化したデータを元に戻す
out_data = [int(x * 32768.0) for x in out_data]
out_data = struct.pack("h" * len(out_data), *out_data)
#af.play(out_data,wf.getsampwidth(),1,wf.getframerate()) #再生
af.save(out_data, fs,1,"ring.wav")

audio_funcは一例です。(かなり雑な実装なため

import wave
import pyaudio
from pylab import *

def printWaveInfo(wf):
    """WAVEファイルの情報を取得"""
    print("チャンネル数 : "+ str(wf.getnchannels()))
    print("サンプル幅 : "+ str(wf.getsampwidth()))
    print("サンプルレート : "+ str(wf.getframerate()))
    print("フレーム数 : "+ str(wf.getnframes()))
    print("総パラメータ(一括表示用) : "+ str(wf.getparams()))
    print("再生時間 : "+ str(float(wf.getnframes()) / wf.getframerate()))

def play (data,sampleWidth,Channel,sampleRate):
    # ストリームを開く
    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(sampleWidth),
                    channels=Channel,
                    rate=int(sampleRate),
                    output= True)
    # チャンク単位でストリームに出力し音声を再生
    chunk = 1024
    sp = 0  # 再生位置ポインタ
    buffer = data[sp:sp+chunk]
    while buffer != '':
        stream.write(buffer)
        sp = sp + chunk
        buffer = data[sp:sp+chunk]
    stream.close()
    p.terminate()


def save(data, fs,Channel,filename):
    """波形データをWAVEファイルへ出力"""
    wf = wave.open(filename, "w")
    wf.setnchannels(Channel)
    wf.setsampwidth(2)
    wf.setframerate(fs)
    wf.writeframes(data)
    wf.close()

Python:ステレオwavファイルをLRに分ける

基礎編です。



信号処理をやって行くにあたりまずは入力となるwavファイルについて知っておく必要がある。
フォーマットに関してはここを読んでほしい
http://sky.geocities.jp/kmaedam/directx9/waveform.html

まあ簡単に言えばヘッダー情報の後はLRLRLRと信号が並んでいる感じ。
I2Sデータもそうだが基本的に信号処理ルーチンを組むならこういったデータを整列して*1いく必要がある。


というわけで2チャンネルのwavファイルを用意してコードを書いてみた
wave_lr.py

import wave
import matplotlib.pyplot as plt
import audio_func as af
import scipy

wf = wave.open("GS03.wav", "r")
af.printWaveInfo(wf)  # デバッグ用
data = wf.readframes(wf.getnframes())
num_data = scipy.fromstring(data,dtype = "int16")

if(wf.getnchannels() == 2):
    left = num_data[::2]
    right= num_data[1::2]

    #スライスの説明
    #a[1,2,3,4,5]ていうリストがあったとして
    #a[::2]  -> 1,3,5
    #a[1::2] -> 2,4

# left channel
plt.subplot(2, 1, 1)
plt.plot(left,label="left")
plt.legend()

# right channel
plt.subplot(2, 1, 2)
plt.plot(right,label="right")
plt.legend()
plt.show()

audio_func.py

def printWaveInfo(wf):
    """WAVEファイルの情報を取得"""
    print("チャンネル数 : "+ str(wf.getnchannels()))
    print("サンプル幅 : "+ str(wf.getsampwidth()))
    print("サンプルレート : "+ str(wf.getframerate()))
    print("フレーム数 : "+ str(wf.getnframes()))
    print("総パラメータ(一括表示用) : "+ str(wf.getparams()))
    print("再生時間 : "+ str(float(wf.getnframes()) / wf.getframerate()))

結果がこんな感じ
もともとモノラルなギターを左右に振ってるだけなので同じ波形が出てくる
f:id:gsmcustomeffects:20180730073550p:plain

コンソール画面

チャンネル数 : 2
サンプル幅 : 2
サンプルレート : 44100
フレーム数 : 196475
総パラメータ(一括表示用) : _wave_params(nchannels=2, sampwidth=2, framerate=44100, nframes=196475, comptype='NONE', compname='not compressed')
再生時間 : 4.455215419501134

補足

フレーム数 : 196475と出ているがこれはwavとしてのフレームのためLR合わせたときのもの

num_data = scipy.fromstring(wf.readframes(wf.getnframes()),dtype = "int16") / 32768.0

とした場合二倍の392950がサイズとなる
f:id:gsmcustomeffects:20180731013557p:plain

上記の例ではこれを分解してるので
Lのサイズ->196475
Rのサイズ->196475

*1:interleaveを外す、解くとか言われてる

logicool K380の紹介

最近キーボードがタンブラー転倒により水没したので買い替えました。

Amazonで2700円ぐらいでした。


最近机の上にマイクロスコープだったりリワークステーションだったが増えてきて邪魔になりつつあったのでBluetoothかつ小ぶりなものを選んだ。

結構使いやすいしアプリから電池残量を確認できたりしてうれしい。

その他半田とかをするときにモニター台の下にすっぽり格納できて机を広く使える。
f:id:gsmcustomeffects:20180513051034j:plain
モニター台座はこれを使ってる(画像クリックでリンク)

横にUSBとACコンセントがあって助かる。

2018開発環境の紹介

ツールだったり調達先だったりをまとめてくれと前々から言われていたので簡単にまとめてみようと思う。
ハードウエア環境、基板メーカー、部品調達先、ソフトウエア環境な感じで紹介していく感じにしようかなと
というわけで今回はハードウエア面のことをやろうと思う。


ハードウエア

YIHUA 992DA+

まずはコテ
YIHUAの992DA+
f:id:gsmcustomeffects:20180402201521p:plain

こいつはAliexpressのYIHUAストアでセールをやっているときに180ドルぐらいで手に入れた。
コテ部分には温調機能とスモークアブソーバーがついているためフラックスを大量に使ったりするときに煙が舞わなくて良い。
海外製ということで消耗品の購入までのリードタイムが長すぎるとか心配する人もいるだろう。
なんと小手先はHAKKOのが使えるw
f:id:gsmcustomeffects:20180402211329p:plain

小手自体は流石に無理なのでこいつ(20ドル程度でヒーターはHAKKOらしい)
https://ja.aliexpress.com/store/product/YIHUA-907I-soldering-iron-for-weldering-machine-tools/401349_1853158228.html?spm=a2g11.12010612.0.0.569f60e0pG8BaX
f:id:gsmcustomeffects:20180402211141p:plain

その他ホットエアーがついている。
その他とは言ってもこれがついているので選んだところが大きい。

中華系サイトで探せば互換品がいくつもあるがこいつについてるホットエアーは結構しっかりしていて安物とは違う感じがある。
Twitterでよく耳にする風が強すぎてチップ部品が飛んでいく等の問題はないレベルまで風量を下げられる。
その他温度も400度ぐらいまで上がる(推奨はしないが)ので結構綺麗にリワークできる。

それ以外の詳細スペックは以下を参照されたい
ja.aliexpress.com

ANDONSTAR ADSM201

普通のHDMIマイクロスコープ
自身にも液晶がついてるがまあ補助程度基本はHDMIで使う方がいいだろう。
f:id:gsmcustomeffects:20180402205524p:plain
SMDパーツの半田付けやリフローのショートチェックに使っている。

あとは基板のリフローの接写とかそういうのにも使える。

山善ホットプレート

ホットプレートリフロー用に購入したもの。
安いので簡単な施策には必須。

基板焦げになりやすいという欠点があるので上にアルミブロックとか置くとヒートショックを避けれる。
参考までにツイート

ホットプレートでもここまで綺麗にできるぞっていう自慢

ピンセット

ピンセットはamazonで買える安いやつ。

先曲がりとストレートが一個づつあるといいと思います。

ディスペンサ

注射器とかシリンジとかその類の話

使用用途としてはフラックス添加とかステンシルがない時のペースト半田印刷

参考ツイートをいくつか貼る。



使っているのはここの青いチップ
https://ja.aliexpress.com/store/product/0-5-Tubing-Length-100-PCS-X-22G-TE-Premier-Dispensing-Tips-dispensing-tips-dispensing-needle/1503742_32627886559.html?spm=a2g11.12010612.0.0.141b46f20TfhQ0

本体はこれ
https://ja.aliexpress.com/store/product/3CC-Manual-Glue-Dispensing-Syringe-Applicator-for-precisely-dispensing-pastes-sealants-and-epoxies/1503742_32219596623.html?spm=a2g11.12010612.0.0.306fb73dh7uskB

本体は好きなの選べばよいがチップを装着する部分がねじ込み式のロックがあるやつを選んだほうがいい。
理由として半田ペーストの粘土が思ったより高いためロックがないタイプのを使うと先端が飛んでいって悲惨なことになる。(僕はこれでなんども萎える経験をしました。)

そのほか補足をしておくと
先端はgという単位で扱われていて数字が大きくなるほど細い
f:id:gsmcustomeffects:20180402223017p:plain

よくわからなければこのようにセットのを買ってみて自分好みのを探すのも良いだろう。
https://ja.aliexpress.com/store/product/1-4-Tubing-Length-100-PCS-TE-Premier-Dispensing-Tips-dispensing-needles-Glue-Dispensing-Tip/1503742_32625074730.html?spm=a2g11.12010612.0.0.45062766LsQwVW


余談だがフラックスを添加するにはこのぐらいの太さがいい(ペーストより多めに添加するため)
https://ja.aliexpress.com/store/product/16G-2-Dispensing-Needles-Tips-For-Glue-100pcs-bag/1503742_32389438536.html?spm=a2g11.12010612.0.0.45062766BYLuVg


ペースト半田およびフラックス

リフローに使う半田と金属表面活性化のためのフラックス

半田はここのを使っている。
https://ja.aliexpress.com/store/product/Leaded-SMT-solder-paste-LED-dedicated-solder-paste-Sn63Pb37-500-grams/2942090_32805542877.html?spm=a2g11.12010612.0.0.405479aeiF6tRC

このケースは結構見るがこのストアは色々な種類を扱っており安心感がある。(まあ他のだと製造日がクソみたいの送ってきて期限がすぎてたりする。)

使用感はこんなもん(フラックスで多少の粘度調整を入れている

Twitterではこのタイプ使ってる人をよく見るけど内蓋のパッキンがないのですぐカピカピになる。
f:id:gsmcustomeffects:20180402224002p:plain

僕の知り合いのあろえさんとかはこれ使ってるらしいけどあの人みたいに一回で結構使わない人だとこれよくない説はある。
無論上記の500gも微妙なところではあるが今の所1年経っても動画のように粘度は低いままだ。

ちなみに鉛フリーも売っている。
https://ja.aliexpress.com/store/product/ECO-solder-paste-M705-s101zh-s4-lead-free-solder-paste-containing-silver-Sn96-5-ag3cu0-5/2942090_32826369659.html?spm=a2g11.12010612.0.0.bed09537RopKNT

見た目はSMICのeco solderっぽい。
ag3.0%って結構いいやつっぽいのでlead freeならこれかな。

無論こっちでもいいのだけれど
https://ja.aliexpress.com/store/product/High-temperature-lead-free-SMT-solder-paste-BGA-solder-paste-special-mobile-phone-repair-Sn99-Ag0/2942090_32807755672.html?spm=a2g11.12010612.0.0.bed09537XlqVBP

ちなみにビスマス含有の場合鉛とか混ざるとクラックがやばいらしいので注意が必要だ。

次にフラックスの話。

コテ使う場合は基本液体の方使ってる。

なぜかでかいの使ってる。
前勤めてた会社で一個もらったのでこんなにでかい。

長くコテ当てたりリフローする場合はこっち使ってる
吉田 YT 60 bga はんだフラックスペーストはんだ 100 グラム SMT を Reballing|flux paste solder|solder flux pasteflux paste - AliExpress


あとはこれ(多分中はあんまし変わらない
Pjlsw はんだペースト,ミルキーホワイトNC 559 ASM g,bga,はんだフラックス,送料無料|solder flux paste|flux pastebga solder flux paste - AliExpress

元ネタ(純正のわけがないw)はamtechのフラックスなんですけど洗浄ありとか無洗浄とかあるけどケースの色で分けてるんかね?(その辺はわかりませんのでとりあえず洗浄することにしてる。

最後にリムーバー

無水エタ使ってる時期もあったけど残渣の結晶化で白い粉が基板に付着するのでトルエンとかキシレンとか入ってるやつの方がいいと思うということでこっちに変えました。
オススメしていただいたざわざわ(@ZawaZawah)さんありがとうございます。

まとめ

今回はツールを紹介した。
万人にオススメできるものではないがsmtで試作をする場合のツールの一つとしてみてくれたら嬉しい。

自作エフェクタデザインのぺーじ



ここ最近エフェクターの創作アイデアをゆきょんくん(@Yukyoooon)と一緒にやってるのでそれの紹介
僕が適当に構図指定するとそれを送ってくれるので非常に助かってます。

デザインは上から順に新しいのがのるはず

はやく形にしたいなぁ
f:id:gsmcustomeffects:20180325011552p:plain

続きを読む

ARMのData Watchpoint and Trace Unitを使って処理時間計測をしてみよう

毎度おなじみのARM共通の機能を使ってみようの記事です。


GPIOを使った処理時間の計測

まあオシロスコープがある方はこういう計測方法をよくやると思います。

MCU_GPIO_WRITE(GPIO_PORT_A,GPIO_PIN_13,TRUE);
/*
計測したい処理を記述
*/
MCU_GPIO_WRITE(GPIO_PORT_A,GPIO_PIN_13,FALSE);

処理時間の幅をはかればある程度の処理時間を計測できます。
f:id:gsmcustomeffects:20171006003331p:plain

ですがこれだと毎回つなぐのめんどいしGPIOの立ち上がり時間が入っちゃう(特に問題にならない)

Data Watchpoint and Trace Unit

DWTははオプションのユニットで、次のようなデバッグ機能を実行します。
DWTユニットには4つのコンパレータが組み込まれており、ハードウェアウォッチポイント、ETMトリガ、PCサンプライベント トリガ、またはデータアドレスサンプラ イベントトリガとして構成することができます。最初のコンパレータDWT_COMP0は、クロックサイクル カウンタCYCCNTに対して比較することもできます。2番目のコンパレータDWT_COMP1は、データコンパレータとしても使用できます。DWTにコンパレータが1つだけ含まれるように構成し、そのコンパレータをウォッチポイントまたはトリガとして使用可能にすることもできます。コンパレータが1つしか存在しない場合、データの一致はサポートされません。
DWTには、次のものに対して使用するカウンタが組み込まれています。

  • クロックサイクル(CYCCNT)
  • フォールドされた命令
  • ロード/ ストアユニット(LSU)操作
  • スリープサイクル
  • CPI(最初のサイクルを除くすべての命令サイクル)
  • 割り込みオーバヘッド

※ARM社より引用

ちなみにこの機能オプションなのでメーカーによってはない場合もある。
STはデータシートの一番下のDEBUGに搭載の記述があるので使える。
f:id:gsmcustomeffects:20171006010306p:plain

今回はこれのCYCCLE(クロックサイクルカウンタ)を利用して処理時間計測を行います。

DWTのレジスタをいじいじしていく

まずやらなければならないことはDWTのアクセスロックの解除です。
LARに対してアクセスキーを入力します。
f:id:gsmcustomeffects:20171006004602p:plain
まあサンプルが出ているのでそのまま使います。

#define DWT_LSR_SLK_Pos                1
#define DWT_LSR_SLK_Msk                (1UL << DWT_LSR_SLK_Pos)
// CoreSight Lock Status Register lock availability bit
#define DWT_LSR_SLI_Pos                0
#define DWT_LSR_SLI_Msk                (1UL << DWT_LSR_SLI_Pos)
// CoreSight Lock Access key, common for all
#define DWT_LAR_KEY                    0xC5ACCE55

static inline void dwt_access_enable(int ena)
{
    uint32_t lsr = DWT->LSR;//ロックステータスのチェック

    if ((lsr & DWT_LSR_SLI_Msk) != 0)
    {
        if (ena)
        {
            if ((lsr & DWT_LSR_SLK_Msk) != 0)    //locked: access need unlock
                DWT->LAR = DWT_LAR_KEY;//ロック解除キーの入力
        }
        else
        {
            if ((lsr & DWT_LSR_SLK_Msk) == 0)    //unlocked
                DWT->LAR = 0;
        }
    }
}

プログラムの最初にこいつを呼んでやります。

あとは以下のようにしてDWTいじってあげます。

void init_cpu_cycle(){
	dwt_access_enable(1);
	CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
	DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
	(*((volatile uint32_t *) 0xE0001004)) = 0;//DWT->CYCCLE = 0;でも可
}

uint32_t get_cpu_cycle(){
	uint32_t cycle = DWT->CYCCNT;
	return cycle;
}

この二つでは主に何をやっているかというと

  • カウンタの有効
  • カウンタの初期化

です。

f:id:gsmcustomeffects:20171006005057p:plain

f:id:gsmcustomeffects:20171006005159p:plain

あとはこれらで取得したサイクルを減算してクロックで除算を行う関数を定義してやると楽です。
僕の場合オーディオ信号処理なので48kHzで割り込みが起きるので処理時間最大値のうちどのぐらいの時間を使っているかで負荷計測をしています。

float get_process_time(uint32_t start,uint32_t stop){
	float time = ((float)stop-(float)start)/(float)SystemCoreClock;
	return time*1000000;//マイクロで返したいから
}

float get_cpu_load(uint32_t start,uint32_t stop){
	float load = (((float)stop-(float)start)/(float)SystemCoreClock) / (float)(1.0/SAMPLE_RATE);
	return load*100;//%で返したいので
}

あとはこんな感じに埋め込む
f:id:gsmcustomeffects:20171006005429p:plain

biquadフィルタを計算させたときの動作はこんな感じ

f:id:gsmcustomeffects:20171006005758p:plain


5.6%とかなのでかなり余裕がある(ART+DSP関数を使っているので)

まとめ

ARMの共通機能のDWTについて理解を深めた
時間があればほかの機能についても試していきたい