Rubyでマリオを奏でよう! _whyのbloopsaphoneの紹介
2009年8月19日Ruby界の鬼才_why1が ネットから忽然と姿を消しました2 この突然の出来事にRubyist達は驚きそして困惑しました なぜなら彼自身がただネットから消えたのではなく _whyはネット上の彼の痕跡も同時にすべて消し去ったのです!
Rubyのガイドブック ブログ 音楽 カートゥーン そしてアクティブなライブラリを含む多数のプログラムコード camping hpricot redcloth shoes TryRuby metaid… _whyは削除コマンドを実行して _whyの作品群をクラウドのメモリーから削除しました
このような行動に対しネット上には賛否両論がありましたが 多くの人はそこに至った_whyの心情を悲しみました
そう_whyは_whyの手によって抹殺されたのです…
しかし
_whyにも削除できない記憶域がありました それはクラウドの向こう側にいる人々の脳内記憶でした _whyというユニークなペルソナを 人々の記憶から消し去ることは彼にもできなかったのです
その結果としてコミュニティにより _whyの作品群はネット上に再生されました
ポストモダン時代の天才として _whyの軌跡を追う記事が最近書かれました
Why: A Tale Of A Post-Modern Genius
改めて_whyのユニークさを実感します
そう_whyは今も生きているのです…
bloopsaphone
先の記事で_whyのbloopsaphoneというライブラリを知りました 以下ではその使い方を簡単に説明したいと思います
bloopsaphoneはファミコンサウンドのような 電子音を作るためのRubyとCのライブラリです
mental’s bloopsaphone at master - GitHub
まずはbloopsaphoneで作った音を聞いてください
これはライブラリに付属の次のコードを再生したものです
test.rb
require 'bloops'
# the song object
b = Bloops.new
b.tempo = 320
# an instrument
saw = b.sound Bloops::SINE
#saw = b.sound Bloops::SAWTOOTH
# assign a track to the song
b.tune saw, "c5 c6 b4 b5 d5 d6 e5 e6b"
# make it go
b.play
sleep 1 while !b.stopped?
# a percussion
beat = b.sound Bloops::NOISE
beat.repeat = 0.6
# assign a track to the song
b.tune beat, "4 4 b4 4 d5 4 e5 e6"
# make it go
b.play
sleep 1 while !b.stopped?
ライブラリにはドキュメントがないですが サンプルコードを見れば大体使い方がわかります
手順としては次のようになります
- Bloops.newでsongオブジェクトを作り(Bloopsクラスのインスタンス生成)
- tempoメソッドでテンポを決め
- soundメソッドで楽器(音)を選び(Soundクラスのインスタンス生成)
- setterメソッドで音質調整をし
- tuneメソッドで楽譜をセットして(Trackクラスのインスタンス生成)
- playメソッドで再生する
複数トラックの再生が可能で この例では1回目は1トラック 2回目は2トラックで再生しています
楽器(sound)にはSQUARE SAWTOOTH SINE NOISEの4種があって そのベース音に対して音質調整用メソッド(ここではrepeat)で 音質調整して最終的な音質を決めます
楽譜はRTTTL(Ring Tone Transfer Language)3を ベースにした記法の文字列を渡します 音符の音価(長さ)と音高と音程(オクターブ)を 数字・アルファベット・数字の組で表します 例えば”8:F#4”は”8分音符F#(ファ#)オクターブ4”を表します 音価と音程は省略可能でありその場合はデフォルト値が使われます オクターブは”+”または”-“で表すこともできます また”:”は省略可能です 休符は数字のみで表されて例えば”4”は”4分休符”を表します
(READMEにある)シンプソンのテーマは次のようになります
32 + C E F# 8:A G E C - 8:A 8:F# 8:F# 8:F# 2:G
これは以下を表しています
32分休符 1オクターブ上げ 4分C 4分E 4分F# 8分A 4分G 4分E 4分C
1オクターブ下げ 8分A 8分F# 8分F# 8分F# 2分G
インストール
OSXへのインストールは以下のようにします4
sudo port install portaudio
sudo gem install bloopsaphone -- --with-opt-lib=/opt/local/lib --with-opt-include=/opt/local/include
bloopsaphoneはportaudioというオーディオI/Oライブラリに依存しています
音質調整メソッド
音質調整はBloops#soundメソッドで生成されるSoundクラスの インスタンスメソッドで行います それぞれ0.0~1.0の値をセットします
lead = b.sound Bloops::SQUARE
lead.volume = 0.5
lead.attack = 0.02
lead.sustain = 0.4
lead.decay = 0.7
lead.phase = 0.0
lead.psweep = 0.0
lead.vibe = 0.0
lead.vspeed = 0.5
各メソッドの機能は名前から推測できるものもありますが そうでないものもあります 値を調整して試してみるほかないようです
音質調整は設定ファイルをロードして行うこともできます
require 'bloops'
bases = Dir["../../sounds/*.blu"]
b = Bloops.new
bases.each do |base|
puts "** playing scale using #{base[/\w+.blu/]}"
sound = b.load base
b.tune sound, "c c# d eb e f f# g ab a bb b + c"
b.play
sleep 1 while !b.stopped?
b.clear
end
このコードはライブラリに添付の複数の設定ファイルを読み出し 各音質で同じコードを再生するものです 音質調整で様々な音が再現できることが分かると思います
ここで再生した各設定ファイルは以下の通りです
dart.blu
type noise
punch 0.524
sustain 0.160
decay 0.367
freq 0.296
slide -0.373
vibe 0.665
vspeed 0.103
phase 0.141
psweep -0.005
error.blu
type square
sustain 0.333
decay 0.380
freq 0.336
slide 0.292
square 0.289
sweep 0.020
vibe 0.002
lpf 0.220
lsweep 0.015
resonance 0.875
aspeed 0.035
repeat 0.551
ice.blu
type square
punch 0.441
sustain 0.067
decay 0.197
freq 0.499
jump.blu
type square
sustain 0.266
decay 0.187
freq 0.268
slide 0.179
square 0.326
vibe 0.227
vspeed 0.231
pogo.blu
type square
volume 0.977
punch 0.190
sustain 0.165
slide 0.100
dslide 0.030
square 0.048
sweep -0.055
vibe 0.437
vspeed 0.310
lpf 0.355
resonance 0.185
hpf 0.205
hsweep 0.255
arp 0.677
aspeed 0.275
phase 0.200
psweep -0.565
repeat 0.500
stun.blu
type sawtooth
sustain 0.306
decay 0.477
freq 0.429
slide 0.217
repeat 0.677
メソッド一覧
bloopsaphoneのメソッド群は次のようになります
Bloopsクラス
instance methods: clear, load, play, sound, stopped?, tempo, tempo=, tune
constants: SQUARE, SAWTOOTH, SINE, NOISE
Soundクラス
instance methods: arp, arp=, aspeed, aspeed=, attack, attack=, decay, decay=, dslide, dslide=, freq, freq=, hpf, hpf=, hsweep, hsweep=, limit, limit=, lpf, lpf=, lsweep, lsweep=, phase, phase=, psweep, psweep=, punch, punch=, repeat, repeat=, resonance, resonance=, slide, slide=, square, square=, sweep, sweep=, sustain, sustain=, type, type=, vibe, vibe=, vspeed, vspeed=, vdelay, vdelay=, volume, volume=
Trackクラス
instance methods: to_s
BloopSong DSL
bloopsaphoneで長い楽譜を再生しようとすると
以下のような問題がありました 1. CPUの占有率が徐々に上がって途中で再生不能になる 1. トラック数が増えるとコードの可読性が下がる 1. トラック数を変えたり楽譜の一部の小節だけを再生をするのに手間が掛かる
そこでこれらの問題に対応するために BloopSongという簡単なDSLを書きました
require "bloops"
class BloopSong
def self.init(tempo)
@bloops = Bloops.new
@bloops.tempo = tempo
yield self
self
end
def self.play(score, opt={})
score = read_score(score)
tunes = Array(opt[:tune] || :lead)
range_max = score[tunes.first].length-1
for i in range(opt[:range], range_max)
tunes.each do |tune|
@bloops.tune send(tune), score[tune][i]
end
@bloops.play
sleep 0.01 until @bloops.stopped?
@bloops.clear
end
end
def self.define_class_method(name)
(class << self; self end).module_eval { define_method(name) { yield } }
end
def self.sound(name=:lead, type)
self.instance_variable_set("@#{name}", @bloops.sound( Bloops.module_eval(type.to_s) ))
define_class_method(name) { self.instance_variable_get("@#{name}") }
yield send(name) if block_given?
@bloops
end
def self.read_score(score)
q = Hash.new([])
flag = :lead
score.each_line do |line|
next if line =~ /^\s*$/
case line
when /^:(\w+)/ then flag = $1
else
q[flag.to_sym] += [line]
end
end
q
end
def self.range(opt, max)
if !opt
(0..max)
elsif opt.end > max || opt.end < 0
(opt.begin..max)
else
opt
end
end
end
以下のように使います
require_relative "bloopsong"
mario =
BloopSong.init(216) do |b|
b.sound(:lead, :SQUARE) do |s|
s.volume = 0.4
s.punch = 0.441
s.sustain = 0.067
s.decay = 0.297
s.freq = 0.499
end
b.sound(:lead2, :SQUARE) do |s|
s.volume = 0.4
s.punch = 0.441
s.sustain = 0.067
s.decay = 0.297
s.freq = 0.499
end
b.sound(:base, :SQUARE) do |s|
s.volume = 0.4
s.punch = 0.641
s.sustain = 0.197
s.decay = 0.197
s.freq = 0.499
end
end
mario.play(DATA, :range => 0..-1, :tune => [:lead, :lead2, :base])
__END__
:lead
8E5 8E5 8 8E5 8 8C5 4E5 4G5 4 4G4 4 4C5 8 8G4 4 4E4 8 4A4 4B4 8A#4 4A4
6G4 6E5 6G5 4A5 8F5 8G5 8 4E5 8C5 8D5 4B4 8 4C5 8 8G4 4 4E4 8 4A4 4B4 8A#4 4A4
6G4 6E5 6G5 4A5 8F5 8G5 8 4E5 8C5 8D5 4B4 8 4 8G5 8F#5 8F5 4D#5 8E5 8 8G#4 8A4 8C5 8 8A4 8C5 8D5
4 8G5 8F#5 8F5 4D#5 8E5 8 4C6 8C6 4C6 4 4 8G5 8F#5 8F5 4D#5 8E5 8 8G#4 8A4 8C5 8 8A4 8C5 8D5
:lead2
8F#4 8F#4 8 8F#4 8 8F#4 4F#4 4G4 4 4G4 4 4E4 8 8E4 4 4C4 8 4C4 4D4 8C#4 4C4
6C4 6E4 6B4 4C5 8A4 8B4 8 4A4 8E4 8F4 4D4 8 4E4 8 8E4 4 4C4 8 4C4 4D4 8C#4 4C4
6C4 6E4 6B4 4C5 8A4 8B4 8 4A4 8E4 8F4 4D4 8 4 8E5 8D#5 8D5 4B4 8C5 8 8E4 8F4 8A4 8 8C4 8E4 8F4
4 8E5 8D#5 8D5 4B4 8C5 8 4G5 8G5 4G5 4 4 8E5 8D#5 8D5 4B4 8C5 8 8E4 8F4 8A4 8 8C4 8E4 8F4
:base
8D3 8D3 8 8D3 8 8D3 4D3 4G3 4 4G3 4 4G3 8 8E3 4 4C3 8 4F3 4G3 8F#3 4E3
6E3 6C4 6E4 4F4 8D4 8E4 8 4C4 8A3 8B3 4G3 8 4G3 8 8E3 4 4C3 8 4F3 4G3 8F#3 4E3
6E3 6C4 6E4 4F4 8D4 8E4 8 4C4 8A3 8B3 4G3 8 4C3 8 8G3 4 4C4 4F3 8 8C4 4C4 4F3
4C3 8 8E3 4 8G3 8C4 2 4 4G3 4C3 8 8G3 4 4C4 4F3 8 8C4 4C4 4F3
BloopSong.initの引数にテンポを渡し そのブロック内で各楽器の設定を行います 楽器を指定するsoundメソッドの引数には 楽器名とその種類をシンボルで渡し そのブロック内で音質調整を行います
楽譜はplayメソッドの第1引数に文字列として渡します 渡す文字列においてパート名はシンボル表記します 省略すると:leadが自動的にセットされます
BloopSong.playメソッドは :rangeと:tuneの2つキーワード引数を取ります rangeは楽譜の再生行(小節ではない)を指定します 例のようにするか省略した時にはすべてを再生します tuneで指定したパートのみを再生します
コードは以下のリンクにあります
melborne’s bloopsong at master - GitHub
(追記:2010-12-4)bloopsongの修正に伴い、コードおよび一部記述を修正しました。
[関連サイト] Early 8-bit Sounds from _why’s Bloopsaphone A chiptune cover of Phoenix’s “1901” AdminMyServer - ruby pong with shoes and bloopsaphone <div class="footnotes"> <ol> <li id='fn:1'>http://en.wikipedia.org/wiki/Why_the_lucky_stiff:title ↩</li><li id='fn:2'>http://www.rubyinside.com/why-the-lucky-stiff-is-missing-2278.html:title ↩</li><li id='fn:3'>http://en.wikipedia.org/wiki/Ring_Tone_Transfer_Language:title ↩</li><li id='fn:4'>Installing _why’s Bloopsaphone on OS X http://deaddeadgood.com/2010/2/13/installing-_why-s-bloopsaphone-on-os-x/ ↩</li> </ol> </div>
blog comments powered by Disqus