これは10月17日の信濃毎日新聞に掲載されていた記事です。詳しい内容には毎日新聞の「言葉を覚えるロボット」ページから有料記事に進むことで読めます。
目次
概要
教えられた言葉を覚えてしゃべるオウムのロボットが2台います。記憶できる言葉は1つだけで、新しい言葉を覚えると、前に覚えた言葉は忘れてしまいます。2台のオウム1号、2号に次の順番で言葉を覚えさせた場合、オウム2号は何をしゃべるか? をプログラミングで表現します。
- オウム1号に言葉”夏休み”を覚えさせた
- オウム2号に言葉”スイカ”を覚えさせた
- オウム2号に言葉”海水浴”を覚えさせた
- オウム1号に言葉”花火”を覚えさせた
人間が自分の頭で考えた場合を考えてみましょう。知りたいのは、オウム2号が何をしゃべるかなので、オウム1号に覚えさせたことは無視できます。オウム2号はまず”スイカ”を覚え、次に”海水浴”を覚えたので、”海水浴”を覚えた時点で”スイカ”は忘れてしまい、覚えているのは”海水浴”です。つまりオウム2号がしゃべるのは”海水浴”です。
論理を考える
オウムロボットは言葉をひとつだけ覚え、新しい言葉を覚えるときには前の言葉を忘れます。プログラミングにはこれと同じ特徴を持つ変数と呼ばれる機能があり、このお題のプログラムにうってつけです。
変数
変数は、後からプログラムで利用できるようにするために、メモリに値を保持します。変数は1つのプログラム内で何度でも使用でき、その値はプログラムの実行中に簡単に変更できます。変数を使用する大きな理由は、コード内での同じことの繰り返しを避けるためです。(「3_1:p5.js 変数」)
変数の概念は、名前にひもをつなげその先に紙の札をつけたようなイメージで表すことができます。札には値が書かれています。たとえば直径を意味するdiameterという名前をつけた変数の場合、JavaScriptは、コードの中にdiameterを見つけると、そのひもをたぐってその先の札に書かれている200という数値を見つけ、diameterは200なのだなと理解します。
letとconst
変数には、上書きできるようにしたいものと、上書きできるようにしたくないものがあります。diameterなら、そのプログラムの中でつねに200を直径の値として使用したいでしょうから、変更できるようにしたくないでしょう。また、お題のオウムロボットの場合なら、何度も新しい言葉を覚えるので、上書きできる変数がいいでしょう。
JavaScriptでは、この2種類の変数を区別して作成できます。
let 変数名 = 値; // 変更できるようにしたい変数
const 変数名 = 値: // 変更できるようにしたくない変数
次のコードでは、変数wordはletキーワードを使って作成しているので、後から値を上書きできます。変数wordにひもでつながった札の文字’スイカ’は消され、新たに’海水浴’が書き込まれるイメージです。これに対し変数diameterはconstキーワードで作成しているので、値を再代入しようとするとエラーが発生します。
let word = 'スイカ';
word = '海水浴';
print(word); // '海水浴
const diameter = 200;
diameter = 400; // エラーになる
* プログラムが大きくなり、使用する変数が多くなってくると、誤って変数を上書きしてしまいかねません。constの使用はそのような事態の回避に役立ちます。
let変数の使用
オウムロボットが言葉を記憶し、前の記憶は新しい言葉を覚えることでなくなってしまう、ということはletキーワードを使った変数で表現できます。次のコードでは、オウム1号を変数oumu1で、オウム2号を変数oumu2で表しています。
// 変数oumu1を値'夏休み'で初期化。oumu1には'夏休み'が代入される
let oumu1 = '夏休み';
// 変数oumu2を値'スイカ'で初期化。oumu2には'スイカ'が代入される
let oumu2 = 'スイカ';
// oumu2に'海水浴'を代入。前の値'スイカ'は上書きされ失われる
oumu2 = '海水浴';
// oumu1に'花火'を代入。前の値'夏休み'は上書きされ失われる
oumu1 = '花火';
// oumu2の値を出力
print(oumu2); // '海水浴'
関数の使用
言葉を覚える用の関数としゃべる用の関数を用意すると、ロボットに記憶させたたりしゃべらせたりする感じが少しでます。
// オウム1号が覚えるのに使用する変数
let oumu1Word = '';
// オウム2号が覚えるのに使用する変数
let oumu2Word = '';
// オウム1号に新しい言葉を覚えさせる関数
function oumu1TeachWord(word) {
// 変数oumu1Wordを渡されたwordで上書きする
oumu1Word = word;
}
// オウム1号が今覚えている言葉を出力する関数
function oumu1Speak() {
print(oumu1Word);
}
// オウム2号用
function oumu2TeachWord(word) {
oumu2Word = word;
}
function oumu2Speak() {
print(oumu2Word);
}
// setup()関数内で実行
oumu1TeachWord('夏休み');
oumu2TeachWord('スイカ');
oumu2TeachWord('海水浴');
oumu1TeachWord('花火');
oumu2Speak(); // '海水浴'
p5.js-speechの使用
お題は基本的には、letキーワードで作成した変数で解決できますが、オウムロボットがしゃべったり、ロボットに言葉を覚えさせたりする楽しさはまったくありません。そこで以下では、p5.jsの拡張機能として使用できるp5.js-speechを使って、この楽しさを醸し出してみます。
p5.js-speech
p5.js-speechはJavaScriptコードの集まりで、p5.jsの使用を前提としたプログラムで使用できます。Webブラウザとは通常、JavaScriptを通してやりとりするわけですが、JavaScriptライブラリのp5.jsを利用すると、p5.jsの機能を使ってJavaScriptを通じ、ブラウザとやりとりできます。p5.jsを利用する環境にp5.js-speechを持ち込むと、p5.js-speechの機能を使ってp5.jsとJavaScriptを通じ、ブラウザとやりとりできます。
p5.js-speechは、p5.js用のWebオーディオ入力音声合成と音声認識の実装です。平たくいうと、p5.jsとp5.js-speechを導入することで、JavaScriptのWeb Speech API、つまりブラウザを使った音声合成と音声認識が、より容易に行えるようになります。詳しい情報はp5.speechページやIDMNYU/p5.js-speechページから得られます。
以降では、マイクを接続したコンピュータで実行すると、マイクからの音声を認識し、それをコンピュータの音声で発話するプログラムを作成します。
オウムに言葉を覚えさせ、しゃべらせる
作成するのは、次のプログラムです。コンピュータにマイクを接続しておきます。[聞く]ボタンをクリックすると、(初めての場合)ブラウザがマイクを使用する許可を求めてくるので許可します。マイクで何かしゃべります。終わったら[しゃべる]ボタンをクリックします。すると今マイクにしゃべった音声がコンピュータの声で再生されます。
index.htmlファイル
index.htmlでp5.jsを読み込んでいる<script>タグの下にp5.js-speechを読み込む<script>タグを追加します。p5.speech.jsファイル自体は上記のp5.js-speechページからダウンロードできます。
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
<script src="libs/p5.speech.js"></script>
、
sketch.jsファイル
sketch.jsには次のコードを記述します。このプログラムでは、imagesフォルダ内にオウムの画像を用意しています。
let oumuImage;
function preload() {
oumuImage = loadImage('images/oumuImage.png');
}
function setup() {
createCanvas(400, 300);
background(200);
// Oumuクラスのインスタンスを作成
const oumu = new Oumu(oumuImage, 150, 50);
// オウムを表示
oumu.display(20, 30);
// [聞く]ボタン
const listenButton = setButton('聞く', {
x: 100,
y: 250
});
// マウスプレスで、オウムにマイクからの音声を記憶する
listenButton.mousePressed(() => {
oumu.listen();
});
// [しゃべる]ボタン
const speachButton = setButton('しゃべる', {
x: 230,
y: 250
});
// マウスプレスで、オウムは今覚えている言葉をしゃべる
speachButton.mousePressed(() => {
oumu.speak();
});
}
function setButton(label, pos) {
const button = createButton(label);
button.size(80, 40);
button.position(pos.x, pos.y);
return button;
}
// Oumuクラス
class Oumu {
// イメージと描画の(x,y)位置を受け取る
constructor(img, x, y) {
// p5.SpeechRecオブジェクト。マイク入力をとらえる
// => 耳の代わり
this.ear = new p5.SpeechRec();
// p5.Speechオブジェクト。口の代わり
this.voice = new p5.Speech();
// しゃべる言語を日本語に設定
this.voice.setLang('ja');
// 記憶に使用するプロパティ
this.word = '';
// 描画に使用するイメージと(x,y)位置
this.image = img;
this.x = x;
this.y = y;
}
// 聞いて覚える
listen() {
// マイク入力の音声をテキスト化できるとonResultイベントハンドラが呼び出される。
// そのとき、SpeechRecが解析した結果の文字列(SpeechRec.resultString)を
// wordプロパティに代入する。
// => これによりオウムは言葉を一語だけ記憶する
this.ear.onResult = () => {
// 結果が存在するなら
if (this.ear.resultValue) {
// 解析した文字列を出力
print(this.ear.resultString);
// オウムの記憶にメモする
// => wordは、teachWord()が呼び出されるたびに上書きされるので、
// オウムは一語しか記憶できないことになる
this.word = this.ear.resultString;
}
}
// マイク入力を聞く
this.ear.start();
}
// しゃべる
speak() {
// p5.Speechオブジェクトのspeak()メソッドにwordプロパティの値を渡して、
// オウムにしゃべらせる
this.voice.speak(this.word);
}
// 描画
display(x, y) {
image(this.image, this.x, this.y);
}
}
p5.js-speechの本体はp5.SpeechRecオブジェクトとp5.Speechオブジェクトです。p5.SpeechRecはオウムの耳の代わりを、p5.Speechはオウムの口の代わりを果たします。
[聞く]ボタンをクリックすると、Oumuクラスのoumuインスタンスのlisten()メソッドが呼び出されます。listen()では、p5.SpeechRecオブジェクト(oumuインスタンスのearプロパティ)のonResultイベントハンドラに関数を割り当てます。このイベントハンドラは、音声の解釈(音声の文字化)が終わったら自動的に呼び出されます。その後、マイク入力の受け取りを開始するstart()メソッドを呼び出します。
onResultイベントハンドラの関数では、Oumuクラスのwordプロパティに、p5.SpeechRecオブジェクトが解釈した
文字列(this.ear.resultString)を割り当てます。wordプロパティは、listen()メソッドが呼び出されるたびに上書きされます。この意味で、letキーワードで作成した変数と同じ役割を果たします。
[しゃべる]ボタンのクリックでは、p5.Speechオブジェクト(Oumuクラスのvoiceプロパティ)のspeak()メソッドに、wordプロパティを渡してこれを呼び出します。wordプロパティはマイク入力からの文字列を持っているので、これがコンピュータのしゃべる声として再生されます。