お久しぶりです。Qiitaへの投稿は3年ぶりらしい。信じられないけど、確かに実感はある。
作曲好きなんです、唐突ですが。
音が好きなんです。
プログラミングも一応できないことはないんです、多分。
というわけで、Node.jsで基本的な部分をやります。
あ、非常に雑に記事を書いてるので間違ってることとかあるかもしれません。誤りを見つけた方はコメント欄やTwitterのDMなどで指摘していただけると良いかと思います。なお、本当に適当に書いているかつ勉強もあんまりしていないので分かりやすく書いていただけると僕は泣いて喜びます。
対象者
- なんとなくシンセサイザの仕組みがわかる方
- なんとなくNode.jsを使うことができる方
- なんとなくJavaScriptが書ける方
使うライブラリ
-
node-web-audio-api
:有志の方々がWeb Audio APIをNodejs用に移植したバージョンです。- ちなみに、
web-audio-api
というライブラリもありますが、これは9年前から開発が止まっているので非推奨です。 - ちなみにこちらです。https://www.npmjs.com/package/node-web-audio-api
- ちなみに、
-
noteFrequency
:自作したやつです。入力された音程("A4"などの文字列)から440のような周波数の数列が返されます。Google Geminiに書いてもらったから、自作と言えるべきかわからんけど- コードは後述します。
あと、npmやNode.jsは大前提として必要です。
以上
node-web-audio-apiについて
前述の通りこれは有志の方々がNode.jsでは動作しないWeb Audio APIをNode.jsで使うために移植したもので、使い方は元々のWeb Audio APIと近いらしいです。
ここではあまり詳しく説明しないので、使い方を知りたい方はWeb Audio APIの使い方を調べると良いかと思います。
Web Audio API の使用 - Web API | MDN
やりましょう
ライブラリの導入
とりあえずライブラリを導入しましょう。
% npm init
% npm i node-web-audio-api
1行目の時のやつはいろいろ出てくると思うのですがEnterぽちぽちしてください。
あ、package.jsonのscripts
の中のtest
が既定では"echo \"Error: no test specified\" && exit 1"
となっていますが、ここを"node ."
に書き換えておくことでnpm test
で実行できるので少し便利かもしれません。
コードについて
こちらがindex.js
// node-web-audio-apiの読み込み
const {
AudioContext,
OscillatorNode,
GainNode
} = require('node-web-audio-api');
const audioContext = new AudioContext();
// noteFrequencyの読み込み
const noteFrequency = require('./noteFrequency.js');
// 音を鳴らす関数
function ring() {
const now = audioContext.currentTime;
// 音階を指定
const frequency = noteFrequency('A4');
// オシレータの生成
const osc = new OscillatorNode(audioContext, {
type: 'triangle', // 波形の設定
frequency: frequency // 鳴らす音の周波数の設定
});
// エンベロープを作成・設定
const env = new GainNode(
audioContext,
{
gain: 0
}
);
env.gain
.setValueAtTime(0, now)
.linearRampToValueAtTime(0.1, now + 0.02)
.exponentialRampToValueAtTime(0.01, now + 1);
// エンベロープをaudioContextに接続する
env.connect(audioContext.destination);
// エンベロープを先ほど生成したオシレータに接続する
osc.connect(env);
// 音を鳴らす(オシレータ起動)
osc.start(now);
// 音を止める(オシレータ停止)
osc.stop(now + 1);
}
// 音を鳴らす関数を実行
ring();
フィルタやモジュレータ、エフェクトなどは省略します。というか、まだやり方知りません。すみません(汗
コードの説明についてはほとんどすることはありませんが、どうやらaudioContextというのが音を全部管理する?機能を持つようで、そこにオシレータなどいろいろな機能を接続することで動作させているようです。
続きましてnoteFrequencyです。上のindex.jsと同じディレクトリに配置してください。
module.exports = (note) => {
const notes = [
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B",
];
const octave = parseInt(note.slice(-1));
const noteName = note.slice(0, -1).toUpperCase();
if (isNaN(octave) || octave < 0 || octave > 8 || !notes.includes(noteName)) {
return null;
}
const noteIndex = notes.indexOf(noteName);
const a4Frequency = 440; // A4の周波数 (Hz)
const semitonesFromA4 = (octave - 4) * 12 + (noteIndex - notes.indexOf("A"));
const frequency = a4Frequency * Math.pow(2, semitonesFromA4 / 12);
return frequency;
};
これをAIが一撃で出力したなんて信じられないよな・・・と思いつつ、全く問題なく動いてるので本当にすごいです。
実行しよう
これを実行してみると、ラの音(440Hz)が一度だけ鳴ります。
ちなみに音の高さを決めている箇所はindex.jsの22行目なんですが、コードの通り定数frequency
を参照していて、これが先ほど掲載したnoteFrequency.jsの関数に"A4"の音程を渡して440Hzを返してもらっています。
つまり、const frequency = noteFrequency('A4')
のA4の部分を変えることで音程を変えられます。音程は、「AからGのアルファベット+任意の数字(大抵0から7くらい)」という形式で表表記されるので、参考にしてください。ちなみにAからGというのは順に「ラシドレミファソ」です。いろいろ試してみると良いでしょう。
ここからどうする?
この状態だと非常に変化がなく面白くない。というわけで、その場の気温やカメラからの入力、マイクで拾った音や乱数を入力してランダムに音を生成するなど工夫はまだまだできそうです。
以上です。眠いです。おやすみ。