FPGAで音を鳴らす ~MachXO2ブレークアウトボードで~

f:id:MinoruKishi:20210331000934j:plain

 

手始めに「FPGAで音を鳴らす」とはどういうことかやってみた。

 いろいろとドタバタすることが多くて、「FPGAをいじってみる」がなかなか進んでおりませんが、それでも慣れてきたところもあって、ちょっとずつ手を出しております。

 秋月電子通商さんでYAMAHA製の音源ICを買ったまではいいのですが、ArduinoIchigoJamで何度かテストしましたが、4MHz水晶モジュールが手に入らなかったためか、うまく動かすことができませんでした。まずは確実に動くことからしようと思ったはずなのですが、初心者の悲哀を感じておりました。回り道をしてしまいましたが、それよりはやっと手になじんできたFPGA(Lattice社製MachXO2ブレークアウトボード)で試してみた、というお話です。

 

楽器の音を鳴らすための基礎知識をネットのブログから学びました

 

ここでネットで調べた音階についての話(「音楽にまつわる数学」)を基に考えてみます。ラの音(周波数は440Hz)も楽器によって音色が異なります。基本となる音の波長には、その仲間として波長の2倍音、3倍音…が存在します。それぞれの基準音、2倍音、3倍音…の音の高さを調節してやることで、それぞれの楽器に合った音色になります。例えば、バイオリンの基準音、2倍音、3倍音…のそれぞれの一番大きい時の高さは26:11:2:1:1の比率になります。基準音がラの音(周波数は440Hz)なら、基準音が上がって下がるまでに2倍音は倍の速さで上下し、3倍音は3倍の速さで上下します。プログラムとしては基準音が一巡するまでの間(上下する間の16種類×5波長分で80種類の音の大きさを準備)、基準音から5倍音までを比率に従って足し合わせた値(音の大きさ)を用意して、音色(たとえばラの音)の周波数に合わせて出力する電圧(大きさ)を切り替えていけばスピーカーから音色となって出てきます。

 

www.slideshare.net

 

FPGAからは1か0かの音(矩形波)しか出てこないので、なめらかに(波形整形)しました

 

FPGAの出力(sound_out信号)に直接ヘッドホンをつなげても音階は聞こえます。ヘッドホン端子は秋月電子通商さんでブレッドボード用のものを購入して、便利でした。回路図と波形整形あり/なしの時の波形を示します。

 

f:id:MinoruKishi:20210331151134p:plain

方形波の波形整形部分

f:id:MinoruKishi:20210331172402j:plain

f:id:MinoruKishi:20210331172437j:plain


結果:当たり前ですが、ちゃんと音を出すのはむつかしい

今回使ったFPGA水晶振動子が4.16MHzと低い周波数なので、各音階で発生できた音は狙った周波数の音を出すだけの正確なものではありませんでした。ギターのチューニングメーターで音階の周波数を測ってみましたが、半音くらいずれているようでした。このFPGAボードには追加の水晶振動子として40MHzくらいのをはんだ付けするスペースがありますが、それでも足りないかもしれません。水晶振動子を変えるのとプログラムをもっと素早くするなど、時間待ちをするためのループカウントの値をもっと大きくしないと周波数を調整するだけの正確さは得られないと思いました。

今回はバイオリンの音を出してみましたが、そういわれればそれらしく聞こえるといったところです。私としてはギターの音で聞いてみたかったので、ギターの場合の比率がわかれば試してみたいところです。

 

<参考>

sound_FPGA1.v

 

`timescale 1ns / 1ps`timescale 1ns / 1ps
module sound_FPGA  (
        input rst,
        output [7:0] led
        , output [15:0] count_clk
        , output [15:0] count_16x5
        , output [7:0] onkai
        , output [15:0] count_clk2
        , output [7:0] count_sound_length
        , output sound_out
        );

        reg [23:0] count;
        reg [15:0] count_clk;
        reg [15:0] count_16x5;
        reg [7:0] onkai;
        reg [7:0] loop_table[0:24];
        reg [15:0] count_clk2;
        reg [7:0] sound_table[0:80];
        reg [7:0] loop_count_10usec;
        reg sound_out;
        reg [7:0] count_sound_length;

// Internal OSC setting (4.16MHz)
        OSCH #( .NOM_FREQ("4.16")) IOSC
        (
          .STDBY(1'b0),
          .OSC(clk),
          .SEDSTDBY()
        );

// LED loop test. 
always @(posedge clk or posedge rst)
    begin
    if (rst)
        count = 0;
        else
        count = count+1;
    end


    assign led = ~count[23:16];


//<波形生成のための二つのテーブル>
// バイオリンの波形は1倍音から5倍音までが26:11:2:1:1の
// 比で表せるそうだ。(SlideShare「音楽にまつわる数学 〜「倍音」で理解する和音と音色〜」)
// https://www.slideshare.net/yuukiiwabuchi9/ss-77105134?next_slideshow=2
// それを合成した波形を作るために、1波長を16分割して5倍音まで組み
// 込むために16×5=80個の配列のテーブルを作った(sound_table)。
//
// そして、80個をつないでいくまでの時間を音階(440Hzのラから
// 880Hz のラまで)ごとに用意するために25個のテーブルを作った
// (loop_table
)。
//
// ただし、ギターのチューニングメーターで音階を測ってみたが、半音くらい
// ずれているようだ。FPGA(MachXO2ブレークアウトボード)の内蔵振動子は
// 4.16MHzで、これが限界かな?と思っている。


//<80個のsound_table[]を音階に従って時間待ちをする処理ルーチン>
// 現在のcount_16x5の値を生成する
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            onkai <= 3;
            count_clk <= 0;
            count_16x5 <= 0;
            loop_table[0] <=      236; //220Hz A
            loop_table[1] <=      223;
            loop_table[2] <=      211; // B
            loop_table[3] <=      199; // C
            loop_table[4] <=      188;
            loop_table[5] <=      177; // D
            loop_table[6] <=      167;
            loop_table[7] <=      158; // E
            loop_table[8] <=      149; // F
            loop_table[9] <=      141;
            loop_table[10] <=      133; // G
            loop_table[11] <=      125;
            loop_table[12] <=      118; //440Hz A
            loop_table[13] <=      112;
            loop_table[14] <=      105;
            loop_table[15] <=      99;
            loop_table[16] <=      94;
            loop_table[17] <=      89;
            loop_table[18] <=      84;
            loop_table[19] <=      79;
            loop_table[20] <=      74;
            loop_table[21] <=      70;
            loop_table[22] <=      66;
            loop_table[23] <=      63;
            loop_table[24] <=      59; //880Hz A
        end
        else if (count_clk == loop_table[onkai]) begin
            if (count_16x5 > 16*5-1) begin
                count_clk <= 0;
                count_16x5 <= 0;
            end
            else begin
                count_clk <= 0;
                count_16x5 <= count_16x5 + 1;
            end
        end
        else begin
            count_clk <= count_clk + 1;
        end
    end


// <10μ秒ごとに信号波形の高さを求める処理ルーチン
//   実際にストレージオシロで観測したら、20μ秒ごとでした>
// sound_outのON時間が出力信号波形の高さとなる。
// 上下する波形の高さを10μ秒ごとにsound_table[count_16x5]から求める。

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            count_clk2 <= 0;
            loop_count_10usec <= 61;
            sound_out <= 0;
            count_sound_length <= 61;

            sound_table[0] <=      31;
            sound_table[1] <=      36;
            sound_table[2] <=      41;
            sound_table[3] <=      45;
            sound_table[4] <=      49;
            sound_table[5] <=      52;
            sound_table[6] <=      55;
            sound_table[7] <=      58;
            sound_table[8] <=      59;
            sound_table[9] <=      60;
            sound_table[10] <=      61;
            sound_table[11] <=      61;
            sound_table[12] <=      62;
            sound_table[13] <=      61;
            sound_table[14] <=      61;
            sound_table[15] <=      60;
            sound_table[16] <=      60;
            sound_table[17] <=      59;
            sound_table[18] <=      58;
            sound_table[19] <=      57;
            sound_table[20] <=      56;
            sound_table[21] <=      54;
            sound_table[22] <=      53;
            sound_table[23] <=      51;
            sound_table[24] <=      49;
            sound_table[25] <=      47;
            sound_table[26] <=      45;
            sound_table[27] <=      43;
            sound_table[28] <=      42;
            sound_table[29] <=      40;
            sound_table[30] <=      39;
            sound_table[31] <=      38;
            sound_table[32] <=      37;
            sound_table[33] <=      36;
            sound_table[34] <=      36;
            sound_table[35] <=      35;
            sound_table[36] <=      34;
            sound_table[37] <=      33;
            sound_table[38] <=      33;
            sound_table[39] <=      32;
            sound_table[40] <=      31;
            sound_table[41] <=      30;
            sound_table[42] <=      29;
            sound_table[43] <=      29;
            sound_table[44] <=      28;
            sound_table[45] <=      27;
            sound_table[46] <=      26;
            sound_table[47] <=      26;
            sound_table[48] <=      25;
            sound_table[49] <=      24;
            sound_table[50] <=      23;
            sound_table[51] <=      22;
            sound_table[52] <=      20;
            sound_table[53] <=      19;
            sound_table[54] <=      17;
            sound_table[55] <=      15;
            sound_table[56] <=      13;
            sound_table[57] <=      11;
            sound_table[58] <=       9;
            sound_table[59] <=       8;
            sound_table[60] <=       6;
            sound_table[61] <=       5;
            sound_table[62] <=       4;
            sound_table[63] <=       3;
            sound_table[64] <=       2;
            sound_table[65] <=       1;
            sound_table[66] <=       1;
            sound_table[67] <=       1;
            sound_table[68] <=       0;
            sound_table[69] <=       1;
            sound_table[70] <=       1;
            sound_table[71] <=       2;
            sound_table[72] <=       3;
            sound_table[73] <=       4;
            sound_table[74] <=       7;
            sound_table[75] <=      10;
            sound_table[76] <=      13;
            sound_table[77] <=      17;
            sound_table[78] <=      21;
            sound_table[79] <=      26;
        end
        else if (count_clk2 >= loop_count_10usec) begin
            count_clk2 <= 0;
        end
        else if (count_clk2 == 0) begin
            sound_out <= 1; // ON
            count_sound_length <= sound_table[count_16x5];
            count_clk2 <= count_clk2 + 1;
        end
        else if (count_clk2 > count_sound_length ) begin
            sound_out <= 0; // OFF
            count_clk2 <= count_clk2 + 1;
        end
        else begin
            count_clk2 <= count_clk2 + 1;
        end
    end


endmodule

 

 

sound_FPGA1.lpf

 

 

BLOCK RESETPATHS;

BLOCK ASYNCPATHS;

LOCATE COMP "led[7]" SITE "107" ;

LOCATE COMP "led[6]" SITE "106" ;

LOCATE COMP "led[5]" SITE "105" ;

LOCATE COMP "led[4]" SITE "104" ;

LOCATE COMP "led[3]" SITE "100" ;

LOCATE COMP "led[2]" SITE "99" ;

LOCATE COMP "led[1]" SITE "98" ;

LOCATE COMP "led[0]" SITE "97" ;

LOCATE COMP "sound_out" SITE "5" ;

LOCATE COMP "rst" SITE "1" ;IOBUF ALLPORTS IO_TYPE=LVCMOS33 ;