x0. 期末課題

以下は、2015年度の資料

部分的には、2016年度の期末課題のヒントになるかも。

課題提出締め切り:

1月xx日(x) まで。

レポートフォルダへ提出:

eclipse の projectのフォルダ や scala の projectのフォルダなど。

フォルダ名、に学籍番号を付けておくこと。

(参考2015)

import javax.sound.sampled._
/**
 * @author kobashi.kazuhide
 */
object Test2 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()
  // 基準となる音の高さ
  val BaseFrequency = 440
  // ド レ ミ ファ  ソ ラ シ ド
  val Fseq = List(1.0, 9.0/8, 5.0/4, 4.0/3, 3.0/2, 5.0/3, 15.0/8, 2.0/1)
  // 音階再生用のデータ(1オクターブ分)音階 x 1秒分のデータの長さ
  // 一つ一つの音の長さを変更すると、再生テンポ全体を変更できる
  // 例 SAMPLE_RATE を SAMPLE_RATE*2 にする
  val notes = Array.ofDim[Byte](Fseq.size,SAMPLE_RATE)
  // バイト列に適当な矩形波を作成
  for ( (note, n) <- notes.zipWithIndex; i <- 0 until note.size) {
    val r = i / (SAMPLE_RATE / BaseFrequency / Fseq(n) ).toInt
    if (r % 2 == 0) { note(i) = 100 } else { note(i) = -100 }
  }
  // 再生
  notes.foreach( note => line.write(note, 0, note.size) )
  line.write(notes(0),0,notes(0).size)
  line.write(notes(0),0,notes(0).size)
  line.write(notes(0),0,notes(0).size)
  line.drain() // 終了まで待機
}

(参考2015) 音を合成して再生する

chord2 は 音源データ2つを引数に取り、合成結果を音源として返す関数

chord3 は 音源データ3つを引数に取り、合成結果を音源として返す関数

※ オブジェクト指向的には、sound クラスを定義し、音響合成用のメソッドを定義するほうがスマートなプログラムです。

※ シンプルなコードだけで合成を実現するため、音源の数ごとに別々の関数として合成方法を定義しています。

※※ このスタイルでは、合成する音の数ごとに新たに関数定義が必要になるので、 引数として、合成する音源のListを渡して合成する関数を定義するべきでしょう。

  // 2音の合成
  def chord2(note1:Array[Byte],note2:Array[Byte]):Array[Byte] ={
    note1.zip(note2).map {case (b1,b2) => ((b1+b2)/2).toByte}
  }
  line.write(chord2(notes(0),notes(2)) ,0, notes(0).size)
  // 3音の合成
  def chord3(note1:Array[Byte], note2:Array[Byte], note3:Array[Byte]):Array[Byte] ={
    val tri_chord=note1.zip(note2).zip(note3)
    tri_chord.map {case ((b1,b2),b3) => ((b1+b2+b3)/3).toByte}
  }
  line.write(chord3(notes(0),notes(2),notes(4)) ,0, notes(0).size)

(参考2015) 音を絞る(ADSR) エンベーロープのエフェクト と 利用

 def effect2(note:Array[Byte]) = {
    note.zipWithIndex.map {case (b,i) => (b*(note.size-i)/note.size).toByte}.toArray
  }

line.write(effect2(notes(0)),0,notes(0).size)

(参考2015) より高い音の数値について

  // ド レ ミ ファ  ソ ラ シ ド      1オクターブ上の音は、元の数値の2倍の値で作成
  val Fseq = List(1.0, 9.0/8, 5.0/4, 4.0/3, 3.0/2, 5.0/3, 15.0/8, 2*1.0 , 2*9.0/8, 2*5.0/4 )

(参考2015) for 文による繰り返し演奏の例

  // エンベロープ 無しの音と 有りの音 を交互に3回再生
  line.write(notes(0),0,notes(0).size) // エンベロープ無し(音は絞られない)
  line.write(effect2(notes(0)),0,notes(0).size) //  エンベロープ有り(音は絞られる)
  line.write(notes(0),0,notes(0).size)
  line.write(effect2(notes(0)),0,notes(0).size)
  line.write(notes(0),0,notes(0).size)
  line.write(effect2(notes(0)),0,notes(0).size)
  // 2分音符でドレミ (1小節=1秒)
  line.write(effect2(notes(0)),0,notes(0).size/2)
  line.write(effect2(notes(1)),0,notes(0).size/2)
  line.write(effect2(notes(2)),0,notes(0).size/2)
  // 4分音符でドレミ (1小節=1秒)
  line.write(effect2(notes(0)),0,notes(0).size/4)
  line.write(effect2(notes(1)),0,notes(0).size/4)
  line.write(effect2(notes(2)),0,notes(0).size/4)
  // 16分音符で連続20回ドレミを再生
  for(i <- 0 to 20) {
    line.write(effect2(notes(0)),0,notes(0).size/16)
    line.write(effect2(notes(1)),0,notes(0).size/16)
    line.write(effect2(notes(2)),0,notes(0).size/16)
  }
  // 4分音符から16分音符へ、だんだんと早く演奏
  for(i <- 4 to 16) {
    line.write(effect2(notes(0)),0,notes(0).size/i)
    line.write(effect2(notes(1)),0,notes(0).size/i)
    line.write(effect2(notes(2)),0,notes(0).size/i)
  }

(参考2015)音の列を1フレーズ登録して、2回再生

def MelodyA = {
    line.write(effect2(notes(7)),0,notes(0).size/4)
    line.write(effect2(notes(7)),0,notes(0).size/4)
    line.write(effect2(notes(7)),0,notes(0).size/2)
    line.write(effect2(notes(6)),0,notes(0).size/4)
    line.write(effect2(notes(6)),0,notes(0).size/4)
    line.write(effect2(notes(6)),0,notes(0).size/2)
    line.write(effect2(notes(5)),0,notes(0).size/4)
    line.write(effect2(notes(5)),0,notes(0).size/4)
    line.write(effect2(notes(5)),0,notes(0).size/4)
    line.write(effect2(notes(4)),0,notes(0).size/1)
  }
  MelodyA
  MelodyA

(参考2015)音符を再生する関数を定義し、直接利用の例と、楽譜を登録して再生する例。

  def play(note:(Int,Int)) = {
    line.write(effect2(notes(note._1)),0,notes(note._1).size/note._2)
  }
  play(0,8)
  play(1,8)
  play(2,8)
  play(3,8)
  play(2,8)
  play(1,8)
  play(0,8)
  val Music = List((4,4),(4,4),(4,4),(1,2), (3,4),(3,4),(3,4),(0,2))
  Music.foreach(note => play(note))

(参考2014)

import javax.sound.sampled._
import scala.util.Random
// オーディオ形式を指定
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()
// 三角波を高さと長さを変えて繰り返し再生。だんだん 高く 短く
for(k <- 1 to 5; j <- 1 to 10) line.write(Array.tabulate(SAMPLE_RATE)(i => (i*j).toByte) , 0 , SAMPLE_RATE/10/k)

// ブザー音

line.write(Array.tabulate(SAMPLE_RATE)(i => (i*1).toByte) , 0 , SAMPLE_RATE)
val C1 = 1.0;
val G1 = 3.0/2.0;
val C2 = 2.0;
val SEQ = List( (C1,2) , (G1, 1) , (C2, 1) );
def play(p:Double, len:Int) = line.write(Array.tabulate(SAMPLE_RATE/len)(i => (i*p).toByte) , 0 , SAMPLE_RATE/len);
play(C1,1)
play(G1,2)
for( note <- SEQ ) { play(note._1, note._2) }

楽譜を逆向きに演奏)

List の並べ替え メソッド reverse を利用する。

 for( note <- SEQ.reverse ) { play(note._1, note._2) }

演奏のピッチ、テンポ の変更

元の2倍の高さで、3倍のスピードで演奏の例)

 for( note <- SEQ ) { play(note._1 *2, note._2 *3) }

ランダム演奏

乱数オブジェクトの準備

import scala.util.Random
val r = new Random

乱数オブジェクトから、ランダムな Double 値 を生成した例

scala> r.nextDouble()
res4: Double = 0.8725661424461074

演奏例)

for( i <- 1 to 20) play(r.nextDouble()*8, 8)

その他)

上がって下がる。

def updown = {
    for(k <- 1 to 15 by 1) line.write(Array.tabulate(SAMPLE_RATE)(i => (i*k).toByte) , 0 , SAMPLE_RATE/30);
    for(k <- 15 to 1 by -1) line.write(Array.tabulate(SAMPLE_RATE)(i => (i*k).toByte) , 0 , SAMPLE_RATE/30);
}
updown