JavaScriptでジャズっぽい曲を作ってみる 「Ongaq JS」
株式会社ユニゾンシステムズのPです。
日頃から趣味でバンド演奏をしている身ですが、
世の中には何やらJavaScriptで作曲ができるライブラリがあるとのこと。
その名も「Ongaq JS」
このライブラリを使って、プログラミングを組んでジャズっぽい曲を作ってみました。
導入方法はチュートリアルを参考にしてください。
https://www.ongaqjs.com/tutorial/register/
基本設定
これから作る曲の基本設定をするため、インスタンスを生成します。
曲の速さ・音量の指定と、ボタンクリックで再生・停止ができるイベントを追加します。
// インスタンス生成
const ongaq = new Ongaq({
api_key: 'XXXXXXXXXXXXXXXXX', // チュートリアルで取得したAPIキーを入力
bpm: 80, // 速さ
volume: 100, // 音量
onReady: () => {
const button = document.getElementById('play');
button.onclick = () => {
if (ongaq.params.isPlaying) {
ongaq.pause(); // 再生
} else {
ongaq.start(); // 停止
}
}
},
});
パート(楽器)の追加
パート毎にインスタンスを生成し、楽器、小節、無音を設定します。
楽器は公式ドキュメントから参照し指定できます。
今回は「my_piano」を指定。
https://www.ongaqjs.com/api/name_sound/
const piano = new Part({
sound: 'my_piano', // 楽器
measure: 16, // 小節
mute: false, // 無音
});
続いて、楽器の音の種類・長さ・タイミングを設定します。
音の種類は公式ドキュメントから参照し指定できます。
https://www.ongaqjs.com/api/name_key/
コードからキーを取得できるAPIも用意されているため、
「Dm7」コードからキーを取得してみます。
タイミングは、beat → 何拍目、measure → 何小節目 と指定します。
↓の例は 「0小節目と12小節目の0拍目」というタイミングで「Dm7」コードを鳴らします。
// new Chord() コードからキーを取得するAPI
console.log(new Chord('Dm7')); // ['2$3', '2$6', '2$10', '3$1']
piano.add(new Filter({
type: 'note',
key: new Chord('Dm7'), // 音の種類
length: 16, // 長さ
active: (beat, measure) => beat === 0 && (measure === 0 || measure === 12), // タイミング
}));
1. ピアノ
また、スコア(楽譜)のように管理ができるようで、今回はスコアを用いて実装してみます。
↓ピアノのスコアを追加
// ピアノ
const piano_score = [
[[new Chord('Dm7')],'','','','','','','','','','','','','','','',], // 0
['','','','','','','','','','','','','','','','',], // 1
[[new Chord('G7')],'','','','','','','','','','','','','','','',], // 2
['','','','','','','','','','','','',[['E3', 'G3']],'','','',], // 3
[[new Chord('CM7')],'','','','','','','','','','','','','','','',], // 4
['','','','','','','','','','','','','','','','',], // 5
[[new Chord('CM7')],'','','','','','','','','','','','','','','',], // 6
['','','','','','','','',[['E3', 'G3']],'','','',[['A3']],[['B3']],[['C4']],[['D4']],], // 7
[[new Chord('Em7')],'','','','','','','','','','','','','','','',], // 8
['','','','','','','','','','','','','','','','',], // 9
[[new Chord('A7')],'','','','','','','','','','','','','','','',], // 10
['','',[['E4', 'G4']],'','','','','',[['C4#']],'','','',[['A3']],'','','',], // 11
[[new Chord('Dm7')],'','','','','','','','','','','','','','','',], // 12
['','','','','','','','','','','','','','','','',], // 13
[[new Chord('E7')],'','','','','','','','','','','','','','','',], // 14
[[['G3', 'B3']],'','','','','','','',[['D4']],'','','','','','','',], // 15
];
const piano = new Part({
sound: 'my_piano',
measure: 16,
mute: false
});
piano.add(new Filter({
type: 'note',
key: (beat, measure) => {
return piano_score[measure][beat][0];
},
length: 16,
}));
見た目も分かりやすいですね。
コード進行はChatGPT先生にお願いしました。
2. ベース
↓ベースのスコアを追加
// ベース
const base_score = [
[[['D1']],'','','','','','','','','','','','','','','',], // 0
[[['F1']],'','','','','','','','','','','','','','','',], // 1
[[['G1']],'','','','','','','','','','','','','','','',], // 2
[[['E1']],'','','','','','','','','','','','','','','',], // 3
[[['C1']],'','','','','','','','','','','','','','','',], // 4
[[['D1']],'','','','','','','',[['G1']],'','','','','','','',], // 5
['','','','','','','','','','','','','','','','',], // 6
['','','','','','','','','','','','','','','','',], // 7
[[['E1']],'','','','','','','','','','','','','','','',], // 8
['','','','',[['C2']],'','','',[['D2']],'','','',[['B1#']],'','','',], // 9
[[['A1']],'','','','','','','','','','','','','','','',], // 10
['','','','','','','','','','','','','','','','',], // 11
[[['D1']],'','','','','','','','','','','','','','','',], // 12
[[['C2']],'',[['B1']],'',[['A1']],'',[['B1#']],'','','',[['G1']],'',[['G1']],'','','',], // 13
['','','','','','','','','','','','','','','','',], // 14
['','','','','','','','','','','','','','','','',], // 15
];
const bass = new Part({
sound: 'gentle_bassist',
measure: 16,
mute: false
});
bass.add(new Filter({
type: 'note',
key: (beat, measure) => {
return base_score[measure][beat][0];
},
length: 16,
}));
こちらも、コード進行はChatGPT先生にお願いしました。
3. ドラム
↓ドラムのスコアを追加
// ドラム
const drum_score = [
[{drum1:[['cymbal2', 'kick']],drum2:[['cymbal']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},], // 0
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},], // 1
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'',{drum1:[['snare2']]},{drum1:[['cymbal2']]},], // 2
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum1:[['snare2']]},'',{drum1:[['cymbal2', 'hihat']]},'',{drum1:[['snare']]},{drum1:[['cymbal2']]},], // 3
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum3:[['tom']]},'',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},], // 4
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'',{drum3:[['tom2']]},{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},], // 5
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum3:[['tom']]},'',{drum1:[['cymbal2', 'hihat']]},'',{drum3:[['tom2']]},{drum1:[['cymbal2']]},], // 6
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum3:[['tom2']]},'',{drum1:[['cymbal2', 'hihat']]},'',{drum1:[['snare2']]},{drum1:[['cymbal2']]},], // 7
[{drum1:[['cymbal2', 'kick']],drum2:[['cymbal']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat', 'snare2']]},'','',{drum1:[['cymbal2']]},], // 8
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum1:[['snare2']]},'',{drum1:[['cymbal2', 'hihat']]},'',{drum1:[['snare']]},{drum1:[['cymbal2']]},], // 9
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum3:[['tom']]},'',{drum1:[['cymbal2', 'hihat']]},'',{drum1:[['snare']]},{drum1:[['cymbal2']]},], // 10
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'',{drum1:[['snare']]},{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum1:[['snare2']]},'',{drum1:[['cymbal2', 'hihat']]},'',{drum3:[['tom2']]},{drum1:[['cymbal2']]},], // 11
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']],drum3:[['tom2']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum3:[['tom2']]},'',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},], // 12
[{drum1:[['cymbal2']],drum3:[['tom2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum3:[['tom']]},'',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},], // 13
[{drum1:[['cymbal2']]},'','','',{drum1:[['cymbal2', 'hihat']]},'',{drum3:[['tom']]},{drum1:[['cymbal2']]},{drum1:[['cymbal2']]},'',{drum1:[['snare2']]},'',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},], // 14
[{drum1:[['cymbal2']]},'',{drum3:[['tom2']]},'',{drum1:[['cymbal2', 'hihat']]},'','',{drum1:[['cymbal2']]},{drum1:[['cymbal2', 'snare2']]},'','','',{drum1:[['cymbal2', 'hihat', 'kick']]},'',{drum1:[['snare2']]},{drum1:[['cymbal2']]},], // 15
];
const drum1 = new Part({
sound: 'bigger_street_drums',
measure: 16,
mute: false
});
const drum2 = new Part({
sound: 'disco_factory_drums',
measure: 16,
mute: false
});
const drum3 = new Part({
sound: 'my_band_drums',
measure: 16,
mute: false
});
drum1.add(new Filter({
type: 'note',
key: (beat, measure) => {
return drum_score[measure][beat].drum1 && drum_score[measure][beat].drum1[0];
},
length: 16,
}));
drum2.add(new Filter({
type: 'note',
key: (beat, measure) => {
return drum_score[measure][beat].drum2 && drum_score[measure][beat].drum2[0];
},
length: 16,
}));
drum3.add(new Filter({
type: 'note',
key: (beat, measure) => {
return drum_score[measure][beat].drum3 && drum_score[measure][beat].drum3[0];
},
length: 16,
}));
はい(笑)
熱中して長くなってしまいました。
3種類のドラムを利用したのでスコアが複雑になってしまいました。
特にシンバルレガートの表現が難しかったです。
※↓シンバルレガードを単体で実装した場合
drum1.add(new Filter({
type: 'note',
key: ['cymbal2'],
length: 16,
active: (beat) => beat === 0 || beat === 4 || beat === 7 || beat === 8 || beat === 12 || beat === 15,
}));
完成
作成したパートを基本設定のインスタンスに追加したら完成です。
// パートを追加
ongaq.add(piano);
ongaq.add(bass);
ongaq.add(drum1);
ongaq.add(drum2);
ongaq.add(drum3);
実際の音源はこちらになります。(1回ループ)
終わりに
今回はJavaScriptで音楽をプログラミングしてみました。
ソースコードで楽器を演奏させるのはもちろん新鮮でしたが、
本物の楽譜のように管理できる点が面白く、作曲の幅も広がるように感じました。
noteに載せて紹介するという点では、もっとシンプルな曲を作れば良かったかもしれません(笑)
ユニークなライブラリはまだまだありそうなので、皆さんも探してみてはいかがでしょうか?
それでは良い日々をお過ごしください!
ユニゾンシステムズでは、一緒に働く仲間を募集しています。
ぜひ一度オフィスに遊びに来てみませんか?お気軽にDMもお待ちしています!
求人の詳細はこちら: https://www.unixon.co.jp/recruit/
Youtube: https://www.youtube.com/channel/UCGacmgfpJ0fkHC0aKrSppVw
X(Twitter): https://twitter.com/unixon_recruit
この記事が気に入ったらサポートをしてみませんか?