08. Javaでオブジェクト指向(3/x)
補足:
前回(第7回)の資料に SNAP!によるサウンド処理の例と、Javaによる高速トレモロ演奏による疑似和音の例を追加した。
「オブジェクト指向」で検索するとTOP近くに表示される人気記事の紹介。
課題1
先週の課題、プロジェクト java1 の SoundTest2 を拡張forループ(foreach)版に修正する。
先回のページの作業済みファイルをDLして利用してもよい。
SoundTest2クラスの作成:
プロジェクト java2 を新規作成する。
プロジェクト java1 の中の SountTest2 クラスをコピーし
プロジェクト java2 の src の中に貼り付ける。
※同じ名前のクラスのファイルは、異なるプロジェクトに配置すればエラーにならない。
※パッケージについて:
eclipse で 新規クラス作成 をメニューまたは右クリックから実行すると、クラスが配置されるパッケージ名がプロジェクト名と同名で作成される。
package 宣言文が追加される。
SoundTest2.javaファイルを java2プロジェクトの src にコピペすると、元のパッケージ宣言は削除され、何もパッケージ宣言のない、デフォルトパッケージにコードが配置される。
プログラムを拡張forループ(foreach)版に修正する。
ただし、以下の例で内側のループでは拡張forループを使わずに、通常のforループで処理している。理由はコメントを参照。
音階データ生成部分:
int i = 0;
for (byte[] bs : buf) {
int[] rs = rate[i++];
// bs は、bufの要素である int[]型の配列を参照している変数。bs[j]への書き込みは、bufの要素に反映される。
for(int j = 0; j < bs.length; j++) {
bs[j] = (byte) (j * rs[0]/rs[1] % 256 - 128);
}
//この初期化の方法は誤り。
//配列の要素の値のコピーが一時変数 b に渡されるだけで、b への書き込みは配列に反映されない。
// for (byte b : bs) {
// ↓↑の変数 b は、ループの度に一時的に用意される変数。値は、bsの各要素から コピー されて、初期化される。bを書き換えても bs に影響なし。
// b = (byte) (j++ * rs[0]/rs[1] % 256 - 128);
// }
}
再生部分:
for (int[] is : score) {
line.write( buf[ is[0] ] , 0, buf[0].length / is[1]);
}
Javaのオブジェクト指向に関する文法の確認
Java で、SNAP! のOOPの例題に出てきた counter と buzzer を実装する。
講義資料:
(参考1) WebClass の 全学共通コース → Java入門 → 第6章(クラス) ・ 第7章(クラス継承)
↑ Javaの文法的な部分についてはこちらのテキストで確認できます(Webにも多数の解説サイトあり)。 オブジェクト指向としての例はちょっと微妙(Webにもなかなか良い例示が少ない)。
課題2
CounterTestクラスの作成:
プロジェクト java2 に
クラス CounterTest を作成 ・メソッド・スタブの main を使用する(チェックボックス ON) を忘れずに!
コード例)
public class CounterTest {
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
System.out.println(c1.next());
System.out.println(c1.next());
System.out.println(c2.next());
System.out.println(c2.next());
System.out.println(c2.next());
System.out.println("----------------------");
System.out.println(c1.total);
System.out.println(c2.total);
System.out.println(Counter.total);
System.out.println("----------------------");
c1.reset(10);
System.out.println(c1.next());
System.out.println(c1.next());
System.out.println(c1.next());
System.out.println(c1.next());
System.out.println("----------------------");
//クラスのスタティックメソッドの呼び出し
Counter.resetTotal();
System.out.println(c1.next());
System.out.println(c1.next());
System.out.println(c1.next());
System.out.println(c2.next());
System.out.println(c2.next());
System.out.println(Counter.total);
System.out.println("----------------------");
Buzzer buzz = new Buzzer();
int i = 0;
while (i++ < 30){
System.out.println(buzz.next());
}
System.out.println("----------------------");
Cycle cc = new Cycle(10);
i = 0;
while (i++ < 30){
System.out.println(cc.next());
}
}
}
class Counter {
static int total = 0;
int count = 0;
int next() {
total++;
return ++count;
}
void reset(int i) {
count = i;
}
static void resetTotal() {
total = 0;
}
}
class Buzzer extends Counter {
int next() {
super.next();
if (count % 7 == 0) {
System.out.println("Buzz");
}
return count;
}
}
class Cycle extends Counter {
int limit;
Cycle(int limit) {
this.limit = limit;
}
int next() {
super.next();
if(count == limit) {
count = 0;
}
return count;
}
}
出力結果
1
2
1
2
3
----------------------
5
5
5
----------------------
11
12
13
14
----------------------
15
16
17
4
5
5
----------------------
1
2
3
4
5
6
Buzz
7
8
9
10
11
12
13
Buzz
14
15
16
17
18
19
20
Buzz
21
22
23
24
25
26
27
Buzz
28
29
30
----------------------
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
類題:
概念としてのポケットと 具体的な普通のポケットと 普通のポケットを特化した不思議なポケットの話。モノを出し入れするメソッドを継承。叩いたときの挙動はオーバーライド。
概念としての移動体と 具体的なネコとパスと ネコとバスを多重継承したネコバスのもつメソッドの話。移動方式はどちらの親クラスから継承するのか問題。
課題3
Javaのサウンド処理について
Javaには標準ライブラリとしてサウンド処理のためのクラス群とAPIが用意されている。
これらを通して、wav や mp3 などのオーディオファイルの再生や作成、MIDIの制御、ミキシング、シーケンサー、シンセサイザーなどが利用できる。
つまり、JavaによるオーディオプレイヤーやDAWアプリケーションの作成も可能。
以下では、JavaのサウンドAPIの ごく一部分だけを利用して、単純なシンセサイザーとシーケンサーを作成する。
手順:
課題1 プロジェクト java2 の SoundTest2 クラスをコピーして 同じプロジェクトに貼り付ける。 自動的に SountTest3クラスが作成される。
演奏プログラム本体
public class SoundTest3 {
public static void main(String[] args) {
int[][] sc1 = new int[][] { { 0, 2 }, { 1, 2 }, { 2, 2 } };
int[][] sc2 = new int[][] { {1, 2}, { 2, 2 }, { 3, 2 } };
int[][] sc3 = new int[][] { { 0, 4 }, { 1, 4 }, { 2, 4 },{ 3, 4 }, { 4, 4 }, { 5, 4 },{ 6, 4 }, { 7, 4 }};
Player p1 = SoundSystem.getPlayer();
Player p2 = SoundSystem.getPlayer();
Player p3 = SoundSystem.getPlayer();
p1.play(sc1);
p2.play(sc2);
p3.play(sc3);
}
}
演奏システムのクラス
class SoundSystem {
static byte[][] buf = new byte[8][44100];
static int[][] rate = { { 1, 2 }, { 9, 16 }, { 5, 8 }, { 2, 3 }, { 3, 4 }, { 5, 6 }, { 15, 16 }, { 1, 1 } };
// クラス変数の初期化
static {
for (int j = 0; j < buf.length; j++) {
for (int i = 0; i < buf[0].length; i++) {
buf[j][i] = (byte) (i * rate[j][0] / rate[j][1] % 256 - 128);
}
}
}
// 再生用lineの作成とオープン
static Player getPlayer() {
SourceDataLine line = null;
try {
line = AudioSystem.getSourceDataLine(new AudioFormat(44100, 8, 1, true, true));
line.open();
line.start();
} catch (LineUnavailableException e) {
e.printStackTrace();
return null;
}
return new Player(line);
}
}
演奏プレイヤーのクラス
class Player {
SourceDataLine line;
Player(SourceDataLine line) {
this.line = line;
}
void play(int[][] score) {
for (int i = 0; i < score.length; i++) {
line.write( SoundSystem.buf[ score[i][0] ] , 0, SoundSystem.buf[0].length / score[i][1]);
}
line.drain();
}
}
この演習のサンプルコードに関する問題点:
1.SoundTest3 のメインクラスで、オーディオプレイヤーを3つ生成して、以下のコードでスコアを再生させているが、
スコアはプログラムの実行に再生される。
p1.play(sc1);
p2.play(sc2);
p3.play(sc3);
つまり、
p1.play(sc1);
p1.play(sc2);
p1.play(sc3);
と同じ再生状況になり、オーディオプレイヤーを3つ生成したメリットはない。
2.Playerクラスで使用している音源用配列 SoundSystem.buf がSoundSystem のコードと強く結びついているので、音源データを複数持など buf に代わる仕様変更があった場合、Player クラスの再設計が必要になる。
次回の講義で、この2つの問題に対する対処方法を示す。
課題提出について:
eclipseのワークプレイスのフォルダを zip 圧縮して、後日、他の回の課題と一緒にまとめて webclass に課題提出。