diff --git a/.gitignore b/.gitignore index 9051880..4cf9442 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ node_modules bundle.js bundle.js.map npm-debug.log - +dist diff --git a/package.json b/package.json index 8349736..4fad743 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Meander Fractal Generator", "scripts": { "start": "webpack serve", - "cli": "webpack serve --mode development --open" + "cli": "webpack serve --mode development --open", + "build":"webpack build" }, "devDependencies": { "@babel/core": "^7.21.4", diff --git a/src/app.tsx b/src/app.tsx index 599a076..c769828 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -16,6 +16,7 @@ import { noteLengths, Knobs } from './knobs' import GlobalTimer from "./util/GlobalTimer" import { EventBus, MidiTracker } from "./util/eventBus" +import { lerp, clamp, floorClamp, parabolic, impulse } from './util/smoothingFunctions' const globalTimer = new GlobalTimer() const eventBus = new EventBus() @@ -88,20 +89,9 @@ const spawnTimer = ( eventBus.subscribe("nameEvent", (func, num) => { if(typeof func === "function"){ func() - } else { - console.log("Why not func") - debugger; } }); -const lerp = (a, b, t) => a + (b-a) * t -const clamp = (val, max) => val > max ? max : val -const floorClamp = (val, min) => val < min ? min : val -const parabolic = (x,k) => Math.pow( 4.0*x*(1.0-x), k ); -// www.iquilezles.org/www/articles/functions/functions.htm -const impulse = ( howSteep, x ) => { - let h = howSteep*x; - return h* Math.exp(1.0 - h); -} + // These values may be for the BSP and not universal const MIDI_STOP_MSG = 252 @@ -279,101 +269,101 @@ const App = (props) => { const depthLFO = new LFO(-500, 200, 20) + // Redraws the canvas with the browser framerate // window.requestAnimationFrame(function(){ // }); - // Redraws the canvas with the browser framerate const mainLoop = () => { updateTimers(globalTimer.getSecondsElapsed()) // console.log("myLFO", myLFO.getSin()); - // if( - // motifModOptions.filter( (option) => { return option.optionName === "noAnimation"})[0].value == false - // ){ - // const configMod = [ - // myLFO3.getSin() / 2, - // myLFO3.getSin() / 3, - // myLFO3.getSin() / 4, - // myLFO3.getSin() / 5, - // myLFO3.getSin() / 2, - // myLFO3.getSin() / 3, - // myLFO3.getSin() / 4, - // myLFO3.getSin() / 5, - // ] - // dispatch({ - // type: UPDATE_ALL_MOTIFS, - // payload: [ - // myLFO3.getSin() + configMod[0], - // myLFO3.getSin() + configMod[1], - // myLFO3.getSin() + configMod[2], - // myLFO3.getSin() + configMod[3], - // myLFO3.getSin() + configMod[4], - // myLFO3.getSin() + configMod[5], - // myLFO3.getSin() + configMod[6], - // myLFO3.getSin() + configMod[7], - // ] - // }) - // dispatch({ - // type: UPDATE_CONFIG, - // payload:{ - // option: - // { optionName: 'sideLength' , min: 2 , max:1200 , value: 300 , type: 'range' }, - // value: myLFO4.getSin() / 4 - // } - // }) - // // dispatch({ - // // type: UPDATE_CONFIG, - // // payload:{ - // // option: - // // { optionName: 'lineWidth' , min: 0 , max:400 , value: 10 , type: 'range' }, - // // value: myLFO4.getSin() / 2 - // // } - // // }) + // if( + // motifModOptions.filter( (option) => { return option.optionName === "animate"})[0].value == false + // ){ + // const configMod = [ + // myLFO3.getSin() / 2, + // myLFO3.getSin() / 3, + // myLFO3.getSin() / 4, + // myLFO3.getSin() / 5, + // myLFO3.getSin() / 2, + // myLFO3.getSin() / 3, + // myLFO3.getSin() / 4, + // myLFO3.getSin() / 5, + // ] + // dispatch({ + // type: UPDATE_ALL_MOTIFS, + // payload: [ + // myLFO3.getSin() + configMod[0], + // myLFO3.getSin() + configMod[1], + // myLFO3.getSin() + configMod[2], + // myLFO3.getSin() + configMod[3], + // myLFO3.getSin() + configMod[4], + // myLFO3.getSin() + configMod[5], + // myLFO3.getSin() + configMod[6], + // myLFO3.getSin() + configMod[7], + // ] + // }) + // dispatch({ + // type: UPDATE_CONFIG, + // payload:{ + // option: + // { optionName: 'sideLength' , min: 2 , max:1200 , value: 300 , type: 'range' }, + // value: myLFO4.getSin() / 4 + // } + // }) + // // dispatch({ + // // type: UPDATE_CONFIG, + // // payload:{ + // // option: + // // { optionName: 'lineWidth' , min: 0 , max:400 , value: 10 , type: 'range' }, + // // value: myLFO4.getSin() / 2 + // // } + // // }) - // dispatch({ - // type: UPDATE_CONFIG, - // payload:{ - // option: - // { optionName: 'red' , min: 0 , max:255 , value: 300 , type: 'range' }, - // value: colorLFORed.getSin() - // } - // }) - // dispatch({ - // type: UPDATE_CONFIG, - // payload:{ - // option: - // { optionName: 'green' , min: 0 , max:255 , value: 300 , type: 'range' }, - // value: colorLFOGreen.getSin(), - // } - // }) - // dispatch({ - // type: UPDATE_CONFIG, - // payload:{ - // option: - // { optionName: 'blue' , min: 0 , max:255 , value: 300 , type: 'range' }, - // value: colorLFOBlue.getSin(), - // } - // }) - // dispatch({ - // type: UPDATE_CONFIG, - // payload:{ - // option: { - // max: 20000, - // min: 2, - // optionName: "numSegments", - // type: "range", - // value: 1057, - // }, - // value: myLFO2.getSin() - // } - // }) - // dispatch({ - // type: UPDATE_CONFIG, - // payload:{ - // option: { optionName: 'depth' , min: -1000 , max:2000 , value: 1 , type: 'range' }, - // value: depthLFO.getSin() - // } - // }) - // } + // dispatch({ + // type: UPDATE_CONFIG, + // payload:{ + // option: + // { optionName: 'red' , min: 0 , max:255 , value: 300 , type: 'range' }, + // value: colorLFORed.getSin() + // } + // }) + // dispatch({ + // type: UPDATE_CONFIG, + // payload:{ + // option: + // { optionName: 'green' , min: 0 , max:255 , value: 300 , type: 'range' }, + // value: colorLFOGreen.getSin(), + // } + // }) + // dispatch({ + // type: UPDATE_CONFIG, + // payload:{ + // option: + // { optionName: 'blue' , min: 0 , max:255 , value: 300 , type: 'range' }, + // value: colorLFOBlue.getSin(), + // } + // }) + // dispatch({ + // type: UPDATE_CONFIG, + // payload:{ + // option: { + // max: 20000, + // min: 2, + // optionName: "numSegments", + // type: "range", + // value: 1057, + // }, + // value: myLFO2.getSin() + // } + // }) + // dispatch({ + // type: UPDATE_CONFIG, + // payload:{ + // option: { optionName: 'depth' , min: -1000 , max:2000 , value: 1 , type: 'range' }, + // value: depthLFO.getSin() + // } + // }) + // } } var elapsed = 0 diff --git a/src/components/ConfigComponent.tsx b/src/components/ConfigComponent.tsx index 1a25742..40b4e4b 100644 --- a/src/components/ConfigComponent.tsx +++ b/src/components/ConfigComponent.tsx @@ -25,9 +25,12 @@ const Config = (props) => { const dispatch = useDispatch() const generateInputs = () => { - return motifModOptions.map( ( option:any ,i:number ) => ( - - )) + return motifModOptions.map( ( option:any ,i:number ) => { + console.log("Optchin", option) + return ( + + ) + }) } const generateMotifInputs = () => { return range(8).map( (option:any, i:number ) =>{ diff --git a/src/service/meander.ts b/src/service/meander.ts index e2858fc..74fcb07 100644 --- a/src/service/meander.ts +++ b/src/service/meander.ts @@ -72,25 +72,16 @@ export default class MeanderCanvas extends Canvas { this.draw(); } init(){ - // this.ctx.canvas.width = window.innerWidth; - // this.ctx.canvas.height = window.innerHeight; this.ctx.save() - // console.log("i nit", this.config) if(this.config.clearScreen){ this.ctx.fillRect (0,0,this.canvas.width, this.canvas.height) } - // this.ctx.translate( this.canvas.width /2 , this.canvas.height/2 ) - // this.ctx.save() this.ctx.fillStyle = "black"; this.ctx.translate( this.canvas.width /2 , this.canvas.height/2 ) this.ctx.scale(0.15,0.15); this.ctx.transform(1,0,0,1,0,0) - // this.ctx.fillStyle = "black" - // this.ctx.fillRect( 0, 0 , this.canvas.width , this.canvas.height) - - // this.canvas.addEventListener('click', (evt:any) => { // this.lastX = evt.offsetX || (evt.pageX - this.canvas.offsetLeft); @@ -108,8 +99,6 @@ export default class MeanderCanvas extends Canvas { } draw(){ - - let savedPos = this.generateSpacing(this.config.sides).map((n:any,i:any, c:any) => { let x = this.config.sideLength * Math.sin(n + this.config.baseRotation) let y = this.config.sideLength * Math.cos(n + this.config.baseRotation) @@ -130,7 +119,7 @@ export default class MeanderCanvas extends Canvas { let numSegments = (this.config.fitToSide) ? numberOfSegments : this.config.numSegments source = - ( this.config.noAnimation ) + ( !this.config.animate ) ? Rx.Observable.from(range( numSegments )) : Rx.Observable.interval(1).take(numSegments) diff --git a/src/sliders.tsx b/src/sliders.tsx index 7420277..0d4ba37 100644 --- a/src/sliders.tsx +++ b/src/sliders.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import { useDispatch } from 'react-redux'; import { UPDATE_CONFIG, UPDATE_MOTIF } from './store/actions/canvasActions'; +// This slider is for the motif options const useSlider = (type, min, max, defaultState, step, label, id, index) => { const [state, setSlide] = useState(defaultState); const dispatch = useDispatch() @@ -25,24 +26,41 @@ const useSlider = (type, min, max, defaultState, step, label, id, index) => { } return props } -const useConfigSlider = (type, min, max, defaultState, step, label, id, index, option, onUpdate) => { +const useConfigCheckbox = (type, min, max, defaultState, step, label, id, index, option, onUpdate) => { const [state, setSlide] = useState(defaultState); const dispatch = useDispatch() - const handleChange = e => { let value - if(type == "checkbox"){ + value = e.target.checked - value = e.target.checked - } else { - value = e.target.value - } + setSlide(value); + dispatch({ + type: UPDATE_CONFIG, + payload: { option: option, checked: e.target.checked } + }) + } + const props = { + type, + id, + min, + max, + step, + checked: state, + onChange: handleChange + } + return props +} +const useConfigSlider = (type, min, max, defaultState, step, label, id, index, option, onUpdate) => { + const [state, setSlide] = useState(defaultState); + const dispatch = useDispatch() + const handleChange = e => { + let value + value = e.target.value setSlide(value); dispatch({ type: UPDATE_CONFIG, payload: { option: option, value } }) - // onUpdate() } const props = { type, @@ -56,11 +74,23 @@ const useConfigSlider = (type, min, max, defaultState, step, label, id, index, o return props } export function ConfigSlider(props){ - const sliderProps = useConfigSlider( + const sliderProps = props.option.type !== 'checkbox' ? useConfigSlider( + props.option.type, + props.option.min, + props.option.max, + props.option.value, + 1, + props.option.label, + props.option.id, + props.option.index, + props.option, + props.renderCanvas, + ) + : useConfigCheckbox( props.option.type, props.option.min, props.option.max, - props.option.value, + props.option.checked, 1, props.option.label, props.option.id, @@ -68,13 +98,25 @@ export function ConfigSlider(props){ props.option, props.renderCanvas, ); - return ( -
- - - -
- ) + const _sliderProps = {...sliderProps, ...{checked: props.option.checked == true }} + if(props.option.type !== 'checkbox'){ + return ( +
+ + + +
+ ) + } else { + return ( +
+ + + +
+ ) + + } } export function MotifSlider(props){ diff --git a/src/store/reducers/canvasReducer.ts b/src/store/reducers/canvasReducer.ts index a804aab..ebc19ec 100644 --- a/src/store/reducers/canvasReducer.ts +++ b/src/store/reducers/canvasReducer.ts @@ -19,35 +19,36 @@ const motif:MotifDefinition = [ ] // Default options const config:MotifModOptions = - [{ optionName: 'depth' , min: -400 , max:2000 , value: 1 , type: 'range' }, - { optionName: 'sides' , min: 2 , max:25 , value: 7 , type: 'range' }, - { optionName: 'lineWidth' , min: 0 , max:400 , value: 10 , type: 'range' }, - { optionName: 'sideLength' , min: 2 , max:1200 , value: 300 , type: 'range' }, - { optionName: 'baseRotation', min: 0 , max:90 , value: 0 , type: 'range' }, - { optionName: 'numSegments' , min: 2 , max:20000 , value: 30 , type: 'range' }, - { optionName: 'drawEvery' , min: 1 , max:100 , value: 1 , type: 'range' }, - { optionName: 'flip' , min: 0 , max:1 , value: false , type: 'checkbox' }, - { optionName: 'noAnimation' , min: 1 , max:9 , value: true , type: 'checkbox' }, - { optionName: 'fitToSide' , min: 0 , max:1 , value: false , type: 'checkbox' }, - { optionName: 'clearScreen' , min: 0 , max:1 , value: true , type: 'checkbox' }, - { optionName: 'red' , min: 0 , max:255 , value: 127 , type: 'range' }, - { optionName: 'green' , min: 0 , max:255 , value: 10 , type: 'range' }, - { optionName: 'blue' , min: 0 , max:255 , value: 50 , type: 'range' }, + [{ optionName: 'depth' , min: -400 , max:2000 , value: 1 , type: 'range' }, + { optionName: 'sides' , min: 2 , max:25 , value: 7 , type: 'range' }, + { optionName: 'lineWidth' , min: 0 , max:400 , value: 10 , type: 'range' }, + { optionName: 'sideLength' , min: 2 , max:1200 , value: 300 , type: 'range' }, + { optionName: 'baseRotation', min: 0 , max:90 , value: 0 , type: 'range' }, + { optionName: 'numSegments' , min: 2 , max:20000 , value: 30 , type: 'range' }, + { optionName: 'drawEvery' , min: 1 , max:100 , value: 1 , type: 'range' }, + { optionName: 'flip' , min: 0 , max:1 , checked: false , type: 'checkbox' }, + { optionName: 'animate' , min: 1 , max:9 , checked: true , type: 'checkbox' }, + { optionName: 'fitToSide' , min: 0 , max:1 , checked: false , type: 'checkbox' }, + { optionName: 'clearScreen' , min: 0 , max:1 , checked: false , type: 'checkbox' }, + { optionName: 'red' , min: 0 , max:255 , value: 127 , type: 'range' }, + { optionName: 'green' , min: 0 , max:255 , value: 10 , type: 'range' }, + { optionName: 'blue' , min: 0 , max:255 , value: 50 , type: 'range' }, ] export const defaultCanvasState:MotifOptionsReducerState = { motifAngles:motif, motifModOptions:config } const updateOptionInMeanderConfig = - ( state, payload ) => { + ( state, payload ) => { return state.motifModOptions.map( configOption => { // If its the changed option if( configOption.optionName === payload.option.optionName ) { // Get value from slider or checkbox - configOption.value = ( payload.option.type === 'range' ) - ? parseInt(payload.value) - : payload.value; + if(payload.option.type === 'range'){ + configOption.value = parseInt(payload.value) + } + configOption.checked = payload.checked } - return configOption; + return configOption }) } const updateAllConfig = diff --git a/src/types.ts b/src/types.ts index 813f460..ffcb682 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,10 +2,18 @@ export interface MotifModOption { optionName: string, min: number, max: number, - value: number | boolean, + value?: number | boolean, + checked?: boolean, type: string } -export type MotifModOptions = Array +export interface MotifCheckboxOption { + optionName: string, + min: number, + max: number, + checked: boolean, + type: string +} +export type MotifModOptions = Array export type MotifDefinition = number[] export type MotifOptionsReducerState = { diff --git a/src/util/eventBus.js b/src/util/eventBus.js index 36a6839..b3673ce 100644 --- a/src/util/eventBus.js +++ b/src/util/eventBus.js @@ -1,4 +1,7 @@ // https://lwebapp.com/en/post/event-bus + +// each event is really a tick, we add events onto the tick +// and iterate through all of the events on the tick export class EventBus { constructor() { // initialize event list @@ -8,10 +11,8 @@ export class EventBus { } // publish event publish(eventName, ...args) { - // console.log("publishing", eventName) // Get all the callback functions of the current event const callbackObject = this.eventObject[eventName]; - // console.log("Event object", this.eventObject) if (!callbackObject) return console.warn(eventName + " not found!"); // execute each callback function diff --git a/src/util/smoothingFunctions.js b/src/util/smoothingFunctions.js new file mode 100644 index 0000000..cfc4023 --- /dev/null +++ b/src/util/smoothingFunctions.js @@ -0,0 +1,17 @@ +const lerp = (a, b, t) => a + (b-a) * t +const clamp = (val, max) => val > max ? max : val +const floorClamp = (val, min) => val < min ? min : val +const parabolic = (x,k) => Math.pow( 4.0*x*(1.0-x), k ); +// www.iquilezles.org/www/articles/functions/functions.htm +const impulse = ( howSteep, x ) => { + let h = howSteep*x; + return h* Math.exp(1.0 - h); +} + +export { + lerp, + clamp, + floorClamp, + parabolic, + impulse, +} \ No newline at end of file diff --git a/src/util/types.ts b/src/util/types.ts index 08e45e3..adee838 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -11,7 +11,7 @@ export type CantorConfig = { } export type MeanderConfig = { - noAnimation : boolean, + animate : boolean, flip : boolean, fitToSide : boolean, sides : number, diff --git a/src/util/util.ts b/src/util/util.ts index f69beb6..fb099f5 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -21,7 +21,11 @@ export const hasKey = key => obj => obj[key] export const parseFloatByKey = key => obj => Object.assign({}, obj, { [key] :parseFloat(obj[key])}) export const addOption = (acc:any, cur:any) => { - acc[cur.optionName] = cur.value + if(cur.type == 'checkbox'){ + acc[cur.optionName] = cur.checked + } else { + acc[cur.optionName] = cur.value + } return acc } export const parseMeanderConfigToMeanderCanvasOptions =