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

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

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

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

MacOS(OSX)

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

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

1147閲覧

メソッド・関数の呼び出しをJavaScriptからGoに変換できない.

aaaa____

総合スコア26

Go

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

MacOS(OSX)

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

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2022/10/18 12:35

編集2022/10/20 11:40

環境

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

やっていること

Go WebAssemblyでブラウザ上でGoを実行する その2 Go側からJavascriptを操作する

Web Audio APIでシンセサイザーを作って遊ぶ

この二つのサイトを組み合わせて,一つ目のサイトにおけるsrc/webassembly.goを,二つ目のサイトの440Hzでsin波を鳴らしているものに変更させ,Go言語からJavaSciptでWeb Audio APIを制御してシンセサイザーを鳴らそうとしています.
それぞれ単体では実行することに成功しました.

WebAssembly.go

Go

1package main 2 3import ( 4 "syscall/js" 5) 6 7func main() { 8 // グローバルオブジェクト(window)を取得します 9 window := js.Global() 10 11 // document オブジェクトを取得します 12 document := window.Get("document") 13 14 // bodyを取得します 15 body := document.Get("body") 16 17 // h1 のDOMを作成します 18 h1 := document.Call("createElement", "h1") 19 h1.Set("innerHTML", "Hello Webassembly!") 20 21 // h1をbodyに追加します 22 body.Call("appendChild", h1) 23 24 // プログラムが終了しないように待機します 25 select {} 26 27}

sin波を生成するJavaScript

JavaScript

1// デフォルト設定でAudioContextを取得 2let ctx = new AudioContext() 3// 基本的な音を発するオシレータのAudioNodeをコンテキストの中に作成 4// 周波数は440Hz、波形はサイン波 5let osc = new OscillatorNode(ctx) 6osc.frequency = 440 7osc.type = "sine" 8// オシレータノードの出力をコンテキストのスピーカーに接続 9osc.connect(ctx.destination) 10// オシレータの処理を開始 11osc.start()
上のJavaScriptを自分で書き換えてみたもの

Go

1package main 2 3import ( 4 "syscall/js" 5) 6 7func main() { 8 // グローバルオブジェクト(window)を取得します 9 window := js.Global() 10 11 // document オブジェクトを取得します 12 document := window.Get("document") 13 14 // bodyを取得します 15 body := document.Get("body") 16 17 ctx := js.Global().Get("AudioContext").New() 18 osc := js.Global().Get("OscillatorNode").New(ctx) 19 js.Global().Set("osc.frequency", 440) 20 js.Global().Set("osc.type", "sine") 21 js.Global().Get("document").Call("osc.connect", ctx.destination) 22 osc = js.Global().Get("document").Call("start") 23 body.Call("appendChild", osc) 24 25 26 27 // プログラムが終了しないように待機します 28 select {} 29 30}

しかしこの状態でビルドを行うと.

terminal

1MacBook-Air src % GOOS=js GOARCH=wasm go build -o ../docs/build.wasm 2# sound_wav 3./webassembly.go:21:54: ctx.destination undefined (type js.Value has no field or method destination)

のようになってしまい,成功しません.

https://zenn.dev/nobonobo/books/85e605893d44ebe7dd3f/viewer/b5ac64d9135e123e367a
このサイトや
ドキュメントの
https://pkg.go.dev/syscall/js
を読んではみたのですが,二つ目の引数はどのようにすればいいのかわかりませんでした.

教えてほしいこと

エラー部分の二つ目の引数の適切な入れ方.

追記

フィールド名にドットを使ってはいけない,フィールドはjs.Valueのものを使って取得する,とのご指摘を受けたので,考えた範囲で修正いたしました.

webassembly.go

Go

1package main 2 3import ( 4 "syscall/js" 5) 6 7func main() { 8 // グローバルオブジェクト(window)を取得します 9 window := js.Global() 10 11 // document オブジェクトを取得します 12 document := window.Get("document") 13 14 // bodyを取得します 15 body := document.Get("body") 16 17 ctx := js.Global().Get("AudioContext").New() 18 osc := js.Global().Get("OscillatorNode").New(ctx) 19 osc.Set("frequency", 440) 20 osc.Set("type", "sine") 21 osc.Call("connect", ctx.Get("destination")) 22 osc.Call("start") 23 body.Call("appendChild", osc) 24 25 26 27 // プログラムが終了しないように待機します 28 select {} 29 30}

ビルドとmain.goの実行まではエラーが出ることなく進んだのですが,ブラウザで対象の場所に行っても音は鳴りませんでした.
ブラウザ上でコンソールを確認してみると以下のようなエラー文が出ていました.

console

1The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page. https://goo.gl/7K7WLu 2syscall/js.valueNew @ wasm_exec.js:383 3Promise.then (async) 4(anonymous) @ (index):7 5 6wasm_exec.js:349 The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page. https://goo.gl/7K7WLu 7syscall/js.valueCall @ wasm_exec.js:349 8Promise.then (async) 9(anonymous) @ (index):7 10wasm_exec.js:22 panic: JavaScript error: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. 11wasm_exec.js:22 12wasm_exec.js:22 goroutine 1 [running]: 13wasm_exec.js:22 syscall/js.Value.Call({{}, 0x7ff800010000000a, 0x410028}, {0x114ef, 0xb}, {0x438f68, 0x1, 0x1}) 14wasm_exec.js:22 /usr/local/go/src/syscall/js/js.go:389 +0x31 15wasm_exec.js:22 main.main() 16wasm_exec.js:22 /Users/usrname/Go/make_sound/01/src/sound_wav/src/webassembly.go:23 +0xd 17 18wasm_exec.js:101 exit code: 2 19exit @ wasm_exec.js:101 20Promise.then (async) 21(anonymous) @ (index):7

最初のエラーについて調べてみたのですが
https://www.wizard-notes.com/entry/javascript/web-audio-api-chrome-user-interaction
のようなサイトを見るに,音を自動再生するのがchromeでは禁止されているのでnew AudioContextをしようというものとなっていました.
しかし,一応上のコードではインスタンスの作成はしていますし,Goを経由しない時には音が出せていたので,コードの構成上の問題ではなく,インスタンスの作り方が間違っているということなのでしょうか.

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2022/10/18 12:49

直接の原因かどうかはわかりませんが js.Global().Get("docuemnt") は js.Global().Get("document") ではないでしょうか
aaaa____

2022/10/18 12:53

ご指摘ありがとうございます,確かにスペルが間違っているようでした. エラー内容は変わらなかったので直接的な原因ではなかったようですが,後々困ることになっていた要因だと思いますので,助かりました.ありがとうございます. 質問では修正させていただこうと思います.
guest

回答1

0

ベストアンサー

  • js.Value.Get(フィールド名)
  • js.Value.Set(フィールド名, 値)
  • js.Value.Call(フィールド名, params...)

これらのフィールド名にドット混じりは使えません。

osc := js.Global().Get("OscillatorNode").New(ctx)にてoscオブジェクトを取得したのであればそれを使ってください。

osc.Set("frequency", 440)

追記

html

1<script> 2 // デフォルト設定でAudioContextを取得 3 let ctx = new AudioContext(); 4 // 基本的な音を発するオシレータのAudioNodeをコンテキストの中に作成 5 // 周波数は440Hz、波形はサイン波 6 let osc = new OscillatorNode(ctx); 7 osc.frequency = 440; 8 osc.type = "sine"; 9 // オシレータノードの出力をコンテキストのスピーカーに接続 10 osc.connect(ctx.destination); 11 // オシレータの処理を開始 12 osc.start(); 13</script>

以上の記述のHTMLをブラウザで表示しようとすると、
The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page.
この表記がコンソールログに出力されます。

「Goを経由しない時には音が出せていたので」というのとは合致しません。
音が出せていた時と状況が異なるようです。

ただ、僕がWebAudioを試しているときもこの条件でpanicが起きるので、
panicが起きちゃうとWASMインスタンスは終了してアクセス不能になります。
必ずユーザー操作イベントに紐づけてWebAudioの利用を開始するようにしていました。

実装例

https://zenn.dev/nobonobo/articles/42d999fee88a1d

投稿2022/10/19 04:13

編集2022/10/20 13:18
nobonobo

総合スコア3367

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

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

nobonobo

2022/10/19 04:16

例えば、`js.Global().Get("document").Call("start")` documentにstart関数などは存在しないです。
aaaa____

2022/10/19 11:41 編集

いくつか質問させていただきたいです. ・Getの引数のdocumentは,syscall/jsの公式ドキュメントのことで,documentにある関数とは,ドキュメントのサイトに書かれているもの,という認識でよろしいでしょうか. ・フィールド名にドット混じりは使えないことは了解しましたのですが,第二引数のctx.destinationの部分はどのように対処すれば良いのでしょうか.oscの要素とは異なるので,ctxを省略することはできないとは思うのですが,どのように回避するのか現状まだ理解できておりません. だめ元で"ctx.destination"としてみましたが,ビルドは通ってもhttp://localhost:8080/に訪れても音は鳴りませんでした. フィールド名を修正したものを追記として質問内容に加えさせていただいております.
nobonobo

2022/10/20 06:59 編集

「JSのあらゆる値」をGoの世界に持ち込むとすべて「js.Value型」にラップされます。 これは「JSの値」のリモコンのようなものです。 コツはJS世界ではこう、Goの世界ではこうという鏡の世界の表と裏のようなイメージを持つとよいです。 「document」が持っているフィールドやメソッドについてはブラウザのJS用リファレンスをみてください。 Go側のリファレンスには「document」の解説はありません。 まずJSのやり方を把握し、それをGoで書くためにはどう書くかという視点で syscall/jsのドキュメントをみましょう。 この道を進むにはJSの知識とGoの知識の両方が結局のところ必要です。 (だからJSだけで書くほうが手っ取り早く目的にたどり着ける) GoではJSのフィールドにGoのやり方でアクセスすることはできません。 (そもそもよそから持ってきたオブジェクトの小文字のフィールドはGoではプライベート扱いなのでアクセスはできません) js.Valueの操作を通じてJSにおけるオブジェクトのフィールドを取得してください。 「ビルドは通っても」とありますが、ビルドにエラーになるはずなのですがそうでないのには何か理由がありますか?
nobonobo

2022/10/20 07:05

ああ、もしかして文字列で渡してみたということですか。 「osc.Call("connect", "ctx.destination")」これをJSに戻すと「osc.connect("ctx.destination")」となります。これはJS側でエラーが発生しているのではないでしょうか。(ブラウザのコンソールログを確認する習慣を持ちましょう)
aaaa____

2022/10/20 11:20

丁寧な解説ありがとうございます. 文字列で渡したという認識で正しいです.ブラウザのコンソールを確認すべきなのに気がつきませんでした.JavaScriptを制御しているので当然のことでした.申し訳ございません. >>js.Valueの操作を通じてJSにおけるオブジェクトのフィールドを取得してください。 に対して, osc.Call("connect", ctx.Get("destination")) と対処してみたところ,テキストエディタ側では問題なくビルドすることができたので,main.goを実行してブラウザを開いたのですが音は鳴らず,コンソールを確認してみるとエラーが確認されました. 返信欄でのコード埋め込みの方法がわからないので質問内容に追記のような形で載せておこうと思います.
aaaa____

2022/10/20 12:40

>>>ユーザー操作イベントに紐づけてWebAudioの利用を開始するようにしていました というのはやはりサイトにもあるように,ボタンを押して音を再生するのようなことですよね.少し試してみることにします. 音が鳴らないことに関しましては,Chromeの性質のことからくることなのか,それとも私が修正した引数の入れ方やコードが間違っているのどちらになるのでしょうか. エラーが出ていることからコードが不適切なのではと感じるのですが,ここからどこを修正できるのか検討がついておりません. こちらの手元で「Goを経由しないやり方」としましては,参考にしていたサイトのやり方にのっとり, どこかのサイトで開発者ツールを開いてそのコンソール上に以下のコードを記述してエンターを押すというもので,実際に今こちらの手元でやってみても問題なく「ぽー」という音が鳴るのですが.これはおかしいやり方なのでしょうか. // デフォルト設定でAudioContextを取得 let ctx = new AudioContext() // 基本的な音を発するオシレータのAudioNodeをコンテキストの中に作成 // 周波数は440Hz、波形はサイン波 let osc = new OscillatorNode(ctx) osc.frequency = 440 osc.type = "sine" // オシレータノードの出力をコンテキストのスピーカーに接続 osc.connect(ctx.destination) // オシレータの処理を開始 osc.start()
nobonobo

2022/10/20 13:05

それはエンターを押すのを契機に動き始めているのでうごきます。 これはユーザビリティや安全のためにブラウザの実装がこうなっています。サイトを開いただけで音が鳴ったり録音されたりすると不快だったり悪用されかねませんので。
nobonobo

2022/10/20 13:19 編集

ユーザー操作を契機にならし始めるなら動作します。一度でもこれを突破すれば以後は任意のタイミングで鳴らせます。
nobonobo

2022/10/20 13:22 編集

追記のサンプルではゲームスタートボタンでいったん音を鳴らしていますが、これが必要です。 その後はプログラムの条件で音が鳴らせます。
aaaa____

2022/10/21 11:19

カウントアップに使っていたコードを用いて試験的に,次のようにしたらきちんと音が鳴ってくれました. こちらの理解不足により長い時間をかけさせてしまい申し訳ありません, とても助かりました,ありがとうございます. ``` package main import ( "syscall/js" "fmt" "strconv" ) func main() { // グローバルオブジェクト(window)を取得します window := js.Global() // document オブジェクトを取得します document := window.Get("document") // bodyを取得します body := document.Get("body") // p のDOMを作成します counter := 0 p := document.Call("createElement", "p") p.Set("id", "counter") p.Set("innerHTML", strconv.Itoa(counter)) // ボタンのDOMを作成し、Clickイベントを設定します btn := document.Call("createElement", "button") btn.Set("textContent", "count up!") btn.Call("addEventListener", "click", js.FuncOf(func(js.Value, []js.Value) interface{} { counter++ fmt.Println(counter) // console.logに出力します ctx := js.Global().Get("AudioContext").New() osc := js.Global().Get("OscillatorNode").New(ctx) osc.Set("frequency", 440) osc.Set("type", "sine") osc.Call("connect", ctx.Get("destination")) osc.Call("start") body.Call("appendChild", osc) return nil })) // pをbodyに追加します body.Call("appendChild", p) // ボタンをbodyに追加します body.Call("appendChild", btn) // プログラムが終了しないように待機します select {} } ```
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問