diff --git a/build/strings/en/documentation.json b/build/strings/en/documentation.json index 323b409..5041657 100644 --- a/build/strings/en/documentation.json +++ b/build/strings/en/documentation.json @@ -159,6 +159,25 @@ "Shapes_HideShape_ShapeName": "The name of the shape.", "Shapes_ShowShape": "Shows a previously hidden shape.", "Shapes_ShowShape_ShapeName": "The name of the shape.", + "Sound": "The Sound object provides operations that allow the playback of sounds. Some sample sounds are provided along with the library.", + "Sound_PlayClick" : "Plays the Click Sound.", + "Sound_PlayClickAndWait" : "Plays the Click Sound and waits for it to finish.", + "Sound_PlayChime" : "Plays the Chime Sound. ", + "Sound_PlayChimeAndWait" : "Plays the Chime Sound and waits for it to finish.", + "Sound_PlayChimes" : "Plays the Chimes Sound.", + "Sound_PlayChimesAndWait" : "Plays the Chimes Sound and waits for it to finish.", + "Sound_PlayBellRing" : "Plays the Bell Ring Sound.", + "Sound_PlayBellRingAndWait" : "Plays the Bell Ring Sound and waits for it to finish.", + "Sound_PlayMusic" : "Plays musical notes.", + "Sound_PlayMusic_Notes" : "A set of musical notes to play. The format is a subset of the Music Macro Language supported by QBasic.", + "Sound_Play" : "Plays an audio file. This could be an mp3 or wav or wma file. Other file formats may or may not play depending on the audio codecs installed on the user's computer. If the file was already paused, this operation will resume from the position where the playback was paused.", + "Sound_Play_FilePath" : "The path for the audio file. This could either be a local file (e.g.: c:\\music\\track1.mp3) or a file on the network (e.g.: http://contoso.com/track01.wma).", + "Sound_PlayAndWait" : "Plays an audio file and waits until it is finished playing. This could be an mp3, wav, or wma file. Other file formats may or may not play depending on the audio codecs installed on the user's computer. If the file was already paused, this operation will resume from the position where the playback was paused.", + "Sound_PlayAndWait_FilePath" : "The path for the audio file. This could either be a local file (e.g.: c:\\music\\track1.mp3) or a file on the network (e.g.: http://contoso.com/track01.wma).", + "Sound_Pause" : "Pauses playback of an audio file. If the file was not already playing, this operation will not do anything.", + "Sound_Pause_FilePath" : "The path for the audio file. This could either be a local file (e.g.: c:\\music\\track1.mp3) or a file on the network (e.g.: http://contoso.com/track01.wma).", + "Sound_Stop" : "Stops playback of an audio file. If the file was not already playing, this operation will not do anything.", + "Sound_Stop_FilePath" : "The path for the audio file. This could either be a local file (e.g.: c:\\music\\track1.mp3) or a file on the network (e.g.: http://contoso.com/track01.wma).", "Stack": "This object provides a way of storing values just like stacking up a plate. You can push a value to the top of the stack and pop it off. You can only pop the values one by one off the stack and the last pushed value will be the first one to pop out.", "Stack_PushValue": "Pushes a value to the specified stack.", "Stack_PushValue_StackName": "The name of the stack.", diff --git a/build/webpack.config.ts b/build/webpack.config.ts index faa288f..3da07a2 100644 --- a/build/webpack.config.ts +++ b/build/webpack.config.ts @@ -61,7 +61,7 @@ export function factory(params: IFactoryParams): webpack.Configuration { ] }, { - test: /\.(png|gif)$/, + test: /\.(png|gif|wav)$/, use: [ { loader: "file-loader", diff --git a/src/app/components/common/sound-plugin.ts b/src/app/components/common/sound-plugin.ts new file mode 100644 index 0000000..b27e2f6 --- /dev/null +++ b/src/app/components/common/sound-plugin.ts @@ -0,0 +1,12 @@ +import { ISoundLibraryPlugin } from "../../../compiler/runtime/libraries/sound"; + +export class SoundLibraryPlugin implements ISoundLibraryPlugin { + + public playAudio(audioFile: string): void { + if (audioFile !== "") + { + let audio = new Audio(audioFile); + audio.play(); + } + } +} diff --git a/src/app/content/sounds/BellRing.wav b/src/app/content/sounds/BellRing.wav new file mode 100644 index 0000000..8c310fb Binary files /dev/null and b/src/app/content/sounds/BellRing.wav differ diff --git a/src/app/content/sounds/Chime.wav b/src/app/content/sounds/Chime.wav new file mode 100644 index 0000000..3393159 Binary files /dev/null and b/src/app/content/sounds/Chime.wav differ diff --git a/src/app/content/sounds/Click.wav b/src/app/content/sounds/Click.wav new file mode 100644 index 0000000..f9b5f08 Binary files /dev/null and b/src/app/content/sounds/Click.wav differ diff --git a/src/app/content/sounds/Pause.wav b/src/app/content/sounds/Pause.wav new file mode 100644 index 0000000..641bbdd Binary files /dev/null and b/src/app/content/sounds/Pause.wav differ diff --git a/src/compiler/runtime/libraries-metadata.ts b/src/compiler/runtime/libraries-metadata.ts index e2583eb..c664197 100644 --- a/src/compiler/runtime/libraries-metadata.ts +++ b/src/compiler/runtime/libraries-metadata.ts @@ -187,6 +187,29 @@ export class LibrariesMetadata { // No Events }); + public readonly Sound: TypeMetadata = new TypeMetadata("Sound", + { + PlayClick: new MethodMetadata("Sound", "PlayClick", false, []), + PlayClickAndWait: new MethodMetadata("Sound", "PlayClickAndWait", false, []), + PlayChime: new MethodMetadata("Sound", "PlayChime", false, []), + PlayChimeAndWait: new MethodMetadata("Sound", "PlayChimeAndWait", false, []), + PlayChimes: new MethodMetadata("Sound", "PlayChimes", false, []), + PlayChimesAndWait: new MethodMetadata("Sound", "PlayChimesAndWait", false, []), + PlayBellRing: new MethodMetadata("Sound", "PlayBellRing", false, []), + PlayBellRingAndWait: new MethodMetadata("Sound", "PlayBellRingAndWait", false, []), + PlayMusic: new MethodMetadata("Sound", "PlayMusic", false, ["Notes"]), + Play: new MethodMetadata("Sound", "Play", false, ["FilePath"]), + PlayAndWait: new MethodMetadata("Sound", "PlayAndWait", false, ["FilePath"]), + Pause: new MethodMetadata("Sound", "Pause", false, ["FilePath"]), + Stop: new MethodMetadata("Sound", "Stop", false, ["FilePath"]) + }, + { + // No Properties + }, + { + // No Events + }); + public readonly Stack: TypeMetadata = new TypeMetadata("Stack", { PushValue: new MethodMetadata("Stack", "PushValue", false, ["StackName", "Value"]), diff --git a/src/compiler/runtime/libraries.ts b/src/compiler/runtime/libraries.ts index 62d7b58..92afb18 100644 --- a/src/compiler/runtime/libraries.ts +++ b/src/compiler/runtime/libraries.ts @@ -3,6 +3,7 @@ import { TextWindowLibrary } from "./libraries/text-window"; import { ProgramLibrary } from "./libraries/program"; import { ClockLibrary } from "./libraries/clock"; import { ArrayLibrary } from "./libraries/array"; +import { SoundLibrary } from "./libraries/sound"; import { StackLibrary } from "./libraries/stack"; import { BaseValue } from "./values/base-value"; import { LibrariesMetadata } from "./libraries-metadata"; @@ -41,6 +42,7 @@ export class RuntimeLibraries { public readonly Math: MathLibrary = new MathLibrary(); public readonly Program: ProgramLibrary = new ProgramLibrary(); public readonly Shapes: ShapesLibrary = new ShapesLibrary(); + public readonly Sound: SoundLibrary = new SoundLibrary(); public readonly Stack: StackLibrary = new StackLibrary(); public readonly TextWindow: TextWindowLibrary = new TextWindowLibrary(); // TODO: public readonly Turtle: TurtleLibrary = new TurtleLibrary(); diff --git a/src/compiler/runtime/libraries/sound.ts b/src/compiler/runtime/libraries/sound.ts new file mode 100644 index 0000000..60e005b --- /dev/null +++ b/src/compiler/runtime/libraries/sound.ts @@ -0,0 +1,74 @@ +import { LibraryTypeInstance, LibraryMethodInstance, LibraryPropertyInstance, LibraryEventInstance } from "../libraries"; +import { SoundLibraryPlugin } from "../../../app/components/common/sound-plugin"; + +export const ClickSound = require("../../../app/content/sounds/click.wav"); +export const ChimeSound = require("../../../app/content/sounds/chime.wav"); +export const ChimesSound = require("../../../app/content/sounds/pause.wav"); +export const BellRingSound = require("../../../app/content/sounds/bellring.wav"); + +enum Sound { + Click, + Chime, + Chimes, + BellRing +} + +export interface ISoundLibraryPlugin { + playAudio(audioFile : string): void; +} + +export class SoundLibrary implements LibraryTypeInstance { + private _pluginInstance: ISoundLibraryPlugin | undefined; + + public get plugin(): ISoundLibraryPlugin { + if (!this._pluginInstance) { + this._pluginInstance = new SoundLibraryPlugin(); + } + + return this._pluginInstance; + } + + public set plugin(plugin: ISoundLibraryPlugin) { + this._pluginInstance = plugin; + } + + private executePlayStockSound(soundName: Sound): void { + let audioFile : string = ""; + switch (soundName) { + case Sound.Click: + audioFile = ClickSound; + break; + case Sound.Chime: + audioFile = ChimeSound; + break; + case Sound.Chimes: + audioFile = ChimesSound; + break; + case Sound.BellRing: + audioFile = BellRingSound; + break; + } + + this.plugin.playAudio(audioFile); + } + + public readonly methods: { readonly [name: string]: LibraryMethodInstance } = { + PlayClick: { execute: this.executePlayStockSound.bind(this, Sound.Click) }, + PlayClickAndWait: { execute: () => { throw new Error("Not Implemented yet."); } }, + PlayChime: { execute: this.executePlayStockSound.bind(this, Sound.Chime) }, + PlayChimeAndWait: { execute: () => { throw new Error("Not Implemented yet."); } }, + PlayChimes: { execute: this.executePlayStockSound.bind(this, Sound.Chimes) }, + PlayChimesAndWait: { execute: () => { throw new Error("Not Implemented yet."); } }, + PlayBellRing: { execute: this.executePlayStockSound.bind(this, Sound.BellRing) }, + PlayBellRingAndWait: { execute: () => { throw new Error("Not Implemented yet."); } }, + PlayMusic: { execute: () => { throw new Error("Not Implemented yet."); } }, + Play: { execute: () => { throw new Error("Not Implemented yet."); } }, + PlayAndWait: { execute: () => { throw new Error("Not Implemented yet."); } }, + Pause: { execute: () => { throw new Error("Not Implemented yet."); } }, + Stop: { execute: () => { throw new Error("Not Implemented yet."); } } + }; + + public readonly properties: { readonly [name: string]: LibraryPropertyInstance } = {}; + + public readonly events: { readonly [name: string]: LibraryEventInstance } = {}; +} diff --git a/src/strings/documentation.ts b/src/strings/documentation.ts index dfc5cd5..724606f 100644 --- a/src/strings/documentation.ts +++ b/src/strings/documentation.ts @@ -161,6 +161,25 @@ export module DocumentationResources { export const Shapes_HideShape_ShapeName = "The name of the shape."; export const Shapes_ShowShape = "Shows a previously hidden shape."; export const Shapes_ShowShape_ShapeName = "The name of the shape."; + export const Sound = "The Sound object provides operations that allow the playback of sounds. Some sample sounds are provided along with the library."; + export const Sound_PlayClick = "Plays the Click Sound."; + export const Sound_PlayClickAndWait = "Plays the Click Sound and waits for it to finish."; + export const Sound_PlayChime = "Plays the Chime Sound. "; + export const Sound_PlayChimeAndWait = "Plays the Chime Sound and waits for it to finish."; + export const Sound_PlayChimes = "Plays the Chimes Sound."; + export const Sound_PlayChimesAndWait = "Plays the Chimes Sound and waits for it to finish."; + export const Sound_PlayBellRing = "Plays the Bell Ring Sound."; + export const Sound_PlayBellRingAndWait = "Plays the Bell Ring Sound and waits for it to finish."; + export const Sound_PlayMusic = "Plays musical notes."; + export const Sound_PlayMusic_Notes = "A set of musical notes to play. The format is a subset of the Music Macro Language supported by QBasic."; + export const Sound_Play = "Plays an audio file. This could be an mp3 or wav or wma file. Other file formats may or may not play depending on the audio codecs installed on the user's computer. If the file was already paused, this operation will resume from the position where the playback was paused."; + export const Sound_Play_FilePath = "The path for the audio file. This could either be a local file (e.g.: c:\music\track1.mp3) or a file on the network (e.g.: http://contoso.com/track01.wma)."; + export const Sound_PlayAndWait = "Plays an audio file and waits until it is finished playing. This could be an mp3, wav, or wma file. Other file formats may or may not play depending on the audio codecs installed on the user's computer. If the file was already paused, this operation will resume from the position where the playback was paused."; + export const Sound_PlayAndWait_FilePath = "The path for the audio file. This could either be a local file (e.g.: c:\music\track1.mp3) or a file on the network (e.g.: http://contoso.com/track01.wma)."; + export const Sound_Pause = "Pauses playback of an audio file. If the file was not already playing, this operation will not do anything."; + export const Sound_Pause_FilePath = "The path for the audio file. This could either be a local file (e.g.: c:\music\track1.mp3) or a file on the network (e.g.: http://contoso.com/track01.wma)."; + export const Sound_Stop = "Stops playback of an audio file. If the file was not already playing, this operation will not do anything."; + export const Sound_Stop_FilePath = "The path for the audio file. This could either be a local file (e.g.: c:\music\track1.mp3) or a file on the network (e.g.: http://contoso.com/track01.wma)."; export const Stack = "This object provides a way of storing values just like stacking up a plate. You can push a value to the top of the stack and pop it off. You can only pop the values one by one off the stack and the last pushed value will be the first one to pop out."; export const Stack_PushValue = "Pushes a value to the specified stack."; export const Stack_PushValue_StackName = "The name of the stack."; diff --git a/tests/compiler/helpers.ts b/tests/compiler/helpers.ts index d319f95..783c919 100644 --- a/tests/compiler/helpers.ts +++ b/tests/compiler/helpers.ts @@ -8,6 +8,7 @@ import { CompilerPosition, CompilerRange } from "../../src/compiler/syntax/range import { TokenKind } from "../../src/compiler/syntax/tokens"; import { ITextWindowLibraryPlugin, TextWindowColor } from "../../src/compiler/runtime/libraries/text-window"; import { BaseValue } from "../../src/compiler/runtime/values/base-value"; +import { ISoundLibraryPlugin } from "../../src/compiler/runtime/libraries/sound"; export function getMarkerPosition(text: string, marker: string): CompilerPosition { expect(marker.length).toBe(1); @@ -162,3 +163,16 @@ export class TextWindowTestBuffer implements ITextWindowLibraryPlugin { } } } + +export class TestSoundLibraryPlugin implements ISoundLibraryPlugin { + + private lastAudioPlayed : string = ""; + + public getLastAudioPlayed() : string { + return this.lastAudioPlayed; + } + + public playAudio(audioFile: string): void { + this.lastAudioPlayed = audioFile; + } +} diff --git a/tests/compiler/runtime/libraries/sound.ts b/tests/compiler/runtime/libraries/sound.ts new file mode 100644 index 0000000..c00adf7 --- /dev/null +++ b/tests/compiler/runtime/libraries/sound.ts @@ -0,0 +1,67 @@ +import "jasmine"; +import { Compilation } from "../../../../src/compiler/compilation"; +import { ExecutionEngine, ExecutionMode, ExecutionState } from "../../../../src/compiler/execution-engine"; +import { TestSoundLibraryPlugin } from "../../helpers"; +import { ClickSound, ChimeSound, ChimesSound, BellRingSound } from "../../../../src/compiler/runtime/libraries/sound"; + +describe("Compiler.Runtime.Libraries.Sound", () => { + it("plays a clicking sound", () => { + const compilation = new Compilation(` +Sound.PlayClick()`); + + const plugin = new TestSoundLibraryPlugin(); + const engine = new ExecutionEngine(compilation); + + engine.libraries.Sound.plugin = plugin; + engine.execute(ExecutionMode.RunToEnd); + + expect(plugin.getLastAudioPlayed()).toBe(ClickSound); + expect(engine.state).toBe(ExecutionState.Terminated); + expect(engine.exception).toBeUndefined(); + }); + + it("plays a chiming sound", () => { + const compilation = new Compilation(` +Sound.PlayChime()`); + + const plugin = new TestSoundLibraryPlugin(); + const engine = new ExecutionEngine(compilation); + + engine.libraries.Sound.plugin = plugin; + engine.execute(ExecutionMode.RunToEnd); + + expect(plugin.getLastAudioPlayed()).toBe(ChimeSound); + expect(engine.state).toBe(ExecutionState.Terminated); + expect(engine.exception).toBeUndefined(); + }); + + it("plays the chimes sound", () => { + const compilation = new Compilation(` +Sound.PlayChimes()`); + + const plugin = new TestSoundLibraryPlugin(); + const engine = new ExecutionEngine(compilation); + + engine.libraries.Sound.plugin = plugin; + engine.execute(ExecutionMode.RunToEnd); + + expect(plugin.getLastAudioPlayed()).toBe(ChimesSound); + expect(engine.state).toBe(ExecutionState.Terminated); + expect(engine.exception).toBeUndefined(); + }); + + it("plays a ringing bell sound", () => { + const compilation = new Compilation(` +Sound.PlayBellRing()`); + + const plugin = new TestSoundLibraryPlugin(); + const engine = new ExecutionEngine(compilation); + + engine.libraries.Sound.plugin = plugin; + engine.execute(ExecutionMode.RunToEnd); + + expect(plugin.getLastAudioPlayed()).toBe(BellRingSound); + expect(engine.state).toBe(ExecutionState.Terminated); + expect(engine.exception).toBeUndefined(); + }); +}); diff --git a/tests/compiler/tests-list.ts b/tests/compiler/tests-list.ts index dcd909f..178d76b 100644 --- a/tests/compiler/tests-list.ts +++ b/tests/compiler/tests-list.ts @@ -27,6 +27,7 @@ import "./runtime/libraries/array"; import "./runtime/libraries/clock"; import "./runtime/libraries/math"; import "./runtime/libraries/program"; +import "./runtime/libraries/sound"; import "./runtime/libraries/stack"; import "./runtime/libraries/text-window";