{"id":20043108,"url":"https://github.com/jinksi/webvr-synth-tutorial","last_synced_at":"2026-02-03T19:03:56.294Z","repository":{"id":122494049,"uuid":"91313012","full_name":"Jinksi/webvr-synth-tutorial","owner":"Jinksi","description":"Tutorial: creating a WebVR musical instrument using A-Frame \u0026 Tone.js","archived":false,"fork":false,"pushed_at":"2017-05-15T10:03:01.000Z","size":8,"stargazers_count":17,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-23T20:07:16.399Z","etag":null,"topics":["a-frame","tonejs","tutorial","webaudio","webvr"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Jinksi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-15T08:24:31.000Z","updated_at":"2024-09-25T22:50:43.000Z","dependencies_parsed_at":null,"dependency_job_id":"c5b9aefd-fa69-4a51-9baa-36ee40e47671","html_url":"https://github.com/Jinksi/webvr-synth-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Jinksi/webvr-synth-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jinksi%2Fwebvr-synth-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jinksi%2Fwebvr-synth-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jinksi%2Fwebvr-synth-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jinksi%2Fwebvr-synth-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Jinksi","download_url":"https://codeload.github.com/Jinksi/webvr-synth-tutorial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jinksi%2Fwebvr-synth-tutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29054056,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T15:43:47.601Z","status":"ssl_error","status_checked_at":"2026-02-03T15:43:46.709Z","response_time":96,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["a-frame","tonejs","tutorial","webaudio","webvr"],"created_at":"2024-11-13T10:55:00.411Z","updated_at":"2026-02-03T19:03:56.276Z","avatar_url":"https://github.com/Jinksi.png","language":"HTML","readme":"# Creating a WebVR musical instrument using A-Frame \u0026 Tone.js\n\nWeb browsers are incredibly powerful today and have APIs for VR devices, 3D rendering, audio synthesis and MIDI I/O. On top of this, the Open Source community has built libraries to enhance these APIs and to help us get ideas out easier. I've been exploring Virtual Reality as a new medium for interacting and experiencing audio/visual art using WebVR. This tutorial will show you the basics of getting up and running with audio synthesis in WebVR.\n\n#### A-Frame\n\n[A-Frame](https://aframe.io) is a Virtual Reality framework for the web, built by the Mozilla VR Team. A-Frame handles the 3D and WebVR boilerplate required to get running across platforms including mobile, desktop, Vive, and Rift.\n\nWe will use A-Frame to create the VR scene and interface of our instrument.\n\n#### Tone.js\n\n[Tone.js](https://tonejs.github.io) is a WebAudio framework for creating interactive music in the browser. Tone's API is designed to be familiar to musicians, allowing control of note pitch \u0026 duration, timeline controls ( playback \u0026 bpm ), sequencing and audio routing. It also provides DSP modules to build your own synthesizers, effects, and complex control signals.\n\nWe'll use Tone to control the audio of our instrument, including the synthesizer, effects and routing.\n\n#### What we will be making\n\nWe’re going to keep it simple and create a sort of 'Hello World' of A-Frame + Tone.js. We'll make rings that will each play a note, triggered on cursor hover. We’ll create our own A-Frame component that will contain our Tone.js logic.\n\n## Getting Started\n\nTo start with, we’ll to set up a HTML page and import both A-Frame and Tone.js. Inside the body, we set up the A-Frame scene with a sky, camera and cursor.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\n\u003chead\u003e\n  \u003cmeta charset=\"utf-8\"\u003e\n  \u003ctitle\u003eWebVR Musical Instrument\u003c/title\u003e\n  \u003c!-- A-Frame CDN --\u003e\n  \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/aframe/0.5.0/aframe.min.js\" charset=\"utf-8\"\u003e\u003c/script\u003e\n  \u003c!-- Tone.js CDN --\u003e\n  \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/tone/0.10.0/Tone.min.js\" charset=\"utf-8\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n  \u003c!-- A-Frame scene --\u003e\n  \u003ca-scene antialias=\"true\"\u003e\n    \u003c!-- plain white background --\u003e\n    \u003ca-sky color=\"white\"\u003e\u003c/a-sky\u003e\n    \u003c!-- our scene's camera --\u003e\n    \u003ca-camera position=\"0 0 4\" user-height=\"0\" wasd-controls=\"enabled: false\"\u003e\n      \u003c!-- cursor with fuse enabled, allowing us to detect when it hovers over an entity --\u003e\n      \u003ca-cursor fuse=\"true\"\u003e\u003c/a-cursor\u003e\n    \u003c/a-camera\u003e\n\n  \u003c/a-scene\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e\n```\n\nIf you view this in your browser, you should see a white screen with a cursor and the 'Enter VR' button.\n\n*A-Frame \u0026 Tone.js support modern browsers, but Chrome is recommended.*\n\n## The Interface\n\nNow for the interface of our instrument, we create an `\u003ca-ring /\u003e` and set it's radius, color and segments-theta ( smooths out the edge ). We'll wrap it in an empty entity and rotate it backwards on the X axis.\n\nThe `synth` attribute references our synth component, which we will get to shortly.\n\n```html\n\u003c!-- We wrap our three rings in an entity and rotate it backwards on the X axis --\u003e\n\u003ca-entity id=\"interface\" rotation=\"-45 0 0\"\u003e\n  \u003c!-- The entity that will play the note --\u003e\n  \u003ca-ring\n    synth=\"note: A4\"\n    radius-inner=\"0.2\"\n    radius-outer=\"0.6\"\n    color=\"#212121\"\n    segments-theta=\"64\"\n  \u003e\u003c/a-ring\u003e\n\u003ca-entity\u003e\n```\n\n#### Visual feedback\n\nNow we want the ring to react when the cursor hovers over it. There are more than a few ways to achieve this, but the simplest is to add an `\u003ca-animation\u003e` that will begin when we hover over the ring.\n\nInside the ring, we add an `\u003ca-animation\u003e` and set it's `begin` attribute to the `fusing` event. The animation will instantly set the ring's opacity to 0.5 and fade it back in over 500ms.\n\n```html\n\u003ca-ring\n  synth=\"note: A4\"\n  radius-inner=\"0.2\"\n  radius-outer=\"0.6\"\n  color=\"#212121\"\n  segments-theta=\"64\"\n\u003e\n  \u003c!-- This animation will be triggered when the cursor starts 'fusing' ( hovering ) --\u003e\n  \u003ca-animation\n    begin=\"fusing\"\n    attribute=\"opacity\"\n    dur=\"500\"\n    from=\"0.5\"\n    to=\"1\"\n  \u003e\u003c/a-animation\u003e\n\u003c/a-ring\u003e\n```\n\n## The Synthesizer\n\nOur synth is going to be a custom A-Frame component. This will live in a `synth-component.js` that we will reference in our document `\u003chead\u003e` underneath our external libraries.\n\n``` html\n\u003chead\u003e\n  \u003cmeta charset=\"utf-8\"\u003e\n  \u003ctitle\u003eWebVR Musical Instrument\u003c/title\u003e\n  \u003c!-- A-Frame CDN --\u003e\n  \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/aframe/0.5.0/aframe.min.js\" charset=\"utf-8\"\u003e\u003c/script\u003e\n  \u003c!-- Tone.js CDN --\u003e\n  \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/tone/0.10.0/Tone.min.js\" charset=\"utf-8\"\u003e\u003c/script\u003e\n  \u003c!-- Our Synth Component --\u003e\n  \u003cscript src=\"synth-component.js\" charset=\"utf-8\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n```\n\nIn `synth-component.js`, we will create a Tone.js Synth. We route the signal of the synth's output to master.\n\n```js\n// The synth\nconst synth = new Tone.Synth({\n  volume: -12, // the oscillator volume set to -12dB\n  oscillator: {\n    type: 'square' // oscillator type to square wave\n  },\n  envelope: {\n    attack: 0.02, // envelope attack set to 20ms\n    release: 1 // envelope release set to 1s\n  }\n}).toMaster() // connect the synth's output to the filter\n\n// tell the synth to play a the note C3 for the duration of an eight note\nsynth.triggerAttackRelease('C3', '8n')\n```\n\nIn your browser, the note should play on load.\n\n#### Connecting to A-Frame\n\nNow to tie it in to A-Frame, we will create an A-Frame component using `AFRAME.registerComponent()`. We attach a component to an entity and pass arguments using the component name as an attribute e.g. `\u003ca-ring synth=\"note: A4\" /\u003e`. Find more about components in the [A-Frame docs](https://aframe.io/docs/0.5.0/core/component.html).\n\nOur synth component takes 2 arguments, note and duration. It triggers a synth note when the `fusing` event is activated.\n\n```js\n// Our customer synth component\nAFRAME.registerComponent('synth', {\n  // The schema defines arguments accepted by this component\n  schema: {\n    // The note / octave\n    note: {\n      type: 'string',\n      default: 'C4'\n    },\n    // The duration: 8n describes an eighth note\n    duration: {\n      type: 'string',\n      default: '8n'\n    }\n  },\n  init: function() {\n    // setup the fusing/hover event listener\n    // this.el refers to the entity\n    this.el.addEventListener('fusing', this.trigger.bind(this))\n  },\n  trigger: function() {\n    // trigger a note on the synth\n    // this.data refers to the arguments defined\n    synth.triggerAttackRelease(this.data.note, this.data.duration)\n  },\n  update: function() {},\n  tick: function() {},\n  remove: function() {},\n  pause: function() {},\n  play: function() {}\n})\n```\n\nNow our ring entity will play a note when the cursor hovers over. Try changing the note or duration: `\u003ca-ring synth=\"note: A4; duration: 1n\" /\u003e` (*1n* is a whole note).\n\n#### Adding effects\n\nTone.js comes with [heaps](https://tonejs.github.io/docs/) of built-in audio effects. Let's route the synth through a lowpass filter and send it to a delay effect.\n\n```js\n// a FeedbackDelay effect, repeating every eighth note with 80% feedback\nconst delay = new Tone.FeedbackDelay('8n', 0.8)\n  // chained into a Volume set to -12dB then to the Master output\n  .chain(new Tone.Volume(-12), Tone.Master)\n\n// a lowpass Filter with a frequency of 1500 Hz\nconst filter = new Tone.Filter(1500, 'lowpass')\n  // the signal is sent to the Delay as well as Master\n  .connect(delay).toMaster()\n\n// The synth\nconst synth = new Tone.Synth({\n  volume: -12, // the oscillator volume set to -12dB\n  oscillator: {\n    type: 'square' // oscillator type to square wave\n  },\n  envelope: {\n    attack: 0.02, // envelope attack set to 20ms\n    release: 1 // envelope release set to 1s\n  }\n}).connect(filter) // connect the synth's output to the filter\n```\n\nTake note of the routing changes. The `synth` is no longer connected directly to the master output. Instead, we connect it to `filter` using `connect(filter)`. The output of `filter` is sent to `delay` as well as the master output. We reduce the volume of `delay` by chaining it through a `Tone.Volume`, then to the master output.\n\n## Adding more rings\n\nLet's add more rings to our instrument. Inside our `#interface` entity, we'll add 2 more rings, each will have a larger radius and note. We'll copy the same opacity animation as a child of each ring.\n\n```html\n\u003c!-- Ring 2 --\u003e\n\u003ca-ring synth=\"note: E4\" radius-inner=\"0.8\" radius-outer=\"1.2\" color=\"#212121\" segments-theta=\"64\"\u003e\n  \u003ca-animation begin=\"fusing\" attribute=\"opacity\" dur=\"500\" from=\"0.5\" to=\"1\"\u003e\u003c/a-animation\u003e\n\u003c/a-ring\u003e\n\n\u003c!-- Ring 3 --\u003e\n\u003ca-ring synth=\"note: F3\" radius-inner=\"1.4\" radius-outer=\"1.8\" color=\"#212121\" segments-theta=\"64\"\u003e\n  \u003ca-animation begin=\"fusing\" attribute=\"opacity\" dur=\"500\" from=\"0.5\" to=\"1\"\u003e\u003c/a-animation\u003e\n\u003c/a-ring\u003e\n```\n\n## Viewing in a VR Headset\n\nIf you are lucky enough to have access to a VR headset, check out [webvr.info](https://webvr.info) to make get your browser setup to work with the headset. I've tested this example in Chromium \u0026 Firefox Nightly with an Oculus Rift on Windows 10. Chromium reproduced it as expected, Firefox didn't seem to reproduce the opacity effect.\n\nVR support in browsers is experimental at the time of writing, so you will probably run into inconsistencies across browsers and devices.\n\n## What's next?\n\nAs it is, this instrument is pretty boring. It can only play 3 notes, has a delay effect, has no dynamic control and can only be interacted with the cursor. There are hundreds of interactive possibilities with VR, including touch/push, controller buttons, head position/rotation, hand position/rotation and microphone input. Then there are endless combinations of audio synthesis sound sources, effects, notes and rhythmic ideas. I hope this tutorial gets you started exploring and creating your own musical instruments in VR.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjinksi%2Fwebvr-synth-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjinksi%2Fwebvr-synth-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjinksi%2Fwebvr-synth-tutorial/lists"}