15. 総合演習(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)