見出し画像

【GAS】スプレッドシートの数字譜を元にピアノの音で自動演奏~解説編(最終)~楽譜で読み取った情報から音楽を奏でる|コードの実装~

この記事は、外部ファイルとしてピアノの音源を用意し、それをスプレッドシートに用意した数字譜を元に自動演奏させる、以下にご紹介するGAS(Google Apps Script)アプリの説明記事です。

音楽を奏でるGASのプログラム例の多くは、ピ・ポ・パ・・・という人工音によるものだと思いますが、本記事でご紹介する方法は、Googleドライブ上に音源ファイルを用意することで、ピアノの音色などを楽しめるのが特長です。

簡単なトイピアノ程度の機能ですが、インストール不要で、楽譜だけ編集するだけで、短い音楽をメンバーと共有できますので、簡単な合唱曲や合奏曲を練習するときの便利ツールなどに使えるかも知れません。

今回の記事は以下の先回までの記事の続きとなります。


本記事では実装コードを最後にご紹介しています。様々な理由により、この説明通りにいかない場合がしばしばあります。申し訳ありませんが、自己責任・自己解決でお進めくださるよう、お願いいたします。

スクリプトの解説~起動時に譜面の情報を取得してテンプレートに埋め込んでおく~


このGAS(Google Apps Script)で作るWEBアプリでは、ピアノの各音階を鳴らすのに、音階毎の音源ファイルを鳴らして対応します。

以下がこのアプリ起動直後の様子ですが、音階毎にAudio要素がずらりと並んでいます。この要素には「sound[音階のMiDiノート番号]」がID名称に付けられていて、MIDI番号を元に指定して鳴らす事ができます。

一方、楽譜シートには、シートの上側に楽譜の内容を集約したテキストを用意しています。

以下がその部分ですが、メロディと伴奏それぞれで、発音数、音階(伴奏では和音種類)、各音階の発音時刻が文字列として集約されています。

このアプリでは、上記の6つの青いセルに記載された情報は、起動時にテンプレートに引き渡しておくようにしています。以下がスクリプトの該当部です。

WEBページを示すインスタンス myHTML を、createTemplateFromFile()関数で宣言したあと、

・メロディー音数を埋め込み:

myHTML.myMeln = mySheet2.getRange(4,7,1,1).getValue();

・メロディーを埋め込み

myHTML.myMel = mySheet2.getRange(4,8,1,1).getValue();

・メロディーのタイミングを埋め込み

myHTML.myMelT = mySheet2.getRange(4,9,1,1).getValue();

こんな風に埋め込んでいきます。伴奏側も同様です。もっとスマートな記述はあるとは思いますが・・・。

埋め込み時の変数 WEBページのインスタンス.子変数 については、 var構文 での変数宣言は不要です。

スクリプトでの該当部のコードです。

~テンプレートの生成と値の埋込みをしています~

 //HTMLファイルのテンプレートをファイル名を指定して取得
 var myHTML = HtmlService.createTemplateFromFile('index');
 
 //★★テンプレートに埋め込む変数値を指定する★★  
 
・・・(中略)・・・
 
 //メロディー音数を埋込み
 myHTML.myMeln = mySheet2.getRange(4,7,1,1).getValue();

 //メロディーを埋込み
 myHTML.myMel = mySheet2.getRange(4,8,1,1).getValue();
 
 //メロディーのタイミングを埋込み
 myHTML.myMelT = mySheet2.getRange(4,9,1,1).getValue();

 //伴奏音数を埋込み
 myHTML.myBasn = mySheet2.getRange(5,7,1,1).getValue();

 //伴奏を埋込み
 myHTML.myBas = mySheet2.getRange(5,8,1,1).getValue();

 //伴奏のタイミングを埋込み
 myHTML.myBasT = mySheet2.getRange(5,9,1,1).getValue();



 //HTMLファイルをホスティング|メタタグを指定してスマホ表示に対応
 return myHTML.evaluate().addMetaTag("viewport", "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=10.0");
}

スクリプト側では、宣言したWEBページのインスタンスをホスティング(ブラウザに実体化させる)するところまでで完了です。

この後の処理はテンプレート側での対応となります。

テンプレートの解説~演奏ボタンと実行させる関数の実装~


次はテンプレートの解説です。
 スクリプト側で埋め込まれた情報を取り出し、
 これを利用した演奏用の関数を定義し、
 演奏ボタンに紐付けます。

順に解説します。

①スクリプト側で埋め込まれた情報を取り出す

スクリプト側で埋め込まれた情報は、発音数、各音の音階(MIDI番号:2文字)、発音時刻(6文字)です。

これらはテンプレート(HTML文)内に Scriptセクション を設けてJavascript文により以下の様に取得します。


発音数Number(<?= myMeln ?>)

Number()は文字を数値に変換する関数です。
<?= ?>はGAS固有のタグで、この中に変数名を入れておくと、
 実行時に「その変数に代入された値」に置き換えられます。

i番目の音の音階<?= myMel ?>.substr([i*2+1]文字目2文字)

substr()は、親となる文字列の一部を取り出す関数です。
 文字列. substr()という様に、親となる文字列の
 子関数として使います。

i番目の音の音量=1(固定)

i番目の音の発音時刻<?= myMel ?>.substr([i*7+2]文字目6文字)

以上は、実際には配列 メロディ音 [i番目] [0~2] に代入しています。


<!--javascriptセクション -->
<script>
・・・(中略)・・・
//ーーーメロディ-を格納する配列の設定ーーー

  //ーーー配列の宣言ーーー
  var myMel = [];
  for (var i = 0; i < Number(<?=myMeln?>); i++) {
    myMel[i] = [3];
  
    myMel[i][0] = <?=myMel?>.substr(i*2+1,2);
    myMel[i][1] = 1;
    myMel[i][2] = Number(<?=myMelT?>.substr(i*7+2,6));
 
  }

  var myBas = [];
  for (var i = 0; i < Number(<?=myBasn?>); i++) {
    myBas[i] = [3];
  
    myBas[i][0] = <?=myBas?>.substr(i*2+1,2);
    myBas[i][1] = 0.3;
    myBas[i][2] = Number(<?=myBasT?>.substr(i*7+2,6));
 
  }
・・・(中略)・・・・
</script>


②取り出した情報で演奏する関数を定義

次は、これらの情報を使って演奏する関数を定義します。

これは、Audio要素の子関数、および指定時刻に実施するsetTimeout()関数を利用します。

メロディ関数(MIDI番号、音量、発音時刻)
setTimeout

・Audio要素を初期状態に戻す
 Audio要素(id名が "sound"+MIDI番号).currentTime = 0

・Audio要素のボリュームをセットする
 Audio要素(id名が "sound"+MIDI番号).volume=音量

・Audio要素を鳴らす
 Audio要素(id名が "sound"+MIDI番号).play()

)、発音時刻

以下がコードです。

  //ーーーメロディーを演奏する関数を定義ーーー
  function myMerody(myNote,myVol,myTiming){
    setTimeout(function(){
      document.getElementById("sound"+myNote).currentTime = 0;
      document.getElementById("sound"+myNote).volume=myVol;
      document.getElementById("sound"+myNote).play();
    }, myTiming);
  }

一方、伴奏は、3音の和音ですので、これを利用した関数を定義しています。

伴奏関数(コードネーム、音量、発音時刻)
 コードネームによって場合分け
  第1音=○○
  第2音=○○
  第3音=○○

 発音
  メロディ関数(第1音、音量、発音時刻)
  メロディ関数(第2音、音量、発音時刻)
  メロディ関数(第3音、音量、発音時刻)

実際のコードは以下の様になります。

//ーーー伴奏を演奏する関数を定義ーーー
  function myBanso(myChord,myVol,myTiming){
    var flag=1
・・・(中略)・・・・
    //ドミソ
    if (myChord == 'CM') {
      var n1=60;var n2=64; var n3=67;

・・・(中略)・・・・

    } else {
      var flag=0;
    }

    if(flag==1){
      myMerody(n1,myVol,myTiming);
      myMerody(n2,myVol,myTiming);
      myMerody(n3,myVol,myTiming);
    }

  }

③演奏ボタンに紐付けする

最後に演奏ボタンをHTML文で記載して、演奏させます。

演奏ボタンは以下の様に<button>タグで記載します。

  <!--演奏ボタン -->
  <p>外部音源の再生</p>
  <button id="musicPlay" class="btn btn-primary">再生(STOPはブラウザを閉じる)</button><!-- #musicPlay  -->


クリックした時の挙動は、上記ボタンのID名を元に以下の記述でscriptセクションに記載しておきます。

まず対象のボタンを取得する。
対象ボタン
= document.getElementById('ボタンのID')

対象の挙動を addEventListener(イベント、作動関数関数で以下の様に定義する。


対象ボタン
.addEventListener('click',
function(){

メロディ音数だけ繰り返し:
 メロディ関数(メロディ音
[i番目]、音量、時刻

伴奏音数だけ繰り返し:
 伴奏関数(伴奏音
[i番目]、音量、時刻)

かなり簡易ですが以上です。この部分は実際のコードでは次の様になります。

<!--javascriptセクション -->
<script>
・・・(中略)・・・・
//ーーー演奏ボタンの取得ーーー
   const musicBtn = document.getElementById('musicPlay');  

・・・(中略)・・・・

//ーーーボタンクリック時ーーー
  musicBtn.addEventListener('click', function(){


    //ーーー演奏するーーー
    for(var i=0; i < Number(<?=myMeln?>); i++){
      myMerody(myMel[i][0],myMel[i][1],myMel[i][2]);
    }

    for(var i=0; i < Number(<?=myBasn?>); i++){
      myBanso(myBas[i][0],myBas[i][1],myBas[i][2]);
    }

  });
・・・(中略)・・・・
</script>



実装コードのご紹介


ご紹介のコード等は、様々な理由により、この説明通りにいかない場合がしばしばあります。申し訳ありませんが、自己責任・自己解決でお進めくださるよう、お願いいたします。


本アプリのための、スプレッドシート、スクリプトとテンプレート、および音源ファイルをご紹介します。

スプレッドシート

Googleドライブ上にスプレッドシートを新規作成し、以下のエクセルワークブックを「ファイル」→「インポート」メニューによりアップロードしてインポートしてください。(シート名は変えないでください)

上記の「楽譜」シートには、『威風堂々』をテスト用に記載しています。

ファイルID調査用のスクリプトを追加


インポートしたら、後で紹介する音源ファイルのファイルIDを調査するための以下のスクリプトを追加しておきます。

//対象フォルダ内のすべてのファイルを取得するプログラム
function myGetFiles() {
var myApp = SpreadsheetApp.openById('★スプレッドシートのID★');
var mySheet = myApp.getSheetByName('ファイル名取得');


var myFolder = DriveApp.getFolderById('★調査するGoogleドライブ上のフォルダのID★');
var myFiles = myFolder.getFiles();
var iLin=1;
var Col=1;
while (myFiles.hasNext()) {
var file = myFiles.next();
mySheet.getRange(iLin,Col).setValue(file.getName());
mySheet.getRange(iLin,Col+1).setValue(file.getId());
iLin++;
}
}

上記コードは、スプレッドシートの「拡張機能」→「Apps Script」でスクリプトを新規作成し、上記コードをエディタ画面から貼り付けます。

メニュー画面
エディタ画面

貼り付けたら、以下の★部分をご自身のものに書き換えてください。

'★スプレッドシートのID★は、ブラウザのヘッダ部分で以下の太字部を確認して記入ください。

HTTPS://docs.google.com/spreadsheets/d/★ファイルID★/edit・・・

調査するGoogleドライブ上のフォルダのID★は、Googleドライブ上で音源ファイルを保存するサブフォルダを作成した後、右クリックして「リンクを取得」メニューで確認できます。

HTTPS://drive.google.com/drive/folders/★フォルダID★?usp=sharing

スクリプトの実行は、保存後にエディタ画面の「実行」メニューによって実施できます。このスクリプトの使い方は後述します。

初回は色々とセキュリティ上の有無を問われます。後述するデプロイと同様の手順で先へ進みます。

スクリプトとテンプレート


Googleドライブ上で「新規」→「その他」→「Google Apps Script」を選択し新規のプロジェクトを追加します。作成したらプロジェクトのエディタを開きます。

コードを以下に記載しますが、手順そのものは以下を参照ください。

スクリプトのコード

以下のコードをスクリプトに貼り付けてください。(★スプレッドシートID★の書き換えを忘れずに)

//-----------------------------------------
//----外部音源ファイルの利用によるトイピアノ-----
//---著作:Particlemethod-2022年01月23日-----
//---使用は自由ですが著作権は作成者に帰属します---
//-----------------------------------------
//-----------------------------------------

function doGet() {

//アプリケーションを取得|★スプレッドシートID★は各自のものを記入
 var myApp = SpreadsheetApp.openById('★スプレッドシートID★');

//=========音源データの取得=========
 //対象シートをシートの名前を指定して取得
 var mySheet = myApp.getSheetByName('音源');
 
 //データ記録範囲の位置を記載
 var nRow0=3;
 var nCol0=1;

 //データ記録範囲として、行数と列数を記載
 var nRow=37;
 var nCol=3;

 //データ記録範囲を指定して範囲を取得
 var myCells = mySheet.getRange(nRow0,nCol0,nRow,nCol);

//=========楽譜データの取得=========
 //楽譜シートをシートの名前を指定して取得
 var mySheet2 = myApp.getSheetByName('楽譜');


//=========テンプレートの生成と値の埋込み=========
 //HTMLファイルのテンプレートをファイル名を指定して取得
 var myHTML = HtmlService.createTemplateFromFile('index');
 
 //★★テンプレートに埋め込む変数値を指定する★★  
 //音源の埋込み
 myHTML.myCells = myCells.getValues();

 //メロディー音数を埋込み
 myHTML.myMeln = mySheet2.getRange(4,7,1,1).getValue();

 //メロディーを埋込み
 myHTML.myMel = mySheet2.getRange(4,8,1,1).getValue();
 
 //メロディーのタイミングを埋込み
 myHTML.myMelT = mySheet2.getRange(4,9,1,1).getValue();

 //伴奏音数を埋込み
 myHTML.myBasn = mySheet2.getRange(5,7,1,1).getValue();

 //伴奏を埋込み
 myHTML.myBas = mySheet2.getRange(5,8,1,1).getValue();

 //伴奏のタイミングを埋込み
 myHTML.myBasT = mySheet2.getRange(5,9,1,1).getValue();



 //HTMLファイルをホスティング|メタタグを指定してスマホ表示に対応
 return myHTML.evaluate().addMetaTag("viewport", "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=10.0");
}

 //テンプレートから呼び出して利用
function getBae64Data(id) {
  const file = DriveApp.getFileById(id);
  const data = file.getBlob().getBytes();
  return Utilities.base64Encode(data);
}



テンプレートのコード

プロジェクトに、テンプレートを追加し、以下のコードを貼り付けます。名前は「index.hrml」とします。

<!----------------------------------------
----外部音源ファイルの利用によるトイピアノ-----
---著作:Particlemethod-20220123日-----
---使用は自由ですが著作権は作成者に帰属します---
-----------------------------------------
-------------------------------------- -->
<!DOCTYPE html>
<html>

<!--ヘッダセクション -->
<head>
   <base target="_top">
    
   <!--audioライブラリ:不要かもしれない -->
   <script src="https://cdnjs.cloudflare.com/ajax/libs/audiojs/1.0.1/audio.min.js"></script>

</head>


<!--本体セクション -->
<body>
   
  <!--演奏ボタン -->
  <p>外部音源の再生</p>
  <button id="musicPlay" class="btn btn-primary">再生(STOPはブラウザを閉じる)</button><!-- #musicPlay  -->


  <!--Audio要素 -->
  <p>音源をセットしたオーディオ要素</p>

   <!--↓スクリプトレット↓ -->
   <?
   for(var i=1;i<37;i++){
     var Row = myCells[i-1];

    //テキスト要素(音階名)、Audio要素:このID名は「sound + MIDI番号」にしておく
    ?><p><?=Row[1]?><audio id="sound<?=i+54?>" controls src="" preload="auto"></audio></p><?

    //隠しテキスト(音階名):このID名は「FileID + MIDI番号」にしておく
    ?><p hidden id="FileID<?=i+54?>"><?=Row[2]?></p><?
   }
   ?>
   <!--↑スクリプトレット↑終わり -->
 
</body>


   
<!--javascriptセクション -->
<script>

    //テキスト要素から音階名を取得する配列
      var fileId = new Array();
    //Audo要素を取得する配列
      var myaudio = new Array();
      
      for(var i=1+54; i<37+54; i++){
        //音階名を取得する配列
        fileId[i] = document.getElementById("FileID"+i).textContent;
        //audio要素を取得する配列
        myaudio[i] = document.getElementById("sound"+i);
      }


    //ーーー↓強制実行させる関数↓ーーー
    (function() {

      for(let i=1+54; i<37+54; i++){


      //ーーーMIDI番号毎に、音源データを埋め込むーーー
      google.script.run
        .withSuccessHandler(res => {
          myaudio[i].setAttribute("src", "data:audio/mp3;base64," + res);
        })
        .withFailureHandler(console.error)
        .getBae64Data(fileId[i]);
      }  

    })();    
    //ーーー↑強制実行させる関数↑終わりーーー
   
   //ーーー演奏ボタンの取得ーーー
   const musicBtn = document.getElementById('musicPlay');




  //ーーーメロディ-を格納する配列の設定ーーー

  //ーーー配列の宣言ーーー
  var myMel = [];
  for (var i = 0; i < Number(<?=myMeln?>); i++) {
    myMel[i] = [3];
  
    myMel[i][0] = <?=myMel?>.substr(i*2+1,2);
    myMel[i][1] = 1;
    myMel[i][2] = Number(<?=myMelT?>.substr(i*7+2,6));
 
  }

  var myBas = [];
  for (var i = 0; i < Number(<?=myBasn?>); i++) {
    myBas[i] = [3];
  
    myBas[i][0] = <?=myBas?>.substr(i*2+1,2);
    myBas[i][1] = 0.3;
    myBas[i][2] = Number(<?=myBasT?>.substr(i*7+2,6));
 
  }

  //ーーーメロディーを演奏する関数を定義ーーー
  function myMerody(myNote,myVol,myTiming){
    setTimeout(function(){
      document.getElementById("sound"+myNote).currentTime = 0;
      document.getElementById("sound"+myNote).volume=myVol;
      document.getElementById("sound"+myNote).play();
    }, myTiming);
  }

  //ーーー伴奏を演奏する関数を定義ーーー
  function myBanso(myChord,myVol,myTiming){
    var flag=1
    //ーーーメジャー3コードーーー
    //ドミソ
    if (myChord == 'CM') {
      var n1=60;var n2=64; var n3=67;
    //シレソ
    } else if (myChord == 'GM') {
      var n1=59;var n2=62; var n3=67;

    //ドファラ
    } else if (myChord == 'FM') {
      var n1=60;var n2=65; var n3=69;

    //ーーーマイナー3コードーーー
    //ドミラ
    } else if (myChord == 'Am') {
      var n1=60;var n2=64; var n3=69;

    //レファラ
    } else if (myChord == 'Dm') {
      var n1=62;var n2=65; var n3=69;

    //シミソ
    } else if (myChord == 'Em') {
      var n1=59;var n2=64; var n3=67;

    //ーーーその他のコードーーー
    //レファ#ラ
    } else if (myChord == 'DM') {
      var n1=62;var n2=66; var n3=69;

    //シファソ
    } else if (myChord == 'G7') {
      var n1=59;var n2=65; var n3=67;


    } else {
      var flag=0;
    }

    if(flag==1){
      myMerody(n1,myVol,myTiming);
      myMerody(n2,myVol,myTiming);
      myMerody(n3,myVol,myTiming);
    }

  }

  //ーーーボタンクリック時ーーー
  musicBtn.addEventListener('click', function(){


    //ーーー演奏するーーー
    for(var i=0; i < Number(<?=myMeln?>); i++){
      myMerody(myMel[i][0],myMel[i][1],myMel[i][2]);
    }

    for(var i=0; i < Number(<?=myBasn?>); i++){
      myBanso(myBas[i][0],myBas[i][1],myBas[i][2]);
    }

  });
   
   
</script>



</html>

音源ファイル

まずは、B6、C6、D6の3音階だけの音源を試してみてください。

まず以下のファイルをダウンロードして、Googleドライブ上の所定のフォルダにアップしておきます。

Googleドライブにアップ

アップしたら、Googleドライブ上のファイルIDを確認し、最初にアップしたスプレッドシートの「音源」シートの「B6」「C6」「D6」の黄色いセル部分にIDを記載します。

黄色い部分に記入

また、このシートの最初の行には、保存フォルダのIDを記入しておきます。

黄色い部分に記入

記入したところはこんな感じです。

記入結果(音源が多い場合の例)

ファイルのIDを一つずつGoogleドライブ上で確認するのは手間なので、最初に設定したスクリプトを使ってみます。

「ファイル名取得」シートを開き、スクリプトを実行させると、ファイル名とファイルIDが書き出されます。これを引用すると、上記作業は効率的にできます。

スクリプトを実行した結果(音源ファイルが沢山ある例)




実行してみる

ここまでできたら、デプロイして実行してみてください。
以下の様な画面になりましたでしょうか?(導入した音階以外はまだグレイのままの筈です)

多数の音源を導入した例

ちゃんと音源が設定された音階は、演奏ボタン▲を押すと、音がでる筈です。

次に、「再生」をクリックすると、セットされた音だけですが音楽が鳴るはずです。(停止ボタンはないので、停止するにはブラウザ画面を閉じてください。)

全部の音をセットしたら以下の様に鳴るはずです。

ここまでできたら、このアプリで使える残りの音階も用意しましたので、同様の手順で追加してください。全てのAudio要素に音がセットされるはずです。(楽器を録音してご自身で用意してもOKです。1秒程度の長さでmp3ファイルにして、名称は同じ要領で付けてください)

37音階すべて

また、他の演奏例として、『仰げば尊し』の楽譜もご用意しました。



ダウンロードするリンク先は--

ここから先は

107字 / 1画像 / 2ファイル

¥ 200

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