08. Javaでオブジェクト指向(3/x)

補足:

前回(第7回)の資料に SNAP!によるサウンド処理の例と、Javaによる高速トレモロ演奏による疑似和音の例を追加した。

「オブジェクト指向」で検索するとTOP近くに表示される人気記事の紹介。

オブジェクト指向と10年戦ってわかったこと

課題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 Sound プログラマーズガイド

パッケージ javax.sound.sampled

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 に課題提出。