diff --git a/README.md b/README.md index 9b1f32e..77a6ee9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The ``|music:Music|`` category has a ``||music:micro:bit(V2)||`` section with blocks for **sound-expressions**. These let you build some amazing sounds, but sometimes you need something more complex. ``|flexFX:FlexFX|`` sounds are *re-usable* objects that can stitch together up to three sound-expressions. -They use **play-settings**, which means you can play them at different pitch, volume, or duration. +They can be **performed** using different play-settings to control the pitch, volume, or duration. ## Anatomy of a FlexFX The basic idea is that a FlexFX is built from one, two or three **parts**. @@ -55,21 +55,6 @@ So it might start at 75% of the specified pitch (a musical fifth below), rise to Similarly, it might start quietly (at 50% volume), then grow to a maximum (100%) before fading away to silence (0%). Percentages also say how the total duration should be split between the different parts. -### Performing a FlexFX -Each FlexFX has a unique name (its **id**), assigned when it is first created. -This is supplied to the block ``|flexFX:performFlexFX|``, together with your chosen **play-settings**. - -``||Pitch||`` sets the basic pitch as a numeric frequency. - -``||Volume||`` sets the basic volume as a number in the range 0-255. - -``||Duration||`` sets how long the overall performance will last in milliseconds. - -This example would play a FlexFX called **MYSOUND** quietly, based on a pitch around middle-C, with the whole performance lasting 2 seconds: -```block -flexFX.performFlexFX("MYSOUND", 250, 50, 2000); -``` - ### Creating a Simple FlexFX The simplest FlexFX has just one part (so is really just a tuneable version of a standard sound-expression) Here is an example: @@ -79,7 +64,7 @@ flexFX.createFlexFX("Ting", 100, 100, ``` ### Creating a 2-Part FlexFX -A two-part FlexFX requires three sets of play-settings (start, middle and end). +A **2-part FlexFX** allows profiles passing through three [pitch,volume] settings: start, middle and end. For example: ```block flexFX.create2PartFlexFX("Miaow", 70, 50, @@ -88,7 +73,7 @@ flexFX.create2PartFlexFX("Miaow", 70, 50, ``` ### Creating a 3-Part FlexFX -The most complex 3-part FlexFX moves between four sets of play-settings. +The most complex **3-part FlexFX** has profiles that move smoothly between four points. For example: ```block flexFX.create3PartFlexFX("SIREN", 50, 50, @@ -109,6 +94,24 @@ flexFX.createDoubleFlexFX("NeeNaw", 95, 80, Wave.SAWTOOTH, Attack.SLOW, Effect.NONE, 75, 80, 45, 10); ``` +## Using your FlexFX + +## Performing a FlexFX +Each FlexFX has a unique name (its **id**), assigned when it is first created. +This is supplied to the block ``|flexFX:performFlexFX|``, together with your chosen play-settings: + +``||Pitch||`` sets the basic pitch as a numeric frequency. + +``||Volume||`` sets the basic volume as a number in the range 0-255. + +``||Duration||`` sets how long the overall performance will last in milliseconds. + +The following example would play a FlexFX called **MYSOUND** quietly, based on a pitch around middle-C, +with the whole performance lasting 2 seconds: +```block +flexFX.performFlexFX("MYSOUND", 250, 50, 2000); +``` + ## Changing a FlexFX Any FlexFX can be freely modified using any of the ``|flexFX:create...|`` blocks, by specifying its **id**. The basic rule is that if it exists, it gets changed; otherwise it is created from scratch. diff --git a/flexFX.ts b/flexFX.ts index b0dbff1..c4cd349 100644 --- a/flexFX.ts +++ b/flexFX.ts @@ -58,14 +58,14 @@ enum Effect { */ //% color=#70e030 weight=100 icon="\uf0a1 block="FlexFX" namespace flexFX { - // Each performance comprises an array of the "compiled" sound-strings for its several parts. + // Each performance will comprise an array of the "compiled" sound-strings for its several parts. class Play { parts: string[]; constructor() { this.parts = []; } } - // Performances are queued on the play-list to ensure proper asynch sequencing + // Performances get queued on the play-list to ensure proper asynch sequencing let playList: Play[] = []; // control flags: @@ -73,10 +73,13 @@ namespace flexFX { export function isPlaying(): boolean { return playerPlaying; } // accessor let playerActive = false; export function isActive(): boolean { return playerActive; } // accessor + let playerLocked = false; // activation of player inhibited for now + export function isLocked(): boolean { return playerLocked; } // accessor + // activity events (for other components to synchronise with) const FLEXFX_ACTIVITY_ID = 1234 // TODO: Check this is a permissable value! - enum Status { + enum PLAYER { STARTING = 1, FINISHED = 2, ALLPLAYED = 3, @@ -100,7 +103,7 @@ namespace flexFX { effectA: number; timeRatioA: number; - skipPartB: boolean; // a double FlexFX has a silent gap in the middle + skipPartB: boolean; // marks a double FlexFX, which has a silent gap in the middle playPartB: boolean; //partB: string; waveB: number; @@ -151,7 +154,7 @@ namespace flexFX { return Math.min(Math.max(time, 0), timeLeft); } // methods... - // Sets up Part A: (Point0)--(PartA)--(Point1)... + // Sets up Part A: (Point0)--PartA--(Point1)... // This implicitly sets the start values for any Part B that might follow setPartA(freq0: number, vol0: number, wave: Wave, attack: number, effect: number, freq1: number, vol1: number, ms1: number) { this.freqRatio0 = this.goodFreqRatio(freq0); @@ -175,7 +178,7 @@ namespace flexFX { this.usesPoint2 = false; this.usesPoint3 = false; } - // Adds a Part B: (Point0)--(PartA)--(Point1)--(PartB)--(Point2)... + // Adds a Part B: (Point0)--PartA--(Point1)--PartB--(Point2)... // This also implicitly sets the start values for any Part C that might follow setPartB(wave: number, attack: number, effect: number, freq2: number, vol2: number, ms2: number) { this.freqRatio2 = this.goodFreqRatio(freq2); @@ -187,7 +190,7 @@ namespace flexFX { this.playPartB = true; this.usesPoint2 = true; } - // Adds a silent Part B: (Point0)--(PartA)--(Point1)--(silence)--(Point2)... + // Adds a silent Part B: (Point0)--PartA--(Point1)--silence--(Point2)... // This implicitly sets start values for the Part C that follows silentPartB(freq2: number, vol2: number, ms2: number) { this.freqRatio2 = this.goodFreqRatio(freq2); @@ -196,7 +199,7 @@ namespace flexFX { this.skipPartB = true; } - // Adds an optional part C: (Point0)--(PartA)--(Point1)--(PartB)--(Point2)--(PartC)--(Point3) + // Adds an optional part C: (Point0)--PartA--(Point1)--PartB--(Point2)--PartC--(Point3) setPartC(wave: number, attack: number, effect: number, freq3: number, vol3: number, ms3: number) { this.freqRatio3 = this.goodFreqRatio(freq3); this.volRatio3 = this.goodVolRatio(vol3); @@ -210,7 +213,7 @@ namespace flexFX { } // Compiles a performance (called a Play) for this FlexFX and adds it to the Play-list - performUsing(freq: number, vol: number, ms: number) { + compilePlay(freq: number, vol: number, ms: number) { let loud = vol * 4 // map from [0...255] into range [0...1023] // Point 0 let f0 = freq * this.freqRatio0; @@ -239,7 +242,7 @@ namespace flexFX { ms3 = ms * this.timeRatioC; } - // now create the actual performance... + // now create the actual performance Play... let play = new Play; if (this.playPartA) { play.parts.push(music.createSoundEffect(this.waveA,f0,f1,v0,v1,ms1,this.effectA, this.attackA)); @@ -253,53 +256,77 @@ namespace flexFX { } if (this.playPartC) { play.parts.push(music.createSoundEffect(this.waveC,f2,f3,v2,v3,ms3,this.effectC, this.attackC)); - } + } + //append new Play onto the end of the playList playList.push(play); } } - // kick off the background player if not already running + // kick off the background player (if not already running) function activatePlayer() { - if (!playerActive){ - control.inBackground(() => { - playerActive = true; - let play = new Play; - while (playList.length > 0){ // play everything on the playList then exit - play = playList.shift(); - let sound = ""; - control.raiseEvent(FLEXFX_ACTIVITY_ID, Status.STARTING); - playerPlaying = true; - while (play.parts.length > 0){ - sound = play.parts.shift(); - if (sound.charAt(0)==' ') { - pause(parseInt(sound.slice(1,sound.length))); - } else { - music.playSoundEffect(sound, SoundExpressionPlayMode.UntilDone) - } - } - control.raiseEvent(FLEXFX_ACTIVITY_ID, Status.STARTING); - playerPlaying = false; + if (!(playerActive || playerLocked)){ + playerActive = true; + control.inBackground(() => player()); + } + } + + function player() { + let play = new Play; + while (playList.length > 0) { // play everything on the playList in turn + play = playList.shift(); + let sound = ""; + control.raiseEvent(FLEXFX_ACTIVITY_ID, PLAYER.STARTING); + playerPlaying = true; + while (play.parts.length > 0) { // play its sounds in turn + sound = play.parts.shift(); + if (sound.charAt(0) == ' ') { + pause(parseInt(sound.slice(1, sound.length))); + } else { + music.playSoundEffect(sound, SoundExpressionPlayMode.UntilDone) } - playerActive = false; - control.raiseEvent(FLEXFX_ACTIVITY_ID, Status.ALLPLAYED); - }); + } + control.raiseEvent(FLEXFX_ACTIVITY_ID, PLAYER.FINISHED); + playerPlaying = false; } + control.raiseEvent(FLEXFX_ACTIVITY_ID, PLAYER.ALLPLAYED); + playerActive = false; } + // ---- UI BLOCKS ---- /** * Add a silent pause to the play-list */ export function performSilence(ms: number, waiting: boolean) { let play = new Play; - play.parts.push("_"+ convertToText(Math.floor(ms))); + play.parts.push(" "+ convertToText(Math.floor(ms))); playList.push(play); activatePlayer(); // make sure it gets played if (waiting) { // ours was the lastest Play, so simply await completion of player. - control.waitForEvent(FLEXFX_ACTIVITY_ID, Status.ALLPLAYED); + control.waitForEvent(FLEXFX_ACTIVITY_ID, PLAYER.ALLPLAYED); } } - // ---- UI BLOCKS ---- + /** + * await completion of everything on the Play-list + */ + //% block + export function finish(){ + control.waitForEvent(FLEXFX_ACTIVITY_ID, PLAYER.ALLPLAYED); + } + /** + * suspend playing from the Play-list + */ + export function suspendPlaying(){ + playerLocked = true; + } + /** + * resume playing from the Play-List + */ + export function startPlaying() { + playerLocked = false; + activatePlayer(); + } + /** * Perform a custom FlexFX */ @@ -316,22 +343,15 @@ namespace flexFX { let target: FlexFX = flexFXList.find(i => i.id === id); if (target != null) { // first compile and add our Play onto the playList - target.performUsing(pitch, vol, ms); + target.compilePlay(pitch, vol, ms); activatePlayer(); // make sure it now gets played if (waiting) { // ours was the lastest Play, so simply await completion of player. - control.waitForEvent(FLEXFX_ACTIVITY_ID, Status.ALLPLAYED); + control.waitForEvent(FLEXFX_ACTIVITY_ID, PLAYER.ALLPLAYED); } } } - /** - * await completion of everything on the Play-list - */ - //% block - export function finish(){ - control.waitForEvent(FLEXFX_ACTIVITY_ID, Status.ALLPLAYED); - } /** Create a simple custom FlexFX diff --git a/test.ts b/test.ts index 816f8d1..84c814a 100644 --- a/test.ts +++ b/test.ts @@ -4,9 +4,9 @@ music.setBuiltInSpeakerEnabled(false); // create and perform a simple chime flexFX flexFX.createFlexFX("Ting", 100, 100, Wave.TRIANGLE, Attack.FAST, Effect.NONE, 100, 10); -flexFX.performFlexFX("Ting", Note.G5, 250, 400, true); -flexFX.performFlexFX("Ting", Note.E5, 250, 400, true); -flexFX.performFlexFX("Ting", Note.C5, 250, 1600, true); +flexFX.performFlexFX("Ting", Note.G5, 250, 400, false); +flexFX.performFlexFX("Ting", Note.E5, 250, 400, false); +flexFX.performFlexFX("Ting", Note.C5, 250, 1600, false); pause(1000); @@ -39,6 +39,7 @@ flexFX.performFlexFX("Horn", Note.E3, 255, 600, true); flexFX.performFlexFX("Horn", Note.D3, 255, 2400, true); pause(1000); +//flexFX.suspendPlaying(); // create and perform a double flexFX flexFX.createDoubleFlexFX("NeeNaw", @@ -54,15 +55,12 @@ flexFX.performFlexFX("NeeNaw", 780, 128, 1000, false); flexFX.performFlexFX("NeeNaw", 780, 64, 1000, false); flexFX.performFlexFX("NeeNaw", 780, 32, 1000, false); flexFX.performFlexFX("NeeNaw", 780, 16, 1000, false); - -while(flexFX.isActive) { // flash the blue light (sort of) +while(flexFX.isActive()) { // flash the blue light (sort of) basic.showIcon(IconNames.SmallDiamond); - basic.pause(400); basic.showIcon(IconNames.Diamond); - basic.pause(400); } -flexFX.finish(); // make sure everything has finished playing +//flexFX.finish(); // make sure everything has finished playing pause(1000); // create and perform a Violin 3-part flexFX @@ -87,14 +85,15 @@ flexFX.performFlexFX("Violin", Note.D5, 250, 150, false); flexFX.performFlexFX("Violin", Note.C5, 250, 300, false); flexFX.performFlexFX("Violin", Note.A4, 250, 900, false); -while (flexFX.isActive) { // jiggle a note around +//flexFX.startPlaying(); +while (flexFX.isActive()) { // jiggle a note around images.iconImage(IconNames.QuarterNote).showImage(-1, 150); images.iconImage(IconNames.QuarterNote).showImage(0, 150); images.iconImage(IconNames.QuarterNote).showImage(1, 150); images.iconImage(IconNames.QuarterNote).showImage(0, 150); } -flexFX.finish(); // make sure everything has finished playing +//flexFX.finish(); // make sure everything has finished playing pause(1000); // create and perform a flowing 3-part flexFX @@ -102,13 +101,22 @@ flexFX.create3PartFlexFX("SIREN", 50, 50, Wave.SQUARE, Attack.SLOW, Effect.NONE, 200, 100, Wave.SQUARE, Attack.SLOW, Effect.NONE, 100, 100, Wave.SQUARE, Attack.SLOW, Effect.NONE, 150, 50, 33, 33); -flexFX.performFlexFX("SIREN", 200, 250, 1000, true); -pause(400); -flexFX.performFlexFX("SIREN", 300, 250, 1000, true); -pause(400); -flexFX.performFlexFX("SIREN", 400, 250, 1000, true); -pause(400); -flexFX.performFlexFX("SIREN", 600, 250, 1000, true); -pause(400); -flexFX.performFlexFX("SIREN", 800, 250, 1000, true); -pause(1000); +flexFX.performFlexFX("SIREN", 200, 250, 1000, false); +flexFX.performSilence(400,false); +flexFX.performFlexFX("SIREN", 300, 250, 1000, false); +flexFX.performSilence(400, false); +flexFX.performFlexFX("SIREN", 400, 250, 1000, false); +flexFX.performSilence(400, false); +flexFX.performFlexFX("SIREN", 600, 250, 1000, false); +flexFX.performSilence(400, false); +flexFX.performFlexFX("SIREN", 800, 250, 1000, false); +// choreograph faces to sounds: 600ms for icon display + 800ms = 1000ms + 400ms +basic.showIcon(IconNames.Happy); +pause(800); +basic.showIcon(IconNames.Sad); +pause(800); +basic.showIcon(IconNames.Confused); +pause(800); +basic.showIcon(IconNames.Angry); +pause(800); +basic.showIcon(IconNames.Surprised);