15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GetWildAdvent Calendar 2016

Day 15

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

Last updated at Posted at 2016-12-15

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というパッケージにして色んなものを演奏出来るようにしたいなと思いました

参考

15
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?