summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/index.jsx1
-rw-r--r--src/lib/util.js5
-rw-r--r--src/relabi/canvas.js3
-rw-r--r--src/relabi/index.js12
-rw-r--r--src/ui/App.jsx10
-rw-r--r--src/ui/Controls.jsx171
-rw-r--r--src/ui/TooltipSlider.tsx95
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;