Help us understand the problem. What is going on with this article?

Goでドレミファを鳴らしてGetWildを演奏する

More than 3 years have passed since last update.

Get Wild Advent Calendar 2016 の15日目です。

概要

  • 機材はないけど、どうしてもGetWildを弾きたい
  • ドレミファが鳴らせればいける…
  • → GoでOpenALを使って実装しました
  • 時間がなかったのでかなりやっつけです

実装

$ curl -O https://github.com/sioncojp/play-getwild/releases/download/1.0.0/play-getwild.tar.gz
$ tar zxvf play-getwild.tar.gz
$ ./play-getwild

中身

package main

import (
    "encoding/binary"
    "fmt"
    "log"
    "math"
    "sync"
    "time"

    "golang.org/x/mobile/exp/audio/al"
    "golang.org/x/mobile/exp/f32"
)

var pctx *Context
var pianoPlayer *Piano

const (
    Pi         = float32(math.Pi)
    Fmt        = al.FormatStereo16
    QUEUE      = 500
    SampleRate = 10000 // 音の高さのベース
)

type Oscillator func() float32

type Context struct {
    sync.RWMutex
    source     al.Source
    queue      []al.Buffer
    oscillator Oscillator
}

type Piano struct {
    notes      []bool
    oscillator Oscillator
}

func G(gain float32, f Oscillator) Oscillator {
    return func() float32 {
        return gain * f()
    }
}

func GenOscillator(freq float32) Oscillator {
    dt := 1.0 / float32(SampleRate)
    k := 2.0 * Pi * freq
    T := 1.0 / freq
    t := float32(0.0)
    return func() float32 {
        res := f32.Sin(k * t)
        t += dt
        if t > T {
            t -= T
        }
        return res
    }
}

func Multiplex(fs ...Oscillator) Oscillator {
    return func() float32 {
        res := float32(0)
        for _, osc := range fs {
            res += osc()
        }
        return res
    }
}

func GenEnvelope(press *bool, f Oscillator) Oscillator {
    dt := 1.0 / float32(SampleRate)
    top := false
    gain := float32(0.0)
    attackd := dt / 0.01
    dekeyd := dt / 0.03
    sustainlevel := float32(0.3)
    sustaind := dt / 7.0
    released := dt / 0.8
    return func() float32 {
        if *press {
            if !top {
                gain += attackd
                if gain > 1.0 {
                    top = true
                    gain = 1.0
                }
            } else {
                if gain > sustainlevel {
                    gain -= dekeyd
                } else {
                    gain -= sustaind
                }
                if gain < 0.0 {
                    gain = 0.0
                }
            }
        } else {
            top = false
            gain -= released
            if gain < 0.0 {
                gain = 0.0
            }
        }
        return gain * f()
    }
}

func NewContext(oscillator Oscillator) *Context {
    if err := al.OpenDevice(); err != nil {
        log.Fatal(err)
    }
    s := al.GenSources(1)
    return &Context{
        source:     s[0],
        queue:      []al.Buffer{},
        oscillator: oscillator,
    }
}

func NewPiano(freqs []float32) *Piano {
    p := new(Piano)
    p.notes = make([]bool, len(freqs))
    envelopes := []Oscillator{}
    for i, f := range freqs {
        base := []Oscillator{}
        for j := float32(1.0); j <= 8; j++ {
            base = append(base, G(0.5/j, GenOscillator(f*j)))
        }
        base = append(base, G(0.3, GenOscillator(f+2)))
        osc := Multiplex(base...)
        envelopes = append(envelopes, G(0.4, GenEnvelope(&p.notes[i], osc)))
    }
    p.oscillator = Multiplex(envelopes...)
    return p
}
func (p *Piano) NoteOn(key int) {
    p.notes[key] = true
}

func (p *Piano) NoteOff(key int) {
    p.notes[key] = false
}

func (p *Piano) GetOscillator() Oscillator { return p.oscillator }

func (c *Context) Play(q int) {
    c.Lock()
    defer c.Unlock()
    n := c.source.BuffersProcessed()
    if n > 0 {
        rm := c.queue[:n]
        c.queue = nil
        c.source.UnqueueBuffers(rm...)
        al.DeleteBuffers(rm...)
    }
    fmt.Println(len(c.queue))
    for len(c.queue) < QUEUE {
        b := al.GenBuffers(q) // 音の長さ
        buf := make([]byte, 2048)
        for n := 0; n < 2048; n += 2 {
            f := c.oscillator()
            v := int16(float32(92767) * f) // 音の大きさ
            binary.LittleEndian.PutUint16(buf[n:n+2], uint16(v))
        }
        b[0].BufferData(Fmt, buf, SampleRate)
        c.source.QueueBuffers(b...)
        c.queue = append(c.queue, b...)
    }
    al.PlaySources(c.source)
}

func (c *Context) Close() {
    c.Lock()
    defer c.Unlock()
    al.StopSources(c.source)
}

func PlaySound(s, q int, slp time.Duration) {
    pianoPlayer.NoteOn(s)
    pctx.Play(q)
    time.Sleep(slp * time.Millisecond)
    pctx.Close()
    pianoPlayer.NoteOff(s)
}

func main() {
    pianoPlayer = NewPiano([]float32{
        246.941650628,
        261.625565301,
        277.182630977,
        293.664767917,
        311.126983722,
        329.627556913,
        349.228231433,
        369.994422712,
        391.995435982,
        415.30469758,
        440.0,
        466.163761518,
        493.883301256,
        523.251130601,
    })

    pctx = NewContext(pianoPlayer.GetOscillator())

    PlaySound(6, 70, 500)
    PlaySound(4, 70, 500)
    PlaySound(2, 10, 1000)

    time.Sleep(100 * time.Millisecond)

    PlaySound(6, 100, 300)
    PlaySound(4, 80, 500)
    PlaySound(2, 80, 500)
    PlaySound(2, 100, 300)
    PlaySound(2, 10, 1000)

    time.Sleep(200 * time.Millisecond)

    PlaySound(2, 100, 300)
    PlaySound(4, 150, 300)
    PlaySound(6, 150, 300)
    PlaySound(6, 150, 300)
    PlaySound(6, 150, 300)
    PlaySound(7, 150, 300)
    PlaySound(6, 150, 300)
    PlaySound(2, 150, 300)
    PlaySound(2, 150, 300)
    PlaySound(6, 150, 300)

    time.Sleep(10 * time.Millisecond)

    PlaySound(6, 80, 300)
    PlaySound(4, 50, 600)
    PlaySound(2, 200, 180)
    PlaySound(2, 10, 1000)
}

感想

  • 1日でいけるかなと思ったけどかなり難しかったOpenAL(でも楽しかった)
  • 改良点ありすぎなので、時間があればちゃんとしたものを作りたいですね
  • pianoというパッケージにして色んなものを演奏出来るようにしたいなと思いました

参考

sion_cojp
元プロゲーマーのインフラエンジニアです。 好きなことは音楽、ギター、食べ歩き、GetWild。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away