遊び tokidoki 仕事

数学と音楽と教育と遊び

Top | おしごと | ゼミ | がくせい | すうがく | かがく | きょういく | おんがく | おきにー | Tips | Photo | イベント | ものもう | あれこれ | About

sin波による疑似フォルマントシミュレーション

今年の4年ゼミ生の一人は音声分析・合成をテーマにしている.
EXCEL上でちまちま作業してもらっているが,
線形予測によるフォルマント抽出がうまくいかないのか,
なかなか母音「あいうえお」すら \sin波から合成できないでいる.

手軽にBASICで母音の合成ぐらいできないものかと探したら,
高々6つ程度の倍音の重ね合わせで「あいうえお」らしい音を作っているページを見つけた.
qiita.com
本当に手軽にできそうだったので,10進BASICでサウンド生成する方法を探して作ってみた.
十進BASIC−外部プログラムの利用

でもって,音程も適当に定めて実行したのが↓

う~ん,そう思って聞けば聞こえなくもない程度.

AquesTalkつかえば,簡単なんだけどね↓

https://www.a-quest.com/index.html


以下,BASICソースと使い方.
実行すると,母音と音程の列を入力するよう聞いてくるので,
母音[aiueo]と音程[0~9]の組合せをinput.
上掲の音源では
   a3i7u9e0o2
とinputしてある.
するとソースのある場所に test.wav が生成されダイレクトにそれが再生される.
いや,その予定だったのだけど,やっているうちにダイレクト再生できなくなった.あれれ.
まぁ,生成された test.wav をクリックすれば再生できるようなので.

REM [疑似母音フォルマント シミュレータ]
REM Ver. 2017/12/23
REM 参考:
REM BASIC 元ネタ:http://hp.vector.co.jp/authors/VA008683/ExtProg.htm
REM               testwave.bas  十進BASICによる音声ファイル生成プログラム 
REM               chr$関数の引数として全ての1バイト整数が許される様に、オプションメニューの
REM               「互換性」「動作」で「文字列処理の単位」を「バイト」に設定して実行すること
REM フォルマント:https://qiita.com/rild/items/339c5c36f4c1ad8d4325

OPTION CHARACTER BYTE
DECLARE FUNCTION formant

INPUT  PROMPT "母音とその高さの列をどうぞ.(「あい↑う↑↑え↓お」は a3i7u9e0o2 など)":vw$
LET vwlen=LEN(vw$)
LET d=0.3  !継続時間(秒)
LET f0=220 !正弦波周波数(Hz)
LET db=-10 !生成波形の最大値(±32767を0dBとする)
LET fs=44100 !標本化周波数(Hz)
LET bps=fs*4 !1秒当りのデータ量(ステレオ16ビット量子化)
LET dsize=d*vwlen*fs*4 !オーディオデータサイズ
LET fsize=dsize+36 !ファイルサイズ(先頭8Bを除く)
LET fmtsize=16 !フォーマットサイズ
LET channel=2^17+1 !ステレオPCMデータの指定
LET reso=2^20+4 !16ビットの指定
LET a=10^(db/20)
OPEN #6 : NAME "test.wav" !ファイルを開き、この名前のファイルが
ERASE #6 !既に存在していた場合には上書きを指定
LET t0=TIME
PRINT #6 : "RIFF"; !以下、wavファイルのヘッダーを作成
CALL out4(fsize) !
PRINT #6 : "WAVEfmt "; !
CALL out4(fmtsize) !
CALL out4(channel) !
CALL out4(fs) !
CALL out4(bps) !
CALL out4(reso) !
PRINT #6 : "data"; !
CALL out4(dsize) !ここまでがヘッダー用の出力
LET al=a*32767 !左チャネル係数
LET ar=a*65535 !右チャネル係数
LET k0=f0/fs*PI*2 !引数の刻み
LET audio$="" !オーディオデータバッファを初期化
FOR vw=0 TO vwlen/2-1
   LET count=0 !カウンタをリセット
   LET vow$=vw$(vw*2+1:vw*2+1)
   LET ht=VAL(vw$(vw*2+2:vw*2+2))
   PRINT vow$,ht
   FOR i=1 TO d*fs
      LET vsin=formant(vow$,k0*(1+ht/20))
      LET lch=INT(vsin*al+0.5)
      IF lch<0 THEN LET lch=lch+65536 !負の数は補数表現
      LET rch=INT(vsin*ar+0.5)
      IF rch<0 THEN LET rch=rch+65536 !負の数は補数表現
      LET audio$=audio$&CHR$(MOD(lch,256))&CHR$(INT(lch/256))&CHR$(MOD(rch,256))&CHR$(INT(rch/256))
      LET count=count+1
      IF count=64 THEN !バッファデータが所定の長さ(64サンプル256Bがほぼ最適)に達したら
         PRINT #6 : audio$; !データをファイルに出力して
         LET audio$="" !バッファを初期化し
         LET count=0 !カウンタをリセット
      END IF 
   NEXT i
   PRINT #6 : audio$; !バッファに残ったデータを出力して
NEXT vw
CLOSE #6 !ファイルを閉じる
PRINT "elapsed time = ";TIME-t0;"seconds"

PLAYSOUND "test.wav"

SUB out4(i4) !4バイト整数の出力(little endian)
   LET j4=i4
   FOR m=0 TO 3
      PRINT #6 : CHR$(MOD(j4,256));
      LET j4=INT(j4/256)
   NEXT m
END SUB

FUNCTION formant(v$,k)
   SELECT CASE v$
   CASE "a"
      LET formant=0.19*SIN(i*k*4)+0.09*SIN(i*k*2)+0.08*SIN(i*k*3)+0.08*SIN(i*k*5)+0.07*SIN(i*k*1)+0.07*SIN(i*k*6)
   CASE "i"
      LET formant=0.19*SIN(i*k)+0.09*SIN(i*k*2)+0.08*SIN(i*k*11)+0.08*SIN(i*k*13)+0.07*SIN(i*k*12)
   CASE "u"
      LET formant=0.19*SIN(i*k)+0.09*SIN(i*k*6)+0.08*SIN(i*k*2)+0.08*SIN(i*k*5)+0.08*SIN(i*k*4)
   CASE "e"
      LET formant=0.19*SIN(i*k)+0.09*SIN(i*k*2)+0.08*SIN(i*k*3)+0.08*SIN(i*k*11)+0.07*SIN(i*k*4)
   CASE "o"
      LET formant=0.19*SIN(i*k*4)+0.09*SIN(i*k*2)+0.08*SIN(i*k)+0.08*SIN(i*k*3)
   CASE ELSE
      LET formant=SIN(i*k)
   END SELECT
END FUNCTION
END