mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-09 23:25:53 +08:00
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:
parent
47c581c5f8
commit
da9b691bb1
6 changed files with 171 additions and 6 deletions
|
@ -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(),
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
6
frontend/src/ts/types/types.d.ts
vendored
6
frontend/src/ts/types/types.d.ts
vendored
|
@ -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";
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Add table
Reference in a new issue