From 207f61c858ffe3bc6412f5017e0eb5d07364b328 Mon Sep 17 00:00:00 2001 From: RDW Date: Wed, 17 Nov 2021 15:26:00 +0100 Subject: [PATCH 1/4] Core: Add support for multiple file types to the Decoder builtin While it's a breaking change, it is needed to easily support multiple audio formats using the C_Resources cache. --- Core/APIs/C_Decoding.js | 16 +++++++++------- Core/Classes/Decoder.js | 12 ++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Core/APIs/C_Decoding.js b/Core/APIs/C_Decoding.js index 502e593..a41b9c9 100644 --- a/Core/APIs/C_Decoding.js +++ b/Core/APIs/C_Decoding.js @@ -24,15 +24,17 @@ const C_Decoding = { return true; }, addDecoder(decoder) { - const fileType = decoder.getSupportedFileType(); + const fileTypes = decoder.getSupportedFileTypes(); - if (this.registeredDecoders[fileType]) { - NOTICE(format("Failed to register decoder for file type %s (already registered)", fileType)); - return; - } + for (const fileType of Object.keys(fileTypes)) { + if (this.registeredDecoders[fileType]) { + NOTICE(format("Failed to register decoder for file type %s (already registered)", fileType)); + return; + } - this.registeredDecoders[fileType] = decoder; - DEBUG(format("Registered new decoder for file type *.%s", fileType.toUpperCase())); + this.registeredDecoders[fileType] = decoder; + DEBUG(format("Registered new decoder for file type *.%s", fileType.toUpperCase())); + } }, decodeFile(filePath) { const resource = C_Resources.load(filePath); diff --git a/Core/Classes/Decoder.js b/Core/Classes/Decoder.js index 554aee3..e7d4fce 100644 --- a/Core/Classes/Decoder.js +++ b/Core/Classes/Decoder.js @@ -1,12 +1,12 @@ class Decoder { - constructor(fileType, decodingFunction) { - this.fileType = fileType; - this.decodingFunction = decodingFunction; + fileTypes = {}; + constructor(decodingFunction) { + this.decode = decodingFunction || this.decode; // NO-OP as a safe default } - getSupportedFileType() { - return this.fileType; + getSupportedFileTypes() { + return this.fileTypes; } decode(resource) { - return this.decodingFunction(resource); + return resource; } } From 2ed7d0a6ec99ff8ac579464330703cb0cfedeb8d Mon Sep 17 00:00:00 2001 From: RDW Date: Wed, 17 Nov 2021 15:40:18 +0100 Subject: [PATCH 2/4] Tests: Add a basic test suite for the Decoder builtin --- Config/eslint.json | 1 + Tests/Builtins/Decoder.js | 16 ++++++++++++++++ Tests/run-renderer-tests.js | 8 +++++++- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 Tests/Builtins/Decoder.js diff --git a/Config/eslint.json b/Config/eslint.json index f8fb8cb..c5a69a9 100644 --- a/Config/eslint.json +++ b/Config/eslint.json @@ -60,6 +60,7 @@ "CheckButton": "writable", "Color": "writable", "CRC32": "writable", + "Decoder": "writable", "EditBox": "writable", "Enum": "writable", "FieldSet": "writable", diff --git a/Tests/Builtins/Decoder.js b/Tests/Builtins/Decoder.js new file mode 100644 index 0000000..ac96b9c --- /dev/null +++ b/Tests/Builtins/Decoder.js @@ -0,0 +1,16 @@ +describe("Decoder", () => { + const decoder = new Decoder(); + it("should be exported into the global environment", () => { + assertEquals(decoder.constructor.name, "Decoder"); + }); + + let exportedApiSurface = ["getSupportedFileTypes", "decode"]; + + exportedApiSurface.forEach((namedExport) => { + it("should export function " + namedExport, () => { + assertEquals(typeof decoder[namedExport], "function"); + }); + }); + + // Since this is just an interface that needs to be implemented, there's no implementation to test +}); diff --git a/Tests/run-renderer-tests.js b/Tests/run-renderer-tests.js index 65f2316..79a224c 100644 --- a/Tests/run-renderer-tests.js +++ b/Tests/run-renderer-tests.js @@ -1,6 +1,12 @@ const testSuites = { SharedConstants: ["SharedConstants/Aliases.js", "SharedConstants/Paths.js"], - Builtins: ["Builtins/Assertions.js", "Builtins/LocalCacheTests.js", "Builtins/UniqueID.js", "Builtins/Validators.js"], + Builtins: [ + "Builtins/Assertions.js", + "Builtins/LocalCacheTests.js", + "Builtins/Decoder.js", + "Builtins/UniqueID.js", + "Builtins/Validators.js", + ], C_Settings: [ "API/C_Settings/validate.js", "API/C_Settings/validateDefaultSettings.js", From 0914394d2056e66890ed8c8e2c5271e0f07ce692 Mon Sep 17 00:00:00 2001 From: RDW Date: Wed, 17 Nov 2021 15:43:30 +0100 Subject: [PATCH 3/4] Refactor: Move the Decoder builtin to the Builtins folder --- Core/{Classes => Builtins}/Decoder.js | 0 Tests/Builtins/Decoder.js | 32 +++++++++++++-------------- index.html | 3 +-- 3 files changed, 17 insertions(+), 18 deletions(-) rename Core/{Classes => Builtins}/Decoder.js (100%) diff --git a/Core/Classes/Decoder.js b/Core/Builtins/Decoder.js similarity index 100% rename from Core/Classes/Decoder.js rename to Core/Builtins/Decoder.js diff --git a/Tests/Builtins/Decoder.js b/Tests/Builtins/Decoder.js index ac96b9c..a8e4fd3 100644 --- a/Tests/Builtins/Decoder.js +++ b/Tests/Builtins/Decoder.js @@ -1,16 +1,16 @@ -describe("Decoder", () => { - const decoder = new Decoder(); - it("should be exported into the global environment", () => { - assertEquals(decoder.constructor.name, "Decoder"); - }); - - let exportedApiSurface = ["getSupportedFileTypes", "decode"]; - - exportedApiSurface.forEach((namedExport) => { - it("should export function " + namedExport, () => { - assertEquals(typeof decoder[namedExport], "function"); - }); - }); - - // Since this is just an interface that needs to be implemented, there's no implementation to test -}); +describe("Decoder", () => { + const decoder = new Decoder(); + it("should be exported into the global environment", () => { + assertEquals(decoder.constructor.name, "Decoder"); + }); + + let exportedApiSurface = ["getSupportedFileTypes", "decode"]; + + exportedApiSurface.forEach((namedExport) => { + it("should export function " + namedExport, () => { + assertEquals(typeof decoder[namedExport], "function"); + }); + }); + + // Since this is just an interface that needs to be implemented, there's no implementation to test +}); diff --git a/index.html b/index.html index 7418e9e..0d572c0 100644 --- a/index.html +++ b/index.html @@ -65,8 +65,6 @@ - - @@ -140,6 +138,7 @@ StartWebClient(); + From e10ba6b3e05c89f10224e7dac6a4dff293ecca6f Mon Sep 17 00:00:00 2001 From: RDW Date: Wed, 17 Nov 2021 17:03:48 +0100 Subject: [PATCH 4/4] API: Add a BuiltinAudioDecoder to allow using the Resource cache While this doesn't do anything of significance, it allows the WebAudio API to load audio files from disk using the C_Decoding API, which means they'll be provided as cached resources. The alternative is to simply use BJS XHR-based loading, but that incurs a lot of overhead since it will load the file from disk every time. There's still overhead for copying buffers and decoding the ArrayBuffer to an AudioBuffer internally, but that'll require a more elaborate solution to eliminate. --- Config/eslint.json | 1 + Core/API/WebAudio/BuiltinAudioDecoder.js | 12 +++++++++ Tests/API/C_WebAudio/BuiltinAudioDecoder.js | 27 +++++++++++++++++++++ Tests/run-renderer-tests.js | 1 + index.html | 1 + 5 files changed, 42 insertions(+) create mode 100644 Core/API/WebAudio/BuiltinAudioDecoder.js create mode 100644 Tests/API/C_WebAudio/BuiltinAudioDecoder.js diff --git a/Config/eslint.json b/Config/eslint.json index c5a69a9..b4e11da 100644 --- a/Config/eslint.json +++ b/Config/eslint.json @@ -54,6 +54,7 @@ "C_WebView": "writable", "BinaryReader": "writable", "Bitmap": "writable", + "BuiltinAudioDecoder": "writable", "Button": "writable", "Canvas": "writable", "Channels": "writable", diff --git a/Core/API/WebAudio/BuiltinAudioDecoder.js b/Core/API/WebAudio/BuiltinAudioDecoder.js new file mode 100644 index 0000000..04ac964 --- /dev/null +++ b/Core/API/WebAudio/BuiltinAudioDecoder.js @@ -0,0 +1,12 @@ +// This doesn't do any actual decoding; it merely serves to utilize the Resource cache for audio playback +// The audio engine supports no caching mechanism of its own, so we wrap any calls to disk and serve Resources instead +class BuiltinAudioDecoder extends Decoder { + fileTypes = { mp3: true, ogg: true, wav: true }; + // No need to do any decoding as these audio formats are supported natively + decode(resource) { + return resource; + } + getSupportedFileTypes() { + return this.fileTypes; + } +} diff --git a/Tests/API/C_WebAudio/BuiltinAudioDecoder.js b/Tests/API/C_WebAudio/BuiltinAudioDecoder.js new file mode 100644 index 0000000..b6a6207 --- /dev/null +++ b/Tests/API/C_WebAudio/BuiltinAudioDecoder.js @@ -0,0 +1,27 @@ +describe("BuiltinAudioDecoder", () => { + const decoder = new BuiltinAudioDecoder(); + it("should be exported into the global environment", () => { + assertEquals(decoder.constructor.name, "BuiltinAudioDecoder"); + }); + + let exportedApiSurface = ["getSupportedFileTypes", "decode"]; + + exportedApiSurface.forEach((namedExport) => { + it("should export function " + namedExport, () => { + assertEquals(typeof decoder[namedExport], "function"); + }); + }); + + const supportedFormats = decoder.getSupportedFileTypes(); + it("should indicate that MP3 files can be decoded", () => { + assertTrue(supportedFormats["mp3"]); + }); + + it("should indicate that OGG files can be decoded", () => { + assertTrue(supportedFormats["ogg"]); + }); + + it("should indicate that WAV files can be decoded", () => { + assertTrue(supportedFormats["wav"]); + }); +}); diff --git a/Tests/run-renderer-tests.js b/Tests/run-renderer-tests.js index 79a224c..27f33a2 100644 --- a/Tests/run-renderer-tests.js +++ b/Tests/run-renderer-tests.js @@ -12,6 +12,7 @@ const testSuites = { "API/C_Settings/validateDefaultSettings.js", "API/C_Settings/validateUserSettings.js", ], + C_WebAudio: ["API/C_WebAudio/BuiltinAudioDecoder.js"], }; for (const namespace in testSuites) { diff --git a/index.html b/index.html index 0d572c0..a676f1d 100644 --- a/index.html +++ b/index.html @@ -141,4 +141,5 @@ +