F#サンタからの贈り物
F# Advent Calendar 2012の最終日、24日のエントリです。
今年のFsAdventJPはテーマが『実用』ということもあって、面白い記事が多かったですね。
皆さんがハードルを上げまくって下さいましたので、胸焼けが治まりません。
今日はクリスマス・イブということで、
『クリスマス・スペシャル』でお送りしたいと思います。
なお、本エントリを読むにあたっての注意事項があります。
- F# Interactive (fsi) をご用意下さい
- disらないでください
それでは本題に入ります。
みなさんはビープ音をご存知でしょうか。
あの「ピッ」とか「プッ」とか鳴るあれです。
その昔、僕がプログラミングを始めたばかりの頃、HSPという言語(!)で遊んでいた時に
「周波数を指定すればビープ音の高さを変えられる」
ということを知りました。
実は.NET FrameworkのAPIにも用意されていて、自由に周波数を指定することができます。
> System.Console.Beep(740, 1000) ;;(BIOSのビープ音ではなく、OS側のビープ音ですのでスピーカーから音が出ます。)
第1引数が周波数、第2引数が鳴らす時間の長さ(ms)です。
国際基準ではA(ラの音)は440.0Hzと定められているそうで、
12平均律に従って計算すると、F#の音は約740Hzになります。
今後「F#!F#!」と口にする際には、音程にも気をつけてみて下さい。
なお、Windows Server製品は恐らくビープ音が鳴りません。
また、XPの64bitエディションおよびVistaの64bitエディションは
このBeepメソッドをサポートしていないみたいです。(なぜ・・・?)
mono環境は良くわかりません。。。
ここまでのまとめ
実用的すぎてやばい
プログラム
fsiの準備はして頂けたでしょうか。
ささやかなX'masプレゼントをご用意しましたので、以下のプログラムをコピペして評価してみてください。
(ファイル形式でダウンロードしたい方はこちらを右クリックして保存してみてください)
その中に
val play1 : unit -> unit
val play2 : unit -> unit
という関数が定義されています。心の準備ができたら実行してみて下さい。
例外が発生した方は、お使いの環境ではAPIがサポートされていない可能性があります。
スピーカーの音量を調節したら、レッツGO!
open System /// 音価を表す型 type PhoneticValue = | Whole | Half | Quarter | Eighth | Sixteenth | ThirtySecond with member this.ToFloat() = match this with | Whole -> 4.0 | Half -> 2.0 | Quarter -> 1.0 | Eighth -> 1. / 2. | Sixteenth -> 1. / 4. | ThirtySecond -> 1. / 8. /// 音階の型 type Tone = | Tone of float | Rest // 休符 with member this.Map(f) = match this with | Tone x -> Tone (f x) | Rest -> Rest /// 音符の型 type Note = Tone * float module Utility = let first f (x, y) = f x, y let second f (x, y) = x, f y // 入力のためのヘルパー群 module NoteExtention = open Utility type private PV = PhoneticValue let private frequencyRatios = List.init 12 <| fun i -> Math.Pow(2.0, float i / 12.0) let private tonePool = List.map Tone frequencyRatios let private toneMap = let tones = ["A"; "A#"; "B"; "C"; "C#"; "D"; "D#"; "E"; "F"; "F#"; "G"; "G#";] List.zip tones tonePool |> dict let A = toneMap.["A"] let As = toneMap.["A#"] let B = toneMap.["B"] let C = toneMap.["C"] let Cs = toneMap.["C#"] let D = toneMap.["D"] let Ds = toneMap.["D#"] let E = toneMap.["E"] let F = toneMap.["F"] let Fs = toneMap.["F#"] // F#!F#! let G = toneMap.["G"] let Gs = toneMap.["G#"] type Tone with member private x.Note(p: PV) : Note = x, p.ToFloat() member x.__ = x.Note(Whole) member x._2 = x.Note(Half) member x._4 = x.Note(Quarter) member x._8 = x.Note(Eighth) member x._16 = x.Note(Sixteenth) member x._32 = x.Note(ThirtySecond) type private NoteFunc = Note -> Note // 付点、複付点 let dot : NoteFunc = second <| ( * ) 1.5 let dot2 : NoteFunc = second <| ( * ) 1.75 // 8va alta, 8va bassa let private octave x (t: Tone) = t.Map <| ( * ) x let alta : NoteFunc = first (octave 2.0) let bassa : NoteFunc = first (octave 0.5) open NoteExtention type BeepPlayer(time: PhoneticValue, bpm: int, standardPitch: int) = let A = float standardPitch let bpm' = float bpm let time' = time.ToFloat() let score = ResizeArray<Note>() let isVisible = true let s2ms s = s * 1000. |> int let sleep = s2ms >> System.Threading.Thread.Sleep let beep (frequency: float) duration = if isVisible then printfn "%fHz, %fs" frequency duration Console.Beep(int frequency, s2ms duration) sleep 0.004 // 隠し味 let calcFrequency = ( * ) A let calcDuration phoneticValue = (60.0 / bpm') * (phoneticValue / time') let sound : Note -> unit = fun (tone, p) -> let duration = calcDuration p match tone with | Tone ratio -> beep (calcFrequency ratio) duration | Rest -> sleep duration new (bpm) = BeepPlayer(Quarter, bpm, 440) // 国際基準 A440 member __.Time = time member __.BPM = bpm member __.StandardPitch = standardPitch member __.SetScore(x) = score.Clear() ; score.AddRange(x) member __.AddScore(x) = score.AddRange(x) member __.RepeatScore() = score.AddRange(score.ToArray()) member this.Play() = try score.ToArray() |> Array.iter sound with e -> printfn "exception!! \n%s" e.Message let test () = let player = BeepPlayer(130) let add x = player.AddScore(x) add [C._4; D._4; E._4; F._4; E._4; D._4; C._4; Rest._4] add [E._4; F._4; G._4; A._4|>alta; G._4; F._4; E._4; Rest._4] add [C._4; Rest._4; C._4; Rest._4; C._4; Rest._4; C._4; Rest._4;] add [C._8; C._8; D._8; D._8; E._8; E._8; F._8; F._8;] add [E._4; D._4; C._4;] player.Play() // みなさんへの贈り物 その1 let play1 () = let player = BeepPlayer(155) let add x = player.AddScore(x) add [C._4; F._4; F._8; G._8; F._8; E._8; D._4; D._4; D._4;] add [G._4; G._8; A._8|>alta; G._8; F._8; E._4; C._4; C._4;] add [A._4|>alta; A._8|>alta; As._8|>alta; A._8|>alta; G._8;] add [F._4; D._4; C._8; C._8; D._4; G._4; E._4; F._2;] player.RepeatScore() add [C._4; F._4; F._4; F._4; E._2; D._4; F._4; E._4; D._4; C._2;] add [G._4; A._4|>alta; G._4; F._4; C._4|>alta; C._4; C._8; C._8;] add [D._4; G._4; E._4; F._2;] player.Play() // みなさんへの贈り物 その2 let play2 () = let player = BeepPlayer(Quarter, 120, 880) let add x = player.AddScore(x) let bassdot = bassa >> dot add [G._4|>bassdot; A._8; G._4|>bassa; E._2|>bassdot;] player.RepeatScore() add [D._2; D._4; B._2|>dot;] add [C._2; C._4; G._2|>bassdot; A._2; A._4;] add [C._4|>dot; B._8; A._4;] add [G._4|>bassdot; A._8; G._4|>bassa; E._2|>bassdot;] add [A._2; A._4; C._4|>dot;] add [B._8; A._4; G._4|>bassdot; A._8; G._4|>bassa;] add [E._2|>bassdot; D._2; D._4; F._4|>dot;] add [D._8; B._4; C._2|>dot;] add [E._2|>dot; C._4; G._4|>bassa; E._4|>bassa;] add [G._4|>bassdot; F._8|>bassa; D._4|>bassa; C._2|>bassdot;] player.Play() printfn "Merry X'mas !" ;;
まとめ
F#!F#!(740Hzで)