見出し画像

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

この記事が気に入ったらサポートをしてみませんか?