From a359ee247b64ce85054cea716f75f9177df08deb Mon Sep 17 00:00:00 2001 From: Wren Hawthorne Date: Sat, 6 Jul 2024 23:52:51 -0700 Subject: [PATCH] Additional chord options, some instructional text --- src/App.css | 30 ++++++++-- src/components/AllSteps.tsx | 11 +++- src/components/ChordPatternSelector.tsx | 4 +- src/components/ChordStep.tsx | 74 +++++++++++++++++++++++-- src/components/DrumStep.tsx | 2 +- src/components/NewSongStep.tsx | 2 +- src/components/canvas/Chords.tsx | 40 +++++++++---- src/components/canvas/Octo.tsx | 9 +-- src/components/canvas/OctoStage.tsx | 16 ++++-- src/components/canvas/constants.ts | 4 +- src/hooks/useChordPart.ts | 61 ++++++++++++++++---- src/index.css | 2 +- src/utils/basicChords.ts | 16 ++++++ src/utils/chordPatterns.ts | 40 ++++++++++++- src/utils/chords.ts | 32 +++++++++++ 15 files changed, 291 insertions(+), 52 deletions(-) diff --git a/src/App.css b/src/App.css index f0152e3..bd3fed6 100644 --- a/src/App.css +++ b/src/App.css @@ -3,8 +3,8 @@ #root { max-width: 1280px; width: 100%; - margin: 0 auto; - padding: 2rem; + /* margin: 0 auto; + padding: 2rem; */ text-align: center; background-color: indigo; } @@ -108,6 +108,16 @@ sl-button:hover>sl-icon { text-align: center; } +.hint { + background-color: hsla(220, 100%, 30%, .8); + border-radius: 25%; + padding: 8px; + margin: 8px; + + p { + margin: 2px; + } +} /* .form-control__label { color: white; } @@ -121,8 +131,16 @@ sl-select::part(base) { color: white } +.step { + background-color: hsla(220, 100%, 30%, .8); + border-radius: 10%; + padding: 8px; + margin: 8px; +} + .main { - background-color: hsla(220, 100%, 30%, .9); + background-image: url(assets/freepik_ocean_background.jpg); + background-size: cover; /* max-height: 100vh; */ min-height: 400px; min-width: 100%; @@ -258,8 +276,10 @@ sl-tab[active]::part(base) { .beat-select { display: flex; - justify-content: space-between; - width: 100%; + justify-content: center; + flex-wrap: wrap; + /* width: 100%; */ + flex: auto auto; flex-direction: row; padding: 16px; align-items: center; diff --git a/src/components/AllSteps.tsx b/src/components/AllSteps.tsx index 7d1a8b3..a8f9bd5 100644 --- a/src/components/AllSteps.tsx +++ b/src/components/AllSteps.tsx @@ -12,7 +12,7 @@ import { useDrumPart } from "../hooks/useDrumPart" import { useDrumSampler } from '../hooks/useDrumSampler'; import { useChordSynth } from "../hooks/useChordSynth" import { ChordSymbol, INITIAL_CHORD_LIST } from "../utils/basicChords" -import { ChordPattern } from "../utils/chordPatterns" +import { ArpPattern, ChordPattern } from "../utils/chordPatterns" import { useChordPart } from "../hooks/useChordPart" import { ChordStep } from "./ChordStep" import { NewSongStep } from "./NewSongStep" @@ -36,9 +36,10 @@ export const AllSteps = () => { const chordSynth = useChordSynth(isStarted) const [chordList, setChordList] = React.useState(INITIAL_CHORD_LIST) + const [useSeventh, setUseSeventh] = React.useState(Array.from({length: INITIAL_CHORD_LIST.length}, () => false)) const [chordPattern, setChordPattern] = React.useState('DDDD') - - useChordPart({ chordList, chordPattern, chordSynth, isStarted, playChords: areChordsEnabled }) + const [arpPattern, setArpPattern] = React.useState(false) + useChordPart({ chordList, chordPattern, arpPattern, chordSynth, isStarted, playChords: areChordsEnabled, useSeventh }) return ( <> @@ -62,6 +63,10 @@ export const AllSteps = () => { setChordList={setChordList} chordPattern={chordPattern} setChordPattern={setChordPattern} + arpPattern={arpPattern} + setArpPattern={setArpPattern} + useSeventh={useSeventh} + setUseSeventh={setUseSeventh} /> } diff --git a/src/components/ChordPatternSelector.tsx b/src/components/ChordPatternSelector.tsx index 2cb047c..334147a 100644 --- a/src/components/ChordPatternSelector.tsx +++ b/src/components/ChordPatternSelector.tsx @@ -16,8 +16,8 @@ export const ChordPatternSelector = (props: Props) => { return }) return
-

❔↓ Mix It Up ↓❔

- { + {/*

❔↓ Mix It Up ↓❔

*/} + { setSelectedPattern((prevPattern) => { const prevIndex = CHORD_PATTERNS_LIST.indexOf(prevPattern) const nextIndex = prevIndex + 1 < CHORD_PATTERNS_LIST.length ? prevIndex + 1 : 0 diff --git a/src/components/ChordStep.tsx b/src/components/ChordStep.tsx index 752cb5f..f5e3fe8 100644 --- a/src/components/ChordStep.tsx +++ b/src/components/ChordStep.tsx @@ -1,25 +1,89 @@ +import SlButton from "@shoelace-style/shoelace/dist/react/button/index.js"; +import SlIcon from "@shoelace-style/shoelace/dist/react/icon/index.js" + import { ChordSymbol } from "../utils/basicChords" -import { ChordPattern } from "../utils/chordPatterns" +import { ArpPattern, ChordPattern } from "../utils/chordPatterns" import { OctoStage } from "./canvas/OctoStage" +import { ChordPatternSelector } from "./ChordPatternSelector"; +import { ArpPatternSelector } from "./ArpPatternSelector"; type ChordStepProps = { chordList: ChordSymbol[] setChordList: React.Dispatch> chordPattern: ChordPattern setChordPattern: React.Dispatch> + useSeventh: boolean[] + setUseSeventh: React.Dispatch> + arpPattern: ArpPattern + setArpPattern: React.Dispatch> } -export const ChordStep = ({ chordList, setChordList }: ChordStepProps) => { +export const ChordStep = ({ chordList, setChordList, chordPattern, setChordPattern, arpPattern, setArpPattern, useSeventh, setUseSeventh }: ChordStepProps) => { return (

Step 3: Chords

-

- Each has a different mood -

+
+ +

+ Chords are sets of notes played together +

+

+ Different types have different feelings +

+

+ Try the arrows +

+
+ + +
+
+ + { + const newList = [...chordList] + newList.pop() + setChordList(newList) + }} + > + + + {chordList.length} Bars + { + // getTransport().loopEnd = `${chordList.length - 1}m` + setChordList([...chordList, 'I']) + }} + > + + + +
+
+
+ +

These change how the chords are played

+
+ + + +
+ +
) } \ No newline at end of file diff --git a/src/components/DrumStep.tsx b/src/components/DrumStep.tsx index b3e94af..ddcf733 100644 --- a/src/components/DrumStep.tsx +++ b/src/components/DrumStep.tsx @@ -24,7 +24,7 @@ export const DrumStep = (props: DrumStepProps) => { return ( -
+

✨Help the Octopus Make A New Song✨

diff --git a/src/components/NewSongStep.tsx b/src/components/NewSongStep.tsx index 462ae8f..9320342 100644 --- a/src/components/NewSongStep.tsx +++ b/src/components/NewSongStep.tsx @@ -14,7 +14,7 @@ import { getContext, getTransport } from "tone"; export const NewSongStep = () => { const dispatch = useContext(WorkflowDispatchContext) - return
+ return

✨Help the Octopus Make A New Song✨

diff --git a/src/components/canvas/Chords.tsx b/src/components/canvas/Chords.tsx index 553b021..9c1ee99 100644 --- a/src/components/canvas/Chords.tsx +++ b/src/components/canvas/Chords.tsx @@ -3,17 +3,20 @@ import { HEIGHT, WIDTH } from "./constants" import { ComponentProps, useCallback, useState } from "react" import '@pixi/events' -import { CHORD_TO_EMOJI, CHORD_TO_EMOJI_SIZE, CHORD_TO_MOOD_IMAGE, ChordSymbol, MOVE_CHORD_LEFT, MOVE_CHORD_MOOD, MOVE_CHORD_RIGHT } from "../../utils/basicChords" +import { CHORD_TO_EMOJI, CHORD_TO_EMOJI_SIZE, CHORD_TO_HUE, CHORD_TO_MOOD_IMAGE, ChordSymbol, MOVE_CHORD_LEFT, MOVE_CHORD_MOOD, MOVE_CHORD_RIGHT } from "../../utils/basicChords" import { Arc, Point } from "@flatten-js/core" import { TextStyle } from "pixi.js" type ChordSetProps = { chordList: ChordSymbol[] setChordList: React.Dispatch> + + useSeventh: boolean[] + setUseSeventh: React.Dispatch> } export const Chords = (props: ChordSetProps) => { - const { chordList, setChordList } = props + const { chordList, setChordList, useSeventh, setUseSeventh } = props const totalChords = chordList.length const getUpdateChordInListFunction = (i: number) => { @@ -25,6 +28,18 @@ export const Chords = (props: ChordSetProps) => { }) } } + + const getUpdateSeventhInListFunction = (i: number) => { + return () => { + setUseSeventh((prevList) => { + const newList = [...prevList] + newList[i] = !newList[i] + console.log(newList) + return newList + }) + } + } + return ( { index={i} key={`${c}-${i}`} chord={c} + useSeventh={useSeventh[i]} totalChords={totalChords} setChord={getUpdateChordInListFunction(i)} + setSeventh={getUpdateSeventhInListFunction(i)} /> )} {/* {_.times(totalChords, (i) => )} */} @@ -47,16 +64,17 @@ export const Chords = (props: ChordSetProps) => { const RADIUS = 200 - type ChordProps = { chord: ChordSymbol + useSeventh: boolean setChord: (c: ChordSymbol) => void; + setSeventh: () => void; index: number totalChords: number } type GraphicsArg = Parameters["draw"]>>[0] -export const Chord = ({ chord, setChord, index, totalChords }: ChordProps) => { +export const Chord = ({ chord, useSeventh, setChord, setSeventh, index, totalChords }: ChordProps) => { // const progressThrough = index / totalChords const ellipsisRatio = Math.min(WIDTH, HEIGHT) / Math.max(WIDTH, HEIGHT) const xRatio = WIDTH > HEIGHT ? 1 : ellipsisRatio @@ -75,6 +93,7 @@ export const Chord = ({ chord, setChord, index, totalChords }: ChordProps) => { // const b = Math.cos(Math.PI * progressThrough * 2) * (RADIUS * -1 * yRatio) const draw = useCallback((g: GraphicsArg) => { + const hue = CHORD_TO_HUE[chord] const halfAngle = (1 / totalChords) * Math.PI const startOffset = Math.PI / 4 const startAngle = ((index / totalChords) * Math.PI * 2) - halfAngle - startOffset @@ -83,15 +102,15 @@ export const Chord = ({ chord, setChord, index, totalChords }: ChordProps) => { // console.log(`Start: ${startAngle}, End: ${endAngle}`) g.clear() g.lineStyle(1, "white") - g.alpha = .5 + g.alpha = .7 g.arc(0, 0, RADIUS / 2, startAngle, endAngle) - // g.beginFill(['green', 'yellow', 'red', 'blue'][index]) + g.beginFill(`hsl(${hue}deg 90% 60%)`) g.arc(0, 0, RADIUS + 50, endAngle, startAngle, true) g.arc(0, 0, RADIUS / 2, startAngle, endAngle) g.endFill() - }, [index, totalChords]) + }, [chord, index, totalChords]) const [hoverLeft, setHoverLeft] = useState(false) const [hoverRight, setHoverRight] = useState(false) @@ -124,7 +143,7 @@ export const Chord = ({ chord, setChord, index, totalChords }: ChordProps) => { > - { stroke: '#ffffff', fill: ['#ffffff', '#eeeeee'] } as ComponentProps['style']} - /> + /> */} { - console.log(index) + setSeventh() }} style={{ diff --git a/src/components/canvas/Octo.tsx b/src/components/canvas/Octo.tsx index 9935e15..0fd83c1 100644 --- a/src/components/canvas/Octo.tsx +++ b/src/components/canvas/Octo.tsx @@ -23,15 +23,16 @@ export const Octo = () => { zIndex={ZINDEX.OCTOPUS} > ['style']} /> > -} -export const OctoStage = ({chordList, setChordList}: OctoStageProps) => { + useSeventh: boolean[] + setUseSeventh: React.Dispatch> +} +export const OctoStage = ({ chordList, setChordList, useSeventh, setUseSeventh }: OctoStageProps) => { + // const scale = window.innerHeight < 900 ? 0.75 : 1; return ( - - + {/* */} + + + {/* */} + ) diff --git a/src/components/canvas/constants.ts b/src/components/canvas/constants.ts index 739a63c..089caaf 100644 --- a/src/components/canvas/constants.ts +++ b/src/components/canvas/constants.ts @@ -1,2 +1,2 @@ -export const HEIGHT = 600 -export const WIDTH = 600 +export const HEIGHT = 500 +export const WIDTH = 500 diff --git a/src/hooks/useChordPart.ts b/src/hooks/useChordPart.ts index cbf5ec8..3ccadfa 100644 --- a/src/hooks/useChordPart.ts +++ b/src/hooks/useChordPart.ts @@ -1,8 +1,8 @@ -import { Part, PolySynth } from "tone" -import { ChordEvent, ChordPattern, fillChordPattern } from "../utils/chordPatterns" +import { getTransport, Part, PolySynth } from "tone" +import { ArpPattern, ChordEvent, ChordPattern, fillChordPattern } from "../utils/chordPatterns" import { CHORD_TO_INDEX, ChordSymbol, INITIAL_CHORD_LIST } from "../utils/basicChords" import React from "react" -import { DEFAULT_SCALE_OPTIONS, scaleToTriads } from "../utils/chords" +import { DEFAULT_SCALE_OPTIONS, scaleToSevenths, scaleToTriads } from "../utils/chords" import _ from "lodash" type ChordPart = Part @@ -12,11 +12,13 @@ type ChordParts = Array<{ }> -const updateChordPart = (part: ChordPart, i: number, chordNumeral: ChordSymbol, chordPattern: ChordPattern) => { +const updateChordPart = (part: ChordPart, i: number, chordNumeral: ChordSymbol, chordPattern: ChordPattern, arpPattern: ArpPattern | false, useSeventh: boolean) => { part.clear() const triads = scaleToTriads(DEFAULT_SCALE_OPTIONS) - const chord = triads[CHORD_TO_INDEX[chordNumeral]]?.notes || ['C4'] - const newValue = fillChordPattern(i, chord, chordPattern) + const seventhChords = scaleToSevenths(DEFAULT_SCALE_OPTIONS) + const chord = useSeventh ? seventhChords[CHORD_TO_INDEX[chordNumeral]]?.notes || ['C4'] : triads[CHORD_TO_INDEX[chordNumeral]]?.notes || ['C4'] + console.log(arpPattern) + const newValue = fillChordPattern(i, chord, chordPattern, arpPattern || undefined) newValue.forEach((v) => { part.at(v.time, v) }) @@ -24,14 +26,16 @@ const updateChordPart = (part: ChordPart, i: number, chordNumeral: ChordSymbol, type ChordPartOptions = { chordList: ChordSymbol[] + useSeventh: boolean[] chordPattern: ChordPattern + arpPattern: ArpPattern | false chordSynth: React.MutableRefObject isStarted: boolean playChords: boolean } export const useChordPart = (options: ChordPartOptions) => { - const { chordList, chordPattern, chordSynth, isStarted, playChords } = options + const { chordList, chordPattern, arpPattern, chordSynth, isStarted, playChords, useSeventh } = options const chordPartRefs = React.useRef(null) @@ -71,12 +75,49 @@ export const useChordPart = (options: ChordPartOptions) => { React.useEffect(() => { + const l = chordList.length + const transport = getTransport() + transport.loopEnd = `${l}m` + chordPartRefs.current?.forEach(({ part }, i) => { + part.loopEnd = `${l}m` + if (i >= l) { + part.clear() + } + }) + }, [chordList.length]) + + React.useEffect(() => { + const l = chordList.length chordList.forEach((c, i) => { - if (chordPartRefs.current) { // && chordPartRefs.current[i].chord !== c) { - updateChordPart(chordPartRefs.current[i].part, i, c, chordPattern) + if (chordPartRefs.current) { + if (chordPartRefs.current[i] == undefined) { + const initialTriads = scaleToTriads(DEFAULT_SCALE_OPTIONS) + + const initialChord = initialTriads[CHORD_TO_INDEX[c]]?.notes || ['C5'] + + const initialPartValue = fillChordPattern(i, initialChord, 'DDDD') + const part = new Part((time, value) => { + chordSynth.current?.triggerAttackRelease(value.notes || ['C4'], '8n', time, value.velocity) + }, + initialPartValue + ).start(0) + + part.loop = true + part.humanize = true + part.loopStart = '0:0' + part.loopEnd = `${l}m` + chordPartRefs.current[i] = ({ + part, + chord: c + }) + part.start(0) + console.log(part) + } + + updateChordPart(chordPartRefs.current[i].part, i, c, chordPattern, arpPattern, useSeventh[i]) chordPartRefs.current[i].chord = c return } }) - }, [chordList, chordPattern]) + }, [chordSynth, chordList, chordPattern, arpPattern, useSeventh]) } \ No newline at end of file diff --git a/src/index.css b/src/index.css index dabab55..3603beb 100644 --- a/src/index.css +++ b/src/index.css @@ -25,7 +25,7 @@ a:hover { body { margin: 0; display: flex; - place-items: center; + /* place-items: center; */ min-width: 320px; min-height: 100vh; } diff --git a/src/utils/basicChords.ts b/src/utils/basicChords.ts index eed3eed..ba39feb 100644 --- a/src/utils/basicChords.ts +++ b/src/utils/basicChords.ts @@ -29,6 +29,22 @@ export const CHORD_TO_EMOJI_SIZE = { V: 14, vi: 12, }; +export const CHORD_TO_HUE = { + I: 300, + ii: 265, + iii: 230, + IV: 195, + V: 160, + vi: 125, + }; +// export const CHORD_TO_HUE = { +// I: 300, +// ii: 257, +// iii: 214, +// IV: 171, +// V: 129, +// vi: 86, +// }; export const CHORD_TO_MOOD_IMAGE = { I: "😊", diff --git a/src/utils/chordPatterns.ts b/src/utils/chordPatterns.ts index d8cb48a..3ed1807 100644 --- a/src/utils/chordPatterns.ts +++ b/src/utils/chordPatterns.ts @@ -1,4 +1,5 @@ import { Time } from "tone" +import { PatternGenerator, PatternName } from "tone/build/esm/event/PatternGenerator" export type ChordEvent = { time: string, @@ -10,18 +11,25 @@ export const generateChordProgression = (chords: string[][], chordPattern: Chord return chords.map((notes, index) => fillChordPattern(index, notes, chordPattern)).flat() } -export const fillChordPattern = (bar: number, chord: string[], chordPattern: ChordPattern): ChordEvent[] => { +export const fillChordPattern = ( + bar: number, + chord: string[], + chordPattern: ChordPattern, + patternName?: PatternName +): ChordEvent[] => { const timeEvents = CHORD_PATTERNS[chordPattern] const patternTimes = [...timeEvents] const filledPattern: ChordEvent[] = [] let currentTime = patternTimes.shift() + const generator = patternName ? PatternGenerator(chord.length, patternName) : null for (let q = 0; q < 4; q += 1) { for (let s = 0; s < 4; s += 1) { + const notes = generator ? [chord[generator.next().value]] : chord if (currentTime && `${0}:${q}:${s}` === Time(currentTime).toBarsBeatsSixteenths()) { filledPattern.push({ time: `${bar}:${q}:${s}`, - notes: chord, + notes, velocity: .7 }) currentTime = patternTimes.shift() @@ -80,4 +88,30 @@ export const CHORD_PATTERNS = { DDDD, DDUUDU, DDDDU, -} \ No newline at end of file +} + +export const ARP_PATTERNS_LIST = [ + false, + "up", + "downUp", + "alternateDown", + "random", + "randomWalk" +] as const + +export const ARP_PATTERN_LABELS = { + false: '', + "up": 'Up', + "downUp": "Down & Up", + "alternateDown": 'Down', + "random": 'Random', + "randomWalk": 'Walk Around' +} + +export type ArpPattern = + false | + "up" | + "downUp" | + "alternateDown" | + "random" | + "randomWalk" diff --git a/src/utils/chords.ts b/src/utils/chords.ts index 865944c..1e931c4 100644 --- a/src/utils/chords.ts +++ b/src/utils/chords.ts @@ -32,6 +32,33 @@ const findChordThatStartsWithNote = (chordList: string[], note: string): string return null } +export const scaleToSevenths = (scaleOptions?: Partial) => { + const scale = { + ...DEFAULT_SCALE_OPTIONS, + ...scaleOptions + } + const scaleName = toScaleName(scale) + const triadNames = getSeventhsForKey(scale) + const scaleNotes = Scale.rangeOf(scaleName)(`${scale.tonic}${scale.octave}`, `${scale.tonic}${scale.octave + 1}`) + const chordMap = scaleNotes.map( + (note) => { + if (note !== undefined) { + const chordName = findChordThatStartsWithNote(triadNames, note) + if (chordName != null) { + const notes = Chord.notes(chordName, note) + return { + chordName, + tonicNote: note, + notes + } + } + } + }, + new Map() + ) + return chordMap +} + export const scaleToTriads = (scaleOptions?: Partial) => { const scale = { ...DEFAULT_SCALE_OPTIONS, @@ -68,3 +95,8 @@ export const getTriadsForKey = (scale: Scale) => { const { mode, tonic } = scale return Mode.triads(mode, tonic) } + +export const getSeventhsForKey = (scale: Scale) => { + const { mode, tonic } = scale + return Mode.seventhChords(mode, tonic) +}