Pythonロボティクスコース レッスン 20 *

テーマ.5-3 「ディスクオルゴール風電子楽器の制作」

ファイルを使用した入出力処理を学ぼう

このレッスンで学ぶこと

このレッスンでは、ファイルからの入力処理(読み込み)と、ファイルへの出力処理(書き込み)について学習します。また、Muの標準搭載されているデータのグラフ表示機能(折れ線グラフとしてデータを描画する機能)の使い方についても確認していきます。

レッスンの後半では、楽譜の情報が書かれたテキストファイルを読み込んでその通りに音を鳴らす「ディスクオルゴール風電子楽器」を制作します。

新しいPython文法の学習

始めに、今回のレッスンで初めて扱うPythonの文法について学習しましょう。

2. 1 ファイルへのデータの入出力

Studuino:bitやArtecRoboには様々なセンサが用意されていました。例えば温度センサを利用すると気温の変化を調べたり、明るさセンサを利用すると日の出や日没の時刻の変化を調べたりすることができます。

取得したセンサーの値は、変数やリストなどに格納することで、一時的に保存しておくことはできますが、コンピューター(Studuino:bit)の電源をOFFにするとその情報はすべて失われてしまいます。

そこで、Pythonにはデータを外部のファイルへ書き込んで記録したり、記録したファイルから情報を読み込んだりする方法が用意されています。外部のファイルとして残すことで、データを永続的に保管しておくことができます。ここでは、その方法を具体的に見ていきましょう。

■ ファイル操作の基本「1.ファイルを開く」

Pythonでは、標準関数として用意されているopen()関数を用いることで、新たにファイルを作成して開いたり、既に存在しているファイルを開いたりすることができます。open() 関数は戻り値として「ファイルオブジェクト」を返します。

open(ファイル名, モード)

open() 関数の1つ目の引数には、開きたいファイルの名前を指定します。もし、実行中のプログラムとは別の階層のフォルダにあるファイルを指定するときは、ファイル名の前にパス(path:経路)を付けます。

2つ目の引数は、読み書きを行うときのモードを文字で指定します。下の表は指定できるモードの一覧です。よく使用するのは、'w' (新規書き込み),'a' (追記書き込み),'r' (読み込み専用) の3つのモードです。

【 open()関数の引数に指定できるモードの一覧 】
モードの指定文字意味
‘r’読み込み用ファイルとして開く(デフォルト)
‘w’書き込み用のファイルとして開き、既存のファイルがある場合は上書きする
‘x’書き込み用のファイルとして開く。既存のファイルがある場合はエラーを出す。
‘a’書き込みようのファイルとして開き、既存のファイルがある場合は末尾から追記する
’+’読み書きの両方ができる更新用のファイルとして開く。
‘t’テキストモード
‘b’バイナリモード
‘U’ユニバーサル改行モード (非推奨)

open() 関数には他にもいくつかの引数がありますが、ここでは扱わないため説明を省略します。

では、ターミナルで次のコードを実行して、「sample_file」という名前でテキストファイル(拡張子「.txt」)を新しく作成して開き、変数fileにファイルオブジェクトを格納しましょう。

※ 新しくファイルを作成するため、新規書き込みモード ( w ) で開きます。

■ ファイル操作の基本「2.データを書き込む」

次に、開いたファイルにデータを書き込む方法を見ていきましょう。

ファイルオブジェクトには書き込みのためのwrite()メソッドが用意されています。

write(書き込みたい文章の文字列)

引数に指定した文字列が開いたファイルに書き込まれます。では、。ターミナルで次のコードを実行して、開いたテキストファイルに「Hello Python!」という文字列を書き込んでみましょう。

コードを実行すると「13」という数字が表示されます。これは、write()メソッドが書き込んだ文字数を返すためです。

■ ファイル操作の基本「3.ファイルを閉じる」

最後にファイルを閉じて、書き込みを終了します。ファイルを閉じるときは、ファイルオブジェクトの close() メソッドを呼び出します。ファイルを閉じなかった場合、データが消失してしまう恐れがありますので、閉じ忘れないように注意してください。

ターミナルで次のコードを実行しましょう。

■ ファイル操作の基本「4.データを読み込む」

作成したファイルは再び開いて、データを読み込むことができます。読み込みのプログラムを書く前に、まずは作成されたファイルが保存されている場所を確認しましょう。

エディタの画面上部にある「ファイル」をクリックします。

画面下部に表示されたウィンドウの左側がStuduino:bit上に保存されているファイルの一覧です。この中に、作成したファイルがあることを確認してください。

確認ができたら、画面上部の「ファイル」をもう一度クリックして、ウィンドウを閉じます。

それでは、このファイルを開いて、データを読み込んでみましょう。

今度は、読み込み専用のモード「'r'」でファイルを開きます。エディタエリアに次のコードを書きましょう。

開いたファイルのデータを読み込むときは、ファイルオブジェクトのread()メソッドやreadline()メソッドを使用します。

read(読み込むデータのサイズ)
・・・引数に指定したサイズだけデータを読み取って返します。引数が省略された場合は、ファイルの全ての内容を読み取ります。
readline()
・・・ファイルから1行だけを読み取って返します。

ここでは、read()メソッドを使用してファイルからデータを読み取り、表示してみましょう。最後にclose()メソッドを呼び出して、ファイルを閉じるのを忘れないように注意してください。

【 サンプルコード 2-1-1 】 ※コードエリアに入力

このコードを実行して、前のコードで書き込んだ「Hello Python!」がターミナルに表示されたら成功です。

(実行結果)

■ with文で自動でファイルを閉じる

ファイルをopen()関数で開くたびに、最後にclose()メソッドを呼びだして閉じるのは少し面倒ですね。また、ファイルを閉じ忘れたまま、別の似たファイルの操作を行うと、予期せぬ結果を引き起こしたりもします。このような意図しないエラーを避けるために、Pythonでは処理が終わると自動でファイルを閉じる「with文」が用意されています。with文は以下のように書きます。

with open(ファイル名, モード) as ファイルオブジェクトを格納する変数名:
    .
    .
    .

if文やfor文の中のコードと同じように、ファイル開いてから閉じるまでに実行したいコードの先頭にはインデントを付けます。with文を使って【 サンプルコード 2-1-1 】を書き換えると次のようになります。

【 サンプルコード 2-1-2 】

【 サンプルコード 2-1-1 】と比べると、どこまでがファイル操作を行う処理なのかが少し分かりやすくなりました。

2. 2 データのグラフ表示機能

プログラムの作成に使用しているエディタ「Mu」には、一定時間おきにprint文で数値データを出力すると、その変化を折れ線グラフとして表示する機能が備わっています。

例として、Studuino:bitに内蔵されている光センサーから値を取得して、その変化をグラフに表示してみましょう。

まずは、光センサーから1秒おきに値を取得するコードを書きましょう。

次に取得した値をprint文で出力しますが、ここで1つ注意点があります。グラフ表示するためには、データが1つであっても必ずタプルとして出力するということです。そのため、次の形式でprint文を追加します。

追加【6行目】
※ (v, )と書くことで、要素を1つだけもつタプルとなります。

このプログラムを実行して、画面上部の「プロッター」をクリックすると、グラフが画面右下に表示されます。

光センサーを手で覆うと、次の図のようにグラフが変化することを確認しましょう。

グラフ表示を行うことで、よりセンサの出力する値の変化が捉えやすくなりますので、ぜひ活用していきましょう。

ディスクオルゴール風電子楽器の制作

下の写真のように、一般的に見かけるオルゴールは、正式には「シリンダーオルゴール」というもので、シリンダーに付いたピンが振動板を弾くことで、音を奏でています。シリンダーオルゴールは、曲の情報をもつシリンダーとそれを再生する振動板が一体になっており、他の曲を再生することはできません。

【シリンダーオルゴール】

一方で、穴の開いた専用のディスクを変えることで様々な曲が再生できる「ディスクオルゴール」があります。ディスクオルゴールは今でいう所のCDやDVDの再生プレイヤーです。シリンダーオルゴールと違い、曲の情報をもつディスクと、それを再生するプレイヤーが分離されています。

【ディスクオルゴール】

ここでは、ディスクオルゴールのように曲情報とプレイヤーを分離した、ディスクオルゴール風の電子楽器を制作します。具体的には、単純なテキストとして保存された曲情報のファイルをプレイヤーにあたるPytonプログラムで読み込み、その通りにStuduino:bitのブザーを制御して曲を再生するものです。

※ 「.txt」はテキストファイルの拡張子、「.py」はPythonプログラムファイルの拡張子です。

3. 1 サンプル曲の保存

サンプルとして、以下の「アメイジング・グレイス」という曲の情報がまとめられたテキストファイルを使い、これから作成するプログラムの動作を確認していきます。次の手順でこのテキストファイルをStuduino:bitに保存しましょう。

■ サンプル曲のダウンロード

以下のリンク「amazing_grace.txt」上で右クリックをして、表示された選択肢の中から「名前を付けてリンク先を保存」を選びます。ダウンロード先のフォルダを確認して、このファイルを保存しましょう。

amazing_grace.txt

※ 特に保存する場所が決められていない場合は、デスクトップ上に保存してください。

このテキストファイルは以下のようなデータになっています。

【 amazing_grace.txtの内容 】

■ サンプル曲のファイルをStuduino:bitの内部に保存する

次の手順で、上でダウンロードしたファイル「amazing_grace.txt」をStuduino:bitの内部へ保存しましょう。

Studuino:bitをPCに接続した状態で、画面上部の「ファイル」をクリックすると、右下に小さなウィンドウが表示されます。ここに表示されるのは、PC内の特定のフォルダに存在するファイルに限定されています。

このフォルダに先ほどダウンロードしたファイルを移動します。フォルダの場所は、画面上部の「開く」をクリックすると表示されるウィンドウ上部の窓で確認できます。

窓をクリックすると、フォルダまでの正式なパスが表示されます。これをコピーしましょう。

エクスプローラーを立ち上げ、同じく窓にコピーしたパスを貼り付けて「Enterキー」を押すと、さきほどのフォルダに移動します。ここにダウンロードしたテキストファイルを移動させましょう。

Muに戻り画面上部の「ファイル」をクリックして一度ウィンドウを閉じます。もう一度「ファイル」をクリックしてウィンドを開くと、先ほど追加したテキストファイルが表示されます。これを左側のウィンドにドラッグ&ドロップして、コピーしましょう。

コピー中は、画面の左下にステータスメッセージが表示されます。この表示が消えれば、コピー完了になります。これで曲の準備ができました。

3. 2 プログラムの処理の流れ

続いて、全体のプログラム処理の流れを確認しましょう。今回のプログラムは、以下の図のようにとてもシンプルな構造になっています。

始めに行う処理としていくつかのデータを定義する部分と、テキストファイルから情報を読み込んでブザーから音を鳴らす部分に分けてプログラムを作成していきます。

3. 3 音符の情報を定義する

曲の情報をもつテキストファイルには、先頭の行から順番に鳴らす音の情報が1行ずつ書かれています。また、1つの行に注目すると、音程と音符名が「,(カンマ)」で区切られて書かれています。

しかし、音符名のままでは、どれくらいの時間でブザーを鳴らすのかが分かりません。そこで、Pythonプログラムの中で音符名から時間の長さに変換します。まずは基準となる四部音符(quarter_note)を600ミリ秒として、それぞれの音符の時間の長さを定義します。

このとき、付点が付いた音符は元の音符の1.5倍の長さになることや、StuduinoBitBuzzerクラスのon()メソッドで時間の長さを表す引数durationには整数値しか指定できなかったことに注意してください。そのため、以下のコードでは、割り算や小数の掛け算が含まれる場合に演算の結果が浮動小数点数値になるため、関数int()で小数点以下を切り捨てして整数値へ変換しています。

音符名を表す文字列をキーに、上で定義した時間の長さを要素とした辞書notes_listを作成します。これによって、テキストファイルから読み込んだ音符名をnotes_list[][]にそのまま指定するだけで、時間の長さが得られるようになります。

追加【11行目~21行目】

3. 4 読み込んだテキストファイルの情報からブザーを制御する

これで準備が整ったので、Studuino:bitに保存したテキストファイル「amazing_grace.txt」を開き、順番にデータを読み込んでブザーから音を鳴らします。

■ ファイルから音程と音符名の情報を取り出す

まずは先頭で、buzzerオブジェクトをインポートしましょう。

追加【1行目】

次に、with文で関数open()を実行し、テキストファイルを開きます。また、as文で開いたファイルオブジェクトを変数fileに格納しましょう。

追加【25行目】

それから、with文の内部で、readline()メソッドを実行し、1行ずつ繰り返しファイル内の文字列を読み込みます。もし、読み込んだ結果が空の場合は、break文でループを抜けるようにします。このプログラムで問題なく読み込みが出来ているかどうか確認するため、print文で出力してみましょう。

追加【26~29行目】

プログラムを実行するとターミナルに以下のように表示されます。一見特別何も変わったところはないように見えますが、間に1行ずつ空の改行が入っていることに注目してください。

(実行結果)

関数print()は自動的に末尾へ改行を指定する特殊な「制御文字」が追加されるようになっているため、実行のたびに新しい行に改行されてターミナルに文字が表示されます。しかし、上の実行結果では、1回ではなく2回の改行が行われています。実は、関数print()で改行の制御文字を付加する前から、変数lineの文字列の末尾にも同じ改行の制御文字が書かれているのです。

実際に1行の文字列を、split()メソッドを使って「,(カンマ)」で分割したリストに変換して表示すると、「\n」または「\r\n」という文字列が末尾にあることが確認できます。

追加・変更【30・31行目】
(実行結果)

それぞれ「\n」をライン・フィールド、「\r」をキャリッジ・リターンといい、以下の役割があります。

  • LF(=ライン・フィールド):「\n
    カーソルを次の行へ送ることを表す制御文字です。このときカーソルは次の行における同位置へと送られます。
  • CR(キャリッジ・リターン):「\r
    カーソルを文頭へ戻すことを表す制御文字です。

改行は「次の行にカーソルを送る」と「先頭にカーソルを合わせる」の組み合わせと考えることができます。つまり、ライン・フィールドとキャリッジリ・ターンの組み合わせで改行を表せることになります。

実際に、Windowsの場合は「\r\n」で改行を表します。Mac(Unix系のOS)の場合はキャリッジ・リターンが不要で、「\n」だけで改行を表します。

これらの制御文字は、あとでnotes_listのキーとして指定するときの妨げになるため、あらかじめ文字列のreplace()メソッドで消去しておきます。

追加【30・31行目】

これでプログラムを実行すると、「\n」や「\r」が消えていることが確認できます。

(実行結果)

■ 取り出した音程と音符名の情報からブザーを制御する

ここまで来たら、あとはbuzzerオブジェクトのon()メソッドで、ファイルから読み込んだ音をブザーから鳴らすだけになります。

変数noteには[音程 ,音符名]で表されたリストのデータが入っています。そのため、note[0]で音程を、note[1]で音符名をそれぞれ取り出すことができます。また、音符名はリストnotes_listを利用して、notes_list[note[1]]で時間の長さに変換します。これらをon()メソッドの引数に指定して、プログラムの完成です。実行して動作を確認してみましょう。

【 サンプルコード 3-4-1 】
追加【34行目】

課題:テキストファイルの先頭の行に書かれた四分音符の時間の長さを読み取り曲の再生速度を制御する機能の追加

先ほどのプログラムでは、曲の再生速度はあらかじめPythonプログラム側で決められた四分音符の長さで固定されていましたが、この課題ではテキストファイル側にその四分音符の長さが書かれており、それを読み込むことで再生速度を制御できるように【 サンプルコード 3-4-1 】を改造してください。

※ 四分音符の長さを基準として、他の音符の長さが決められているため、四部音符の長さを変更するだけで、曲全体の再生速度を変更することができます。

この課題で使用する曲情報の入ったテキストファイルは以下になります。先ほど利用したテキストファイルの先頭の行に「300」と四分音符の長さ(単位はミリ秒)を表す数字が書かれています。そこから下の行は全く同じ情報が書かれています。以下のリンク先から「amazing_grace_for_challenge.txt」を同じ手順でダウンロードして、Studuino:bit内に保存しましょう。

amazing_grace_for_challenge.txt

【 amazing_grace_for_challenge.txtの内容 】
<ヒント>
  • 「音符情報を定義する」コードを関数define_notes()に、「ファイルを読み込みブザーから音を鳴らす」コードを関数play_music_box()にそれぞれまとめます。
※ オルゴールは英語で「music box」といいます。
  • 関数define_notes()は引数として、四分音符の長さを受け取るようにし、それで他の音符の長さを計算します。また、関数play_music_box()の中で呼び出すように変更します。
  • 関数play_music_box()は引数として、再生する曲のファイル名を受け取るようにします。そのファイルを開き、先頭の行を読み込むときだけ、別の処理を行い、読み込んだ四分音符の長さを関数define_notes()に渡して実行します。
  • 辞書notes_listは両方の関数の中で利用するため、グローバル変数として定義しておき、関数define_notes()の中では、global宣言を行い、データを入れるようにしましょう。
  • プログラムの動作を確認するときは、先に画面上部の「実行」をクリックしてから、以下のコードをターミナルに入力して実行します。

4. 1 プログラムの作成例

以下の手順で、この課題のプログラムを作成することができます。

■ 関数define_notes()を定義する

音符情報を定義するコードをすべて関数define_notes()にまとめますが、この中のリストnotes_listだけは、この関数の外でも利用するため、グローバル変数としてグローバルスコープ上で定義します。関数内ではローカル変数として扱われないように、global宣言を行った上でデータを格納します。

また、関数define_notes()は、引数として四分音符の長さを受け取ります。【 サンプルコード 3-4-1 】では、変数quarterに四分音符の長さを入れていましたが、代わりにこれを引数にして、他の音符の長さを計算するようにします。

これらの変更を行ったコードが以下になります。

追加・変更【3行目、5行目~13行目、15行目】

■ 関数play_music_box()を定義する

ファイルを読み込み、ブザーから音を鳴らすコードを関数define_notes()にまとめます。この関数では、ファイル名を引数にとり、そのファイルを開き読み取りを行います。

ファイルは先頭の行だけ、「四分音符の長さ」を表しており、その下はすべて「音程, 音符名」の情報になっています。そのため、1行目の読み取りだけ別で扱う必要があります。

そこで、読み取った行数を変数countを用意して数えます。そして、1行目を読み取ったときだけ、define_notes(line)を実行して、リストnotes_listに各音符の長さを格納します。

2行目以降は、【 サンプルコード 3-4-1 】と同じく、読み取った音をブザーから鳴らします。

これらの変更を行ったコードが以下になります。

追加・変更【29行目~30行目、34行目、39行目~44行目】

これでプログラムの完成です。完成したプログラムの全体像は以下になります。

【 サンプルコード 4-1-1 】

完成したプログラムを実行して、関数名をターミナルから入力し、動作を確認しましょう。

(プログラムの実行例)

おわりに

5. 1 このレッスンのまとめ

このレッスンでは新たに次のことを学習しました。

  • 外部のファイルを開きデータを読み込んだり、反対にデータを書き込んだりするための方法。
  • ファイルを開くときは、open()関数を使用すること。また、open()関数はファイルオブジェクトを返すこと。
  • ファイルオブジェクトは、write()メソッドでファイルへの書き込みができ、read()メソッドやreadline()でファイルからの読み取りができること。
  • ファイルは必ずclose()メソッドで閉じること。
  • with文を使うと、ファイル操作の終了後に自動的にファイルが閉じられること。
  • エディタとして使用している「Mu」にはデータのグラフ表示機能があること。

これらの他にも前のレッスンで学習した「ループ処理内のbreak文」や「スコープ」を利用して、作品を制作しました。課題では、少し難しいプログラムに挑戦しましたが、ここまでで学んできたことがどのように応用できるのか確認できたのはないでしょうか。

多くの文法を理解するにつれて、複雑な処理をより簡潔に書けるようになっていきます。これから先さらに難しい文法を学習していきますが、頑張って乗り越えていきましょう。

5. 2 次のレッスンについて

次のレッスンでは、プログラムの実行途中でターミナルから入力を受け付ける方法と、エラーが発生した場合に、プログラムを中断することなく、正常に処理を終える方法について学習します。

TOP