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)