Skip to content

Commit

Permalink
Seems to all work!
Browse files Browse the repository at this point in the history
  • Loading branch information
GrandpaBond committed Aug 3, 2023
1 parent 9328864 commit c50b8db
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 84 deletions.
39 changes: 21 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**.
Expand Down Expand Up @@ -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:
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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.
Expand Down
112 changes: 66 additions & 46 deletions flexFX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,25 +58,28 @@ 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:
let playerPlaying = false; // a performance is being played
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,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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
*/
Expand All @@ -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
Expand Down
48 changes: 28 additions & 20 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -87,28 +85,38 @@ 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
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);

0 comments on commit c50b8db

Please sign in to comment.