diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/README.md b/extensions/2.0/Khronos/KHR_audio_emitter/README.md new file mode 100644 index 0000000000..cf256574f8 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/README.md @@ -0,0 +1,371 @@ +# KHR_audio_emitter + +## Contributors + +- Robert Long, Element Inc. +- Anthony Burchell, Individual Contributor +- K. S. Ernest (iFire) Lee, Individual Contributor +- Michael Nisbet, Individual Contributor +- humbletim, Individual Contributor +- Norbert Nopper, UX3D [@UX3DGpuSoftware](https://twitter.com/UX3DGpuSoftware) +- Aaron Franke, The Mirror Megaverse Inc. + +## Status + +Draft + +## Dependencies + +Written against the glTF 2.0 spec. + +## Overview + +This extension allows for the addition of spatialized and non-spatialized audio to glTF scenes. + +Audio emitter objects may be added to 3D nodes for positional audio or to the scene for environmental or ambient audio such as background music. + +### Example: + +```json +{ + "extensions": { + "KHR_audio_emitter": { + "emitters": [ + { + "name": "Positional Emitter", + "type": "positional", + "gain": 0.8, + "sources": [0, 1], + "positional": { + "coneInnerAngle": 6.283185307179586, + "coneOuterAngle": 6.283185307179586, + "coneOuterGain": 0.0, + "distanceModel": "inverse", + "maxDistance": 10.0, + "refDistance": 1.0, + "rolloffFactor": 0.8 + } + }, + { + "name": "Global Emitter", + "type": "global", + "gain": 0.8, + "sources": [1] + } + ], + "sources": [ + { + "name": "Clip 1", + "gain": 0.6, + "autoPlay": true, + "loop": true, + "audio": 0 + }, + { + "name": "Clip 2", + "gain": 0.6, + "autoPlay": true, + "loop": true, + "audio": 1 + } + ], + "audio": [ + { + "uri": "audio1.mp3" + }, + { + "bufferView": 0, + "mimeType": "audio/mpeg" + } + ] + } + }, + "scenes": [ + { + "name": "Default Scene", + "extensions": { + "KHR_audio_emitter": { + "emitters": [1] + } + } + } + ], + "nodes": [ + { + "name": "Duck", + "translation": [1.0, 2.0, 3.0], + "extensions": { + "KHR_audio_emitter": { + "emitter": 0 + } + } + } + ] +} +``` + +## glTF Schema Updates + +This extension consists of three primary data structures: Audio Data, Audio Sources, and Audio Emitters. Data, sources and emitters are defined on an `KHR_audio_emitter` object added to the `extensions` object on the document root. + +The extension must be added to the file's `extensionsUsed` array and because it is optional, it does not need to be added to the `extensionsRequired` array. + +#### Example: + +```json +{ + "asset": { + "version": "2.0" + }, + "extensionsUsed": [ + "KHR_audio_emitter" + ], + "scenes": [...], + "nodes": [...], + "extensions": { + "KHR_audio_emitter": { + "audio": [...], + "sources": [...], + "emitters": [...] + } + } +} +``` + +### Audio Data + +Audio data objects define where audio data is located and what format the data is in. The data is either accessed via a bufferView or uri. + +When storing audio data in a buffer view, the `mimeType` field must be specified. The base specification supports `audio/mpeg` and `audio/wav` MIME types. These were chosen with consideration for the wide support for these types acrosss 3D engines and common use cases. Other supported audio formats may be added via extensions. + +Note that in tools that process glTF files, but do not implement the `KHR_audio_emitter` extension, external files referenced via the `uri` field may not be properly copied to their final destination or baked into the final binary glTF file. In these cases, using the `bufferView` property may be a better choice assuming the referenced `bufferView` index is not changed by the tool. The `uri` field might be a better choice when you want to be able to quickly change the referenced audio asset. + +#### `bufferView` + +The index of the bufferView that contains the audio data. Use this instead of the audio source's uri property. + +#### `mimeType` + +The audio's MIME type. Required if `bufferView` is defined. Unless specified by another extension, the only supported mimeType is `audio/mpeg`. + +#### `uri` + +The uri of the audio file. Relative paths are relative to the .gltf file. + +### Audio Sources + +Audio sources reference audio data and define playback properties for it. Audio sources may be used by zero to many audio emitters. + +#### Property Summary + +| | Type | Description | Default value | +| ------------ | --------- | --------------------------------------------------------------------------------------------------------- | -------------------- | +| **gain** | `number` | Unitless linear multiplier against original audio file volume used for determining audio source loudness. | 1.0 | +| **loop** | `boolean` | Whether or not to loop the specified audio when finished. | false | +| **autoPlay** | `boolean` | Whether or not to play the specified audio when the glTF is loaded. | false | +| **audio** | `number` | The index of the audio data assigned to this clip. | Required, no default | + +#### `gain` + +Unitless linear multiplier against original audio file volume used for determining audio source loudness. If not specified, the audio source volume gain is `1.0`. + +This value is linear, a value of `0.0` is no volume, `0.5` is half volume, `1.0` is the original volume, `2.0` is double the volume, etc. The final volume of the audio is a combination of this value, the audio emitter's gain, and if the audio emitter is positional, the relative positions of the emitter and listener. + +#### `loop` + +Whether or not to loop the specified audio when finished. If `false` or not specified, the audio source does not loop. + +#### `autoPlay` + +Whether or not to play the specified audio when the glTF is loaded. If `false` or not specified, the audio source does not play automatically. + +#### `audio` + +The index of the audio data in the "audio" array assigned to this audio source. This property is required. + +### Audio Emitter + +Audio emitters define how audio sources are played back. Emitter properties are defined at the document level and are references by nodes. Audio may be played globally or positionally. Positional audio has further properties that define how audio volume scales with distance and angle. + +#### Property Summary + +| | Type | Description | Default value | +| -------------- | ---------- | --------------------------------------------------------------------------------------------------- | -------------------- | +| **type** | `string` | The emitter type of this audio emitter: `global` or `positional`. | Required, no default | +| **gain** | `number` | Unitless linear multiplier against audio source volume used for determining audio emitter loudness. | 1.0 | +| **sources** | `number[]` | An array of audio source indices used by the audio emitter. This array may be empty. | [] | +| **positional** | `object` | A sub-JSON containing the positional audio emitter properties. | {} | + +#### `type` + +Specifies the audio emitter type. This property is required. + +- `global` Global audio emitters are not affected by the position of audio listeners. All `positional` properties may not be defined on global audio emitters. +- `positional` Positional audio emitters play audio at a position in the scene. The properties are defined in the `positional` object. Using sound cones, the orientation is `-Z` having the same emission direction as [`KHR_lights_punctual`](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_lights_punctual) and [glTF cameras](https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_016_Cameras.md). + +#### `gain` + +Unitless linear multiplier against audio source volume used for determining audio emitter loudness. If not specified, the audio emitter volume gain is `1.0`. + +This value is linear, a value of `0.0` is no volume, `0.5` is half volume, `1.0` is the original volume, `2.0` is double the volume, etc. The final volume of the audio is a combination of this value, the audio source's gain, and if the audio emitter is positional, the relative positions of the emitter and listener. + +#### `sources` + +An array of audio source indices used by the audio emitter. This array may be empty. If empty or not specified, this emitter can be used to define how audio should emit from a node, but not which audio source to play. + +#### `positional` + +A sub-JSON object containing the positional audio emitter properties. This may only be defined if `type` is set to `positional`. + +### Positional Audio Emitter Properties + +When the audio emitter type is set to `positional`, additional properties may be defined on the `positional` object. + +#### Property Summary + +| | Type | Description | Default value | +| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| **coneInnerAngle** | `number` | The anglular diameter of a cone inside of which there will be no angular volume reduction. | 6.2831853... (τ or 2π rad, 360 deg) | +| **coneOuterAngle** | `number` | The anglular diameter of a cone outside of which the volume will be reduced to a constant value of `coneOuterGain`. | 6.2831853... (τ or 2π rad, 360 deg) | +| **coneOuterGain** | `number` | The linear volume gain of the audio emitter set when outside the cone defined by the `coneOuterAngle` property. | 0.0 | +| **distanceModel** | `string` | Specifies the distance model for the audio emitter. | `"inverse"` | +| **maxDistance** | `number` | The maximum distance between the emitter and listener, beyond which the audio cannot be heard. | 0.0 | +| **refDistance** | `number` | A reference distance for reducing volume as the emitter moves further from the listener. | 1.0 | +| **rolloffFactor** | `number` | Describes how quickly the volume is reduced as the emitter moves away from listener. | 1.0 | + +#### `coneInnerAngle` + +The angle, in radians, of a cone inside of which there will be no volume reduction. This angle represents the angular "diameter" of the cone, from side to side. If not specified, the angle of Tau radians (`6.283185307179586476925286766559` or 360 degrees) is used, which means the audio emits in all directions (not in a cone). + +#### `coneOuterAngle` + +The angle, in radians, of a cone outside of which the volume will be reduced to a constant value of `coneOuterGain`. This angle represents the angular "diameter" of the cone, from side to side. If not specified, the angle of Tau radians (`6.283185307179586476925286766559` or 360 degrees) is used, which means some audio will emit in all directions. + +#### `coneOuterGain` + +The linear volume gain of the audio emitter set when outside the cone defined by the `coneOuterAngle` property. It is a linear value (not dB). If not specified, the cone outer gain is `0.0`, meaning the audio will be silent outside of the cone. + +#### `distanceModel` + +Specifies the distance model for the audio emitter. + +- `linear` A linear distance model calculating the gain induced by the distance according to: + `1.0 - rolloffFactor * (distance - refDistance) / (maxDistance - refDistance)` +- `inverse` (default) An inverse distance model calculating the gain induced by the distance according to: + `refDistance / (refDistance + rolloffFactor * (Math.max(distance, refDistance) - refDistance))` +- `exponential` An exponential distance model calculating the gain induced by the distance according to: + `pow((Math.max(distance, refDistance) / refDistance, -rolloffFactor))` + +#### `maxDistance` + +The maximum distance between the emitter and listener, after which the volume will not be reduced any further. If zero or not specified, the audio emitter does not have a maximum distance, and it can be heard from any distance. + +For the linear distance model, the max distance must be greater than the ref distance. For all distance models, max distance cannot be a negative number. + +#### `refDistance` + +A reference distance for reducing volume as the emitter moves further from the listener. For distances less than this, the volume is not reduced. This value cannot be zero or a negative number. If not specified, the default value is `1.0`. + +#### `rolloffFactor` + +Describes how quickly the volume is reduced as the emitter moves away from listener. When distanceModel is set to linear, the maximum value is `1.0`. Otherwise, there is no upper limit to the rolloff factor. If not specified, the default value is `1.0`. + +### Using Audio Emitters + +Audio emitters of type `global` may be added to scenes using the following syntax: + +```json +{ + "scenes": [ + { + "extensions": { + "KHR_audio_emitter": { + "emitters": [1, 3] + } + } + } + ] +} +``` + +Audio emitters of type `global` may be added to nodes using the following syntax: + +```json +{ + "nodes": [ + { + "extensions": { + "KHR_audio_emitter": { + "emitter": 1 + } + } + } + ] +} +``` + +Audio emitters of type `positional` may be added to nodes using the following syntax: + +```json +{ + "nodes": [ + { + "extensions": { + "KHR_audio_emitter": { + "emitter": 0 + } + } + } + ] +} +``` + +Note that multiple global audio emitters are allowed on the scene, but only a single audio emitter may be added to a node. + +### Audio Rolloff Formula + +The Audio Rolloff range is `(0.0, +∞)`. The default is `1.0`. + +The rolloff formula is dependant on the distance model defined. The available distance models are `linear`, `inverse`, and `exponential`. + +- linear formula: `1.0 - rolloffFactor * (distance - refDistance) / (maxDistance - refDistance)` +- inverse formula: `refDistance / (refDistance + rolloffFactor * (Math.max(distance, refDistance) - refDistance))` +- exponential formula: `pow((Math.max(distance, refDistance) / refDistance, -rolloffFactor))` + +### Audio Gain Units + +The gain unit range is `(0.0, +∞)`. The default is `1.0`. + +- gain formula: `originalVolume * gain` + +### Audio Cone Vizualized + +Audio cone showing how cone parameters impact volume based on relative distance to the source. + +Figure 1. A modified graphic based on the W3C Web Audio API Audio cone Figure + +The cone properties relate to the `PannerNode` interface and determine the amount of volume relative to a listeners position within the defined cone area. + +The gain relative to cone properties is determined in a similar way as described in the web audio api with the difference that this audio emitter extension uses radians in place of degrees. [Cone Gain Algorithm Example](https://webaudio.github.io/web-audio-api/#Spatialization-sound-cones) + +### Unit for Angles + +Radians are used for angles matching glTF2. + +### JSON Schema + +[glTF.KHR_audio_emitter.schema.json](/extensions/2.0/KHR_audio_emitter/schema/glTF.KHR_audio_emitter.schema.json) + +## Known Implementations + +- Third Room - https://github.com/thirdroom/thirdroom +- Three Object Viewer (WordPress Plugin) - https://wordpress.org/plugins/three-object-viewer/ +- UX3D Experimental C++ implementation - https://github.com/ux3d/OMI + +## Resources + +Prior Art: + +- [W3C Web Audio API](https://www.w3.org/TR/webaudio/) +- [MSFT_audio_emitter](https://github.com/KhronosGroup/glTF/pull/1400) +- [MOZ_hubs_components Audio](https://github.com/MozillaReality/hubs-blender-exporter/blob/04fc1d1/default-config.json#L298-L324) diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/boom_box.gltf b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/boom_box.gltf new file mode 100644 index 0000000000..fc2de836b5 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/boom_box.gltf @@ -0,0 +1,310 @@ +{ + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5126, + "count": 5546, + "max": [0.158739, 0.218841, 0.16122], + "min": [-0.158738, -0.093846, -0.16122], + "normalized": false, + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 5546, + "max": [1, 0.999997, 1, 1], + "min": [-0.999999, -0.99991, -1, 1], + "normalized": false, + "type": "VEC4" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 5546, + "max": [1, 1, 0.999977], + "min": [-1, -1, -1], + "normalized": false, + "type": "VEC3" + }, + { + "bufferView": 3, + "byteOffset": 0, + "componentType": 5126, + "count": 5546, + "max": [0.999893, 0.977859], + "min": [0.000656, 0.00322], + "normalized": false, + "type": "VEC2" + }, + { + "bufferView": 4, + "byteOffset": 0, + "componentType": 5125, + "count": 18108, + "max": [3586], + "min": [0], + "normalized": false, + "type": "SCALAR" + }, + { + "bufferView": 5, + "byteOffset": 0, + "componentType": 5126, + "count": 1512, + "max": [0.158767, 0.147275, 0.161401], + "min": [-0.158767, -0.093846, -0.160383], + "normalized": false, + "type": "VEC3" + }, + { + "bufferView": 6, + "byteOffset": 0, + "componentType": 5125, + "count": 1512, + "max": [1511], + "min": [0], + "normalized": false, + "type": "SCALAR" + }, + { + "bufferView": 7, + "byteOffset": 0, + "componentType": 5126, + "count": 486, + "max": [0.147321, 0.194401, 0.128095], + "min": [-0.147379, 0.075066, 0.019262], + "normalized": false, + "type": "VEC3" + }, + { + "bufferView": 8, + "byteOffset": 0, + "componentType": 5125, + "count": 486, + "max": [485], + "min": [0], + "normalized": false, + "type": "SCALAR" + } + ], + "asset": { + "copyright": "2023 The Khronos Group", + "extensions": { "KHR_xmp_json_ld": { "packet": 0 } }, + "generator": "Godot Engine v4.3.dev.custom_build", + "version": "2.0" + }, + "bufferViews": [ + { "buffer": 0, "byteLength": 66552, "byteOffset": 0 }, + { "buffer": 0, "byteLength": 88736, "byteOffset": 66552 }, + { "buffer": 0, "byteLength": 66552, "byteOffset": 155288 }, + { "buffer": 0, "byteLength": 44368, "byteOffset": 221840 }, + { "buffer": 0, "byteLength": 72432, "byteOffset": 266208 }, + { "buffer": 0, "byteLength": 18144, "byteOffset": 338640 }, + { "buffer": 0, "byteLength": 6048, "byteOffset": 356784 }, + { "buffer": 0, "byteLength": 5832, "byteOffset": 362832 }, + { "buffer": 0, "byteLength": 1944, "byteOffset": 368664 } + ], + "buffers": [{ "byteLength": 370608, "uri": "boom_box0.bin" }], + "extensions": { + "KHR_audio_emitter": { + "audio": [ + { + "extensions": { "KHR_xmp_json_ld": { "packet": 0 } }, + "mimeType": "audio/mpeg", + "uri": "kevin_macleod_bassa_island_loop.mp3" + } + ], + "emitters": [ + { + "gain": 0.3, + "name": "BoomBoxAudioEmitter", + "positional": { "refDistance": 1 }, + "sources": [0], + "type": "positional" + } + ], + "sources": [ + { + "audio": 0, + "autoPlay": true, + "loop": true, + "name": "kevin_macleod_bassa_island_loop" + } + ] + }, + "KHR_xmp_json_ld": { + "packets": [ + { + "@context": { "dc": "http://purl.org/dc/elements/1.1/" }, + "@id": "", + "dc:creator": { "@list": ["The Khronos Group"] }, + "dc:description": "Showcase of the KHR_audio_emitter and OMI_physics_body extensions", + "dc:format": "model/gltf+json", + "dc:publisher": { "@set": ["The Khronos Group"] }, + "dc:rights": "CC0 1.0 Universal", + "dc:title": "Boom Box with audio and physics" + }, + { + "@context": { "dc": "http://purl.org/dc/elements/1.1/" }, + "@id": "", + "dc:creator": { "@list": ["Kevin MacLeod"] }, + "dc:date": "2011", + "dc:description": "Bassa Island Game Loop by Kevin MacLeod", + "dc:format": "audio/mpeg", + "dc:identifier": "ISRC: USUAN1100840", + "dc:publisher": { "@set": ["Incompetech"] }, + "dc:rights": "CC BY 3.0: Attribution", + "dc:source": "https://incompetech.com/music/royalty-free/mp3-royaltyfree/Bassa%20Island%20Game%20Loop.mp3", + "dc:title": "Bassa Island Game Loop" + } + ] + }, + "OMI_physics_shape": { + "shapes": [ + { + "convex": { "mesh": 1 }, + "type": "convex" + }, + { + "convex": { "mesh": 2 }, + "type": "convex" + }, + { + "capsule": { "height": 0.04, "radius": 0.007 }, + "type": "capsule" + }, + { + "capsule": { "height": 0.199, "radius": 0.005 }, + "type": "capsule" + } + ] + } + }, + "extensionsUsed": [ + "GODOT_single_root", + "KHR_audio_emitter", + "KHR_xmp_json_ld", + "OMI_physics_body", + "OMI_physics_shape" + ], + "images": [ + { + "mimeType": "image/png", + "name": "boom_box_base_color", + "uri": "textures/boom_box_base_color.png" + }, + { + "mimeType": "image/png", + "name": "boom_box_orm", + "uri": "textures/boom_box_orm.png" + }, + { + "mimeType": "image/png", + "name": "boom_box_normal", + "uri": "textures/boom_box_normal.png" + }, + { + "mimeType": "image/png", + "name": "boom_box_emission", + "uri": "textures/boom_box_emission.png" + } + ], + "materials": [ + { + "emissiveFactor": [0, 0, 0], + "emissiveTexture": { "index": 3 }, + "extensions": {}, + "name": "BoomBox_Mat", + "normalTexture": { "index": 2, "scale": 1 }, + "occlusionTexture": { "index": 1 }, + "pbrMetallicRoughness": { + "baseColorFactor": [1, 1, 1, 1], + "baseColorTexture": { "index": 0 }, + "metallicFactor": 1, + "metallicRoughnessTexture": { "index": 1 }, + "roughnessFactor": 1 + } + } + ], + "meshes": [ + { + "extras": { "targetNames": [] }, + "primitives": [ + { + "attributes": { "NORMAL": 2, "POSITION": 0, "TANGENT": 1, "TEXCOORD_0": 3 }, + "indices": 4, + "material": 0, + "mode": 4 + } + ] + }, + { + "extras": { "targetNames": [] }, + "primitives": [{ "attributes": { "POSITION": 5 }, "indices": 6, "mode": 4 }] + }, + { + "extras": { "targetNames": [] }, + "primitives": [{ "attributes": { "POSITION": 7 }, "indices": 8, "mode": 4 }] + } + ], + "nodes": [ + { + "children": [1, 2, 3, 4, 5, 6], + "extensions": { "OMI_physics_body": { "motion": { "mass": 5, "type": "dynamic" } } }, + "name": "BoomBox" + }, + { + "mesh": 0, + "name": "BoomBoxMesh", + "rotation": [0, 1, 0, 0] + }, + { + "extensions": { + "KHR_audio_emitter": { "emitter": 0 } + }, + "name": "BoomBoxAudioEmitter" + }, + { + "extensions": { + "OMI_physics_body": { "collider": { "shape": 0 } } + }, + "name": "MainColliderShape", + "rotation": [0, 1, 0, 0] + }, + { + "extensions": { + "OMI_physics_body": { "collider": { "shape": 1 } } + }, + "name": "HandleColliderShape", + "rotation": [0, 1, 0, 0] + }, + { + "extensions": { + "OMI_physics_body": { "collider": { "shape": 2 } } + }, + "name": "AntennaBaseColliderShape", + "translation": [0.145, 0.085, 0.004] + }, + { + "extensions": { + "OMI_physics_body": { "collider": { "shape": 3 } } + }, + "name": "AntennaShaftColliderShape", + "rotation": [-0.4547680914402, 0, 0, 0.890609860420227], + "translation": [0.1445, 0.159, -0.0775] + } + ], + "samplers": [{ "magFilter": 9729, "minFilter": 9987, "wrapS": 10497, "wrapT": 10497 }], + "scene": 0, + "scenes": [{ "nodes": [0] }], + "textures": [ + { "sampler": 0, "source": 0 }, + { "sampler": 0, "source": 1 }, + { "sampler": 0, "source": 2 }, + { "sampler": 0, "source": 3 } + ] +} diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/boom_box0.bin b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/boom_box0.bin new file mode 100644 index 0000000000..f7ffc26f81 Binary files /dev/null and b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/boom_box0.bin differ diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/kevin_macleod_bassa_island_loop.mp3 b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/kevin_macleod_bassa_island_loop.mp3 new file mode 100644 index 0000000000..563c00cccd Binary files /dev/null and b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/kevin_macleod_bassa_island_loop.mp3 differ diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_base_color.png b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_base_color.png new file mode 100644 index 0000000000..20737b76eb Binary files /dev/null and b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_base_color.png differ diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_emission.png b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_emission.png new file mode 100644 index 0000000000..23371ae6c5 Binary files /dev/null and b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_emission.png differ diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_normal.png b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_normal.png new file mode 100644 index 0000000000..796304248a Binary files /dev/null and b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_normal.png differ diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_orm.png b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_orm.png new file mode 100644 index 0000000000..6ce1fa70c6 Binary files /dev/null and b/extensions/2.0/Khronos/KHR_audio_emitter/examples/boom_box/textures/boom_box_orm.png differ diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/figures/cone-diagram.svg b/extensions/2.0/Khronos/KHR_audio_emitter/figures/cone-diagram.svg new file mode 100644 index 0000000000..92cc7fed37 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/figures/cone-diagram.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + Listener + + + Example Values + coneInnerAngle + : 0.872665 ( 50° ) + coneOuterAngle + : 2.0944 ( 120° ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + orientation + + + forwar + + + d + + + coneInnerAngle + + + coneOuterAngle + + + gain = 1 + + + gain = coneOuterGain + + + V + + + ariable gain + + + + + + diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/schema/audio.schema.json b/extensions/2.0/Khronos/KHR_audio_emitter/schema/audio.schema.json new file mode 100644 index 0000000000..b284926aea --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/schema/audio.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_audio_emitter Audio Data", + "type": "object", + "description": "Audio data objects define where audio data is located and what format the data is in. The data is either accessed via a bufferView or uri. `mimeType` is required when `bufferView` is used.", + "allOf": [{ "$ref": "glTFProperty.schema.json" }], + "properties": { + "uri": { + "type": "string", + "description": "The uri of the audio file.", + "format": "uriref", + "gltf_detailedDescription": "The uri of the audio file. Relative paths are relative to the .gltf file.", + "gltf_uriType": "audio" + }, + "mimeType": { + "type": "string", + "description": "The audio's MIME type. Required if `bufferView` is defined. Unless specified by another extension, the only supported mimeTypes are `audio/mpeg` and `audio/wav`.", + "anyOf": [ + { + "enum": ["audio/mpeg"] + }, + { + "enum": ["audio/wav"] + }, + { + "type": "string" + } + ] + }, + "bufferView": { + "allOf": [{ "$ref": "glTFid.schema.json" }], + "description": "The index of the bufferView that contains the audio data. Use this instead of the audio source's uri property." + }, + "name": {}, + "extensions": {}, + "extras": {} + }, + "dependencies": { + "bufferView": ["mimeType"] + }, + "oneOf": [{ "required": ["uri"] }, { "required": ["bufferView"] }] +} diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/schema/emitter.positional.schema.json b/extensions/2.0/Khronos/KHR_audio_emitter/schema/emitter.positional.schema.json new file mode 100644 index 0000000000..467b64f864 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/schema/emitter.positional.schema.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_audio_emitter Positional Audio Emitter", + "type": "object", + "description": "Positional audio emitter properties.", + "allOf": [{ "$ref": "glTFProperty.schema.json" }], + "properties": { + "coneInnerAngle": { + "description": "The angle, in radians, of a cone inside of which there will be no volume reduction. This angle represents the angular diameter of the cone, from side to side. If not specified, the angle of Tau radians (360 degrees) is used, which means the audio emits in all directions (not in a cone).", + "type": "number", + "minimum": 0.0, + "maximum": 6.2831853071795864, + "default": 6.2831853071795864 + }, + "coneOuterAngle": { + "description": "The angle, in radians, of a cone outside of which the volume will be reduced to a constant value of `coneOuterGain`. This angle represents the angular diameter of the cone, from side to side. If not specified, the angle of Tau radians (360 degrees) is used, which means some audio will emit in all directions.", + "type": "number", + "minimum": 0.0, + "maximum": 6.2831853071795864, + "default": 6.2831853071795864 + }, + "coneOuterGain": { + "description": "The linear volume gain of the audio emitter set when outside the cone defined by the `coneOuterAngle` property. It is a linear value (not dB). If not specified, the cone outer gain is `0.0`, meaning the audio will be silent outside of the cone.", + "type": "number", + "minimum": 0.0, + "default": 0.0 + }, + "distanceModel": { + "type": "string", + "description": "Specifies the distance model for the audio emitter.", + "anyOf": [ + { + "enum": ["linear"], + "description": "A linear distance model calculating the gain induced by the distance according to: 1.0 - rolloffFactor * (distance - refDistance) / (maxDistance - refDistance)" + }, + { + "enum": ["inverse"], + "description": "An inverse distance model calculating the gain induced by the distance according to: refDistance / (refDistance + rolloffFactor * (Math.max(distance, refDistance) - refDistance))" + }, + { + "enum": ["exponential"], + "description": "An exponential distance model calculating the gain induced by the distance according to: pow((Math.max(distance, refDistance) / refDistance, -rolloffFactor))" + } + ], + "default": "inverse" + }, + "maxDistance": { + "description": "The maximum distance between the emitter and listener, after which the volume will not be reduced any further. If zero or not specified, the audio emitter does not have a maximum distance, and it can be heard from any distance.", + "type": "number", + "minimum": 0.0, + "default": 0.0 + }, + "refDistance": { + "description": "A reference distance for reducing volume as the emitter moves further from the listener. For distances less than this, the volume is not reduced. This value cannot be zero or a negative number. If not specified, the default value is `1.0`.", + "type": "number", + "minimum": 0.0, + "exclusiveMinimum": true, + "default": 1.0 + }, + "rolloffFactor": { + "type": "number", + "description": "Describes how quickly the volume is reduced as the emitter moves away from listener. When distanceModel is set to linear, the maximum value is 1. Otherwise, there is no upper limit to the rolloff factor. If not specified, the default value is `1.0`.", + "minimum": 0.0, + "default": 1.0 + }, + "extensions": {}, + "extras": {} + } +} diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/schema/emitter.schema.json b/extensions/2.0/Khronos/KHR_audio_emitter/schema/emitter.schema.json new file mode 100644 index 0000000000..03550defd6 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/schema/emitter.schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_audio_emitter Audio Emitter", + "type": "object", + "description": "Audio emitters define how audio sources are played back. Emitter properties are defined at the document level and are references by nodes. Audio may be played globally or positionally. Positional audio has further properties that define how audio volume scales with distance and angle.", + "allOf": [{ "$ref": "glTFProperty.schema.json" }], + "properties": { + "type": { + "type": "string", + "description": "Specifies the audio emitter type.", + "anyOf": [ + { + "enum": ["global"], + "description": "Global audio emitters are not affected by the position of audio listeners. All `positional` properties may not be defined on global audio emitters." + }, + { + "enum": ["positional"], + "description": "Positional audio emitters play audio at a position in the scene. The properties are defined in the `positional` object." + }, + { + "type": "string" + } + ] + }, + "gain": { + "description": "Unitless linear multiplier against original source volume for determining emitter loudness.", + "type": "number", + "minimum": 0.0, + "default": 1.0 + }, + "sources": { + "description": "An array of audio source indices used by the audio emitter. This array may be empty.", + "type": "array", + "allOf": [ + { + "$ref": "glTFid.schema.json" + } + ] + }, + "positional": { + "type": "object", + "$ref": "emitter.positional.schema.json" + }, + "name": {}, + "extensions": {}, + "extras": {} + }, + "required": ["type"] +} diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/schema/glTF.KHR_audio_emitter.schema.json b/extensions/2.0/Khronos/KHR_audio_emitter/schema/glTF.KHR_audio_emitter.schema.json new file mode 100644 index 0000000000..2a3e8cb005 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/schema/glTF.KHR_audio_emitter.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_audio_emitter glTF Document Extension", + "type": "object", + "allOf": [{ "$ref": "glTFProperty.schema.json" }], + "properties": { + "sources": { + "description": "An array of audio sources to be used in audio emitters.", + "type": "array", + "items": { + "type": "object", + "$ref": "source.schema.json" + }, + "minItems": 1 + }, + "emitters": { + "description": "An array of positional or global audio emitters that can be referenced by nodes or scenes.", + "type": "array", + "items": { + "type": "object", + "$ref": "emitter.schema.json" + }, + "minItems": 1 + }, + "audio": { + "description": "An array of audio that can be referenced by nodes.", + "type": "array", + "items": { + "type": "object", + "$ref": "audio.schema.json" + }, + "minItems": 1 + }, + "extensions": {}, + "extras": {} + }, + "required": ["sources", "emitters", "audio"] +} diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/schema/node.KHR_audio_emitter.schema.json b/extensions/2.0/Khronos/KHR_audio_emitter/schema/node.KHR_audio_emitter.schema.json new file mode 100644 index 0000000000..716ee6af8f --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/schema/node.KHR_audio_emitter.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_audio_emitter glTF Node Extension", + "type": "object", + "allOf": [{ "$ref": "glTFProperty.schema.json" }], + "properties": { + "emitter": { + "description": "The id of the positional audio emitter referenced by this node.", + "allOf": [ + { + "$ref": "glTFid.schema.json" + } + ] + }, + "extensions": {}, + "extras": {} + }, + "required": ["emitter"] +} diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/schema/scene.KHR_audio_emitter.schema.json b/extensions/2.0/Khronos/KHR_audio_emitter/schema/scene.KHR_audio_emitter.schema.json new file mode 100644 index 0000000000..dd13b63dc3 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/schema/scene.KHR_audio_emitter.schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_audio_emitter glTF Scene Extension", + "type": "object", + "allOf": [{ "$ref": "glTFProperty.schema.json" }], + "properties": { + "emitters": { + "type": "array", + "description": "The indices of each global audio emitter. Positional audio emitters may not be added to the scene node.", + "items": { + "$ref": "glTFid.schema.json" + }, + "uniqueItems": true, + "minItems": 1 + }, + "extensions": {}, + "extras": {} + }, + "required": ["emitters"] +} diff --git a/extensions/2.0/Khronos/KHR_audio_emitter/schema/source.schema.json b/extensions/2.0/Khronos/KHR_audio_emitter/schema/source.schema.json new file mode 100644 index 0000000000..243214a1f3 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_audio_emitter/schema/source.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_audio_emitter Audio Source", + "type": "object", + "description": "Audio sources reference audio data and define playback properties for it. Audio sources may be used by zero to many audio emitters.", + "allOf": [{ "$ref": "glTFProperty.schema.json" }], + "properties": { + "autoPlay": { + "description": "Whether or not to play the specified audio when the glTF is loaded.", + "type": "boolean", + "default": false + }, + "gain": { + "description": "Unitless multiplier against original audio file volume for determining audio source loudness.", + "type": "number", + "minimum": 0.0, + "default": 1.0 + }, + "loop": { + "description": "Whether or not to loop the specified audio when finished.", + "type": "boolean", + "default": false + }, + "audio": { + "description": "The index of the audio data assigned to this clip.", + "allOf": [ + { + "$ref": "glTFid.schema.json" + } + ] + }, + "name": {}, + "extensions": {}, + "extras": {} + } +}