-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
1,971 additions
and
1,893 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
plugins: [require("tailwindcss"), require("autoprefixer")], | ||
}; |
This file was deleted.
Oops, something went wrong.
File renamed without changes.
80 changes: 40 additions & 40 deletions
80
src/components/controls/Dropdown.js → src/components/controls/Dropdown.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,41 @@ | ||
import { SlArrowDown } from "react-icons/sl"; | ||
import switchButton from "../../assets/switch.png"; | ||
|
||
const Dropdown = ({ id, label, value = 0, options, switchValue, isActive, onChange, onSwitch }) => { | ||
const handleSelection = (i) => { | ||
document.activeElement.blur() | ||
onChange && onChange(i) | ||
} | ||
|
||
return ( | ||
<div className={`input-select flex flex-1 px-4 items-center gap-4 ${isNaN(switchValue) && 'py-1.5'}`}> | ||
{ label && <label className="text-secondary text-xs uppercase font-bold" htmlFor={id}>{label}</label> } | ||
{ !isNaN(switchValue) && | ||
<input type="checkbox" | ||
{...(label ? {id: id} : {"aria-label": id}) } | ||
name={label} | ||
onChange={ (e) => onSwitch(e.target.checked) } | ||
checked={ isActive || false } | ||
className="input-switch" | ||
data-diameter="60" | ||
data-src={ switchButton } | ||
/> | ||
} | ||
<div className="group dropdown flex-1 my-2"> | ||
<button type="button" tabIndex="0" className={`relative text-left cursor-pointer bg-grid px-2 py-1 w-full font-sevenSegment rounded text-accent outline outline-base-100 outline-offset-2 outline-1 hover:outline-accent focus:outline-accent`}> | ||
{ options?.length ? options?.[value] : "No option" } | ||
<SlArrowDown className="text-accent absolute top-2 right-2 h-4 w-4" /> | ||
</button> | ||
<ul tabIndex="0" className="mt-2 dropdown-content menu shadow-lg bg-neutral text-secondary rounded w-full max-h-[8rem] flex-row overflow-y-scroll"> | ||
{ | ||
options?.map((option, index) => { | ||
return <li key={ index } onClick={ () => handleSelection(index) } className="px-2 py-1 cursor-pointer hover:bg-base-100 w-full">{ option }</li> | ||
}) | ||
} | ||
</ul> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
import { SlArrowDown } from "react-icons/sl"; | ||
import switchButton from "../../assets/switch.png"; | ||
|
||
const Dropdown = ({ id, label, value = 0, options, switchValue, isActive, onChange, onSwitch }) => { | ||
const handleSelection = (i) => { | ||
document.activeElement.blur() | ||
onChange && onChange(i) | ||
} | ||
|
||
return ( | ||
<div className={`input-select flex flex-1 px-4 items-center gap-4 ${isNaN(switchValue) && 'py-1.5'}`}> | ||
{ label && <label className="text-secondary text-xs uppercase font-bold" htmlFor={id}>{label}</label> } | ||
{ !isNaN(switchValue) && | ||
<input type="checkbox" | ||
{...(label ? {id: id} : {"aria-label": id}) } | ||
name={label} | ||
onChange={ (e) => onSwitch(e.target.checked) } | ||
checked={ isActive || false } | ||
className="input-switch" | ||
data-diameter="60" | ||
data-src={ switchButton } | ||
/> | ||
} | ||
<div className="group dropdown flex-1 my-2"> | ||
<button type="button" tabIndex="0" className={`relative text-left cursor-pointer bg-grid px-2 py-1 w-full font-sevenSegment rounded text-accent outline outline-base-100 outline-offset-2 outline-1 hover:outline-accent focus:outline-accent`}> | ||
{ options?.length ? options?.[value] : "No option" } | ||
<SlArrowDown className="text-accent absolute top-2 right-2 h-4 w-4" /> | ||
</button> | ||
<ul tabIndex="0" className="mt-2 dropdown-content menu shadow-lg bg-neutral text-secondary rounded w-full max-h-[8rem] flex-row overflow-y-scroll"> | ||
{ | ||
options?.map((option, index) => { | ||
return <li key={ index } onClick={ () => handleSelection(index) } className="px-2 py-1 cursor-pointer hover:bg-base-100 w-full">{ option }</li> | ||
}) | ||
} | ||
</ul> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default Dropdown; |
162 changes: 81 additions & 81 deletions
162
src/components/controls/Keyboard.js → src/components/controls/Keyboard.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,82 +1,82 @@ | ||
import { Fragment, useEffect, useRef, useState } from "react" | ||
|
||
import { octaveLayout } from "../../config/layout"; | ||
import { useLayout } from "../../hooks/useLayout" | ||
import { useMidi } from "../../hooks/useMidi"; | ||
import { useSequencer } from "../../hooks/useSequencer"; | ||
|
||
const Keyboard = () => { | ||
const { breakpoint } = useLayout(); | ||
const { playNote, octave, input, passthrough } = useMidi(); | ||
const { isRecording, stepStart, stepEnd } = useSequencer(); | ||
const [ octaves, setOctaves ] = useState( octaveLayout[breakpoint] ); | ||
const [ activeNote, setActiveNote ] = useState(null); | ||
|
||
const keyboardRef = useRef(null); | ||
const notes = ["C", "D", "E", "F", "G", "A", "B"] | ||
|
||
const onPlayStart = (e) => { | ||
window.navigator.vibrate && window.navigator.vibrate(10); | ||
isRecording && stepStart(e.target.dataset.note, 0); | ||
playNote(e.target.dataset.note); | ||
} | ||
|
||
const onPlayEnd = (e) => { | ||
isRecording && stepEnd(); | ||
playNote(e.target.dataset.note, false); | ||
e.target.blur(); | ||
} | ||
|
||
const setNote = (e) => { | ||
const note = `${e.note._name}${!!e.note._accidental ? e.note._accidental : ""}${e.note._octave}` | ||
if(e.type === "noteon") setActiveNote(note) | ||
else setActiveNote(false) | ||
} | ||
|
||
const events = { | ||
onTouchStart: onPlayStart, | ||
onTouchEnd: onPlayEnd, | ||
onMouseDown: onPlayStart, | ||
onMouseUp: onPlayEnd, | ||
onMouseLeave: onPlayEnd, | ||
onTouchCancel: onPlayEnd | ||
} | ||
|
||
useEffect(() => { | ||
setOctaves(octaveLayout[breakpoint]) | ||
}, [breakpoint]); | ||
|
||
useEffect(() => { | ||
!input?.hasListener("noteon", setNote) && input?.addListener("noteon", setNote) | ||
!input?.hasListener("noteoff", setNote) && input?.addListener("noteoff", setNote) | ||
!passthrough?.hasListener("noteon", setNote) && passthrough?.addListener("noteon", setNote) | ||
!passthrough?.hasListener("noteoff", setNote) && passthrough?.addListener("noteoff", setNote) | ||
|
||
return () => { | ||
input?.hasListener("noteon", setNote) && input?.removeListener("noteon", setNote) | ||
input?.hasListener("noteoff", setNote) && input?.removeListener("noteoff", setNote) | ||
passthrough?.hasListener("noteon", setNote) && passthrough?.removeListener("noteon", setNote) | ||
passthrough?.hasListener("noteoff", setNote) && passthrough?.removeListener("noteoff", setNote) | ||
} | ||
}, [input, passthrough]) | ||
|
||
return ( | ||
<div ref={keyboardRef} className="flex-1 grid relative overflow-hidden auto-cols-auto grid-rows-2 gap-x-1 grid-flow-col h-56 -mt-20"> | ||
{ | ||
[...Array(octaves).keys()].map((o) => { | ||
return [...Array(7).keys()].map((n) => { | ||
return ( | ||
<Fragment key={`${o}${n}`}> | ||
{ [1,2,4,5,6].includes(n + 1) && <button {...events } aria-label={ `${notes[n]}#${o + octave}`} data-note={ `${notes[n]}#${o + octave}`} className={`key relative btn btn-secondary row-start-1 top-3/4 w-3/4 left-2/3 h-3/4 rounded z-10 focus:shadow-inner shadow shadow-black ${ activeNote === `${notes[n]}#${o + octave}` && 'btn-pushed'}`}></button> } | ||
<button aria-label={`${notes[n]}${o + octave}`} {...events } data-note={ `${notes[n]}${o + octave}` } className={`key btn btn-active btn-ghost row-start-2 w-full h-full rounded z-0 focus:shadow-inner shadow shadow-black ${ activeNote === `${notes[n]}${o + octave}` && 'btn-pushed'}`}></button> | ||
</Fragment> | ||
) | ||
}) | ||
}) | ||
} | ||
{ octaves && <button aria-label={ `C${octave + octaves}` } {...events } data-note={ `C${octave + octaves}` } className={`key btn btn-active btn-ghost row-start-2 w-full h-full rounded z-0 focus:shadow-inner shadow shadow-black ${ activeNote === `C${octave + octaves}` && 'btn-pushed'}`}></button> } | ||
</div> | ||
) | ||
} | ||
|
||
import { Fragment, useEffect, useRef, useState } from "react" | ||
|
||
import { octaveLayout } from "../../config/layout"; | ||
import { useLayout } from "../../hooks/useLayout" | ||
import { useMidi } from "../../hooks/useMidi"; | ||
import { useSequencer } from "../../hooks/useSequencer"; | ||
|
||
const Keyboard = () => { | ||
const { breakpoint } = useLayout(); | ||
const { playNote, octave, input, passthrough } = useMidi(); | ||
const { isRecording, stepStart, stepEnd } = useSequencer(); | ||
const [ octaves, setOctaves ] = useState( octaveLayout[breakpoint] ); | ||
const [ activeNote, setActiveNote ] = useState(null); | ||
|
||
const keyboardRef = useRef(null); | ||
const notes = ["C", "D", "E", "F", "G", "A", "B"] | ||
|
||
const onPlayStart = (e) => { | ||
window.navigator.vibrate && window.navigator.vibrate(10); | ||
isRecording && stepStart(e.target.dataset.note, 0); | ||
playNote(e.target.dataset.note); | ||
} | ||
|
||
const onPlayEnd = (e) => { | ||
isRecording && stepEnd(); | ||
playNote(e.target.dataset.note, false); | ||
e.target.blur(); | ||
} | ||
|
||
const setNote = (e) => { | ||
const note = `${e.note._name}${!!e.note._accidental ? e.note._accidental : ""}${e.note._octave}` | ||
if(e.type === "noteon") setActiveNote(note) | ||
else setActiveNote(false) | ||
} | ||
|
||
const events = { | ||
onTouchStart: onPlayStart, | ||
onTouchEnd: onPlayEnd, | ||
onMouseDown: onPlayStart, | ||
onMouseUp: onPlayEnd, | ||
onMouseLeave: onPlayEnd, | ||
onTouchCancel: onPlayEnd | ||
} | ||
|
||
useEffect(() => { | ||
setOctaves(octaveLayout[breakpoint]) | ||
}, [breakpoint]); | ||
|
||
useEffect(() => { | ||
!input?.hasListener("noteon", setNote) && input?.addListener("noteon", setNote) | ||
!input?.hasListener("noteoff", setNote) && input?.addListener("noteoff", setNote) | ||
!passthrough?.hasListener("noteon", setNote) && passthrough?.addListener("noteon", setNote) | ||
!passthrough?.hasListener("noteoff", setNote) && passthrough?.addListener("noteoff", setNote) | ||
|
||
return () => { | ||
input?.hasListener("noteon", setNote) && input?.removeListener("noteon", setNote) | ||
input?.hasListener("noteoff", setNote) && input?.removeListener("noteoff", setNote) | ||
passthrough?.hasListener("noteon", setNote) && passthrough?.removeListener("noteon", setNote) | ||
passthrough?.hasListener("noteoff", setNote) && passthrough?.removeListener("noteoff", setNote) | ||
} | ||
}, [input, passthrough]) | ||
|
||
return ( | ||
<div ref={keyboardRef} className="flex-1 grid relative overflow-hidden auto-cols-auto grid-rows-2 gap-x-1 grid-flow-col h-56 -mt-20"> | ||
{ | ||
[...Array(octaves).keys()].map((o) => { | ||
return [...Array(7).keys()].map((n) => { | ||
return ( | ||
<Fragment key={`${o}${n}`}> | ||
{ [1,2,4,5,6].includes(n + 1) && <button {...events } aria-label={ `${notes[n]}#${o + octave}`} data-note={ `${notes[n]}#${o + octave}`} className={`key relative btn btn-secondary row-start-1 top-3/4 w-3/4 left-2/3 h-3/4 rounded z-10 focus:shadow-inner shadow shadow-black ${ activeNote === `${notes[n]}#${o + octave}` && 'btn-pushed'}`}></button> } | ||
<button aria-label={`${notes[n]}${o + octave}`} {...events } data-note={ `${notes[n]}${o + octave}` } className={`key btn btn-active btn-ghost row-start-2 w-full h-full rounded z-0 focus:shadow-inner shadow shadow-black ${ activeNote === `${notes[n]}${o + octave}` && 'btn-pushed'}`}></button> | ||
</Fragment> | ||
) | ||
}) | ||
}) | ||
} | ||
{ octaves && <button aria-label={ `C${octave + octaves}` } {...events } data-note={ `C${octave + octaves}` } className={`key btn btn-active btn-ghost row-start-2 w-full h-full rounded z-0 focus:shadow-inner shadow shadow-black ${ activeNote === `C${octave + octaves}` && 'btn-pushed'}`}></button> } | ||
</div> | ||
) | ||
} | ||
|
||
export default Keyboard |
104 changes: 52 additions & 52 deletions
104
src/components/controls/Knob.js → src/components/controls/Knob.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,53 @@ | ||
import { useCallback, useEffect, useRef, useState } from "react"; | ||
|
||
import knob from "../../assets/knob.png"; | ||
|
||
const Knob = ({id, value = 0, min = 0, max = 127, step = 1, label, onChange}) => { | ||
const [ currentValue, setValue ] = useState(value ? value : min); | ||
const knobRef = useRef(null); | ||
|
||
const handleValue = useCallback((e) => { | ||
setValue(e.target.value); | ||
onChange && onChange(parseInt(e.target.value)); | ||
}, [onChange]) | ||
|
||
useEffect(() => setValue(value), [value]); | ||
|
||
useEffect(() => { | ||
const currentKnob = knobRef.current; | ||
|
||
currentKnob.addEventListener("change", handleValue) | ||
currentKnob.addEventListener("input", handleValue) | ||
|
||
return () => { | ||
currentKnob.removeEventListener("change", handleValue) | ||
currentKnob.removeEventListener("input", handleValue) | ||
} | ||
}, [handleValue]) | ||
|
||
return ( | ||
<div className="flex flex-col items-center justify-center"> | ||
{ label && <label className="text-secondary text-xs uppercase font-bold" htmlFor={id}>{label}</label> } | ||
<div className="flex-1"> | ||
<input | ||
{...(label ? {id: id} : {"aria-label": id}) } | ||
ref={knobRef} | ||
type="range" | ||
name={label} | ||
className="input-knob focus-visible:ring-offset-0" | ||
data-src={ knob } | ||
data-sprites="100" | ||
diameter="90" | ||
value={ currentValue } | ||
min={ min } | ||
max={ max } | ||
step={ step } | ||
onChange={ handleValue } | ||
/> | ||
</div> | ||
<div className="bg-neutral bg-grid font-sevenSegment h-4 w-8 text-xs text-center rounded text-accent outline outline-base-100 outline-offset-1 outline-1">{ value }</div> | ||
</div> | ||
) | ||
} | ||
|
||
import { useCallback, useEffect, useRef, useState } from "react"; | ||
|
||
import knob from "../../assets/knob.png"; | ||
|
||
const Knob = ({id, value = 0, min = 0, max = 127, step = 1, label, onChange}) => { | ||
const [ currentValue, setValue ] = useState(value ? value : min); | ||
const knobRef = useRef(null); | ||
|
||
const handleValue = useCallback((e) => { | ||
setValue(e.target.value); | ||
onChange && onChange(parseInt(e.target.value)); | ||
}, [onChange]) | ||
|
||
useEffect(() => setValue(value), [value]); | ||
|
||
useEffect(() => { | ||
const currentKnob = knobRef.current; | ||
|
||
currentKnob.addEventListener("change", handleValue) | ||
currentKnob.addEventListener("input", handleValue) | ||
|
||
return () => { | ||
currentKnob.removeEventListener("change", handleValue) | ||
currentKnob.removeEventListener("input", handleValue) | ||
} | ||
}, [handleValue]) | ||
|
||
return ( | ||
<div className="flex flex-col items-center justify-center"> | ||
{ label && <label className="text-secondary text-xs uppercase font-bold" htmlFor={id}>{label}</label> } | ||
<div className="flex-1"> | ||
<input | ||
{...(label ? {id: id} : {"aria-label": id}) } | ||
ref={knobRef} | ||
type="range" | ||
name={label} | ||
className="input-knob focus-visible:ring-offset-0" | ||
data-src={ knob } | ||
data-sprites="100" | ||
diameter="90" | ||
value={ currentValue } | ||
min={ min } | ||
max={ max } | ||
step={ step } | ||
onChange={ handleValue } | ||
/> | ||
</div> | ||
<div className="bg-neutral bg-grid font-sevenSegment h-4 w-8 text-xs text-center rounded text-accent outline outline-base-100 outline-offset-1 outline-1">{ value }</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default Knob; |
Oops, something went wrong.