Monkey言語:サンプル音源の再生

目標

組み込み関数sampleを作成して,引数によって録音された音源を流せるようにしたい.

この記事を参考に書いていく.


テストケースを書く

func TestSampleFunction(t *testing.T){
	tests := []struct {
		input string
		expected interface{}
	}{
		{`sample("drubase")`, "drubase"},
		{`sample()`, "wrong number of arguments. got=0, want=1"},
		{`sample("beat", 3)`, "wrong number of arguments. got=2, want=1"},
		{`sample(4)`, "argument to `sample` must be STRING, got INTEGER"},
		{`sample("foo")`, "There is not foo in SoundSources."},
	}
	for _, tt := range tests{
		evaluated := testEval(tt.input)
	
		switch expected := tt.expected.(type){
		case string:
			switch evaluated.(type){
			case *object.Error:
				errObj, ok := evaluated.(*object.Error)
				if !ok {	
					continue
				}
				if errObj.Message != expected {
					t.Errorf("wrong error message. expected=%q, got=%q", expected, errObj.Message)
				}
			case *object.String:
				str, ok := evaluated.(*object.String)
				if !ok {
					t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated)
				}
				if str.Value != "drum&base.wav"{
					t.Errorf("There is not %s in SoundSources.", str.Value)
				}
			}

			
		}
	}
}

ErrorfとすべきところをFatalfとして少し沼ったのでそれについての記事を載せておく.


組み込み関数の認識

再生できる音源の名前を格納するのに,mapの機能を使う.これで後からそのサンプルがあるのかのチェックが容易になる.

音源は以下の箇所から拝借させていただいている.


ひとまず3つほど音源を用意した.

var  soundSource = map[string]string{
	"drubase":		"drum&base.wav",
	"distortion":	"distortionguitar.wav",
	"simple":		"simple.wav",
}


関数の中身1

音を鳴らす部分を除いてテストを試せるようにした.

	"sample": &object.Builtin{
		Fn: func(args ...object.Object) object.Object{
			if len(args) != 1{
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			switch args[0].(type){
			case *object.String:
				if val, ok := soundSource[args[0].Inspect()]; ok{
					return &object.String{Value: val}
				}else{
					return newError("There is not %s in SoundSources.", args[0].Inspect())
				}
			default:
				return newError("argument to `sample` must be STRING, got %s", args[0].Type())
			}
		},
	},

これでテストケースは通るので,あとは記事を参考に.wavを再生させる.


関数の中身2

	"sample": &object.Builtin{
		Fn: func(args ...object.Object) object.Object{
			if len(args) != 1{
				return newError("wrong number of arguments. got=%d, want=1", len(args))
			}

			switch args[0].(type){
			case *object.String:
				if val, ok := soundSource[args[0].Inspect()]; ok{
					f, err := os.Open("sound_source/" + val)
					if err != nil {
						log.Fatal(err)
					}
					st, format, err := wav.Decode(f)
					if err != nil {
						log.Fatal(err)
					}
					defer st.Close()
				
					speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
				
					done := make(chan bool)
					speaker.Play(beep.Seq(st, beep.Callback(func() {
						done <- true
					})))
					<-done
					return &object.String{Value: val}
				}else{
					return newError("There is not %s in SoundSources.", args[0].Inspect())
				}
			default:
				return newError("argument to `sample` must be STRING, got %s", args[0].Type())
			}
		},
	},
}

この状態だと,sample関数を使って音を鳴らすことはできるが,鳴らしてる間命令を実行させることができなくなってしまう.
これでは使い道がほとんどないので,どちらも並行してできるようにしたい.

と思ってgoroutineについて調べて次のようにしてみたのだが,結局終わるまで待たせるので使う前と何も変わらなかった.

var wg sync.WaitGroup // WaitGroupの生成
wg.Add(1)             // カウンタをインクリメント

done := make(chan bool)
go func(){
	speaker.Play(beep.Seq(st, beep.Callback(func() {
		done <- true
	})))
	<-done
	wg.Done() // カウンタをデクリメント
}()

wg.Wait() //新規のゴールーチンが完了するのを待つ

なので,sample関数では一旦,この状態のままで完成とさせていく.代わりに,ブラウザ上でファイルを落として,そのファイルの再生をできるようにしようと考えている.


この記事が気に入ったらサポートをしてみませんか?