diff --git a/README.md b/README.md
index 9d1ced2..7bea050 100644
--- a/README.md
+++ b/README.md
@@ -42,3 +42,7 @@ For development and building:
### Testing/CICD
* Add/fix tests
* Add CICD --> automated testing and branch rules
+
+## Docs
+* Improve documentation --> `jsdoc`, `better-docs`
+* Fix all docstrings `Slider` and `Dropdown`
diff --git a/package.json b/package.json
index a4d6250..dbbf58f 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"dependencies": {
"babel-cli": "^6.26.0",
"only-allow": "^1.1.1",
+ "prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2fda85c..e40e91b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,6 +9,7 @@ importers:
'@testing-library/user-event': ^14.4.3
babel-cli: ^6.26.0
only-allow: ^1.1.1
+ prop-types: ^15.8.1
react: ^18.2.0
react-dom: ^18.2.0
react-scripts: ^5.0.1
@@ -17,6 +18,7 @@ importers:
dependencies:
babel-cli: 6.26.0
only-allow: 1.1.1
+ prop-types: 15.8.1
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-scripts: 5.0.1_w5u3lwoijcfmmatjhtyh5gmbwq
@@ -3133,7 +3135,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.21.4
- caniuse-lite: 1.0.30001426
+ caniuse-lite: 1.0.30001427
fraction.js: 4.2.0
normalize-range: 0.1.2
picocolors: 1.0.0
@@ -3644,7 +3646,7 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
- caniuse-lite: 1.0.30001426
+ caniuse-lite: 1.0.30001427
electron-to-chromium: 1.4.284
node-releases: 2.0.6
update-browserslist-db: 1.0.10_browserslist@4.21.4
@@ -3728,13 +3730,13 @@ packages:
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
dependencies:
browserslist: 4.21.4
- caniuse-lite: 1.0.30001426
+ caniuse-lite: 1.0.30001427
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
dev: false
- /caniuse-lite/1.0.30001426:
- resolution: {integrity: sha512-n7cosrHLl8AWt0wwZw/PJZgUg3lV0gk9LMI7ikGJwhyhgsd2Nb65vKvmSexCqq/J7rbH3mFG6yZZiPR5dLPW5A==}
+ /caniuse-lite/1.0.30001427:
+ resolution: {integrity: sha512-lfXQ73oB9c8DP5Suxaszm+Ta2sr/4tf8+381GkIm1MLj/YdLf+rEDyDSRCzeltuyTVGm+/s18gdZ0q+Wmp8VsQ==}
dev: false
/case-sensitive-paths-webpack-plugin/2.4.0:
@@ -5562,7 +5564,7 @@ packages:
eslint: 8.26.0
fs-extra: 9.1.0
glob: 7.2.3
- memfs: 3.4.8
+ memfs: 3.4.9
minimatch: 3.1.2
schema-utils: 2.7.0
semver: 7.3.8
@@ -7576,8 +7578,8 @@ packages:
engines: {node: '>= 0.6'}
dev: false
- /memfs/3.4.8:
- resolution: {integrity: sha512-E8QAFfd4csESWOqKIpN+khILPFSAZwPR9S+DO/5UtJNcuanF1jLZz0oWUAPF7xd2c1r6dGjGx+jH1st+MFWufA==}
+ /memfs/3.4.9:
+ resolution: {integrity: sha512-3rm8kbrzpUGRyPKSGuk387NZOwQ90O4rI9tsWQkzNW7BLSnKGp23RsEsKK8N8QVCrtJoAMqy3spxHC4os4G6PQ==}
engines: {node: '>= 4.0.0'}
dependencies:
fs-monkey: 1.0.3
@@ -10896,7 +10898,7 @@ packages:
webpack: ^4.0.0 || ^5.0.0
dependencies:
colorette: 2.0.19
- memfs: 3.4.8
+ memfs: 3.4.9
mime-types: 2.1.35
range-parser: 1.2.1
schema-utils: 4.0.0
diff --git a/src/App.js b/src/App.js
index 57ca442..7a475c7 100644
--- a/src/App.js
+++ b/src/App.js
@@ -8,9 +8,10 @@ export default function App() {
const [osc1Playing, setOsc1Playing] = useState(false);
const [osc2Playing, setOsc2Playing] = useState(false);
const [osc3Playing, setOsc3Playing] = useState(false);
-
- // TODO: set these nicer
-
+ /**
+ * TODO: look at removing isPlaying, setPlaying here
+ * Instantiate objects with refactored code, then pass to global
+ */
const osc1 = new InteractiveOscillator({
initOscType: "sine",
initFreq: "73",
diff --git a/src/oscillator.js b/src/oscillator.js
index 5b0c498..204fecc 100644
--- a/src/oscillator.js
+++ b/src/oscillator.js
@@ -1,116 +1,89 @@
-import React, { useRef, useEffect, useState } from "react";
+import React, { useEffect } from "react";
import Dropdown from "./dropdown";
import Slider from "./slider";
+import SetupOscillator from "./setupOscillator";
+import SetupStates from "./setupOscillatorStates";
import "./audioStyles.scss";
+import PropTypes from "prop-types";
const oscillatorTypes = [
{ label: "\u223F", value: "sine" },
{ label: "\u2293", value: "square" },
{ label: "\u3030", value: "sawtooth" },
{ label: "^⌄", value: "triangle" },
- // { label: 'Custom', value: 'custom' },
];
+/**
+ * Interactive Oscillator.
+ * Renders oscillators with gain, frequency sliders/input boxes
+ * Separate play pause buttons.
+ * @component
+ * @example
+ * const [oscPlaying, setOscPlaying] = useState(false);
+ * const osc = new InteractiveOscillator({
+ * initOscType: "sine",
+ * initFreq: "73",
+ * minFreq: "20",
+ * maxFreq: "1000",
+ * id: "osc",
+ * isPlaying: osc1Playing,
+ * setPlaying: setOsc1Playing,
+ * });
+ * return (
+ *
+ * {osc}
+ *
+ * )
+ */
export default function InteractiveOscillator(props) {
- // TODO: reduce the calls here... Define out of fn
- // setup default useState objects
- // const props.(props.isPlaying && props.setPlaying)
- const [freq, setFreq] = useState(props.initFreq);
- const [oscType, setOscType] = useState(props.initOscType);
- const [gain, setGain] = useState(1);
- const audioContextRef = useRef();
- const gainNodeRef = useRef();
- const oscRef = useRef();
- const playingRef = useRef(false);
-
- // handler: freq slider
- const onSlideFreq = (event, props) => {
- console.log(`${props.id} Frequency set to ${event.target.value} Hz`);
- setFreq(event.target.value);
- };
- // handler: gain slider
- const onSlideGain = (event, props) => {
- console.log(`${props.id} Gain set to ${event.target.value}`);
- setGain(event.target.value);
- };
- // handler: oscType
- const handleChangeOscType = (event, props) => {
- console.log(
- `${props.id} Oscillator changed from ${oscType} to ${event.target.value} wave type`
- );
- setOscType(event.target.value);
+ // Call setup SetupOscillator and SetupStates
+ const { gainNodeRef, oscRef } = SetupOscillator(props);
+ const { freqState, oscType, gainState } = SetupStates(props);
+ //! State change handler //
+ // Callback should be useState setter fn
+ const handleStateChange = (event, cb) => {
+ console.log(`${event.target.id} set to ${event.target.value}`);
+ cb(event.target.value);
};
+ //! Instantiating Components //
const oscSelector = new Dropdown({
label: "Shape: ",
- initValue: oscType,
- handleChange: (e) => handleChangeOscType(e, props),
+ initValue: oscType.get,
+ handleChange: (e) => handleStateChange(e, oscType.set),
optionList: oscillatorTypes,
id: `${props.id}-osc-type-dropdown`,
});
const freqSlider = new Slider({
- val: freq,
- onSlide: (e) => onSlideFreq(e, props),
+ val: freqState.get,
+ onSlide: (e) => handleStateChange(e, freqState.set),
min: props.minFreq,
max: props.maxFreq,
label: `Frequency [Hz] (min: ${props.minFreq}, max: ${props.maxFreq})`,
id: `${props.id}-freq-slider`,
});
const gainSlider = new Slider({
- val: gain,
- onSlide: (e) => onSlideGain(e, props),
+ val: gainState.get,
+ onSlide: (e) => handleStateChange(e, gainState.set),
min: 0,
max: 1,
step: 0.01,
label: `Gain (0-1)`,
id: `${props.id}-gain-slider`,
});
-
- // initial osc starting
- useEffect(() => {
- const audioContext = new AudioContext();
- const osc = audioContext.createOscillator();
- const gainNode = audioContext.createGain();
- // Connect and start
- osc.connect(gainNode);
- gainNode.connect(audioContext.destination);
- osc.start();
-
- // Create refs to updatable params, start suspended
- gainNodeRef.current = gainNode;
- oscRef.current = osc;
- audioContextRef.current = audioContext;
- audioContext.suspend();
- // Disconnect osc
- return () => {
- osc.disconnect(gainNode);
- gainNode.disconnect(audioContext.destination);
- audioContext.close();
- };
- }, []);
+ //! useEffects //
// update oscType
useEffect(() => {
- if (oscRef.current) oscRef.current.type = oscType;
- }, [oscType]);
+ if (oscRef.current) oscRef.current.type = oscType.get;
+ }, [oscType.get, oscRef]);
// update freq values
useEffect(() => {
- if (oscRef.current) oscRef.current.frequency.value = freq;
- }, [freq]);
+ if (oscRef.current) oscRef.current.frequency.value = freqState.get;
+ }, [freqState.get, oscRef]);
// update gain values
useEffect(() => {
- if (gainNodeRef.current) gainNodeRef.current.gain.value = gain;
- }, [gain]);
- // updates play/pause state
- useEffect(() => {
- if (playingRef.current !== props.isPlaying) {
- console.log(
- `${props.id} oscillator ` + (playingRef.current ? "stopped" : "started")
- );
- playingRef.current
- ? audioContextRef.current.suspend()
- : audioContextRef.current.resume();
- playingRef.current = !playingRef.current;
- }
- }, [props.isPlaying, props.id, props.setPlaying]);
+ if (gainNodeRef.current) gainNodeRef.current.gain.value = gainState.get;
+ }, [gainState.get, gainNodeRef]);
+
return (
{oscSelector}
@@ -126,3 +99,20 @@ export default function InteractiveOscillator(props) {
);
}
+
+InteractiveOscillator.propTypes = {
+ /** Initial frequency of oscillator [Hz] */
+ initFreq: PropTypes.number,
+ /** Max frequency of oscillator [Hz] */
+ maxFreq: PropTypes.number,
+ /** Min frequency of oscillator [Hz] */
+ minFreq: PropTypes.number,
+ /** Initial oscillator waveshape */
+ initOscType: PropTypes.oneOf(oscillatorTypes.map((item) => item.value)),
+ /** playing useState */
+ isPlaying: PropTypes.bool,
+ /** sets playing useState */
+ setPlaying: PropTypes.func,
+ /** id */
+ id: PropTypes.string,
+};
diff --git a/src/setupOscillator.js b/src/setupOscillator.js
new file mode 100644
index 0000000..1dc4047
--- /dev/null
+++ b/src/setupOscillator.js
@@ -0,0 +1,69 @@
+import { useRef, useEffect } from "react";
+import PropTypes from "prop-types";
+
+/**
+ * Sets up webAudioAPI oscillator object.
+ * New audio context, oscillator and gain node connected.
+ * Rendered paused on startup.
+ * @component
+ * @example
+ * const id = "my-osc"
+ * const [isPlaying, setPlaying] = useState()
+ * const { gainNodeRef, oscRef } = SetupOscillator({id:id, isPlaying:isPlaying,setPlaying:setPlaying});
+ */
+export default function SetupOscillator(props) {
+ const audioContextRef = useRef();
+ const gainNodeRef = useRef();
+ const oscRef = useRef();
+ const playingRef = useRef(false);
+
+ // initial osc starting
+ useEffect(() => {
+ const audioContext = new AudioContext();
+ const osc = audioContext.createOscillator();
+ const gainNode = audioContext.createGain();
+ // Connect and start
+ osc.connect(gainNode);
+ gainNode.connect(audioContext.destination);
+ osc.start();
+
+ // Create refs to updatable params, start suspended
+ gainNodeRef.current = gainNode;
+ oscRef.current = osc;
+ audioContextRef.current = audioContext;
+ audioContext.suspend();
+ // Disconnect osc
+ return () => {
+ osc.disconnect(gainNode);
+ gainNode.disconnect(audioContext.destination);
+ audioContext.close();
+ };
+ }, []);
+ // updates play/pause state
+ useEffect(() => {
+ if (playingRef.current !== props.isPlaying) {
+ console.log(
+ `${props.id} oscillator ` + (playingRef.current ? "stopped" : "started")
+ );
+ playingRef.current
+ ? audioContextRef.current.suspend()
+ : audioContextRef.current.resume();
+ playingRef.current = !playingRef.current;
+ }
+ }, [props.isPlaying, props.id, props.setPlaying]);
+
+ return {
+ audioContextRef: audioContextRef,
+ gainNodeRef: gainNodeRef,
+ oscRef: oscRef,
+ playingRef: playingRef,
+ };
+}
+SetupOscillator.propTypes = {
+ /** playing useState */
+ isPlaying: PropTypes.bool,
+ /** sets playing useState */
+ setPlaying: PropTypes.func,
+ /** id */
+ id: PropTypes.string,
+};
diff --git a/src/setupOscillatorStates.js b/src/setupOscillatorStates.js
new file mode 100644
index 0000000..2f73d88
--- /dev/null
+++ b/src/setupOscillatorStates.js
@@ -0,0 +1,32 @@
+import { useState } from "react";
+import PropTypes from "prop-types";
+
+/**
+ * Sets up react states for oscillator.
+ * @component
+ * @example
+ * const freq = 440
+ * const oscType = "sine"
+ *
+ * const oscRef = useRef() // Typically defined with `SetupOscillator`
+ * const { freqState, oscType, gainState } = SetupStates({initFreq:freq, initOscType:oscType});
+ */
+export default function SetupStates(props) {
+ const [freq, setFreq] = useState(props.initFreq);
+ const [oscType, setOscType] = useState(props.initOscType);
+ const [gain, setGain] = useState(1);
+
+ return {
+ freqState: { get: freq, set: setFreq },
+ oscType: { get: oscType, set: setOscType },
+ gainState: { get: gain, set: setGain },
+ };
+}
+
+SetupStates.propTypes = {
+ /** Initial frequency of oscillator [Hz] */
+ initFreq: PropTypes.number,
+ /** Initial oscillator waveshape */
+ // TODO: set this from the oscillatorTypes struct
+ initOscType: PropTypes.oneOf(["sine", "square", "sawtooth", "triangle"]),
+};
diff --git a/src/slider.js b/src/slider.js
index a77702d..9b136c7 100644
--- a/src/slider.js
+++ b/src/slider.js
@@ -1,14 +1,14 @@
-export default function Slider({ min, max, step, val, onSlide, label }) {
+export default function Slider({ min, max, step, val, onSlide, label, id }) {
// requires min, mix, val, onSlider, label
return (
-
+
onSlide(val)}
/>
@@ -16,7 +16,7 @@ export default function Slider({ min, max, step, val, onSlide, label }) {