New Sound on Click - Pentatonic Scale (#3970)

* added scale function

* cleaned up merge

* cleaned up scale implementation and created closure for previewing scales

* finalized changes

* fixed ci/de

* switched to upper camel case

* undid accidental changes

* updates

* fixed indexing bug and audioCtxt being intialized

* removed mapping

* updated schema with non-inclusive range for two new sound-on-clicks

---------

Co-authored-by: Bruce Berrios <bberr022@fiu.edu>
Co-authored-by: Bruce Berrios <58147810+Bruception@users.noreply.github.com>
This commit is contained in:
Jerry Zhou 2023-02-10 18:11:52 -05:00 committed by GitHub
parent 47c581c5f8
commit da9b691bb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 171 additions and 6 deletions

View file

@ -77,7 +77,7 @@ const CONFIG_SCHEMA = joi.object({
playSoundOnError: joi.boolean(),
playSoundOnClick: joi
.string()
.valid("off", ..._.range(1, 12).map(_.toString)),
.valid("off", ..._.range(1, 14).map(_.toString)),
soundVolume: joi.string().valid("0.1", "0.5", "1.0"),
startGraphsAtZero: joi.boolean(),
showOutOfFocusWarning: joi.boolean(),

View file

@ -145,6 +145,30 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
SoundController.playNote("KeyQ", "triangle");
},
},
{
id: "setSoundOnClick12",
display: "pentatonic",
configValue: "12",
hover: (): void => {
SoundController.scaleConfigurations["12"].preview();
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("12");
SoundController.scaleConfigurations["12"].preview();
},
},
{
id: "setSoundOnClick13",
display: "wholetone",
configValue: "13",
hover: (): void => {
SoundController.scaleConfigurations["13"].preview();
},
exec: (): void => {
UpdateConfig.setPlaySoundOnClick("13");
SoundController.scaleConfigurations["13"].preview();
},
},
],
};

View file

@ -175,7 +175,22 @@ export function setPlaySoundOnClick(
): boolean {
if (
!isConfigValueValid("play sound on click", val, [
["off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"],
[
"off",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
],
])
) {
return false;

View file

@ -1,7 +1,11 @@
import Config from "../config";
import Howler, { Howl } from "howler";
import * as ConfigEvent from "../observables/config-event";
import { createErrorMessage, randomElementFromArray } from "../utils/misc";
import {
createErrorMessage,
randomElementFromArray,
randomIntFromRange,
} from "../utils/misc";
import { leftState, rightState } from "../test/shift-tracker";
import { capsState } from "../test/caps-warning";
import * as Notifications from "../elements/notifications";
@ -255,12 +259,15 @@ const notes = {
A: [27.5, 55.0, 110.0, 220.0, 440.0, 880.0, 1760.0, 3520.0],
Bb: [29.14, 58.27, 116.54, 233.08, 466.16, 932.33, 1864.66, 3729.31],
B: [30.87, 61.74, 123.47, 246.94, 493.88, 987.77, 1975.53, 3951.07],
};
} as const;
type ValidNotes = keyof typeof notes;
type ValidFrequencies = typeof notes[ValidNotes];
type GetNoteFrequencyCallback = (octave: number) => number;
function bindToNote(
noteFrequencies: number[],
noteFrequencies: ValidFrequencies,
octaveOffset = 0
): GetNoteFrequencyCallback {
return (octave: number): number => {
@ -341,6 +348,95 @@ function initAudioContext(): void {
}
}
type ValidScales = "pentatonic" | "wholetone";
const scales: Record<ValidScales, ValidNotes[]> = {
pentatonic: ["C", "D", "E", "G", "A"],
wholetone: ["C", "D", "E", "Gb", "Ab", "Bb"],
};
interface ScaleData {
octave: number; // current octave of scale
direction: number; // whether scale is ascending or descending
position: number; // current position in scale
}
function createPreviewScale(scaleName: ValidScales): () => void {
// We use a JavaScript closure to create a preview function that can be called multiple times and progress through the scale
const scale: ScaleData = {
position: 0,
octave: 4,
direction: 1,
};
return () => {
if (clickSounds === null) init();
playScale(scaleName, scale);
};
}
interface ScaleMeta {
name: ValidScales;
preview: ReturnType<typeof createPreviewScale>;
meta: ScaleData;
}
const defaultScaleData: ScaleData = {
position: 0,
octave: 4,
direction: 1,
};
export const scaleConfigurations: Record<
Extract<MonkeyTypes.PlaySoundOnClick, "12" | "13">,
ScaleMeta
> = {
"12": {
name: "pentatonic",
preview: createPreviewScale("pentatonic"),
meta: defaultScaleData,
},
"13": {
name: "wholetone",
preview: createPreviewScale("wholetone"),
meta: defaultScaleData,
},
};
export function playScale(scale: ValidScales, scaleMeta: ScaleData): void {
if (audioCtx === undefined) {
initAudioContext();
}
if (!audioCtx) return;
const randomNote = randomIntFromRange(0, scales[scale].length - 1);
if (Math.random() < 0.5) {
scaleMeta.octave += scaleMeta.direction;
}
if (scaleMeta.octave >= 6) {
scaleMeta.direction = -1;
}
if (scaleMeta.octave <= 4) {
scaleMeta.direction = 1;
}
const currentFrequency = notes[scales[scale][randomNote]][scaleMeta.octave];
const oscillatorNode = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
oscillatorNode.type = "sine";
gainNode.gain.value = parseFloat(Config.soundVolume) / 10;
oscillatorNode.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillatorNode.frequency.value = currentFrequency;
oscillatorNode.start(audioCtx.currentTime);
gainNode.gain.setTargetAtTime(0, audioCtx.currentTime, 0.3);
oscillatorNode.stop(audioCtx.currentTime + 2);
}
export function playNote(
codeOverride?: string,
oscillatorTypeOverride?: SupportedOscillatorTypes
@ -380,6 +476,16 @@ export function playNote(
export function playClick(): void {
if (Config.playSoundOnClick === "off") return;
if (Config.playSoundOnClick in scaleConfigurations) {
const { name, meta } =
scaleConfigurations[
Config.playSoundOnClick as keyof typeof scaleConfigurations
];
playScale(name, meta);
return;
}
if (Config.playSoundOnClick in clickSoundIdsToOscillatorType) {
playNote();
return;

View file

@ -93,6 +93,8 @@ declare namespace MonkeyTypes {
9 = sawtooth
10 = square
11 = triangle
12 = pentatonic
13 = wholetone
*/
type PlaySoundOnClick =
| "off"
@ -106,7 +108,9 @@ declare namespace MonkeyTypes {
| "8"
| "9"
| "10"
| "11";
| "11"
| "12"
| "13";
type SoundVolume = "0.1" | "0.5" | "1.0";

View file

@ -1006,6 +1006,22 @@
>
triangle
</div>
<div
class="button"
playSoundOnClick="12"
tabindex="0"
onclick="this.blur();"
>
pentatonic
</div>
<div
class="button"
playSoundOnClick="13"
tabindex="0"
onclick="this.blur();"
>
wholetone
</div>
</div>
</div>
<div class="section playSoundOnError">