I asked a very similar question a few weeks ago, trying to bind a UI control to a web audio AudioParam object in a reactive manner. The only thing I could make work reliably was using a getter/setter on my model object. That wasn't too bad when not using the composition API. Here's that in action:
class MyApp {
constructor() {
// core model which I'd prefer to bind to
this.audio = new AudioContext();
this.audioNode = this.audio.createGain();
this.audioNode.gain.value = .8; // want to bind a control to this
// attempts to add reactivity
this.reactiveWrapper = Vue.reactive(this.audioNode.gain);
this.refWrapper = Vue.ref(this.audioNode.gain.value);
}
get gainValue() { return this.audioNode.gain.value; }
set gainValue(value) { this.audioNode.gain.value = value; }
}
let appModel = new MyApp();
let app = Vue.createApp({
template: '#AppView',
data() { return { model: appModel } }
});
app.mount('#mount');
<script type='text/x-template' id='AppView'>
<div>
model.audioNode.gain.value: {{model.audioNode.gain.value}}
</div>
<hr>
<div>
<div>Binding to getter/setter (works)</div>
<input type='range' min='0' max='1' step='.1' v-model='model.gainValue'>
</div>
<div>
<div>Binding directly to <code>model.audioNode.gain.value</code> (doesn't work)</div>
<input type='range' min='0' max='1' step='.1' v-model='model.audioNode.gain.value'>
</div>
<div>
<div>Binding to <code>reactive(model.audioNode.gain)</code> (doesn't work)</div>
<input type='range' min='0' max='1' step='.1' v-model='model.reactiveWrapper.value'>
</div>
<div>
<div>Binding to <code>ref(model.audioNode.gain.value)</code> (doesn't work)</div>
<input type='range' min='0' max='1' step='.1' v-model='model.refWrapper.value'>
</div>
</script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id='mount'></div>
Now I'm trying to use the composition API in a SFC (single file component). I'm having the same issue, but this time the solution requires even more boilerplate. Unfortunately this can't be a runnable snippet, so you'll have to take my word that, exactly as in the example above, trying to use reactive on the AudioParam doesn't work:
<script setup>
import { reactive, computed } from "vue";
let audio = new AudioContext();
let volume = audio.createGain(); // I want to bind to this
// this doesn't work
let reactiveGain = reactive(volume.gain);
// this also doesn't work
const gainComputed = computed({
get: () => volume.gain.value,
set: val => volume.gain.value = val,
})
// This does.
// Is it possible to achieve this effect without all this boilerplate?
// Also, such that I can still use `v-model` in my template?
class Model {
set gain(value) { volume.gain.value = value; }
get gain() { return volume.gain.value; }
}
let model = reactive(new Model());
</script>
<template>
<div>
Volume: {{ model.gain.toFixed(2) }}
</div>
<div>
This works
<input type="range" min="0" max="1" step=".01" v-model="model.gain">
</div>
<div>
This doesn't
<input type="range" min="0" max="1" step=".01" v-model="reactiveGain.value">
</div>
<div>
This doesn't
<input type="range" min="0" max="1" step=".01" v-model="gainComputed">
</div>
</template>
Is there a better way to reactively bind to objects outside of Vue?