質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

MacOS(OSX)

MacOSとは、Appleの開発していたGUI(グラフィカルユーザーインターフェース)を採用したオペレーションシステム(OS)です。Macintoshと共に、市場に出てGUIの普及に大きく貢献しました。

Q&A

解決済

1回答

1129閲覧

標準入力によってGoのwasmの関数に引数を入れたい

aaaa____

総合スコア26

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

MacOS(OSX)

MacOSとは、Appleの開発していたGUI(グラフィカルユーザーインターフェース)を採用したオペレーションシステム(OS)です。Macintoshと共に、市場に出てGUIの普及に大きく貢献しました。

0グッド

0クリップ

投稿2022/10/31 12:30

編集2022/11/01 07:05

環境

・macOS Big Sur ver. 11.5.1(20G80) -> 12.6 (21G115)
・MacBook Air M1, 2020 メモリ 8GB
・go1.18.3 darwin/arm64

最終的に目指していること

https://www.amazon.co.jp/dp/4873118220?tag=note0e2a-22&linkCode=ogi&th=1&psc=1
この本で作っているMonkeyという言語に,組み込み関数としてplay関数を作り,play(60)のようにMIDIノートナンバーを引数としてその音を鳴らすことができるようにする.

まず,Live Codingという,即興で音楽をコーディングによって作るものに使用する言語の設計を考えていまして,そのための基盤(Monkey言語と呼ばれる参考書を見ながら作る対話型独自言語)を質問本文に記載した参考書にしたがってGo言語で作成いたしました.
https://interpreterbook.com/waiig_code_1.4.zip
その言語に対して,組み込み関数(例えばlen("aiueo");と入力したら5と返してくるようなlen関数のようなもの)として,MIDIノートナンバーを引数として,その音をWebAudioAPIを用いて鳴らすplay()関数を付け加えようと考えています.
文字数の都合上,細かい実装した手順などは本文に記載させていただいた記事に任させていただこうとおもうのですが,現状,音を鳴らす機能がない(与えられてMIDIノートナンバーをそのまま返すだけ)play()組み込み関数の追加はすることが出来ました.そのあとに,命令としてplay(60);としたら60の音が鳴るように評価の部分を変更しようとしたのですが,方法が分からない,といったことになっております.

ブラウザ上で操作するのではなく,あくまでもコーディングとしてターミナル上で命令を記述し,ブラウザではボタンを押すだけでコーディングしたように音が鳴るようにしたいと考えております.

今できていること

https://note.com/clean_camel994/n/ne3dd24b6f116
この記事でやっているように,コードの段階でMIDIノートナンバーを入れてGo言語のwebassembly変換によってWebAudioAPIの音を鳴らすことはできています.

https://note.com/clean_camel994/n/n74f27e86abb8
この記事で書いてあるように,処理を意味のない簡単なものに置き換えたplay関数を認識させることもできています.

わからないこと

対話型インタプリタで入力された数字を用いてWebAudioAPIで音を鳴らす方法.
main.goを実行する前にビルドをしてwasm変換をしていると思うのですが,入力による引数で音を鳴らすことは可能なのでしょうか.

ひとまず,次のwebseembly.goでランダムにMIDIノートナンバーを入れている部分を入力で指定できるようにする方法でも助かります.(この記事のものです:https://note.com/clean_camel994/n/ne3dd24b6f116)

Go

1package main 2 3import ( 4 "syscall/js" 5 "time" 6 "math/rand" 7 "math" 8) 9 10func num_to_freq (notenum int) float64{ 11 // 基準音から何音高い/低いかを計算する 12 from_concert_a := notenum - 69 13 // 周波数を実際に計算する 14 // 十二平均律では2音の最小の周波数差は`2^(1/12)`となる 15 freq := math.Pow(2, float64(from_concert_a) / 12) * 440; 16 return freq; 17} 18 19func main() { 20 // グローバルオブジェクト(window)を取得します 21 window := js.Global() 22 23 // document オブジェクトを取得します 24 document := window.Get("document") 25 26 // bodyを取得します 27 body := document.Get("body") 28 29 // ボタンのDOMを作成し、Clickイベントを設定します 30 btn := document.Call("createElement", "button") 31 btn.Set("textContent", "music start!") 32 btn.Call("addEventListener", "click", js.FuncOf(func(js.Value, []js.Value) interface{} { 33 ctx := js.Global().Get("AudioContext").New() 34 osc := js.Global().Get("OscillatorNode").New(ctx) 35 rand.Seed(time.Now().UnixNano()) 36 randNote := rand.Intn(80) 37 osc.Get("frequency").Set("value", num_to_freq(randNote)) 38 osc.Set("type", "sine") 39 osc.Call("connect", ctx.Get("destination")) 40 osc.Call("start") 41 body.Call("appendChild", osc) 42 return nil 43 })) 44 // ボタンをbodyに追加します 45 body.Call("appendChild", btn) 46 47 // プログラムが終了しないように待機します 48 select {} 49 50}

調べてでてきたもの

https://ludwig125.hatenablog.com/entry/2022/03/06/080759
このサイトにあるように,ブラウザに値を打ち込んでやる方法を取るしかないのでしょうか.

追記

一つ上のサイトを参考に,以下のようにして,ブラウザにテキストボックスとボタンを配置しそのMIDIノートなんばーの音を鳴らすことは出来ましたが,これは少し趣旨とは異なってしまいます.

index.html

html

1<html> 2 <head> 3 <meta charset="utf-8"/> 4 <script src="wasm_exec.js"></script> 5 <script> 6 const go = new Go(); 7 WebAssembly.instantiateStreaming(fetch("build.wasm"), go.importObject).then((result) => { 8 go.run(result.instance); 9 }); 10 </script> 11 </head> 12 <body> 13 <input type="text" id="noteNum" /> 14 <button onClick="play('noteNum');" id="playButton">play</button> 15 </body> 16</html>
webassembly.go

go

1package main 2 3import ( 4 "syscall/js" 5 "time" 6 "math/rand" 7 "math" 8 "fmt" 9 "strconv" 10) 11 12func num_to_freq (notenum int) float64{ 13 // 基準音から何音高い/低いかを計算する 14 from_concert_a := notenum - 69 15 // 周波数を実際に計算する 16 // 十二平均律では2音の最小の周波数差は`2^(1/12)`となる 17 freq := math.Pow(2, float64(from_concert_a) / 12) * 440; 18 return freq; 19} 20 21func registerCallbacks() { 22 js.Global().Set("play", js.FuncOf(play)) 23} 24 25func textToStr(v js.Value) string { 26 return js.Global().Get("document").Call("getElementById", v.String()).Get("value").String() 27} 28 29func play(this js.Value, args []js.Value) interface{} { 30 value := textToStr(args[0]) 31 32 noteNum, _ := strconv.Atoi(value) 33 ctx := js.Global().Get("AudioContext").New() 34 osc := js.Global().Get("OscillatorNode").New(ctx) 35 gain := js.Global().Get("GainNode").New(ctx) 36 gain.Get("gain").Set("value", 0) 37 38 bpm := 120.0 39 note_length := 60.0 / bpm 40 41 osc.Call("connect", gain) 42 gain.Call("connect", ctx.Get("destination")) 43 osc.Call("start") 44 45 for n := 0; n < 60; n++{ 46 start_t := float64(n) * note_length 47 end_t := start_t + 1.0 48 osc.Get("frequency").Call("setValueAtTime", num_to_freq(noteNum), ctx.Get("currentTime").Float()+start_t) 49 gain.Get("gain").Call("setValueAtTime", 0.3, ctx.Get("currentTime").Float()+start_t) 50 gain.Get("gain").Call("setValueAtTime", 0., ctx.Get("currentTime").Float()+end_t) 51 } 52 osc.Set("type", "sawtooth") 53 return nil 54} 55 56func main() { 57 58 registerCallbacks() 59 60 // グローバルオブジェクト(window)を取得します 61 window := js.Global() 62 63 // document オブジェクトを取得します 64 document := window.Get("document") 65 66 // bodyを取得します 67 body := document.Get("body") 68 69 // ボタンのDOMを作成し、Clickイベントを設定します 70 btn := document.Call("createElement", "button") 71 btn.Set("textContent", "music start!") 72 btn.Call("addEventListener", "click", js.FuncOf(func(js.Value, []js.Value) interface{} { 73 ctx := js.Global().Get("AudioContext").New() 74 osc := js.Global().Get("OscillatorNode").New(ctx) 75 gain := js.Global().Get("GainNode").New(ctx) 76 gain.Get("gain").Set("value", 0) 77 78 bpm := 120.0 79 note_length := 60.0 / bpm 80 81 osc.Call("connect", gain) 82 gain.Call("connect", ctx.Get("destination")) 83 osc.Call("start") 84 85 rand.Seed(time.Now().UnixNano()) 86 87 for n := 0; n < 120; n++{ 88 randNote := rand.Intn(20) + 55 89 fmt.Println(randNote) 90 start_t := float64(n) * note_length 91 end_t := start_t + 1.0 92 osc.Get("frequency").Call("setValueAtTime", num_to_freq(randNote), ctx.Get("currentTime").Float()+start_t) 93 gain.Get("gain").Call("setValueAtTime", 0.3, ctx.Get("currentTime").Float()+start_t) 94 gain.Get("gain").Call("setValueAtTime", 0., ctx.Get("currentTime").Float()+end_t) 95 } 96 osc.Set("type", "sawtooth") 97 return nil 98 })) 99 // ボタンをbodyに追加します 100 body.Call("appendChild", btn) 101 102 // プログラムが終了しないように待機します 103 select {} 104 105}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

「標準入力」という概念はコマンドラインのプログラムのための仕組みなのでブラウザにはありません。
ノート番号を引数に渡して音を鳴らす関数を作成することはもちろんできるはずです。
その引数にインタラクティブに渡す手段を質問されているという仮定で回答します。

「調べてでてきたもの」でなにか不都合はありますか?
ブラウザのフォームに番号を入力したものを読み取って動くというのはどうでしょうか?

  1. ボタンを押す
  2. ボタンクリック処理中にフォームのValueを取り出してそれをノート番号とする
  3. 音を鳴らす

もっとわかりやすくするならピアノの鍵盤状にボタンをレイアウトしてそれぞれの属性にノート番号を振っておくなどはどうでしょうか?

  1. ボタンを押す
  2. ボタンクリック処理中に自身「Event.Get("src")」から属性を取り出してそれをノート番号とする
  3. 音を鳴らす

というのではどうでしょうか。

もしくは「やりたいこと」はどういうことなのかをもう少し具体的に示してください。

追記

質問の意味を取り違えていました。
スクリプトエンジンを使って書いたシナリオに基づき音を鳴らしたいという事でした。

下記に「JSスクリプトエンジン」を使ってシナリオプログラムを記述しそれに基づいて音楽を鳴らす最小のサンプルを示します。
ここからMonkeyに移植するなどしてもらえば実現できそうですね。

https://go.dev/play/p/4LW0Jjzr5mO

go

1package main 2 3import ( 4 "math" 5 "syscall/js" 6) 7 8var ( 9 window = js.Global() 10 document = window.Get("document") 11 12 AudioContext = js.Global().Get("AudioContext") 13 OscillatorNode = js.Global().Get("OscillatorNode") 14 GainNode = js.Global().Get("GainNode") 15 16 note2freq = []float64{} 17) 18 19func init() { 20 for i := 0; i < 128; i++ { 21 note2freq = append(note2freq, 440*math.Pow(2, float64(i-69)/12)) 22 } 23} 24 25type Note struct { 26 Number int 27 Duration float64 28} 29 30type MusicBox struct { 31 ctx js.Value 32 osc js.Value 33 gain js.Value 34 current float64 35} 36 37func NewMusicBox() *MusicBox { 38 ctx := AudioContext.New() 39 osc := OscillatorNode.New(ctx) 40 gain := GainNode.New(ctx) 41 osc.Call("connect", gain) 42 osc.Get("frequency").Set("value", 0) 43 gain.Call("connect", ctx.Get("destination")) 44 gain.Get("gain").Set("value", 0) 45 osc.Call("start") 46 return &MusicBox{ 47 ctx: ctx, 48 osc: osc, 49 gain: gain, 50 current: ctx.Get("currentTime").Float(), 51 } 52} 53 54func (mb *MusicBox) Play(note Note) { 55 mb.osc.Get("frequency").Call("setValueAtTime", note2freq[note.Number], mb.current) 56 mb.gain.Get("gain").Call("setValueAtTime", 0.3, mb.current) 57 mb.gain.Get("gain").Call("setValueAtTime", 0.0, mb.current+note.Duration) 58 mb.current += note.Duration 59} 60 61const sample = `for (let i=0; i< 3; i++) { 62 play(64, 0.2) 63 play(0, 0.1) 64 play(62, 0.2) 65 play(0, 0.1) 66 play(60, 0.2) 67 play(0, 0.1) 68} 69` 70 71func main() { 72 code := document.Call("createElement", "textarea") 73 code.Set("id", "code") 74 code.Set("value", sample) 75 code.Get("style").Set("width", "50%") 76 code.Get("style").Set("height", "20em") 77 document.Get("body").Call("appendChild", code) 78 btn := document.Call("createElement", "button") 79 btn.Set("textContent", "music start!") 80 btn.Call("addEventListener", "click", js.FuncOf(func(js.Value, []js.Value) interface{} { 81 mb := NewMusicBox() 82 window.Set("play", js.FuncOf(func(this js.Value, args []js.Value) interface{} { 83 mb.Play(Note{args[0].Int(), args[1].Float()}) 84 return nil 85 })) 86 code := document.Call("getElementById", "code") 87 src := code.Get("value") 88 window.Call("eval", src) 89 return nil 90 })) 91 document.Get("body").Call("appendChild", btn) 92 select {} 93}

動作例:
イメージ説明
ボタンクリックで「ミ->レ->ド」を3回繰り返します。

投稿2022/11/01 05:35

編集2022/11/01 11:15
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

aaaa____

2022/11/01 07:06 編集

追記として詳細は記させていただくのですが,記載のようにして,ブラウザ上にテキストボックスとボタンを配置して,テキストボックスにMINIノートナンバーを入れてボタンを押すとその音が鳴るようにすることは出来ました. やりたいことをより具体的に書かせていただきますと, まず,Live Codingという,即興で音楽をコーディングによって作るものに使用する言語の設計を考えていまして,そのための基盤(Monkey言語と呼ばれる参考書を見ながら作る対話型独自言語)を質問本文に記載した参考書にしたがってGo言語で作成いたしました. https://interpreterbook.com/waiig_code_1.4.zip その言語に対して,組み込み関数(例えばlen("aiueo");と入力したら5と返してくるようなlen関数のようなもの)として,MIDIノートナンバーを引数として,その音をWebAudioAPIを用いて鳴らすplay()関数を付け加えようと考えています. 文字数の都合上,細かい実装した手順などは本文に記載させていただいた記事に任させていただこうとおもうのですが,現状,音を鳴らす機能がない(与えられてMIDIノートナンバーをそのまま返すだけ)play()組み込み関数の追加はすることが出来ました.そのあとに,命令としてplay(60);としたら60の音が鳴るように評価の部分を変更しようとしたのですが,方法が分からない,といったことになっております. ブラウザ上で操作するのではなく,あくまでもコーディングとしてターミナル上で命令を記述し,ブラウザではボタンを押すだけでコーディングしたように音が鳴るようにしたいと考えております.
nobonobo

2022/11/01 07:42

つまり、「ターミナル上の入力をどうやってブラウザに持ってきたらいいでしょうか」という質問ですか?
aaaa____

2022/11/01 07:54 編集

確かに整理してみると,そちらの方法が良いかもしれません. ターミナル上だけでは難しいとのことですので. https://www.youtube.com/watch?v=KJPdbp1An2s この動画のような感じで,コードを記述して音を操作できるのならば,コードを記述する先はブラウザ上でも問題ありません.
nobonobo

2022/11/01 08:04

ああ。ようやく理解できてきたような気がします! スクリプトエンジンを載せてスクリプトエンジンに機能追加したいという事なんですね。 ターミナルというのはブラウザ上のコードエディタという解釈であってますか?
nobonobo

2022/11/01 08:38

なかなかにハードなことを目指していたのですね。時間ができたら小さなサンプルを追記してみますが・・・。 Monkeyである必然性などはありますか? 例えばブラウザにはJSスクリプトエンジンがすでにあります。 「js.Global().Call("eval", "スクリプト")」とすることで任意のコードの実行が可能です。 このevalの事前にjs.Global().Set("play", play)というようにGoの処理関数を入れておくとスクリプトの中から「play()」で呼び出すことができます。
aaaa____

2022/11/01 11:14

なかなかうまく表現することが出来ておらず,理解に時間をかけさせてしまったみたいで申し訳ございません. スクリプトエンジンについて知らなかったので少し調べてみましたが,そこまで高尚なものでもなく,普通のプログラミング言語にシンセサイザを鳴らせる組み込み関数を入れただけのようなものを想像しています. ターミナルはmain.goなどを実行していたvscodeのターミナルで話しておりました. 手に余るような難しさなのはある程度理解しているのですが,興味があったのでやってみようと思っています. Monkeyである必然性というのは,正直あまり考えつかないかもしれないです.自分がJavaScriptやその他の言語に精通しているというわけではないので,他の言語ならこのようにできるなどの知識はほとんどないですし. ただ,主目的は音楽を鳴らすことではなく,参考書を参考にGoで新しい言語を設計してみようというものなので,そこからはあまりずれたくないというところで考えております.
aaaa____

2022/11/01 12:16

サンプルコードありがとうございます. 少し時間はかかってしまうかもしれませんが,よく読んで,調べてみて,わからない点があれば確認させていただきたいと思います.
aaaa____

2022/11/02 11:49 編集

code := document.Call("getElementById", "code") の部分は,なぜ自分自信を表すElementオブジェクトを返させているのでしょうか,調べてみて以下のサイトなどを読んでみたのですが,あまり理解できませんでした. https://developer.mozilla.org/ja/docs/Web/API/Document/getElementById constにて定義していないコードをテキストボックスに追加しても問題なくplayで音を鳴らすことができると思うのですが,テキストボックスに追加したものをcodeに加えるものが上のものなのでしょうか.
nobonobo

2022/11/02 13:18

ああ確かにここでは先に定義したcodeを使っても良いですね。ただ、色々作り込んでいくとセットアップとイベントの発火処理はだんだん別のところに書く様になりがちなのでイベント処理の中で別途取得する癖が出てしまいました。ただこうするのがおすすめです。将来はおそらくcreateElementでチマチマDOMの組み立てをせず、テンプレートや静的ファイルで書く様になると思います。
aaaa____

2022/11/04 01:04

なるほど,そのような背景があったのですね,ありがとうございます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問