F#サンタからの贈り物

F# Advent Calendar 2012の最終日、24日のエントリです。
今年のFsAdventJPはテーマが『実用』ということもあって、面白い記事が多かったですね。
皆さんがハードルを上げまくって下さいましたので、胸焼けが治まりません。


今日はクリスマス・イブということで、
『クリスマス・スペシャル』でお送りしたいと思います。
なお、本エントリを読むにあたっての注意事項があります。

  • F# Interactive (fsi) をご用意下さい
  • disらないでください


それでは本題に入ります。


みなさんはビープ音をご存知でしょうか。
あの「ピッ」とか「プッ」とか鳴るあれです。


その昔、僕がプログラミングを始めたばかりの頃、HSPという言語(!)で遊んでいた時に
「周波数を指定すればビープ音の高さを変えられる」
ということを知りました。
実は.NET FrameworkAPIにも用意されていて、自由に周波数を指定することができます。

> 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で)