diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/index.jsx | 1 | ||||
| -rw-r--r-- | src/lib/util.js | 5 | ||||
| -rw-r--r-- | src/relabi/canvas.js | 3 | ||||
| -rw-r--r-- | src/relabi/index.js | 12 | ||||
| -rw-r--r-- | src/ui/App.jsx | 10 | ||||
| -rw-r--r-- | src/ui/Controls.jsx | 171 | ||||
| -rw-r--r-- | src/ui/TooltipSlider.tsx | 95 |
7 files changed, 284 insertions, 13 deletions
diff --git a/src/index.jsx b/src/index.jsx index cc5b7a2..6c58df3 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -10,6 +10,7 @@ document.body.style.margin = 0; document.body.style.padding = 0; document.body.style.height = "100%"; document.body.style.width = "100%"; +document.body.style.fontFamily = "Helvetica, Arial"; document.body.parentNode.style.margin = 0; document.body.parentNode.style.padding = 0; document.body.parentNode.style.height = "100%"; diff --git a/src/lib/util.js b/src/lib/util.js index 750c5b7..7980220 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -26,7 +26,7 @@ export const randnullsign = () => { export function requestAudioContext(fn) { const container = document.createElement("div"); const button = document.createElement("div"); - button.innerHTML = "Tap to start - please unmute your phone"; + button.innerHTML = "Tap to start - please unmute your device"; Object.assign(container.style, { display: "block", position: "absolute", @@ -45,12 +45,13 @@ export function requestAudioContext(fn) { padding: "20px", backgroundColor: "#7F33ED", color: "white", - fontFamily: "monospace", + fontFamily: "'Menlo', monospace", borderRadius: "3px", transform: "translate3D(-50%,-50%,0)", textAlign: "center", lineHeight: "1.5", width: "150px", + cursor: "pointer", }); container.appendChild(button); document.body.appendChild(container); diff --git a/src/relabi/canvas.js b/src/relabi/canvas.js index d2c55a2..2003a5a 100644 --- a/src/relabi/canvas.js +++ b/src/relabi/canvas.js @@ -77,6 +77,7 @@ export default class RelabiCanvas { this.lastAppendTime = time; this.lastAppendFrame = this.lastFrame; this.values = this.values + .filter((value) => value && value[0] < time) .concat(values) .slice(-this.canvas.width) .sort((a, b) => a[0] - b[0]); @@ -142,7 +143,7 @@ export default class RelabiCanvas { // Start the path ctx.beginPath(); ctx.strokeStyle = "#dff"; - ctx.lineWidth = 0.75; + ctx.lineWidth = 1.1; ctx.setLineDash([]); // This is the offset in seconds from the last frame we computed diff --git a/src/relabi/index.js b/src/relabi/index.js index 89e4ce9..8e23d2a 100644 --- a/src/relabi/index.js +++ b/src/relabi/index.js @@ -10,7 +10,7 @@ const TWO_PI = 2 * Math.PI; /** * Wave functions */ -const WAVE_FUNCTIONS = { +const WAVE_SHAPES = { sine: Math.cos, triangle: (time) => (4 / TWO_PI) * @@ -33,10 +33,10 @@ export default class Relabi { this.updateTime = 1; this.steps = 50; this.waves = waves || [ - { type: "triangle", frequency: randrange(0.5, 1.5) }, - { type: "triangle", frequency: randrange(0.75, 2.25) }, - { type: "triangle", frequency: randrange(1, 3) }, - { type: "triangle", frequency: randrange(2, 4) }, + { shape: "triangle", frequency: randrange(0.5, 1.5) }, + { shape: "triangle", frequency: randrange(0.75, 2.25) }, + { shape: "triangle", frequency: randrange(1, 3) }, + { shape: "triangle", frequency: randrange(2, 4) }, ]; this.bounds = bounds; this.previousValue = null; @@ -91,7 +91,7 @@ export default class Relabi { // Compute the wave functions for this event for (index = 0; index < waveCount; index += 1) { const wave = this.waves[index]; - value += WAVE_FUNCTIONS[wave.type](timeOffset * wave.frequency); + value += WAVE_SHAPES[wave.shape](timeOffset * wave.frequency); } // Scale to [-1, 1] diff --git a/src/ui/App.jsx b/src/ui/App.jsx index eaca9a4..fbbbd35 100644 --- a/src/ui/App.jsx +++ b/src/ui/App.jsx @@ -7,6 +7,7 @@ import * as React from "react"; import { useState, useEffect, useRef } from "react"; import Relabi from "../relabi"; import { Kalimba, Drums } from "../lib/instruments"; +import Controls from "./Controls.jsx"; export default function App() { const [relabi, setRelabi] = useState(); @@ -19,10 +20,10 @@ export default function App() { if (!relabi) { const relabiGenerator = new Relabi({ waves: [ - { type: "sine", frequency: 0.75 }, - { type: "sine", frequency: 1.0 }, - { type: "sine", frequency: 1.617 }, - { type: "sine", frequency: 3.141 }, + { shape: "sine", frequency: 0.75 }, + { shape: "sine", frequency: 1.0 }, + { shape: "sine", frequency: 1.617 }, + { shape: "sine", frequency: 3.141 }, ], bounds: [ { @@ -86,6 +87,7 @@ export default function App() { }} > <div ref={relabiRef} /> + <Controls relabi={relabi} /> </div> ); } diff --git a/src/ui/Controls.jsx b/src/ui/Controls.jsx new file mode 100644 index 0000000..f999e4f --- /dev/null +++ b/src/ui/Controls.jsx @@ -0,0 +1,171 @@ +/** + * Relabi controls + * @module src/ui/Controls.jsx; + */ + +import * as React from "react"; +import { useCallback, useRef } from "react"; + +import Slider from "rc-slider"; +import TooltipSlider, { handleRender } from "./TooltipSlider.tsx"; +import "rc-slider/assets/index.css"; + +const WAVE_SHAPE_NAMES = ["sine", "triangle", "saw", "square"]; + +export default function Controls({ relabi }) { + /** + * Handle updating a slider + */ + const onChange = useCallback( + (type, index) => (value) => { + if (type === "bound") { + relabi.bounds[index].level = value; + } + if (type === "frequency") { + relabi.waves[index].frequency = Math.exp(value); + } + if (type === "shape") { + relabi.waves[index].shape = WAVE_SHAPE_NAMES[value]; + } + }, + [relabi] + ); + + return ( + <ControlRow> + <ControlBox> + <Label>Bounds</Label> + <SliderRow> + {relabi?.bounds.map((bound, index) => ( + <ControlSlider + rel={`bound_${index}`} + type="bound" + index={index} + onChange={onChange} + min={-1} + max={1} + step={0.01} + defaultValue={bound.level} + color={bound.color} + reverse + /> + ))} + </SliderRow> + </ControlBox> + + <ControlBox> + <Label>Wave speeds</Label> + <SliderRow> + {relabi?.waves.map((wave, index) => ( + <ControlSlider + rel={`frequency_${index}`} + type="frequency" + index={index} + onChange={onChange} + min={-2} + max={3} + step={0.001} + defaultValue={Math.log(wave.frequency)} + color={"#8a8"} + /> + ))} + </SliderRow> + </ControlBox> + + <ControlBox> + <Label>Wave shapes</Label> + <SliderRow> + {relabi?.waves.map((wave, index) => ( + <ControlSlider + rel={`shape_${index}`} + type="shape" + index={index} + onChange={onChange} + min={0} + max={3} + step={1} + defaultValue={WAVE_SHAPE_NAMES.indexOf(wave.shape)} + color={"#998"} + tipFormatter={(value) => WAVE_SHAPE_NAMES[value]} + /> + ))} + </SliderRow> + </ControlBox> + </ControlRow> + ); +} + +/** + * UI Elements + */ +const ControlRow = ({ children }) => ( + <div + style={{ + display: "flex", + flexDirection: "row", + marginTop: "1rem", + }} + > + {children} + </div> +); + +const ControlBox = ({ children }) => ( + <div + style={{ + marginLeft: "1rem", + }} + > + {children} + </div> +); + +const SliderRow = ({ children }) => ( + <div + style={{ + display: "flex", + flexDireciton: "row", + height: "10rem", + marginTop: "0.5rem", + }} + > + {children} + </div> +); + +const Label = ({ children }) => ( + <div style={{ fontSize: "0.75rem" }}>{children}</div> +); + +const ControlSlider = ({ + type, + index, + min, + max, + step, + reverse, + defaultValue, + color, + onChange, + tipFormatter, +}) => ( + <div style={{ marginRight: "0.5rem" }}> + <TooltipSlider + vertical + min={min} + max={max} + step={step} + reverse={reverse} + defaultValue={defaultValue} + onChange={onChange(type, index)} + handleStyle={{ + backgroundColor: color, + borderColor: color, + opacity: 1, + }} + trackStyle={{ backgroundColor: "#888", width: "2px" }} + railStyle={{ backgroundColor: "#888", width: "2px" }} + tipFormatter={tipFormatter || ((value) => value)} + /> + </div> +); diff --git a/src/ui/TooltipSlider.tsx b/src/ui/TooltipSlider.tsx new file mode 100644 index 0000000..0bb8350 --- /dev/null +++ b/src/ui/TooltipSlider.tsx @@ -0,0 +1,95 @@ +/** + * rc-tooltip tooltip slider + */ + +import * as React from "react"; +import "rc-tooltip/assets/bootstrap.css"; +import Slider from "rc-slider"; +import type { SliderProps } from "rc-slider"; +import raf from "rc-util/lib/raf"; +import Tooltip from "rc-tooltip"; + +const HandleTooltip = (props: { + value: number; + children: React.ReactElement; + visible: boolean; + tipFormatter?: (value: number) => React.ReactNode; +}) => { + const { + value, + children, + visible, + tipFormatter = (val) => `${val} %`, + ...restProps + } = props; + + const tooltipRef = React.useRef<any>(); + const rafRef = React.useRef<number | null>(null); + + function cancelKeepAlign() { + raf.cancel(rafRef.current!); + } + + function keepAlign() { + rafRef.current = raf(() => { + // tooltipRef.current?.forcePopupAlign(); + }); + } + + React.useEffect(() => { + if (visible) { + keepAlign(); + } else { + cancelKeepAlign(); + } + + return cancelKeepAlign; + }, [value, visible]); + + return ( + <Tooltip + placement="top" + overlay={tipFormatter(value)} + overlayInnerStyle={{ minHeight: "auto" }} + ref={tooltipRef} + visible={visible} + {...restProps} + > + {children} + </Tooltip> + ); +}; + +export const handleRender: SliderProps["handleRender"] = (node, props) => { + return ( + <HandleTooltip value={props.value} visible={props.dragging}> + {node} + </HandleTooltip> + ); +}; + +const TooltipSlider = ({ + tipFormatter, + tipProps, + ...props +}: SliderProps & { + tipFormatter?: (value: number) => React.ReactNode; + tipProps: any; +}) => { + const tipHandleRender: SliderProps["handleRender"] = (node, handleProps) => { + return ( + <HandleTooltip + value={handleProps.value} + visible={handleProps.dragging} + tipFormatter={tipFormatter} + {...tipProps} + > + {node} + </HandleTooltip> + ); + }; + + return <Slider {...props} handleRender={tipHandleRender} />; +}; + +export default TooltipSlider; |
