x15. 総合演習(2/2)

■ scala の 演算記号 と 数値オブジェクト

他のほとんどのプログラム言語同様 + - / * などの四則演算用の演算子(記号)は、数値 と 数値に適用して計算結果を得るために利用します。

scala では、数値 も オブジェクト として扱われるので、 演算子 は 数値オブジェクトに対して定義されている メソッド として利用できます。

例)

scala> 2 * 2

res27: Int = 4

scala> 2 * 3

res28: Int = 6

scala> 2.*(3)

res29: Int = 6

scala> (2.+(3)).*(4)

res30: Int = 20

上記の例で、1つ目と3つ目が2項演算子としての * の表記法、 2つ目と4つ目が 数値オブジェクト の メソッド しての表記法です。

■ scala の演算子オーバーロード

オーバーロード = 多重定義

演算記号は 計算式 のための専用記号ではなく、オブジェクトのメソッドですから、処理内容をプログラムで定義することが出来ます。

例として、数値オブジェクト に対する * と + の処理結果 と 文字列オブジェクトに対する * と + の処理結果を見て見ましょう。

例)

scala> 1 + 2

res31: Int = 3

scala> 1 * 2

res32: Int = 2

scala> "1" + "2"

res33: String = 12

scala> "1" * 3

res35: String = 111

上記の様に、 scala の + 記号は、文字列に対しては文字列の結合メソッド、 * 記号は、文字列から文字列の繰返を生成するメソッドして作用します。

この様に、作用するオブジェクトに対して、異なる処理を多重定義できる機能を 演算子オーバーロード といいます。

記号の左右の オブジェクトの型 の組合せでその演算子の振る舞いが決まります。

例)

scala> "Apple" * 3

res36: String = AppleAppleApple

scala> 3 * "Apple"

<console>:12: error: overloaded method value * with alternatives:

(x: Double)Double <and>

(x: Float)Float <and>

(x: Long)Long <and>

(x: Int)Int <and>

(x: Char)Int <and>

(x: Short)Int <and>

(x: Byte)Int

cannot be applied to (String)

3 * "Apple"

^

scala> 1 + "Apple"

res38: String = 1Apple

上記で 2 番目の例は、左が数値、 右が文字列、に対応する オーバーロードされたメソッドの定義が見つからない為に、エラーになっています。

先回のサウンド生成のコードを implict 宣言を利用して、DSL 風に修正。

import javax.sound.sampled._

import java.util.Random

object OOP15 extends App {

// オーディオ形式を指定

val SAMPLE_RATE = 44100; // 44.1KHz

val audio_format = new AudioFormat(SAMPLE_RATE, 8, 1, true, true)

val line = AudioSystem.getSourceDataLine(audio_format)

line.open()

line.start()

// Int 値(32bit)を Byte 値(8bit)に自動変換(型の変換)

implicit def intToByte(i:Int) = i.toByte

// Int 値を サンプルレート値に自動変換(オブジェクトの変換)

implicit class timeToSAMPLE_RATE(time:Int) {

def s = time * SAMPLE_RATE

def ms = time * SAMPLE_RATE / 1000

}

// パターン作成

val a: Stream[Byte] = 100 #:: -100 #:: a

// パターン伸長

def repeat(xs: Stream[Byte], n: Int) = xs.flatMap(e => Array.fill(n)(e))

// 音源作成

val n1 = repeat(a, 100).take(1 s).toArray

val n2 = repeat(a, 50).take(500 ms).toArray

val n3 = repeat(a, 1000).take(500 ms).toArray

// 再生コマンド

def play(xs: Array[Byte]) = line.write(xs, 0, xs.length)

// 単音再生

//play(n1)

// 連続再生

//List(n1, n2, n1, n2, n1).foreach(play)

// Array[Byte]をList[Byte]に自動変換

implicit def ArrayByteToList(xs:Array[Byte]) = xs.toList

// Array[Byte]をList[Byte]に自動変換

implicit def ListByteToArrayByte(xs:List[Byte]) = xs.toArray

val part1 = n1 ::: n2 ::: n1 ::: n2 ::: n1

val part2 = n2 ::: n1 ::: n2 ::: n1 ::: n2

play(part1)

play(n3)

play(part2)

line.drain

}

■ 数列の可視化

先回、音声データ様に用意した 配列の 8bit データ を コンソール上に文字列としてグラフ化して表示してみます。

音源データの波形を表示する。

// b1 から100個データを取り出し、 各データの値 e を e個の "." で1行に表示する

b1.take(100) foreach {e => println( "." * e )}

// b1 から300個データを取り出し、 各データの値 e を e/2個の "." で1行に表示する

// 高さが半分になる

b1.take(300) foreach {e => println( "." * e/2 )}

// データの値 e が -128..0 の場合、-128 は0個 -127 は1個のように、"."を表示する

//

b1.take(300) foreach {e => println( "." * (e+128) )}

// コンソールの範囲内に表示が収まるように調整

b1.take(300) foreach {e => println( "." * ((e+128)/4 ))}

解説:

{e => println( "." * e )}

の部分は、 e が引数の関数として foreach でループ処理されます。

クロージャについて(参照) http://www.mwsoft.jp/programming/scala/foreach.html

■ 音声データ用の バイト配列の音色を変える

音のデータに、減衰効果を加える。

例)

val b92:Array[Byte] = Array.tabulate(SAMPLE_RATE)(i => (128*Math.sin(2*Math.PI *i/(SAMPLE_RATE/frequency) )*(SAMPLE_RATE -i)/SAMPLE_RATE).toByte)

リップル音

val b93:Array[Byte] = Array.tabulate(SAMPLE_RATE)(i => (128*Math.sin(2*Math.PI *i/(SAMPLE_RATE/frequency) )*(SAMPLE_RATE -(i*20 % SAMPLE_RATE) )/SAMPLE_RATE).toByte)

FM音源風の式

line.write(Array.tabulate(SAMPLE_RATE)(i => (127*Math.sin(Math.PI*i/40 + 20*Math.sin(Math.PI*i/2000))).toByte) , 0 , SAMPLE_RATE);

上記から、+127 ~ -128 の範囲制限を除去

val b94:Array[Byte] = Array.tabulate(SAMPLE_RATE)(i => (128*Math.sin(2*Math.PI *i/(SAMPLE_RATE/frequency) )*(SAMPLE_RATE -i*20)/SAMPLE_RATE).toByte)

シンプルな式で、音声データを表現する。(8bit の範囲内には toByte メソッドにより強制変換する)

val b95:Array[Byte] = Array.tabulate(SAMPLE_RATE)(i => (i*2).toByte)

音を重ねる

val b96:Array[Byte] = Array.tabulate(SAMPLE_RATE)(i => ((i%64 + i%96)/2).toByte)

でたらめなパターンで実験

val b97:Array[Byte] = Array.tabulate(SAMPLE_RATE)(i => (i*i*i*i*i).toByte)

音声データを生成しながらラインで再生

line.write(Array.tabulate(SAMPLE_RATE)(i => (i*3).toByte) , 0 , SAMPLE_RATE)

上記をさらにループ処理

for(j <- 1 to 10) line.write(Array.tabulate(SAMPLE_RATE)(i => (i*j).toByte) , 0 , SAMPLE_RATE/10)

ループ処理を重ねる

for(k <- 1 to 5; j <- 1 to 10) line.write(Array.tabulate(SAMPLE_RATE)(i => (i*j).toByte) , 0 , SAMPLE_RATE/10/k)

余談:リスト処理

scala の view : コレクションを 非正格化し 遅延処理する。 http://docs.scala-lang.org/ja/overviews/collections/views.html

force はコレクションを 正格化する。

scala の par : コレクションを並列処理対応にする。 http://docs.scala-lang.org/ja/overviews/parallel-collections/overview.html

scala の List.map2: deprecated http://www.scala-lang.org/api/2.10.4/index.html#scala.collection.immutable.List$

scala の Tuple2Zippedオブジェクト: https://github.com/scala/scala/blob/v2.10.4/src/library/scala/runtime/Tuple2Zipped.scala#L1

Replaying: (1 to 10)

res97: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7,8, 9, 10)

Replaying: (0 until 10 by 2)

res100: scala.collection.immutable.Range = Range(0, 2, 4, 6, 8)

Replaying: (0 to 10 by 2)

res101: scala.collection.immutable.Range = Range(0, 2, 4, 6, 8, 10)

Replaying: for(i <- 0 to 10) println( i)

0

1

2

3

4

5

6

7

8

9

10

Replaying: for(i <- 0 until 10) println( i)

0

1

2

3

4

5

6

7

8

9

Replaying: (0,1,2)

res104: (Int, Int, Int) = (0,1,2)

Replaying: (0 to 2)

res105: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2)

Replaying: Range(0,10,2)

res107: scala.collection.immutable.Range = Range(0, 2, 4, 6, 8)

Replaying: Range(0,10,2).map( _ + 1)

res111: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 3, 5, 7, 9)

Replaying: Range(0,10,2).map( x => println(x))

0

2

4

6

8

res113: scala.collection.immutable.IndexedSeq[Unit] = Vector((), (), (), (), ()

Replaying: Range(0,10,2).flatMap( x => x::Nil)

res114: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 2, 4, 6, 8)

Replaying: ((1,1),(2,2))

res116: ((Int, Int), (Int, Int)) = ((1,1),(2,2))

Replaying: (List(1,1),List(2,3,4))

res119: (List[Int], List[Int]) = (List(1, 1),List(2, 3, 4))

Replaying: (List(1,1),List(2,3,4)).zipped

res120: scala.runtime.Tuple2Zipped[Int,List[Int],Int,List[Int]] = scala.runtimeTuple2Zipped@511a798a

Replaying: (List(1,1),List(2,3,4)).zipped.map(_ + _)

res121: List[Int] = List(3, 4)

Replaying: List(1,1) zip List(2,3,4)

res122: List[(Int, Int)] = List((1,2), (1,3))

Replaying: (List(1,1) zip List(2,3,4)).map( e => e._1 + e._2)

res124: List[Int] = List(3, 4)

Replaying: (List(1,1) zip List(2,3,4)).map( e => {val (k,v) =e; k + v})

res125: List[Int] = List(3, 4)

Replaying: (List(1,1) zip List(2,3,4)).map( e => {val (k:Int,v:Int) =e; k + v})

res126: List[Int] = List(3, 4)

Replaying: (List(1,1) zip List(2,3,4)).map( (e:(Int,Int)) => {val (k:Int,v:Int)=e; k + v})

res127: List[Int] = List(3, 4)

Replaying: (List(1,1) zip List(2,3,4)).map( { case (k,v) => k + v })

res131: List[Int] = List(3, 4)

Replaying: (List(1,1),List(2,3,4)).zipped.map( {case e:Tuple2[Int,Int] =>e._1 + e._2})

res134: List[Int] = List(3, 4)

Replaying: (List(1,1),List(2,3,4)).zipped.map( {case (k:Int,v:Int) =>k + v})

res135: List[Int] = List(3, 4)

Replaying: (List(1,1),List(2,3,4)).zipped.map( {case (k,v) =>k + v})

res136: List[Int] = List(3, 4)

Replaying: (List(1,1),List(2,3,4)).zipped.map( {(k,v) =>k + v})

res137: List[Int] = List(3, 4)

Replaying: (List(1,1),List(2,3,4)).zipped.map( (k,v) =>k + v )

res138: List[Int] = List(3, 4)

Replaying: (List(1,1),List(2,3,4)).zipped.map( _ + _ )

res139: List[Int] = List(3, 4)

Replaying: (List(1,1),List(2,3,4)).zipped.map( {_ + _} )

res140: List[Int] = List(3, 4)

Replaying: (List(1,1),List(2,3,4)).zipped.map( _ - _ )

res141: List[Int] = List(-1, -2)

Replaying: (List(1,1),List(2,3,4)).swap.zipped.map( _ - _ )

res142: List[Int] = List(1, 2)