#!/usr/bin/env ruby
# coding: utf-8

#--------------------------------------------------------------------------------
#
#	メロディー・デーモン
#

require 'socket'
include Math

wav = Hash.new													# sin波データ

class String
	if(RUBY_VERSION < '1.9')
		def ord
			self[0]
		end
	end
end

#---------------------------------------------------------------
#
#	/dev/dspを設定
#
open('/dev/dsp', 'r+') {|dsp|

	def dsp.ioctl(cmd, arg = 0)
		super(cmd, arg)
	end

	speed = 11025												# サンプリング速度

	# フラグメントサイズ変更(……できないけど)
	x = [0x7fff0009].pack('i*')
	printf("%x\n", *(x.unpack('i*')))
	p dsp.ioctl(0xc004500a, x)									# SNDCTL_DSP_SETFRAGMENT

	# サンプリング速度変更
	x = [speed].pack('i*')
	p dsp.ioctl(0xc0045002, x)									# SNDCTL_DSP_SPEED

	# データフォーマット変更
	x = [8].pack('i*')											# AFMT_U8(unsigned 8bit)
	p dsp.ioctl(0xc0045005, x)									# SNDCTL_DSP_SETFMT

	# チャンネル数変更
	x = [1].pack('i*')											# monaural
	p dsp.ioctl(0xc0045006, x)									# SNDCTL_DSP_CHANNELS

	# データフォーマット確認
	x = [0].pack('i*')											# AFMT_QUERY
	p dsp.ioctl(0x8004500b, x)									# SNDCTL_DSP_GETFMTS
	printf("%032b\n", *(x.unpack('i*')))

	# バッファ状態確認
	x = [0, 0, 0, 0].pack('i*')
	p dsp.ioctl(0x8010500c, x)									# SNDCTL_DSP_GETOSPACE
	printf("%04x %04x %04x %04x\n", *(x.unpack('i*')))
	fragsize = x.unpack('i*')[2]								# フラグメントサイズを得る

#--------------------------------------------------------------------------------
#
#	sin波のwavデータを生成
#
	gain = 64													# 音量(1-120)
	length = 0.8												# 音長(秒)

	fb = 110.0													# 8:0G# ... 12:1C ... 49:4C#
	(-4..49).each {|k|											#-4:F2  ... 24:A4 ... 38:B5
		p [k, f = fb * 2 ** (k * 1.0 / 12)]
		omega = 2.0 * 3.1415927 * f / speed
		theta = 0
		wav[k] = Array.new
		(1..(length * speed)).each {|i|
			wav[k].push(((gain * sin(theta += omega)) * (length * speed - i) / (length * speed) + 128).to_i)
		}
	}

#--------------------------------------------------------------------------------
#
#	メイン処理
#
	kt = Hash['C', 0, 'D', 2, 'E', 4, 'F', 5, 'G', 7, 'A', 9, 'B', 11]

	socket = UDPSocket.new()
	socket.bind('localhost', 47624)

	ringbuf = Array.new(fragsize * 24, 128)						# リングバッファ(みたいに使う)
	addbuf  = Array.new(fragsize, 128)							# リングバッファ補給用

	buftime = 0													# 起動からの絶対時間(サンプル数)
	schebuf = Array.new											# スケジューリングバッファ
	tempo   = 120												# スケジューリング演奏用テンポ

	barc    = 0													# 小節チェック情報付加用
	tbarc   = 0

	loop {
		#
		# UDP経由での要求を処理する
		#
		if(IO.select([socket], nil, nil, 0.04))					# 0.046 秒より少し早く(x < fragsize / speed)
			key = socket.recv(65535)

			barc = 0

			if(key =~ /^#/)										# コメント
				print key
				next
			elsif(key =~ /^!/)									# スケジューリングモード
				key += ' 9R'									#  →同時に複数の音階＋音長情報を受け付ける
				s = buftime
				s = schebuf.last[0] if(schebuf.last)			# 演奏中なら続けて演奏(重ねて演奏するモードも欲しい)
				key.split(/\s+/).each {|e|
					if(e =~ /^\d[A-GR]#?$/)						# 音符、休符(旧)
						schebuf.push([s, e, barc])
					elsif(e =~ /^([A-GR])(#?)(\d)/)				# 音符、休符
						schebuf.push([s, e, barc])
					elsif(e =~ /^\d+$/)							# 音長
						s += speed * 60 * 4 / tempo / e.to_i
						barc += 1.0 / e.to_i					# 小節内音長カウント
					elsif(e =~ /^t(\d+)$/)						# テンポ変更
						tempo = $1.to_i
						print "=== t=#{$1} ===\n"
					elsif(e =~ /^clear$/)
						schebuf.clear
						s = buftime
						tbarc = 0
						print "=== CLEAR ===\n"
					end
				}
			else												# リアルタイムモード
				key += ' '										#  →単一の3文字の音階情報を受け付ける
				if(key =~ /^\d[A-GR][ #]/)
					p = 0
					wav[12 * (key.ord - 48).to_i + kt[key[1, 1]].to_i + (key[2, 1] == '#' ? 1 : 0)].each {|c|
						ringbuf[p] = (ringbuf[p] + c) / 2
						p += 1
					}
				elsif(key =~ /^([A-GR])(#?)(\d)/)
					p = 0
					wav[12 * $3.to_i + kt[$1] + ($2 == '#' ? 1 : 0) - 33].each {|c|
						ringbuf[p] = (ringbuf[p] + c) / 2
						p += 1
					}
				end
			end
		end

		#
		# スケジューリングモードの波形を重畳する
		#
		while(schebuf.first and schebuf.first[0] < buftime + fragsize)
			sche = schebuf.shift
			if(sche[1] =~ /^\d[A-GR]#?/)
				if((key = sche[1])[1] != ?R)
					printf "        %10.3f: %s\n", sche[2], sche[1]
					p = sche[0] - buftime
					wav[12 * (key.ord - 48).to_i + kt[key[1, 1]].to_i + (key[2, 1] == 's' ? 1 : 0)].each {|c|
						ringbuf[p] = (ringbuf[p] + c) / 2
						p += 1
					}
				elsif(sche[1][0] == ?9)
					printf "%10.3f ---------------- %10.3f\n", sche[2], tbarc += sche[2]
				end
			elsif(sche[1] =~ /^([A-GR])(#?)(\d)/)
				if((key = sche[1])[0] != ?R)
					printf "        %10.3f: %s\n", sche[2], sche[1]
					p = sche[0] - buftime
					wav[12 * $3.to_i + kt[$1] + ($2 == '#' ? 1 : 0) - 33].each {|c|
						ringbuf[p] = (ringbuf[p] + c) / 2
						p += 1
					}
				elsif(sche[1][1] == ?9)
					printf "%10.3f ---------------- %10.3f\n", sche[2], tbarc += sche[2]
				end
			end
		end

		#
		# バッファの状態をチェック、バッファへ波形を出力する
		#
		x = [0, 0, 0, 0].pack('i*')
		dsp.ioctl(0x8010500c, x)								# SNDCTL_DSP_GETOSPACE
#		printf("%x %x %x %x\n", *(x.unpack('i*')))
		if(x.unpack('i*')[1] - x.unpack('i*')[0] < 3)			# 投入済みデータが減ってきたら追加する
#			printf("%x %x %x %x\n", *(x.unpack('i*')))
			block = ringbuf.slice!(0, fragsize).pack('c*')
			ringbuf[ringbuf.size..ringbuf.size] = addbuf
			dsp.syswrite(block); buftime += fragsize
#			raw.syswrite(block)
		end
	}
}

