2015年10月11日日曜日

固定小数点演算でDecay波形を作る

テーブル参照で波形は作れた(^q^/


固定小数点型の問題点

前Visual Studioで作ったリズム・マシンのコード(浮動小数点演算)をそのまま固定小数点演算にしようと思ったが、固定小数点演算を使うと問題が発生した。

固定小数点型というのは、32bitなら32bitのうち上位数桁を整数部、残りの下位を小数部として扱う方式だ。

32bitでQ8.24だと上位8bitを整数部として扱い、下位24bitを小数部として扱う。整数部が8bitだと最大で-128 .. +127までしか扱えない。

浮動小数点のつもりで、でかい整数を固定小数点型にキャストするととんでもないことになるし、実際そうなった。

整数演算で線形補完するアルゴリズムもどっかにあった気がするが、調べたりするのがめんどくさいのでDecay波形もテーブル参照でやることにした。

テーブル参照ならテーブルを作ってやれば線形補完だけではなく、疑似的にいろんな波形を使えるかも?という目論見もある。

Decay波形のテーブル


PSoC4 のFlash ROMは意外とサイズが制限されているので、128個のテーブルにした。演算は32bitの固定小数点でやるので固定小数点の中の値(32bit整数)をそのままテーブルにした。

波形テーブル
https://github.com/ryood/DDS_RhythmMachine_Test/blob/fixedpoint/DDS_Decay_Test/ModTableFp32.h


プログラム・コード

浮動小数点演算で結果を見ながら整数演算にに変換していった。

 // DDS_Decay_Test.cpp : Defines the entry point for the console application.  
 //  
 // Decay波形生成テスト  
 //  
 // 2015.10.11 Decayの長さに合わせてDecayの再生周波数に重み付け  
 // 2015.10.11 Created decayのindexの増加はperiodの終了で中断する  
 //  
 #include "stdafx.h"  
 #include <stdio.h>  
 #include <stdint.h>  
 #include <io.h>  
 #include <fcntl.h>  
 #include "fixedpoint.h"  
 #include "ModTableFp32.h"  
 #define SAMPLE_CLOCK     (48000u)  
 //#define MOD_LOOKUP_TABLE_SIZE (128u)  
 #define POW_2_32                (4294967296ull) // 2の32乗 (64bit整数)  
 // カウンター  
 int tick = -1;                    // 初回に0にインクリメント  
 int noteCount = 0;  
 // BPM  
 uint8_t bpm = 120;               // 1分あたりのbeat数 (beat=note*4)  
 int32_t ticksPerNote;          // noteあたりのサンプリング数  
 int period = 24000;  
 // Parameter  
 const fp32 *decayLookupTable;  
 uint8_t decayAmount = 127;  
 uint32_t decayPhaseRegister;  
 uint32_t decayTuningWord;  
 uint32_t decayPeriod;  
 uint8_t decayStop;  
 fp32 decayValue;  
 int _tmain(int argc, _TCHAR* argv[])  
 {  
      _setmode(_fileno(stdout), _O_BINARY);  
      decayLookupTable = modTableDown;  
      // BPMの計算  
      //  
      printf("bpm:\t%d\n", bpm);  
      ticksPerNote = SAMPLE_CLOCK * 60ul / (bpm * 4);  
      // ↑整数演算のため丸めているので注意  
      printf("ticksPerNote:\t%d\n", ticksPerNote);  
      // DDS変数の初期化------------------------------------------------------------------------  
      //  
      // 浮動小数点演算  
      //decayPeriod = (SAMPLE_CLOCK / (((double)bpm / 60) * 4)) * ((double)decayAmount / 256);  
      // 整数演算(64bit)  
      decayPeriod = ((uint64_t)SAMPLE_CLOCK * 60 * decayAmount) / ((uint64_t)bpm * 4 * 256);  
      // decay波形の周期は1note分  
      // 浮動小数点演算  
      //decayTuningWord = (((double)bpm / 60) * 4) * (uint64_t)POW_2_32 / SAMPLE_CLOCK;  
      // 整数演算(64bit)  
      //decayTuningWord = bpm * ((uint64_t)POW_2_32 / 60) * 4 / SAMPLE_CLOCK;  
      // decay波形の周期にdecayAmountで重み付け  
      // 浮動小数点演算  
      //decayTuningWord = ((((double)bpm / 60) * 4) / ((double)decayAmount / 256)) * (double)POW_2_32 / SAMPLE_CLOCK;  
      // 整数演算(64bit)  
      decayTuningWord = (bpm * ((uint64_t)POW_2_32 / 60) * 4 * 256 / decayAmount) / SAMPLE_CLOCK;  
      printf("decayAmount:\t%u\n", decayAmount);  
      printf("decayPeriod:\t%u\n", decayPeriod);  
      printf("decayTunigWord:\t%u\n", decayTuningWord);  
      decayPhaseRegister = 0;  
      decayStop = 0;  
      for (int i = 0; i < period; i++) {  
           tick++;  
           if (tick >= ticksPerNote) {  
                noteCount++;  
                //printf("%d\t%d\n", tick, noteCount);  
                // noteの先頭でtickをリセット  
                tick = 0;  
                // noteの先頭でdecay波形生成の再開  
                decayPhaseRegister = 0;  
                decayStop = 0;  
           }  
           printf("%d\t%d\t", noteCount, tick);  
           // DDS  
           // decayPeriodでdecay波形の生成を終了  
           if (!decayStop) {  
                decayPhaseRegister += decayTuningWord;  
           }  
           if (tick == decayPeriod - 1) {  
                decayStop = 1;  
           }  
           // 32bitのphaseRegisterをテーブルの7bit(128個)に丸める  
           int decayIndex = decayPhaseRegister >> 25;  
           printf("%d\t%d\t", decayPhaseRegister, decayIndex);  
           decayValue = *(decayLookupTable + decayIndex);  
           printf("%f\n", fp32_to_double(decayValue));  
      }  
      return 0;  
 }  

32bitの固定小数点型で0 .. +1.0の範囲で波形を作れたので、これと元波形を乗算すれば振幅変調できるかな?

バグはかなりありそうだが。

メモ:

snareやhihatで使うつもりのノイズ波形はどうすればいい? random()関数で生成してDecay波形と乗算すればいいかな?→random()関数の出力を固定小数点の内部値にそのままつっこむ?