【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();
こんな風に埋め込んでいきます。伴奏側も同様です。もっとスマートな記述はあるとは思いますが・・・。
スクリプトでの該当部のコードです。
~テンプレートの生成と値の埋込みをしています~
//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 ?>)
i番目の音の音階= <?= myMel ?>.substr([i*2+1]文字目、2文字)
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★は、ブラウザのヘッダ部分で以下の太字部を確認して記入ください。
★調査するGoogleドライブ上のフォルダのID★は、Googleドライブ上で音源ファイルを保存するサブフォルダを作成した後、右クリックして「リンクを取得」メニューで確認できます。
スクリプトの実行は、保存後にエディタ画面の「実行」メニューによって実施できます。このスクリプトの使い方は後述します。
スクリプトとテンプレート
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-2022年01月23日-----
---使用は自由ですが著作権は作成者に帰属します---
-----------------------------------------
-------------------------------------- -->
<!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ドライブ上のファイルIDを確認し、最初にアップしたスプレッドシートの「音源」シートの「B6」「C6」「D6」の黄色いセル部分にIDを記載します。
また、このシートの最初の行には、保存フォルダのIDを記入しておきます。
記入したところはこんな感じです。
ファイルのIDを一つずつGoogleドライブ上で確認するのは手間なので、最初に設定したスクリプトを使ってみます。
「ファイル名取得」シートを開き、スクリプトを実行させると、ファイル名とファイルIDが書き出されます。これを引用すると、上記作業は効率的にできます。
実行してみる
ここまでできたら、デプロイして実行してみてください。
以下の様な画面になりましたでしょうか?(導入した音階以外はまだグレイのままの筈です)
ちゃんと音源が設定された音階は、演奏ボタン▲を押すと、音がでる筈です。
次に、「再生」をクリックすると、セットされた音だけですが音楽が鳴るはずです。(停止ボタンはないので、停止するにはブラウザ画面を閉じてください。)
ここまでできたら、このアプリで使える残りの音階も用意しましたので、同様の手順で追加してください。全てのAudio要素に音がセットされるはずです。(楽器を録音してご自身で用意してもOKです。1秒程度の長さでmp3ファイルにして、名称は同じ要領で付けてください)
また、他の演奏例として、『仰げば尊し』の楽譜もご用意しました。
ダウンロードするリンク先は--
ここから先は
¥ 200
この記事が気に入ったらサポートをしてみませんか?