Skip to content

Commit

Permalink
Added share step
Browse files Browse the repository at this point in the history
  • Loading branch information
wrenhawth committed Jul 15, 2024
1 parent c77288b commit a0bd874
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 43 deletions.
4 changes: 4 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ sl-select::part(base) {
background-color: var(--sl-color-teal-500);
}

.share-tab::part(base) {
background-color: var(--sl-color-cyan-500);
}

sl-tab[active]::part(base) {
font-weight: bolder;
text-decoration: underline;
Expand Down
43 changes: 40 additions & 3 deletions src/components/AllSteps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ChordStep } from "./ChordStep"
import { NewSongStep } from "./NewSongStep"
import { getTransport } from "tone";
import { MelodyStep } from "./MelodyStep";
import { ShareStep } from "./ShareStep";

export type Song = {
title: string;
Expand Down Expand Up @@ -59,7 +60,7 @@ const parseChordListParam = (param: string[] | null) => {
}

const parseSeventhsParam = (param: string[] | null, length: number) => {
const sevenths = Array.from({ length: INITIAL_CHORD_LIST.length }, () => false)
const sevenths = Array.from({ length }, () => false)
param?.forEach((i) => {
const num = Number.parseInt(i)
if (Number.isInteger(num) && num > 0 && num < length) {
Expand Down Expand Up @@ -89,9 +90,12 @@ export const AllSteps = () => {
const urlParams = new URLSearchParams(window.location.search);
const { isStarted, step, areDrumsEnabled, areChordsEnabled, isMelodyEnabled } = useContext(WorkflowContext)
const dispatch = useContext(WorkflowDispatchContext)

const [title, setTitle] = React.useState(urlParams.get('title') || '')
const [tempo, setTempo] = React.useState<number>(Number.parseInt(urlParams.get('tempo') || `${DEFAULT_TEMPO}`) ?? DEFAULT_TEMPO)

const isLoadedSong = urlParams.get('title') != null

useEffect(() => {
const transport = getTransport()
transport.bpm.rampTo(tempo, 2)
Expand All @@ -112,14 +116,33 @@ export const AllSteps = () => {
const [arpPattern, setArpPattern] = React.useState<ArpPattern>(parseArpPatternParam(urlParams.get('arp')))
useChordPart({ chordList, chordPattern, arpPattern, chordSynth, isStarted, playChords: areChordsEnabled, useSeventh })

const [lyrics, setLyrics] = React.useState('')
const [lyrics, setLyrics] = React.useState(urlParams.get('lyrics') || '')

const shareUrl = React.useMemo(() => {
const params = new URLSearchParams({
title,
tempo: tempo.toFixed(0),
drums: drumPreset,
chordPattern,
arp: arpPattern,
lyrics,
})
chordList.forEach((c) => params.append('chords', c))
useSeventh.forEach((s, i) => {
if (s) {
params.append('sev', i.toFixed(0))
}
})
return `${window.location.origin}${window.location.pathname}?${params.toString()}`
}, [title, tempo, drumPreset, chordPattern, arpPattern, lyrics, chordList, useSeventh])

return (
<>
{/* <nav> */}
<div>
<main className="main">
{step === WorkflowStep.NEW_SONG &&
<NewSongStep title={title} persistTitle={setTitle} />
<NewSongStep title={title} isLoadedSong={isLoadedSong} persistTitle={setTitle} />
}
{step === WorkflowStep.DRUMS &&
<DrumStep
Expand Down Expand Up @@ -152,6 +175,12 @@ export const AllSteps = () => {
setLyrics={setLyrics}
/>
}
{step === WorkflowStep.SHARE &&
<ShareStep
title={title}
shareUrl={shareUrl}
/>
}
</main>
{step === WorkflowStep.CHORDS && <p>
<span onClick={() => setChordPreset(ChordSynthPreset.DEFAULT)}>🖥️</span>
Expand Down Expand Up @@ -199,6 +228,14 @@ export const AllSteps = () => {

>4: Sing
</SlTab>
<SlTab
className="tab share-tab"
slot="nav"
active={step === WorkflowStep.SHARE}
disabled={!isMelodyEnabled}
onClick={() => isMelodyEnabled && dispatch?.({ type: "setStep", step: WorkflowStep.SHARE })}
>5: Share
</SlTab>
</SlTabGroup>
</div>

Expand Down
1 change: 1 addition & 0 deletions src/components/ChordStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export const ChordStep = ({ title, chordList, setChordList, chordPattern, setCho
onClick={() => {
dispatch?.({ type: "setStep", step: WorkflowStep.MELODY })
}}

>
<SlIcon slot="prefix" name="caret-right-fill" style={{ fontWeight: 'bold' }} />
Next Step
Expand Down
2 changes: 1 addition & 1 deletion src/components/DrumStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const DrumStep = (props: DrumStepProps) => {

return (
<div className="step">
<h3 className="step-header"><span className="step-num">Step 2</span>: Rhythm</h3>
<h3 className="step-header"><span className="step-num">Step 2</span>: Drums</h3>
<h2 className="title" style={{ margin: 12 }}>Song: <span className="emphasize">{title}</span></h2>
<PlayButton />

Expand Down
14 changes: 13 additions & 1 deletion src/components/MelodyStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const MelodyStep = (props: MelodyStepProps) => {
You can sing soft, loud, or any way you want 💖
</li>
<li>
Start by trying to sing close to the sounds you hear
Start by trying to sing or hum close to the sounds you hear
</li>
</ul>
</div>
Expand All @@ -66,6 +66,18 @@ export const MelodyStep = (props: MelodyStepProps) => {
}}
></SlTextArea>
</div>

<SlButton
variant="success"
size="large"
onClick={() => {
dispatch?.({ type: "setStep", step: WorkflowStep.SHARE })
}}

>
<SlIcon slot="prefix" name="caret-right-fill" style={{ fontWeight: 'bold' }} />
Next Step
</SlButton>
</div>
)
}
62 changes: 33 additions & 29 deletions src/components/NewSongStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { generateTitleOptions } from "../utils/title";
type NewSongProps = {
title: string
persistTitle: React.Dispatch<React.SetStateAction<string>>
isLoadedSong: boolean
}

export const NewSongStep = (props: NewSongProps) => {
const { title, persistTitle } = props
const { title, isLoadedSong, persistTitle } = props
const dispatch = useContext(WorkflowDispatchContext)
const [options, setOptions] = React.useState(generateTitleOptions())
// const [title, setTitle] = React.useState('')
Expand All @@ -25,34 +26,37 @@ export const NewSongStep = (props: NewSongProps) => {
<h2 className="octo" style={{ margin: 0 }}>🐙</h2>
<div className="tempo-selector">
<h3 className="step-header"><span className="step-num">Step 1</span>: Song Name</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', alignItems: 'center' }}>
<p className="hint" style={{ gridColumn: 2 }}>Pick a silly one ↓</p>
<div>
<SlButton
// size="small"
// style={{ margin: 16 }}
onClick={() => setOptions(generateTitleOptions())}
>
🎲Random🎲
</SlButton>
{!isLoadedSong && <>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', alignItems: 'center' }}>
<p className="hint" style={{ gridColumn: 2 }}>Pick a silly one ↓</p>
<div>
<SlButton
// size="small"
// style={{ margin: 16 }}
onClick={() => setOptions(generateTitleOptions())}
>
🎲Random🎲
</SlButton>
</div>
</div>
</div>
<div className="name-options">
{options.map((o) => (
<p
key={o}
className={o === title ? "selected" : undefined}
onClick={() => {
// setTitle(o)
persistTitle(o)
}}
>
{o}
</p>
))}
</div>
<div className="name-options">
{options.map((o) => (
<p
key={o}
className={o === title ? "selected" : undefined}
onClick={() => {
// setTitle(o)
persistTitle(o)
}}
>
{o}
</p>
))}
</div>
</>}

<div>
<div className="hint">Or make your own ↓</div>
{!isLoadedSong && <div className="hint">Or make your own ↓</div>}
<SlInput
value={title}
onSlChange={(e) => {
Expand Down Expand Up @@ -83,8 +87,8 @@ export const NewSongStep = (props: NewSongProps) => {

}}
>
<SlIcon slot="prefix" name="plus-lg" style={{ fontWeight: 'bold' }} />
New Song
<SlIcon slot="prefix" name="caret-right-fill" style={{ fontWeight: 'bold' }} />
{isLoadedSong ? "Start Song" : "New Song"}
</SlButton>
</div>
}
72 changes: 72 additions & 0 deletions src/components/ShareStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import SlButton from "@shoelace-style/shoelace/dist/react/button/index.js";
import SlIcon from "@shoelace-style/shoelace/dist/react/icon/index.js"


import { WorkflowDispatchContext } from "./WorkflowContext";
import { useContext } from "react";
import { WorkflowStep } from "../utils/workflow";
import { PlayButton } from "./PlayButton";

type ShareStepProps = {
title: string
shareUrl: string
}

export const ShareStep = (props: ShareStepProps) => {
const { title, shareUrl } = props
const dispatch = useContext(WorkflowDispatchContext)

return (
<div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr 1fr', alignItems: 'center', width: '80%', margin: 'auto' }}>
<SlButton
size="small"
style={{ justifySelf: 'center' }}
onClick={() => {
dispatch?.({
type: 'setStep',
step: WorkflowStep.MELODY
})
}}>
<SlIcon name="rewind" slot="prefix"></SlIcon>🎙️ Sing
</SlButton>
<h3 className="step-header">
<span className="step-num">Step 5</span>: Share
</h3>
</div>
<div className="hint">
<ul className="hint-list">
<li>
If you click the button below, you can send your song to someone else
</li>
</ul>
</div>

<h4 className="title" style={{ margin: 0 }}>Song: <span className="emphasize">{title}</span></h4>
<div className="step">
<div>
<PlayButton />
</div>
<SlButton
size="medium"
variant="success"
style={{ justifySelf: 'center' }}
onClick={async () => {
if (navigator?.canShare?.()) {
await navigator.share({
url: shareUrl,
text: "Check out my song!",
title
})
} else {
console.log(shareUrl)
navigator.clipboard.writeText(shareUrl)
}

}}>
<SlIcon name="send" slot="prefix"></SlIcon>Share
</SlButton>
</div>
</div>
)
}
14 changes: 13 additions & 1 deletion src/components/WorkflowContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ function workflowReducer(workflow: Workflow, action: WorkflowAction) {
}
}
case "start":
return { ...workflow, isStarted: true }
return {
...workflow,
isStarted: true
}
case "unlock":
return {
...workflow,
areDrumsEnabled: true,
areChordsEnabled: true,
isMelodyEnabled: true
}
default:
return workflow
}
Expand All @@ -47,6 +57,8 @@ type WorkflowAction = {
step: WorkflowStep
} | {
type: "start"
} | {
type: "unlock"
}

export const WorkflowProvider = ({ children }: { children: React.ReactNode }) => {
Expand Down
16 changes: 8 additions & 8 deletions src/hooks/useChordPart.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
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 { CHORD_TO_INDEX, ChordSymbol } from "../utils/basicChords"
import React from "react"
import { DEFAULT_SCALE_OPTIONS, scaleToSevenths, scaleToTriads } from "../utils/chords"
import _ from "lodash"

type ChordPart = Part<ChordEvent>
type ChordParts = Array<{
Expand Down Expand Up @@ -44,16 +43,17 @@ export const useChordPart = (options: ChordPartOptions) => {
if (isStarted && playChords) {
if (chordPartRefs.current == null) {
const initialTriads = scaleToTriads(DEFAULT_SCALE_OPTIONS)
_.times(4, (i) => {
const initialSevenths = scaleToSevenths(DEFAULT_SCALE_OPTIONS)
chordList.forEach((c, i) => {
if (chordPartRefs.current == null) {
chordPartRefs.current = []
}

const initialRomanNumeral = INITIAL_CHORD_LIST[i]
const initialRomanNumeral = c
const chordIndex = CHORD_TO_INDEX[initialRomanNumeral]
const initialChord = useSeventh[i] ? initialSevenths[chordIndex]?.notes || ['C5'] : initialTriads[chordIndex]?.notes || ['C5']

const initialChord = initialTriads[CHORD_TO_INDEX[initialRomanNumeral]]?.notes || ['C5']

const initialPartValue = fillChordPattern(i, initialChord, 'DDUUDU', arpPattern)
const initialPartValue = fillChordPattern(i, initialChord, chordPattern, arpPattern)

const part = new Part((time, value) => {
chordSynth.current?.triggerAttackRelease(value.notes || ['C4'], value.duration || '8n', time, value.velocity)
Expand All @@ -72,7 +72,7 @@ export const useChordPart = (options: ChordPartOptions) => {
})
}
}
}, [chordSynth, playChords, isStarted, arpPattern])
}, [chordList, chordSynth, playChords, isStarted, chordPattern, arpPattern, useSeventh])


React.useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions src/utils/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export enum WorkflowStep {
DRUMS,
CHORDS,
MELODY,
SHARE
}

0 comments on commit a0bd874

Please sign in to comment.