なので、FM音源を作ってみたくなりました。
まずは、基本的な仕組みをYAMAHAの記事(以下)で確認。
FM音源とは
YAMAHAの記事に書いてあることを一言で纏めてみると「FM音源=波形を揺らしたもの」ということです。VGSの波形メモリ音源の場合、三角波、矩形波、ノコギリ波、ノイズの4種類の波形テーブルを持っていますが、これらの波形の読み込み方を変えることで波形を揺らすことができます。
波形の揺らし方
それでは、どうやってやるのか?一応、その辺の仕組みもYAMAHAの記事に書いてありますが、ちょっと専門用語が多くて分かり難い。そこら辺の仕組みはWikipediaで数式を確認することで理解できました。
サイン波は、その名の通り sin(r) で求めることができます。sin関数の引数r(ラジアン値)は時間経過で増加させるのですが、増加させる値が大きければ音が波の周期が短く(音が高く)なり、小さくすれば波の周期が長く(音が低く)なります。
Wikipediaに書かれていた数式を見れば分かるように、sin関数の引数内でβsin(2πMt)という別のsin関数で求まる値を加算しています。こうすることで元のAsinのラジアン値の進み方に揺らぎが生まれます。つまり、音が揺れます。
実際に揺らしてみる
先日記事にしたVGS-SPUを使って、実際に波形を揺らして鳴らしてみます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <pthread.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include "vgsspu.h" | |
#include "table.h" /* S:サイン波, Q:矩形波, W:ノコギリ波, A:音階テーブル */ | |
#define SAMPLING_RATE 44100 | |
#define BIT_RATE 16 | |
#define CHANNEL 1 | |
struct VgsPsgContext { | |
unsigned int hz; | |
int r; | |
int m; | |
int scale; | |
}; | |
static struct VgsPsgContext _context; | |
static struct VgsPsgContext* _ctx = &_context; | |
void cb(void* buffer, size_t size) | |
{ | |
int i, w; | |
short* buf = (short*)buffer; | |
int s = 0; | |
int fm; | |
size >>= 1; | |
for (i = 0; i < size; i++, buf++, _ctx->hz++) { | |
fm = _ctx->r; // キャリア | |
fm += S[_ctx->m >> 8] << 12; // モジュレータ1(サイン波) | |
fm += Q[_ctx->m >> 8] << 12; // モジュレータ2(矩形波) | |
fm += W[_ctx->m >> 8] << 12; // モジュレータ3(ノコギリ波) | |
// バッファオーバフロー防止 | |
if (fm < 0) { | |
fm += 4410 * 256; | |
} else { | |
fm %= 4410 * 256; | |
} | |
// 求まった値を使ってテーブルから波形を読んで書く | |
fm >>= 8; | |
w = S[fm] << 4; | |
*buf = (short)w; | |
// キャリア & モジュレータ それぞれの周波数を進める | |
_ctx->r += A[_ctx->scale]; | |
_ctx->r %= 4410 * 256; | |
_ctx->m += A[_ctx->scale]; | |
_ctx->m %= 4410 * 256; | |
} | |
} | |
int main(int argc, char* argv[]) | |
{ | |
char buf[1024]; | |
void* vgsspu = vgsspu_start2(SAMPLING_RATE, BIT_RATE, CHANNEL, 4096, cb); | |
_ctx->scale = 40; | |
while (1) { | |
printf("COMMAND: "); | |
memset(buf, 0, sizeof(buf)); | |
if (NULL == fgets(buf, sizeof(buf) - 1, stdin)) break; | |
if ('q' == buf[0]) break; | |
if (isdigit(buf[0])) { | |
_ctx->scale = atoi(buf); | |
} | |
} | |
return 0; | |
} |
上記プログラム中で使っている table.h は コチラ。
普通に math.h の sin関数 でも良いのですが、それだと浮動小数点を扱う必要があるし、関数呼び出しのオーバーヘッドもバカにならないので、固定小数点で扱えるsin関数テーブルみたいなものです。ついでに、sin(サイン波)だけではなく、矩形波とノコギリ波と音程を求めるためのラジアン値増分も入れてあります。
普通に math.h の sin関数 でも良いのですが、それだと浮動小数点を扱う必要があるし、関数呼び出しのオーバーヘッドもバカにならないので、固定小数点で扱えるsin関数テーブルみたいなものです。ついでに、sin(サイン波)だけではなく、矩形波とノコギリ波と音程を求めるためのラジアン値増分も入れてあります。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。