チュートリアル シンプルなメロディーアプリ

シンプルなメロディーアプリ

By Portia Morrell, Kate Maschmeyer, Layla Quiñones

はじめに

音楽は、世界中の文化で声楽や器楽のを組み合わせて、その歴史、感情、経験を伝えるために使用されています。音楽の基本要素の1つはメロディーです。これは、聴いて心地よい音の構成を形成する音符のグループです。このチュートリアルでは、p5.Oscillatorオブジェクトを使用して音符を生成し、ユーザーがメロディーを作成して再生できるWebアプリケーションを開発する方法を学びます!

このチュートリアルは、メロディーアプリの異なるバージョンを作成する3部構成のシリーズの第1部です。

  • パート1: このチュートリアルでは、ユーザーが音階からメロディーを作曲し、再生できるシンプルなメロディーアプリを開発します。
  • パート2: Node.jsを始めようでは、Node.jsExpress.jsを使用して、コンピュータに保存されたメロディーを取得して再生するHTTPリクエストをルーティングする方法を学びます。
  • パート3: Node.jsを使用したメロディーアプリ(近日公開!)では、シンプルなメロディーアプリNode.jsExpress.jsと統合する方法を学びます。ユーザーがメロディーをコンピュータに保存し、後で再生のために取得できるより複雑なメロディーアプリを開発します。

前提条件

このチュートリアルには以下が必要です:

注意: セクション1.1〜1.5では、物理学や音楽理論などの他の分野の概念を紹介しています。これらのセクションは、p5.Oscillatorオブジェクトがどのようにメロディーを作成できるかを理解するための背景知識のリソースです。シンプルなメロディーアプリを完成させるために必須ではありませんが、これらのセクションを確認することを強く推奨します。

以下の内容に既に精通している場合:

背景知識をスキップしてオシレーターで音符を演奏するに進んでください。

オシレーター、科学と音楽の背景情報

p5.Oscillatorオブジェクト

p5.Oscillatorオブジェクトは、音符を演奏できるオシレーターと呼ばれる電気信号を生成するための情報を保持します。これらの信号は、特定の速度で繰り返すパターンで最小値と最大値の間を変化します。この信号がスピーカーを通して再生されると、音として聞こえます!

オシレーターがどのように音符を生成するかを理解するために、の背後にある科学について掘り下げてみましょう。

注意

次のセクション音と音楽の科学では、以下の概念が紹介されます:

スキップする

上記の背景概念に既に精通している場合は、メロディーアプリを作るセクションにジャンプしてプロジェクトを始めましょう!

音と音楽の科学

メロディーは、聴いて心地よいを形成する音符の集まりです。圧力波として説明され、オシレーターによって生成することができます。

1.1 - 音と圧力波

宇宙空間で音が聞こえるかどうか、または水中で音が低くなる理由を考えたことはありますか?

物理科学では、音は空気やその他の物質分子と粒子の振動によって作られるエネルギーです。音を一つの場所から別の場所へ運ぶ物質を媒質と呼び、液体(水など)、気体(空気など)、固体(壁やドアなど)があります。これらの振動する物質の粒子は圧力波を作り出し、媒質を通じて音を一つの場所から別の場所へ運びます。

圧力波は、媒質内の粒子が近づき合って高圧領域を作り出すときに、エネルギーを運ぶ任意の乱れのことです。音叉が音を作り出す様子を示す以下のgifを考えてみましょう:

音叉が周囲の物質の粒子を振動させ、粒子が圧縮されて集まった領域と、粒子が広がった領域が繰り返すパターンとして示される音のエネルギーの圧力波を作り出します。物質の粒子は、互いにぶつかり合いながら、音波を左から右へ運びます

音叉が周囲の物質の粒子を振動させ、粒子が圧縮されて集まった領域と、粒子が広がった領域が繰り返すパターンとして示される音のエネルギーの圧力波を作り出します。物質の粒子は、互いにぶつかり合いながら、音波を左から右へ運びます

出典:The Physics Classroom: Sound Waves and Music - Lesson 1 - The Nature of a Sound Wave

上のgifでは、音叉が周囲の物質の粒子を振動させ、音のエネルギーを運ぶ圧力波を作り出しているのが分かります。その圧力波が耳に届くと、粒子の振動を音として聞くことができます!宇宙空間で音が聞こえない理由は、音のエネルギーを運ぶ媒質がないからです!

音がどのように物質を通って伝わるかをよりよく理解するために、このビデオで拍手の音によって形成される圧力波のデモンストレーションをご覧ください!

圧力波と音の背後にある科学についてもっと学ぶには、以下のリソースをご覧ください:物質の粒子理論圧力波としての音波と鼓膜

1.2 - 周期波と音

圧力波は、媒質内の圧縮された粒子の繰り返しパターンとして説明することができます。圧縮は、粒子が密集している高圧領域によって特徴付けられます。希薄は、粒子がより広がっている低圧領域によって特徴付けられます。以下の画像は、音叉によって生成された音波による空気の高圧領域と低圧領域を示しています。空気粒子が密集している領域は高気圧に、空気粒子が離れている領域は低気圧に対応していることに注目してください。

音叉によって生成された音が、媒質内に高圧と低圧の周期的なパターンを引き起こします。粒子が密集している領域は「高圧」、粒子が離れている領域は「低圧」とラベル付けされています。

音叉によって生成された音が、媒質内に高圧と低圧の周期的なパターンを引き起こします。粒子が密集している領域は「高圧」、粒子が離れている領域は「低圧」とラベル付けされています。

出典:Sound for Music Technology - The Open University

音を作り出す圧力波は、単に媒質内の圧縮と希薄の繰り返しサイクルの連続です。波の1サイクル、1つの振動には、高圧領域(圧縮)と低圧領域(希薄)が含まれます。一定の時間間隔で繰り返される振動は、私たちが音符やその他の音として聞く周期波を形成します!

例えば、スピーカーは電気回路によって生成された周期的なパターンで表面を上下に動かすことで音波を生成します。以下のgifは、スピーカーによって生成された音波が空気粒子を通って伝わる様子を示しています:

左側の黒い線が、音を生成するスピーカーの表面を模して周期的なパターンで動きます。表面の右側の黒い点の列は、スピーカー周辺の空気粒子を表しています。表面が右に動くと、周囲の空気粒子を押し、それらが他の粒子にぶつかります。表面が左に動くと、粒子が戻るときに広がるスペースが作られます。粒子がぶつかり合う領域を示す赤い線が、空気中を右に移動しているように見えます。空気圧を表す周期的な正弦曲線が音波の下に描かれ、各ピークが赤い線と一致しています。赤い線が空気粒子を通って移動するにつれて、正弦曲線も右に移動しているように見えます。

左側の黒い線が、音を生成するスピーカーの表面を模して周期的なパターンで動きます。表面の右側の黒い点の列は、スピーカー周辺の空気粒子を表しています。表面が右に動くと、周囲の空気粒子を押し、それらが他の粒子にぶつかります。表面が左に動くと、粒子が戻るときに広がるスペースが作られます。粒子がぶつかり合う領域を示す赤い線が、空気中を右に移動しているように見えます。空気圧を表す周期的な正弦曲線が音波の下に描かれ、各ピークが赤い線と一致しています。赤い線が空気粒子を通って移動するにつれて、正弦曲線も右に移動しているように見えます。

出典:Flipping Physics

左側の黒い線はスピーカーの表面を表し、黒い点の列は空気粒子を表しています。

スピーカーは、その表面を周期的なパターンで右から左に動かすことで音を生成します。スピーカーの表面が右に動くと、空気粒子を右に押し、それらが近づき合う(圧縮する)原因となります。粒子が圧縮すると、高気圧領域が作られ、媒質を通じて音のエネルギーが伝達されます。

上のgifでは、赤い線が空気中で粒子が圧縮され音のエネルギーを伝達する領域を示しています。周期的な正弦曲線の形をした青い線は、音が空気を通って伝わる際の圧力変化を示しています。空気粒子が圧縮される領域は青い線の最高点(高圧)と一致します。粒子がより広がっている領域は青い線の最低点(低圧)と一致します。

媒質内の粒子が音のエネルギーを運ぶために前後に振動するものの、実際には移動しないことに気づきましたか?赤い線は、媒質の粒子からのエネルギー伝達によって音のエネルギーがどのように運ばれるかを示しています。音が粒子の振動と同じ方向に伝わることに注目してください。これは、音が縦波であることを意味します!縦波についてもっと学ぶにはこのリソースをご覧ください。

音波は一般的に、媒質を通って音が伝わる際の圧力変化を表す周期的な正弦曲線を使って図示されます(上のgifの青い線のように)。

周期波の特性

周期波は、以下の図に示される特性を使って説明されます:

中央の水平線の上に描かれた周期的な正弦曲線の図。矢印が波の特性を示しています:「波長(サイクルあたりの距離)」、「周波数(時間あたりのサイクル数)」、「振幅」、「平衡位置」。あるピークから次のピークまでを指す水平矢印は、波長が1波サイクルの長さを測定し、周波数が特定の時間内に発生する波サイクルの数を測定することを示しています。波の中央の線は「平衡位置」とラベル付けされています。線の最高点から平衡位置まで、または最低点から平衡位置までを指す垂直矢印は、振幅が平衡位置からの距離を測定することを示しています。

中央の水平線の上に描かれた周期的な正弦曲線の図。矢印が波の特性を示しています:「波長(サイクルあたりの距離)」、「周波数(時間あたりのサイクル数)」、「振幅」、「平衡位置」。あるピークから次のピークまでを指す水平矢印は、波長が1波サイクルの長さを測定し、周波数が特定の時間内に発生する波サイクルの数を測定することを示しています。波の中央の線は「平衡位置」とラベル付けされています。線の最高点から平衡位置まで、または最低点から平衡位置までを指す垂直矢印は、振幅が平衡位置からの距離を測定することを示しています。

ラベルの説明:

  • 平衡位置:振動の静止位置を表し、波の中央に水平な直線として表されることが多いです。
    • 圧力波の場合、平衡位置は音がない時の媒質内の圧力を表します。
  • 波長:波の2つの同一点間の距離、または1つの振動の長さ。
    • 音波の波長は、連続する2つの圧縮間、または連続する2つの希薄間の距離です。
  • 振幅:平衡線と波の最高点または最低点との間の距離
    • 音波の場合、振幅は音の強さ、つまり音量を表します。
  • 周波数:特定の時間内に発生する振動の数。
    • 周波数は多くの場合**ヘルツ(Hz)**で測定され、1秒あたりのサイクル数を測定します。1 Hz = サイクル/秒
    • 圧力波の周波数は、Hzで測定され、1秒間に1点を通過する圧縮または希薄の回数です。

1.3 - 音符と波の特性

音符は周期的な圧力波によって運ばれるなので、波の特性を使って説明することもできます。

音波の振幅は、音の強さを表す尺度で、音量として聞くことができます。高い音量で演奏される音符は振幅が大きく、低い音量で演奏される音符は振幅が小さくなります。振幅と音楽についてもっと学ぶには、このリソースをご覧ください

音波の平衡位置は、音が伝わっていない時に媒質内の粒子によって伝達されるエネルギーを表します。平衡位置では、音の音量は0で、媒質内の圧力はある静止値にあると考えることができます。

音波の周波数は、高音から低音までの音の高さ、つまりピッチとして聞くことができます。音符は、特定の周波数で短時間演奏される音波です。低い周波数で演奏される音符は、ベースギターで演奏される音符のような低い音に対応します。高い周波数で演奏される音符は、フルートで演奏される音符のような高い音に対応します。音符が特定の波の周波数にどのように対応するかについてもっと学ぶには、このリソースをご覧ください

p5.Oscillatorオブジェクトと波の簡単な紹介については、The Coding TrainのSound Synthesisチュートリアルをご覧ください。

オシレーターで音符を演奏する

それでは、音、音楽、オシレーターの素晴らしい世界に飛び込んで、シンプルなメロディーアプリを作成しましょう!

ステップ1 – 音符を演奏できるp5.oscillatorオブジェクトを作成する

新しいp5.jsプロジェクトを開き、“Play a note”という名前を付けて保存します。

音符に一致する周波数を特定し、それをグローバル変数myFreqに格納します。

  • ミドルCの音符の場合、setup()の上に以下のコードを追加してmyFreq262で初期化します:

    // 周波数の変数(ミドルC)
    let myFreq = 262;

音符と対応する周波数のリストについてはこのリソースをご覧ください。ミドルCの周波数は262 Hzです。

p5.Oscillatorオブジェクトを格納するグローバル変数を宣言します。

  • setup()の上に以下のコードを追加します:

    // オシレーターの変数
    let osc;

myFreqを引数として使用してp5.Oscillatorオブジェクトでoscを初期化します。

  • setup()内に以下のコードを追加します:

    /* 変数myFreqで定義された周波数を持つ
    オシレーターオブジェクトを作成 */
    osc = new p5.Oscillator(myFreq);
ヒント

次の行にconsole.log(osc)を追加して、オシレーターオブジェクトが正常に初期化されたことを確認してください。以下のようなオブジェクトが表示されるはずです:

r {started: false, phaseAmount: undefined, oscillator: OscillatorNode, f: 262, output: GainNode…}

コンソールで”f”プロパティを調べるか、console.log(osc.f)を使用して出力することで、正しい周波数でオシレーターオブジェクトを作成したことを確認できます。osc.fの周波数は、あなたの音符に指定した周波数と一致するはずです。

p5.Oscillatorオブジェクトのプロパティについて詳しく学ぶにはp5.Oscillatorリファレンスをご覧ください。特定の音符の周波数を列挙したチャートについてはこのリソースをご覧ください。

sketch.jsファイルは以下のようになるはずです:

// 周波数の変数(ミドルC)
let myFreq = 262;

// オシレーターの変数
let osc;

function setup() {
  createCanvas(400, 400);

  /* 変数myFreqで定義された周波数を持つ
  オシレーターオブジェクトを作成 */
  osc = new p5.Oscillator(myFreq);
  console.log(osc.f);
}

function draw() {
  background(220);
}

ステップ2 – 音符を演奏する

ほとんどのブラウザは、音、画像、動画を再生または取得するためにユーザーの許可を必要とします。この機能をバイパスするために、音がいつ再生されるかをユーザーが制御できる機能を追加することができます。

次に、ユーザーがキャンバスをクリックしたときにオシレーターを開始できるようにします。osc.start()を呼び出すmousePressed()関数を定義します。

  • draw()の下に以下のコードを追加します:

    // マウスが押されたときにオシレーターを開始
    function mousePressed() {
      osc.start();
    }
  • コードを実行します。キャンバスをクリックした後にミドルCの音が聞こえるはずです。

キャンバスをクリックしても音が聞こえない場合は、コードがこのようになっているか確認してください。音が再生されない場合は、ブラウザの設定がマルチメディアの再生を許可しているか確認してください。以下のリソースが参考になります:

これで、コードの実行を停止するまで永遠に演奏される音符を作成しました!

次に、ユーザーがキャンバスをクリックしたときに音を開始および停止できるようにします。

  • mousePressed()関数内のコードを以下の条件文に置き換えます:

    // 演奏中の音符を切り替える
    if (osc.started) {
      osc.stop();
    } else {
      osc.start();
    }
  • プロジェクトを実行します。キャンバスをクリックしたときにミドルCの音符が演奏され、もう一度クリックしたときに停止するはずです!

sketch.jsファイルは以下のようになるはずです:

// オシレーターの変数
let osc;

// ミドルCの周波数
let myFreq = 262;
function setup() {
  createCanvas(400, 400);
  // オシレーターオブジェクトを作成
  osc = new Oscillator(myFreq);
  // console.log(osc.f);
}

function draw() {
  background(220);
}

function mousePressed() {
  // 演奏中の音符を切り替える
  if (osc.started) {
    osc.stop();
  } else {
    osc.start();
  }
}

上のコードでは、262 Hzの周波数で新しいp5.Oscillatorオブジェクトを初期化し、osc変数に格納しています。ユーザーはキャンバスをクリックしてオシレーターを開始および停止できます。この対話性を追加するために、mousePress()内に条件文を追加し、オシレーターの.startedプロパティを使用してオシレーターが既に演奏を開始しているかどうかを確認します。osc.startedは、オシレーターが開始している場合はtrue、そうでない場合はfalseです。オシレーターが開始している場合、条件文はプログラムにオシレーターを.stop()関数で停止するよう指示し、そうでない場合は.start()関数でオシレーターを開始します。

p5.Oscillatorのリファレンスをご覧になり、その関数とプロパティについてもっと学んでください。

サンプルプロジェクト

試してみよう!
  • オシレーターの周波数をHzでキャンバスに表示します。()
  • このチャートを参照して周波数変数の値を変更し、異なる周波数を演奏します。周波数は最も近い整数に丸めてください。()
注意

次のセクション「音階とオシレーター」では、音階メロディー作曲オクターブ音階と周波数を紹介することで、音階とメロディー作曲の背後にある理論を説明します。

上記の背景概念に既に精通している場合は、シンプルなメロディーアプリを作るセクションに進んでください!

音階とオシレーター

音楽の作曲家やプロデューサーは、リスナーにとって心地よい音になるように、特定の音階から音符を選んで簡単なメロディーを作ることがよくあります。西洋文化では、音階は周波数に従って昇順または降順に配置された音符の集まりです。昇順の音階は周波数が増加し(低い音から高い音へ)、降順の音階は周波数が減少します(高い音から低い音へ)。オクターブは8つの均等に配置された音符からなる昇順の音階で、最後の音符の周波数は最初の音符の周波数の2倍になります。

このチュートリアルの例では、西洋音楽の最も基本的な音階の1つであるCメジャーを選びました。Cメジャー音階の最初の音符はミドルCで、周波数は264 Hzです。音階の最後の音符は、ミドルCの周波数の2倍の周波数を持ちます。つまり、音階の最後の音符は524 Hzの周波数を持つCシャープとなります。以下の表で、周波数と対応する音符のリストをご覧ください:

Cメジャーにおける音符と周波数

音符

(4th Octave)

周波数 (Hz) 

(Hz)

表現

(myFreq = 242 Hz)

C

264 

myFreq * 1

D

294.75

myFreq * 9/8

E

327.5

myFreq * 5/4

F

349.33

myFreq * 4/3

G

393

myFreq * 3/2

A

436

myFreq * 5/3

B

491.25

myFreq * 15/8

C# 

(5th Octave)

524

myFreq * 2

他の音階オクターブメロディー作曲と音階、そしてCメジャーについてもっと学ぶには、これらのリソースをご覧ください。

このプロジェクトでは、p5.Oscillatorオブジェクトを使用して、メロディーの演奏中に再生される音符を生成します。ステップ1で、特定の周波数を持つ新しいp5.Oscillatorオブジェクトを変数内で初期化する方法を学びました。音階のオシレーターを生成するために、異なる周波数を持つ複数のp5.Oscillatorオブジェクトを配列内で初期化することができます。

配列についてもっと学ぶには、JavaScript ArraysのMDNリソースをご覧ください。

シンプルなメロディーアプリを作る

シンプルなメロディーアプリのプロジェクトでは、ユーザーがCメジャーの任意の音符を選んでメロディーを作曲できるようにします。周波数とオシレーターオブジェクトは配列に格納され、ユーザーが選択したときにすぐに再生できる状態になっています。Cメジャーの各音符は特定の周波数を持っているため、各音符にはそれを再生する特定のオシレーターオブジェクトが割り当てられます。

ステップ1 – 音階のためのp5.Oscillatorオブジェクトを作成する

p5.js Webエディタで新しいプロジェクトを開き、“Simple Melody App”という名前を付けて保存します。

アプリで使用する音階を選びます。

  • このチュートリアルでは、最初の音符がミドルCであるCメジャー音階(第4オクターブ)を使用します - これは変数myFreqに格納された周波数と同じ音符です。
  • 変数myFreqの周波数から始まる、音階の8つの音符を表す周波数を計算します。

Cメジャーの音符の周波数を計算する方法についての詳細は、上のセクションのこの表を参照してください。

グローバル変数myFreqを宣言し、音階の最初の音符の周波数で初期化します。frequenciesという別のグローバル変数を宣言し、計算した音階の8つの音符に一致する周波数の配列で初期化します。

  • setup()の前に以下のコードを追加します:

    // 周波数の変数(ミドルC)
    let myFreq = 262;
    
    // Cメジャーの周波数配列
    let frequencies = [
      myFreq,
      myFreq * 9/8,
      myFreq * 5/4,
      myFreq * 4/3,
      myFreq * 3/2,
      myFreq * 5/3,
      myFreq * 15/8,
      myFreq * 2
    ];

frequencies配列の要素にアクセスして、Cメジャーの各音符のp5.Oscillatorオブジェクトを初期化します。

  • oscillatorsというグローバル変数を作成し、空の配列で初期化します。この配列は各音符のp5.Oscillatorオブジェクトを保持します。

    • setup()の上に以下のコードを追加します:

      // オシレーターオブジェクトのための空の配列
      let oscillators = [];
  • frequencies配列に一致する音符のオシレーターを初期化し、各新しいオシレーターで配列に対して.push()を呼び出してoscillators配列に追加します。

    • setup()内に以下のコードを追加します:

      // オシレーターを初期化してoscillators配列に配置
      for (let freq of frequencies) {
        osc = new p5.Oscillator(freq);
        oscillators.push(osc);
      }

ここではforループを使用してfrequencies配列の各周波数にアクセスし、各音符の新しいp5.Oscillatorオブジェクトを初期化します。各オシレーターオブジェクトはoscillators配列に格納されます。

注意

frequencies配列の特定の周波数のインデックスは、oscillators配列のオシレーターオブジェクトのインデックスと一致します。

各オシレーターが正しく作成されたことを確認するため、コンソールで各オシレーターの周波数を出力します。

  • setup()に以下のコードを追加します:

    // 各オシレーターが正しい周波数を持っているか確認
    for (let freq of frequencies) {
      console.log(osc.f); 
    }

sketch.jsファイルは以下のようになるはずです:

// ミドルCの周波数
let myFreq = 262;

// Cメジャーの周波数配列
let frequencies = [
  myFreq,
  myFreq * 9/8,
  myFreq * 5/4,
  myFreq * 4/3,
  myFreq * 3/2,
  myFreq * 5/3,
  myFreq * 15/8,
  myFreq * 2
];

// オシレーターオブジェクトのための空の配列
let oscillators = [];

function setup() {
  createCanvas(400, 400);

  // オシレーターを初期化してoscillators配列に配置
  for (let freq of frequencies) {
    osc = new p5.Oscillator(freq);
    oscillators.push(osc);
  }

  // 各オシレーターが正しい周波数を持っているか確認
  for (let freq of frequencies){
    console.log(osc.f);
  }
}
function draw() {
  background(220);
}

p5.Oscillatorオブジェクトには、生成する音の強さを制御するのに役立つ.start().stop().amp()などのメソッドがあります。また、それぞれHz単位の周波数と、オシレーターが開始したときはtrue、そうでないときはfalseとなるブール値を格納する.f.startedなどのプロパティもあります。これらを使用して、後でスケッチにさらにインタラクティブな要素を追加します。

注意

次のセクション「メロディーとテンポ」では、テンポ音符の長さメロディーの作成にどのように関係するかを紹介します。

上記の背景概念に既に精通している場合は、ステップ2に進んでください!

メロディーとテンポ

メロディーは、複数の音符が連続して演奏されることで作られます。シンプルなメロディーは、音符、リズム、テンポで構成されます。リズムは各音符が演奏される長さを表し、一般的に拍子で測定されます。テンポはメロディーの**1分あたりの拍数(bpm)**を表します。例えば、一般的なポップスのメロディーは120 bpmで演奏され、これは各拍が0.5秒の長さであることを意味します。拍子はメロディーの脈動を表し、テンポは各拍が時間の中でどれだけ速く動くかを表します。

音符の長さとして知られる各音符が演奏される時間は、拍子で測定され、メロディーのリズムに直接影響します。音符は多くの場合、4拍、2拍、1拍、半拍などの長さで演奏されます。シンプルなメロディーでは、各音符は同じ長さで演奏されます。120 bpmで演奏されるシンプルなメロディーでは、音符は0.5秒ごとに演奏され、0.5秒間続きます。音符の長さ、リズム、テンポについてもっと学ぶには、このリソースをご覧ください

JavaScript JSONオブジェクトのMDNリファレンスを確認してください - メロディーを再生可能なJSONオブジェクトとして保存します!

JSONオブジェクトを使用して、メロディーとその特性(名前、音符、テンポなど)を保存します。これらをメロディーオブジェクトと呼びます。

ステップ2 – メロディーオブジェクトを作成する

setup()の前に以下のコードを追加することで、Cメジャーの音符を表すメロディーオブジェクトを作成できます:

// Cメジャーのメロディーオブジェクト
let melody = {
  name: 'Cメジャー音階',
  notesIndex: [0, 1, 2, 3, 4, 5, 6, 7],
  tempo: 120
};

メロディーオブジェクトは以下のプロパティを持ちます:

  • name: メロディーの名前
  • notesIndex: oscillators配列内の各音符に対応するインデックスを指定する数値の配列。notesIndexは各音符が演奏される順序を定義します
  • tempo: メロディー内の各音符の長さを計算するために使用される1分あたりの拍数

プロパティ値へのアクセス方法についてもっと学ぶには、JSONオブジェクトのMDNリファレンスをご覧ください。

ステップ3: メロディーオブジェクトの音符を演奏する

メロディー全体を演奏する前に、まず個々の音符をどのように演奏するかについてプログラムに指示を与える必要があります。

パラメータnを持つplayNote()関数を定義します。nmelody.noteIndexのインデックスと一致します。nはまた、oscillators配列の音符のインデックスと、frequencies配列の周波数のインデックスとも一致します。

  • draw()の下に以下の関数宣言を追加します:

    // 音符の演奏を開始
    function playNote(n) {
    }

音符がまだ演奏されていない場合に演奏する条件文を使用します。

  • playNote(n)に以下のコードを追加します:

    // 必要な場合はオシレーターを開始
    if (oscillators[n].started === false) {
      oscillators[n].start();
    }

より自然な聴覚体験のために、.amp()を使用して音符の演奏時に短いフェードインを追加します。このメソッドはオシレーターの振幅を変更し、開始時に徐々に音量を上げます。

  • draw()の下に以下の関数宣言を追加します:

    // 0.01秒のフェードインで音量を上げて音符の演奏を開始
    oscillators[n].amp(1, 0.01);

playNote()関数は以下のようになるはずです:

function playNote(n) {
  // 必要な場合はオシレーターを開始
  if (oscillators[n].started === false) {
    oscillators[n].start();

    // 0.01秒のフェードインで音量を上げて音符の演奏を開始
    oscillators[n].amp(1, 0.01);
  }
}

playNote()関数は:

  • melodyオブジェクトのnotesIndexプロパティに基づいて音符のインデックス値を示す数値nを受け取ります。
  • まだ開始していない場合は、oscillators[n]のオシレーターを開始します。
    • オシレーターが演奏中かどうかを確認するには、条件文で.startedプロパティを使用します。
    • まだ開始していない場合は.start()メソッドを使用してオシレーターを開始します
  • オシレーターの.amp()メソッドを使用して、0.01秒のフェードインで音量を1に設定します

音符を停止するには、.amp()を使用して音量を0まで下げるフェードアウトを追加し、.stop()を使用してオシレーターを停止できます。

  • playNote()の下に以下のコードを追加して、パラメータnを持つstopNote()関数を定義します:

    // 音符の演奏を停止
    function stopNote(n) {
      // オシレーターの音量を0に下げる
      oscillators[n].amp(0, 0.01);
      
      // オシレーターを停止
      oscillators[n].stop();
    }

stopNote()関数は:

  • 演奏中のオシレーターオブジェクトのインデックスを示す数値nを受け取ります
  • オシレーターのamp()メソッドを使用して、0.01秒のフェードアウトで音量を0に設定します
  • stop()メソッドを使用してオシレーターを停止します

playNote()関数内でstopNote()を呼び出し、各音符の演奏時間を制御できます。まず、melody.tempoプロパティを使用して各音符が演奏される時間を計算するグローバル変数noteDurationを定義します。

  • setup()の前に以下のコードを追加します:

    // 各音符の長さを秒単位で計算
    let noteDuration = 60 / melody.tempo;

メロディーとテンポのセクションで学んだように、シンプルなメロディーでは各音符が同じ時間だけ演奏されるため、テンポを使用して音符の長さを計算できます。テンポは1分あたりの拍数で測定されるため、1分(60秒)をテンポ(60 / melody.tempo)で割ることで、音符が演奏される秒数を決定できます。

setTimeout()を使用してmelody.notesIndexから特定の音符が演奏される時間をスケジュールすることで、playNote()を修正します。setTimeout()には3つの引数が必要です:設定された時間の後に呼び出す関数、関数が呼び出されるまでの待機時間(ミリ秒単位)、関数が呼び出されるときに使用する引数です。

  • setTimeout()を使用して、noteDuration * 1000ミリ秒後にnを第3パラメータとしてstopNote()をトリガーします:

  • playNote()の最後に以下のコードを追加します:

    // noteDurationに格納された秒数後に音符の演奏を停止
    setTimeout(stopNote, noteDuration * 1000, n);

setTimeout()は、ミリ秒単位で測定された時間の後にstopNoteを呼び出します:

  • noteDurationは式noteDuration * 1000を使用して秒からミリ秒に変換できます。
  • 第3引数のnは、stopNote()関数が呼び出されるときに渡す値を示します。これは、演奏中のoscillators配列内のoscillatorのインデックスと一致するmelody.notesIndex配列内のインデックスの値です。
  • noteDurationに基づいてstopNote()を遅延させることで、音符を1/2拍分演奏します。

setTimeout()関数についての詳細は、MDNリファレンスをご覧ください。

playNote()stopNote()関数は以下のようになるはずです:

// 音符の演奏を開始
function playNote(n) {
  // 必要な場合はオシレーターを開始
  if (oscillators[n].started === false) {
    oscillators[n].start();
  }

  // 0.01秒のフェードインで音量を上げて音符の演奏を開始
  oscillators[n].amp(1, 0.01);

  // noteDurationに格納された秒数後に音符の演奏を停止
  setTimeout(stopNote, noteDuration * 1000, n); 
}

// 音符の演奏を停止
function stopNote(n) {
  // オシレーターの音量を0に下げる
  oscillators[n].amp(0, 0.01);

  // オシレーターを停止
  oscillators[n].stop();
}
  • メロディーオブジェクトから特定の音符を使用してsetupの最後でplayNote()を呼び出し、テストします。例えば、以下のコードをsetup()の最後に追加し、引数を変更して各音符をテストできます:

    // playNoteをテスト
    playNote(0);

ステップ4: メロディーオブジェクトを演奏する

melody.notesIndex配列内でインデックスが出現する順序とメロディーのnoteDurationに基づいて、oscillators配列から各音符をいつ演奏するかをスケジュールするplay()関数を定義します。melody.notesIndexの要素には、演奏される順序で、メロディー内のすべての音符のインデックスが含まれています。

melody.notesIndex[0]の値は、メロディーの最初の音符を演奏するoscillators配列内のインデックスの値です。melody.notesIndexの最後の要素は、最後の音符のoscillators配列内のインデックスです。forループ内でsetTimeout()を使用してplayNote()をトリガーすることで、メロディーが開始した後の特定の時間に各音符を演奏できます。

  • sketch.jsの最後にplay()の関数宣言を追加します:

    // メロディー内の音符を演奏
    function play() {
      // melody.notesIndexの各[インデックス、音符]を読み取る
      for (let [index, note] of melody.notesIndex.entries()) {
        // スケジュールされた時間に各音符を演奏
        setTimeout(playNote, noteDuration * 1000 * index, note);
      }
    }
  • mousePressed()関数でplay()関数を呼び出してテストします。

コードは以下のようになるはずです:

// 周波数の変数(ミドルC)
let myFreq = 262;

// Cメジャーの周波数配列
let frequencies = [
  myFreq,
  myFreq * 9/8,
  myFreq * 5/4,
  myFreq * 4/3,
  myFreq * 3/2,
  myFreq * 5/3,
  myFreq * 15/8,
  myFreq * 2
];

// Cメジャーのメロディーオブジェクト
let melody = {
  name: 'Cメジャー音階',
  notesIndex: [0, 1, 2, 3, 4, 5, 6, 7],
  tempo: 120
};

// オシレーターオブジェクトのための空の配列
let oscillators = [];

// 各音符の長さを秒単位で計算
let noteDuration = 60 / melody.tempo;

function setup() {
  createCanvas(400, 400);

  // オシレーターを初期化してoscillators配列に配置
  for (let freq of frequencies) {
    osc = new p5.Oscillator(freq);
    oscillators.push(osc);
  }

  //カラーモードをHSBに設定(音符でキーに色を付けるのに適している)
  colorMode(HSB);
}

function draw() {
  background(220);
  drawMelody();  
}

// 音符の演奏を開始
function playNote(n) {
  // 必要な場合はオシレーターを開始
  if (oscillators[n].started === false) {
    oscillators[n].start();

    // 0.01秒のフェードインで音量を上げて音符の演奏を開始
    oscillators[n].amp(1, 0.01);
  }

  // noteDuration * 1000秒後に音符の演奏を停止
  setTimeout(stopNote, noteDuration * 1000, n); 
}

// 音符の演奏を停止
function stopNote(n) {
  // オシレーターの音量を0に下げる
  oscillators[n].amp(0, 0.01);

  // オシレーターを停止
  oscillators[n].stop();
}

// メロディー内の音符を演奏
function play() {
  // melody.notesIndexの各[インデックス、音符]を読み取る
  for (let [index, note] of melody.notesIndex.entries()) {
    // スケジュールされた時間に各音符を演奏
    setTimeout(playNote, noteDuration * 1000 * index, note);
  }
}

//マウスクリックでメロディーを演奏
function mousePressed() {
  play();
}

play()関数は:

  • forループを使用してmelody.notesIndex配列のすべての要素を反復処理し、要素のインデックスをindex変数に、その値をnote変数に格納します。
    • indexは音符が演奏される順序を示します。
    • noteは音符を演奏するoscillators配列内のインデックスを示します。
  • setTimeout()を使用してmelody.notesIndexの各音符の再生をスケジュールします
    • noteDuration * 1000 * indexで計算された時間(ミリ秒)後にplayNote(note)を呼び出します

setTimeout()は各音符がいつ演奏されるべきかをスケジュールし、2つ以上の引数を取ります:実行する関数(playNote)、実行までの遅延時間(ミリ秒単位)、実行する関数に渡す引数です。この場合、playNote関数に音符を渡したいと思います。遅延時間はnoteDuration * 1000 * indexを使用して計算され、indexoscillators配列内のoscillatorのインデックスに対応するmelody.noteIndex配列内の現在の音符のインデックスです。

このタイミングにより、メロディー内の音符は以下のようなスケジュールで演奏されます:

  • melody.noteIndexの最初の音符はメロディーが開始されたとき(0ミリ秒)に即座に演奏されます。
  • 2番目の音符はnoteDuration * 1000 * 1ミリ秒後に演奏されます。これは最初の音符が演奏を開始してから1/2拍後に発生します。
  • 3番目の音符は2番目の音符の後、noteDuration * 1000 * 2ミリ秒の遅延を経て演奏されます。
  • このプロセスはmelody.notesIndex配列の各要素が使用されるまで繰り返されます。

各音符の演奏時間は、メロディー内の単一の音符の長さと位置によって段階的に設定されます。これにより、メロディー全体を通じて音符がスムーズに進行し、各音符がメロディーのテンポに合わせて一定の間隔で演奏されるリズムが作られます。

サンプルコード

試してみよう!
  • melody.notesIndexの要素の値を変更し、メロディーがどのように変化するか観察してください
  • melody.tempoの値を変更し、メロディーがどのように変化するか観察してください
  • 自分自身の新しいメロディーオブジェクトを作成して演奏してみましょう!
注意

次のセクション「ユーザーインターフェースとエクスペリエンス」では、ユーザーインターフェース(UI)ユーザーエクスペリエンス(UX)、そしてアプリ使用中の視覚的フィードバックの重要性について紹介します。

上記の背景概念に既に精通している場合は、ステップ7に進んでください!

ユーザーインターフェースとエクスペリエンス

メロディーオブジェクトから音符を演奏するようにプロジェクトをプログラムしました!次は、ユーザーがCメジャー音階から音符を選択し、独自のメロディーを作曲できるようにするユーザーインターフェース(UI)を追加できます。ユーザーインターフェース(UI)は、ユーザーがアプリを正常に使用し、楽しく使用するために必要と思われるものを提供できます。それは楽しく快適なユーザーエクスペリエンス(UX)を提供できます。

エクスペリエンス中の視覚的フィードバックは、ユーザーがアプリに興味を持ち続けるための重要な部分です。各音符が演奏されるときのユーザーへの視覚的フィードバックは、メロディーの作曲と再生をより満足のいく体験にし、人々にもっと作曲を続けさせることができます!

ユーザーインターフェース(UI)ユーザーエクスペリエンス(UX)視覚的フィードバックについてもっと学ぶには、これらのリソースをご覧ください。

このステップでは、好きなユーザーインターフェースを設計できます。例として、ピアノの鍵盤に似たボタンの列を描画します。直感的なユーザーエクスペリエンスを可能にするために、Cメジャー音階の各音符はキャンバス上のボタンに対応し、周波数の降順に配置されます。特定のキーに関連付けられた音符が演奏されると、メロディー再生体験に視覚的に楽しいフィードバックを追加するために、キーが明るい色で光ります!

ステップ5 – ユーザーインターフェースを作成する

このステップでは、ピアノの鍵盤に似たボタンをキャンバス全体に描画するdrawMelody()関数を定義します。各ボタンは、oscillators配列内の特定のp5.Oscillatorオブジェクトで表される音符に対応します。oscillators配列には8つのオシレーターオブジェクトがあり、frequencies配列には8つの音符の周波数があるため、キャンバス全体に8つのボタンが描画されます。

  • ユーザーが利用できる音符の数を保持するnumNotesと、キャンバスの幅を保持するcWidthのグローバル変数を定義します。setup()の上に以下のコードを追加します:

    // 演奏可能な音符の数(周波数/オシレーターの数と同じ)
    let numNotes = frequencies.length;
    
    // キャンバスの幅
    let cWidth = 400;
  • createCanvas()の幅の寸法をcWidthに置き換え、カラーモードをHSBに設定します。

グローバル変数とsetup()は以下のようになります:

//...その他の変数

// 演奏可能な音符の数(周波数/オシレーターの数と同じ)
let numNotes = frequencies.length;

// キャンバスの幅
let cWidth = 400;

function setup() {
  createCanvas(cWidth, 400);

  // オシレーターを初期化してoscillators配列に配置
  for (let freq of frequencies) {
    osc = new p5.Oscillator(freq);
    oscillators.push(osc);
  }

  //カラーモードをHSBに設定(音符でキーに色を付けるのに適している)
  colorMode(HSB);
}

cWidthnumNotesは、キャンバス上の各ボタンのx座標を計算するために使用されます。各ボタンは、音符が演奏されるときにHSBカラーモードを使用して色が変化し、音符が演奏されていないときはデフォルトの色に戻ります。

この例では、ピアノの鍵盤のように動作する長方形のボタンの列をキャンバス上に描画します。各キーはCメジャー音階の音符に対応します。特定のキーに一致する音符が演奏されるとキーの色が変わります。各ボタン(キー)はCメジャーの音符を表し、音の高さが増加する順序で配置されてキャンバス上に表示されます。

  • キャンバス上に各音符のボタンを1列に描画するdrawMelody()関数を定義します。
  • keyWidthというローカル変数を定義し、各キーの幅を引いたcWidth/numNotesで初期化します。
  • forループを使用して各ボタンのxとy座標のためのkeyWidthというローカル変数を定義します:
    • yは固定値です
    • xはインデックス変数ikeyWidthに依存します
    • xykeyWidthを使用してキャンバス全体に丸みを帯びた長方形のキーを描画します。

drawMelody()関数は以下のようになるはずです:

// ユーザーインターフェース
function drawMelody() {
  // 各キーの幅
  let keyWidth = cWidth/numNotes;

  // 利用可能な音符の数だけループ
  for (let i = 0; i < numNotes; i ++) {
    //各要素のxを設定
    let x = i * keyWidth;
    let y = keyWidth*3; // yは幅の3倍
    
    // 丸みを帯びたキーを描画
    rect(x, y, keyWidth, keyWidth*2, 10);
  }
}
  • draw()関数にdrawMelody()を追加し、マウスでキャンバスをクリックしてください!まだ色が変化しないことを観察してください!

ステップ6: 視覚的フィードバックを追加する

drawMelody()は、音符を表す各ボタンの色を生成します。音符が演奏されるたびに、対応するキーの色が変化します。これにより、体験に視覚的に楽しい要素が加わります。map()関数を使用して、oscillators配列内の各音符のインデックスを使用して演奏中の各ボタンのHSBカラーモードでの色を設定できます。

  • rect()の前に以下のコードをdrawMelody()に追加します:

    //oscillators[i]が演奏を開始したかチェック    
    if (oscillators[i].started) {
      // true: map()、numNotes、iを使用してローカル変数hを定義
      // map()を使用してキーの色を設定
      let h = map(i, 0, numNotes, 0, 360);
      
      // fillでhを使用
      fill(h, 100, 100);
    } else {
      fill("white");
    }

drawMelody()関数は以下のようになるはずです:

// ユーザーインターフェース
function drawMelody() {
  // 長方形のボタンを描画
  let keyWidth = cWidth/numNotes;

  // 利用可能な音符の数だけループ
  for (let i = 0; i < numNotes; i ++) {
    // 各要素のxを設定
    let x = i * keyWidth;
    let y = keyWidth*3; // 高さは幅の3倍

    // oscillators[i]が演奏を開始したかチェック 
    if (oscillators[i].started) {
      // true: map()とnumNotesを使用してローカル変数hを定義
      // map()を使用してキーの色を設定
      let h = map(i, 0, numNotes, 0, 360);

      // fillでhを使用
      fill(h, 100, 100);
    } else {
      fill("white");
    }

    // 幅の2倍の高さを持つ丸みを帯びたキーを描画
    rect(x, y, keyWidth, keyWidth*2, 10);
  }
}
  • キャンバスをクリックしてコードをテストしてください!音符が演奏されると色とりどりになるキーの列が表示されるはずです!

コードはこのようになるはずです。

試してみよう!

map()関数の範囲を変更してください。drawMelody()内のmap(n, 0, numNotes, 0, 360)の行で、範囲を0, 360から異なる範囲に変更できます。これにより、音符がマッピングされる色相のスペクトルが変更されます。

例えば、0, 360の代わりに50, 250にマッピングすると、色スペクトルの異なるセグメントが使用されます。

次のステップの準備として、createP()createSelect()createInput()createButton()などのp5.Elementについて復習することを検討してください。また、DOM要素HTML要素のMDNリファレンスも確認してください。

HTMLの作成とスタイリングのチュートリアルを参照して、DOMオブジェクトの使用と変更方法についても復習できます。

ステップ7 – メロディーをカスタマイズするためのユーザー入力を追加する

メロディーを演奏し、音が鳴るときに視覚効果を表示するスケッチができたので、ユーザーがメロディーオブジェクトをカスタマイズできるようにDOM要素とキャンバスのインタラクティビティを追加できます。DOM要素は、p5.jsウェブアプリに含めることができるHTML要素です。

メロディーオブジェクトには、カスタマイズ可能な3つのプロパティがあります:namenotesIndextempoです。また、ユーザーはボタンを使ってメロディーを再生することもできます。まずは再生ボタンを追加しましょう!

ステップ7.1 - 再生ボタンを追加する

コードからmousePressed()関数を削除し、createButton()を呼び出してメロディーを再生するボタンを作成します。setup()でボタンなどのDOMオブジェクトをプロジェクトに追加できます:

  • .position(x, y)を呼び出してアプリ上のボタンの位置を設定します。

  • .mouseClicked(play)を呼び出して、ユーザーがボタンをクリックしたときにplay()関数を呼び出します。

  • setup()に以下のコードを追加します:

    // 再生ボタン
    let playButton = createButton('🎵 完成したら曲を再生しましょう!🎶');
    playButton.position(cWidth * 0.2, 540);
    playButton.mouseClicked(play);

setup関数は以下のようになるはずです:

function setup() {
  createCanvas(cWidth, 400);

  // オシレーターを初期化してoscillators配列に配置
  for (let freq of frequencies) {
    osc = new p5.Oscillator(freq);
    oscillators.push(osc);
  }

  // カラーモードをHSBに設定(音符でキーに色を付けるのに適している)
  colorMode(HSB);

  // 再生ボタン
  let playButton = createButton('🎵 完成したら曲を再生しましょう!🎶');
  playButton.position(cWidth * 0.2, 540);
  playButton.mouseClicked(play);
}
  • ボタンが機能するかテストしてください!

サンプル

ステップ7.2 - テンポをカスタマイズする

ユーザーが選択できるテンポのリストを含むドロップダウンメニューを追加しましょう。選択した値は、演奏中のmelodyオブジェクトを更新します。まず、ドロップダウンメニューの使い方についての説明をアプリに配置しましょう。

  • createP()を使用してテンポを設定するようユーザーに促すパラグラフを作成します。.style().position()を使用してパラグラフ要素のスタイルと位置を設定します。
    • setup()に以下のコードを追加します:

      // テンポを設定するようユーザーに促すテキスト
      let p = createP('ステップ1: テンポを選択してください!');
      p.style("color", "magenta");
      p.position(10, 415);

テンポのオプションを含むドロップダウンメニューを追加するために、選択可能な値(bpm単位で測定)を含む配列tempoListが必要です。また、ドロップダウンメニューのDOM要素を格納する変数も必要です。

  • setup()の前に以下のコードを追加します:

    // テンポ選択リスト(1分あたりの拍数)
    let tempoList = [
      "100","110","120",
      "130", "140", "150",
      "160","170","180",
      "190", "200", "210",    
      "220","230", "240",
      "250", "260","270",
      "280", "290", "300"
    ];
    
    // テンポドロップダウンの変数
    let tempoSelect;

ドロップダウンメニューなどのDOMオブジェクトsetup()でプロジェクトに追加できます:

  • createSelect()を使用してドロップダウンボックスを作成し、tempoSelectという変数に格納します。.position()でドロップダウンボックスの位置を設定します。.option()を使用して最初のオプションをインデックス0に設定します。

    • setup()に以下のコードを追加します:

      // テンポドロップダウン
      tempoSelect = createSelect();
      tempoSelect.position(10, 455);
      tempoSelect.option(0);
  • forループを使用してドロップダウンオプションを設定するために、setup()に以下のコードを追加します:

    // ドロップダウンオプションにテンポを追加
    for (let tempo of tempoList){
      tempoSelect.option(tempo);
    }
  • ドロップダウンをテストして、テンポリストが正しく設定されているか確認します。

  • ドロップダウンメニューからユーザーが選択した値に基づいてメロディーオブジェクトのmelody.temponoteDurationを設定するカスタム関数setTempo()を宣言します。

    • まず、選択されたテンポが0(デフォルト値)でないことを確認します。

    • 0でない場合はmelody.temponoteDurationを更新します。

    • setup()の外に以下のコードを追加します:

      // メロディーオブジェクトのテンポを設定
      function setTempo() {
        // テンポの選択が0でないことを確認
        if (tempoSelect.selected() !== 0) {
          melody.tempo = tempoSelect.selected();
          noteDuration = 60 / melody.tempo;
        }
      }
  • ユーザーがテンポを選択したときにsetTempo()を呼び出すために.changed()を使用します。setup()に以下のコードを追加します:

    // 選択時にsetTempo()を呼び出す
    tempoSelect.changed(setTempo);
  • キャンバス上にメロディーのテンポを表示します。

    • draw()に以下のコードを追加します:

      // メロディーのテンポを表示
      fill("magenta")
      textSize(20)
      text(`テンポ: ${melody.tempo}`, 300, 50);
  • ドロップダウンを使用してテンポを変更し、再生ボタンを押してsetTempo()が機能することを確認します。

コードはこのサンプルのようになるはずです。

ステップ7.3 - 名前をカスタマイズする

メロディーに名前を付ける方法についての説明、メロディー名を入力するためのテキスト入力ボックス、そしてメロディーオブジェクトの名前を更新するためのボタンを追加します。

  • createP()を使用してメロディーの名前を設定するようユーザーに促すパラグラフを作成し、.style().position()を使用してスタイルと位置を設定します。

  • setup()に以下のコードを追加します:

    // テキスト入力の説明
    let p2 = createP('ステップ2: メロディーの名前を入力し、"名前を設定"をクリックしてください');
    p2.style("color", "magenta");
    p2.position(10, 455);
  • createInput()を使用してユーザーがメロディー名を入力するためのテキストボックスを作成し、.position().size()を使用して位置とサイズを設定します。

    • setup()に以下のコードを追加します:

      // 曲名入力
      nameInput = createInput("名前を入力して設定してください");
      nameInput.position(10, 490);
      nameInput.size(200);
  • createButton()を使用してユーザーがメロディー名を設定するためのボタンを作成します。.position()を使用してアプリ上に配置し、.mouseClicked()を使用してボタンがクリックされたときにsetName()を呼び出します。

    • setup()に以下のコードを追加します:

      // 名前設定ボタン
      let nameButton = createButton('名前を設定');
      nameButton.position(250, 490);
      nameButton.mouseClicked(setName);
  • テキスト入力フィールドの値でmelody.nameプロパティを更新するsetName()関数を定義します。

    • setup()の外に以下のコードを追加します:

      // メロディーの名前を設定
      function setName(){
        melody.name = nameInput.value();
      }
  • キャンバス上にメロディーの名前を表示します。

    • draw()に以下のコードを追加します:

      // メロディー名を表示
      fill("magenta")
      textSize(20)
      text(`メロディー名: ${melody.name}`, 50, 50);

プロジェクトはこのサンプルのようになるはずです。

ステップ7.4 - リセットボタンを追加する

ユーザーが新しいメロディーオブジェクトを作成できるように、メロディーオブジェクトをリセットするボタンを作成します。メロディーオブジェクトをリセットすることで、ユーザーはすべてのプロパティをクリアして新しいメロディーの作曲を始めることができます。その結果、ユーザーは空のメロディーオブジェクトから始めることができます。空のメロディーオブジェクトは以下のようになります:

// 空のメロディーオブジェクト
let melody = {
  name: "", 
  notesIndex: [], 
  tempo: 0,
  duration: 0
};
  • メロディーオブジェクトをリセットするresetMelody()関数を、他のすべての関数の外に以下のコードを追加して宣言します:
// メロディーオブジェクトをリセット
function resetMelody() {
  // メロディーオブジェクトのプロパティをリセット
  melody.name = "";
  melody.notesIndex = [];
  melody.tempo = 0;

  // テンポドロップダウンをリセット
  tempoSelect.selected(0);
}
  • createButton()を使用してユーザーがメロディーオブジェクトをリセットするためのボタンを作成します。.position()を使用してアプリ上に配置し、.mouseClicked()を使用してボタンがクリックされたときにsetName()を呼び出します。
    • setup()に以下のコードを追加します:

      // リセットボタン
      let resetButton = createButton('メロディーをリセット');
      resetButton.position(150, 580);
      resetButton.mouseClicked(resetMelody);

プロジェクトはこのサンプルのようになるはずです。

ステップ8: インタラクティブなキーを追加する

updateMelody()という関数を宣言して、キャンバス上の各キーにインタラクティブなマウスプレスを追加します。この関数は、キャンバス上で押されたキーに一致する音符を演奏し、その音符をmelody.notesIndexに追加します。updateMelody()では:

  • forループを使用して各ボタンのx座標とy座標のためのkeyWidthというローカル変数を定義します。ステップ5でユーザーインターフェースの各キーを生成するために使用したのと同じコードを使用します。

    • updateMelody()は以下のようになるはずです:

      // 画面上の長方形に基づいて音符を保存
      function updateMelody() {
        // キーの幅
        let keyWidth = width / numNotes;
        
        // 各キーをループ処理  
        for (let i = 0; i < numNotes; i++) {
          // 各要素のxとyを設定
          let x = i * keyWidth;
          let y = keyWidth * 3;
        }
      }
  • マウスが各キーの境界上にあるかをチェックし、特定の音符をローカル変数notesに保存し、配列メソッド.push()を使用して音符をmelody.notesIndexに追加し、音符を演奏する条件文を追加します。

    • updateMelody()に以下のコードを追加します:

      // マウスがキーの上にあるかチェック
      if (mouseX > x && 
          mouseX < x + keyWidth && 
          mouseY > y && 
          mouseY < y + keyWidth * 2) {
      
        // 音符のインデックス配列を保存
        let notes = melody.notesIndex;
        
        // 新しい音符のインデックスを配列に追加
        notes.push(i);
        
        // メロディーオブジェクトに再割り当て
        melody.notesIndex = notes;
        
        // そのインデックスの音符を演奏
        playNote(i);
      }
  • mousePressed()内の関数呼び出しをupdateMelody()関数の呼び出しに置き換えます。

updateMelody()mousePressed()は以下のようになるはずです:

// キャンバスがクリックされたときにメロディーオブジェクトを更新
function mousePressed() {
  updateMelody();
}

// 画面上の長方形に基づいて音符を保存
function updateMelody() {
  // キーの幅
  let keyWidth = width / numNotes;

  // 利用可能な音符の数だけループ
  for (let i = 0; i < numNotes; i++) {
    // 各要素のxとyを設定
    let x = i * keyWidth;
    let y = keyWidth * 3;

    // マウスがキーの上にあるかチェック
    if (mouseX > x && 
        mouseX < x + keyWidth && 
        mouseY > y && 
        mouseY < y + keyWidth * 2) {

      // 音符のインデックス配列を保存
      let notes = melody.notesIndex;

      // 新しい音符のインデックスを配列に追加
      notes.push(i);

      // メロディーオブジェクトに再割り当て
      melody.notesIndex = notes;

      // そのインデックスの音符を演奏
      playNote(i);
    }
  }
}

よりシームレスなユーザーエクスペリエンスのために、Cメジャー音階のメロディーオブジェクトを空のメロディーオブジェクトに置き換えます。これにより、Simple Melody Appは空のメロディーオブジェクトで開始され、ユーザーは独自のメロディーの作曲を始めることができます!

最終的なSimple Melody Appのサンプル

次のステップ:

  • Node.jsを始めるに従って、ユーザーがコンピューターからメロディーを保存して再生できるようにします。

リソース

Coding Train: Sound Synthesis p5.jsチュートリアル

科学的リソース

音楽リソース

プログラミングリソース