From a17a07c1ad6fe8b7d7d79b3d7317637bb277b173 Mon Sep 17 00:00:00 2001 From: Jimmy MacGregor Date: Mon, 3 May 2021 15:37:26 +0100 Subject: [PATCH 1/6] Improve global soundControls management --- src/App.js | 76 +++++++++---------- .../segment-component/segment.js | 29 ++++++- src/music/segments/chords.js | 3 + 3 files changed, 64 insertions(+), 44 deletions(-) diff --git a/src/App.js b/src/App.js index db243c0..bdfccb9 100644 --- a/src/App.js +++ b/src/App.js @@ -67,16 +67,17 @@ const light_theme = createMuiTheme({ * - 'Regenerate all' button to force all notes to regenerate in a track * - Add help system to assist with all parts of the app * Options Menu: + * - Overall direction (determine root note by increasing/decreasing from source) + * - just generally keep track of lower down things from higher up rather than trying to do things strictly hierarchically! + * - Allow/add multiple generators per pattern + * Synths: * - Make attack/decay/sustain/release more formalized (i.e. specify sustain!) (& ensure that the values _are able to_ scale to the length of time)! * - Import/export wavetables as JSON 1 + * - Import wavetables as mathematical formulae* - can't be free text entry to be eval'ed! * - Allow converting from ifft form to wavetable* * - Use display to input back into wavetable* - * - Overall direction (determine root note by increasing/decreasing from source) - * - just generally keep track of things rather than trying to do things hierarchically! - * - Allow/add multiple generators per pattern * - Allow composing synths together - * - Allow specifying chord bias - * - Allow distortion + * - Allow distortion in synths * Sections: * - Allow specifying the mood of subsections* * - Allow inserting specfic runs of notes @@ -125,7 +126,6 @@ const light_theme = createMuiTheme({ * - POST-BETA FEATURE * - on pasting notes, locks the segment index and reverse engineers the tension/release patterns * Code: - * - Refactor to generate sound controls from just one array 1 * - Bugfix: keep track of global state/BPM better * */ @@ -137,7 +137,14 @@ const App = () => { const [context,] = useState(new AudioContext()); const [oscillators, setOscillators] = useState([]); const [customSynths, setCustomSynths] = useState([]); - const [additionalSoundControls, setSoundControls] = useState([]); + const [soundControls, setSoundControls] = useState([{ + key: uuidv4(), + primary: true, + notes: [], + context: null, + synth: null, + ...defaultGlobalOptions + }]); const [globalOptions, setGlobalOptions] = useState(defaultGlobalOptions); const synthControls = { addCustomSynth: () => synth => setCustomSynths(prev => [...prev, synth]), @@ -163,8 +170,7 @@ const App = () => { notes: [], context: null, synth: null, - ...defaultGlobalOptions, - addNewSoundControls + ...defaultGlobalOptions }]); const addToAdditionalNotes = (key) => ({ value, context, synth, bpm }) => { setSoundControls(prev => prev.map( @@ -187,8 +193,8 @@ const App = () => { const clearOscillators = () => setOscillators([]); const playAdditionalNotes = () => { clearOscillators(); - console.log('playing additional:', additionalSoundControls) - additionalSoundControls.forEach( + console.log('playing additional:', soundControls) + soundControls.forEach( ({ notes, context, synth, bpm }, _, __) => { if(notes && context && synth){ playNotes(notes.flat(), context, synth, bpm, replaceOscillator); @@ -200,38 +206,24 @@ const App = () => { return ( - {} } - playAdditionalNotes={ playAdditionalNotes } - setGlobalOption={ setGlobalOption } - globalOptions={ globalOptions } - /> { - additionalSoundControls.length > 0 && - additionalSoundControls.map( - desc => - removeSelf(desc.key) } /> - ) + soundControls.map( + desc => + removeSelf(desc.key) } /> + ) } diff --git a/src/music/segment-components/segment-component/segment.js b/src/music/segment-components/segment-component/segment.js index 3306fd2..f48d36d 100644 --- a/src/music/segment-components/segment-component/segment.js +++ b/src/music/segment-components/segment-component/segment.js @@ -6,8 +6,10 @@ import { MenuItem, InputLabel, TextField, - Typography + Typography, + ListSubheader } from '@material-ui/core'; +import chordStrategies from '../../segments/chords'; import { Chord } from 'octavian'; import { RELEASE, TENSION } from '../../segments/constants'; @@ -80,7 +82,30 @@ const Segment = ({ } } } label={'Repeats'} id={`repeat-${ name }-${ repeats }`} placeholder={'1'} value={ repeats } type={ 'number' } /> - + {/* + + Chord Strategy + + + */} diff --git a/src/music/segments/chords.js b/src/music/segments/chords.js index 9050126..da6f3f1 100644 --- a/src/music/segments/chords.js +++ b/src/music/segments/chords.js @@ -2,6 +2,9 @@ import { TENSION, RELEASE, EITHER, tenseMoves, releaseMoves } from './constants' import { pick } from './index'; const chordStrategies = { + none: { + default: chord => chord + }, random: { withMood: ({ chord, mood, threshold = 0.5 }) => { let interval; From 3bd4b036d2f002284ac191761f88f1f3e260b17b Mon Sep 17 00:00:00 2001 From: Jimmy MacGregor Date: Mon, 3 May 2021 16:54:48 +0100 Subject: [PATCH 2/6] Fix for BPM; not the cleanest --- src/App.js | 20 ++++++++++---- src/music/index.js | 27 ++++++++++++++----- src/music/options/global.js | 16 ++++++----- src/music/options/index.js | 9 ++++++- .../segment-components/segment-container.js | 8 ++++-- 5 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/App.js b/src/App.js index bdfccb9..1702d48 100644 --- a/src/App.js +++ b/src/App.js @@ -126,12 +126,18 @@ const light_theme = createMuiTheme({ * - POST-BETA FEATURE * - on pasting notes, locks the segment index and reverse engineers the tension/release patterns * Code: - * - Bugfix: keep track of global state/BPM better + * - Refactor: global and local state. * */ + +/** + * Move to own config file + */ const defaultGlobalOptions = { - bpm: 120 + bpm: 120, + useGlobalBPM: true } +// Config ends const App = () => { const [context,] = useState(new AudioContext()); @@ -172,17 +178,20 @@ const App = () => { synth: null, ...defaultGlobalOptions }]); - const addToAdditionalNotes = (key) => ({ value, context, synth, bpm }) => { + const addToAdditionalNotes = (key) => ({ value, context, synth, bpm, useGlobalBPM }) => { setSoundControls(prev => prev.map( soundControls => soundControls.key !== key ? - soundControls : + soundControls.useGlobalBPM && useGlobalBPM ? + { ...soundControls, bpm } : + soundControls : Object.assign( { ...soundControls }, { notes: value(soundControls.notes), context, synth, - bpm + bpm, + useGlobalBPM } ) )) @@ -220,6 +229,7 @@ const App = () => { addToAdditionalNotes={ addToAdditionalNotes(desc.key) } playAdditionalNotes={ playAdditionalNotes } primary={ desc.primary } + useGlobalBPM={ desc.useGlobalBPM } setGlobalOption={ setGlobalOption } globalOptions={ globalOptions } removeSelf={ () => removeSelf(desc.key) } /> diff --git a/src/music/index.js b/src/music/index.js index 4dc6b0f..92345bd 100644 --- a/src/music/index.js +++ b/src/music/index.js @@ -36,8 +36,7 @@ const playNotes = (notes, context, synth, bpm, useOscillator=()=>{}) => { // Note helpers ends //Config to go in own file -const soundControlsShouldUpdateOn = ['bpm']; - +const soundControlsShouldUpdateOn = ['bpm', 'useGlobalBPM']; // Config ends const SoundControls = ({ @@ -75,7 +74,7 @@ const SoundControls = ({ )); } - const { bpm } = deFactoOptions; + const { bpm, useGlobalBPM } = deFactoOptions; const soundControlValues = soundControlsShouldUpdateOn.reduce((acc, curr) => { acc[curr] = deFactoOptions[curr]; return acc; @@ -87,12 +86,12 @@ const SoundControls = ({ } const handleSetNotes = value => { - addToAdditionalNotes({ value, context, synth, bpm }); + addToAdditionalNotes({ value, context, synth, bpm, useGlobalBPM }); setNotes( value ); } const handleSetSynth = value => { - addToAdditionalNotes({ value: id=>id, context, synth: value, bpm }); + addToAdditionalNotes({ value: id=>id, context, synth: value, bpm, useGlobalBPM }); setSynth( value ); } @@ -111,9 +110,23 @@ const SoundControls = ({ } setDeFactoOption(key, value); } + const setDefactoOptions = (opts) => { + const updateObject = Object.entries(opts).reduce((acc, [key, value]) => { + setDeFactoOption(key, value); + if(soundControlsShouldUpdateOn.includes(key)) { + return Object.assign(acc, { [key]: value }); + } + return acc; + }, { + value: id=>id, + context, + synth, + ...soundControlValues + }); + addToAdditionalNotes(updateObject); + } const handlePlayAll = () => { - handlePlay(); playAdditionalNotes(); } @@ -139,9 +152,11 @@ const SoundControls = ({ synthControls={ synthControls } globalOptions={ globalOptions } setGlobalOption={ setGlobalOption } + useGlobalBPM={ useGlobalBPM } localOptions={ localOptions } setLocalOption={ setLocalOption } setDeFactoOption={ handleSetDeFactoOption } + setDeFactoOptions={ setDefactoOptions } addNewSoundControls={ addNewSoundControls } /> diff --git a/src/music/options/global.js b/src/music/options/global.js index 14a74d2..de98ffa 100644 --- a/src/music/options/global.js +++ b/src/music/options/global.js @@ -5,10 +5,15 @@ import { FormControlLabel, Switch } from '@material-ui/core'; -import React, { useState } from 'react'; +import React from 'react'; -const Option = ({ global, setGlobal, setOpt, Type, tooltipDetails, options=false, setLocal, local, ...props }) => { - const [useGlobal, setUseGlobal] = useState(true); +const Option = ({ + global, setGlobal, + useGlobal, setUseGlobal, + setLocal, local, + setOpt, + Type, tooltipDetails, options=false, + ...props }) => { const value = useGlobal ? global : local; const nextValue = useGlobal ? local : global; const setValue = useGlobal ? setGlobal : setLocal; @@ -42,10 +47,7 @@ const Option = ({ global, setGlobal, setOpt, Type, tooltipDetails, options=false control={ { - setUseGlobal(prev => !prev) - setOpt({ target: { value: nextValue }}); - }} + onChange={setUseGlobal(nextValue, !useGlobal)} name="useGlobal" color="primary" /> diff --git a/src/music/options/index.js b/src/music/options/index.js index 2578816..2ed04c8 100644 --- a/src/music/options/index.js +++ b/src/music/options/index.js @@ -48,9 +48,11 @@ const OptionMenu = ({ setDefaultRootNote, globalOptions, setGlobalOption, + useGlobalBPM, localOptions, setLocalOption, - setOpt + setOpt, + setOpts }) => { const [ customSynthOpen, setCustomSynthOpen ] = useState(false); const [ synthMethodReplace, setSynthMethodReplace ] = useState(false); @@ -252,6 +254,11 @@ const OptionMenu = ({ Type={ TextField } global={ bpm } setGlobal={ setGlobalOption('bpm') } + useGlobal={ useGlobalBPM } + setUseGlobal={ (next, useGlobalBPM) => () => setOpts({ + bpm: next, + useGlobalBPM + }) } tooltipDetails={{ placement: 'top', title: 'Beats Per Minute', label: 'BPM:', aria_label: 'beats per minute' }} setLocal={ setLocalOption('bpm') } local={ localBPM } type={ 'number' } setOpt={ setOpt('bpm') }/> diff --git a/src/music/segment-components/segment-container.js b/src/music/segment-components/segment-container.js index e7eeb37..5960fe7 100644 --- a/src/music/segment-components/segment-container.js +++ b/src/music/segment-components/segment-container.js @@ -37,9 +37,11 @@ const SegmentContainer = ({ addNewSoundControls, globalOptions, setGlobalOption, + useGlobalBPM, localOptions, setLocalOption, - setDeFactoOption + setDeFactoOption, + setDeFactoOptions }) => { const [Menu, setMenu] = useState(false); const [segments, setSegments] = useState([]); @@ -141,9 +143,11 @@ const SegmentContainer = ({ setDefaultRootNote={ setDefaultRootNote} globalOptions={ globalOptions } setGlobalOption={ setGlobalOption } + useGlobalBPM={ useGlobalBPM } localOptions={ localOptions } setLocalOption={ setLocalOption } - setOpt={ setDeFactoOption }/> : '' + setOpt={ setDeFactoOption } + setOpts={ setDeFactoOptions }/> : '' } From 4a1a2020bf91b438fcb185c99e07d10a4628f082 Mon Sep 17 00:00:00 2001 From: Jimmy MacGregor Date: Mon, 3 May 2021 18:38:40 +0100 Subject: [PATCH 3/6] Give in and use strings --- .../segment-component/segment.js | 8 ++++---- src/music/segments/chords.js | 15 ++++++++++++++- src/music/segments/constants.js | 4 ++-- src/music/segments/index.js | 4 ++-- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/music/segment-components/segment-component/segment.js b/src/music/segment-components/segment-component/segment.js index f48d36d..bf318c4 100644 --- a/src/music/segment-components/segment-component/segment.js +++ b/src/music/segment-components/segment-component/segment.js @@ -82,11 +82,12 @@ const Segment = ({ } } } label={'Repeats'} id={`repeat-${ name }-${ repeats }`} placeholder={'1'} value={ repeats } type={ 'number' } /> - {/* + Chord Strategy - */} + diff --git a/src/music/segments/chords.js b/src/music/segments/chords.js index da6f3f1..04fc890 100644 --- a/src/music/segments/chords.js +++ b/src/music/segments/chords.js @@ -1,12 +1,25 @@ import { TENSION, RELEASE, EITHER, tenseMoves, releaseMoves } from './constants'; +import { Note, Chord } from 'octavian'; import { pick } from './index'; +const gospel9 = chord => { + // Move 'root' down to major Seventh below + const chordRoot = new Note(chord.signatures[0]); + const seventhBase = chordRoot.downOctave().majorSeventh().toChord(); + // From the major, so this is just 1-3-5-7-9 voiced dramatically + return seventhBase // 7 + .addInterval('minorSecond') // 1 + .addInterval('minorThird') // 9 + .addInterval('perfectFourth') // 3 + .addInterval('minorSixth') // 5 +} + const chordStrategies = { none: { default: chord => chord }, random: { - withMood: ({ chord, mood, threshold = 0.5 }) => { + "with mood": ({ chord, mood, threshold = 0.5 }) => { let interval; switch(mood) { case TENSION: diff --git a/src/music/segments/constants.js b/src/music/segments/constants.js index f5eba84..9f7fac3 100644 --- a/src/music/segments/constants.js +++ b/src/music/segments/constants.js @@ -12,13 +12,13 @@ const tenseMoves = [ 'diminishedFifth', 'minorSixth', 'majorSixth', - 'minorSeventh', - 'majorSeventh' + 'minorSeventh' ]; const releaseMoves = [ 'majorThird', 'perfectFifth', + 'majorSeventh', 'downOctave', // 'perfectOctave', false diff --git a/src/music/segments/index.js b/src/music/segments/index.js index a8ecc26..d5e2c9b 100644 --- a/src/music/segments/index.js +++ b/src/music/segments/index.js @@ -56,7 +56,7 @@ const diatom = (root, mood) => { if(method === false) { console.log('false; selecting root', root); let next = root.toChord(); - next = chordStrategies.random.withMood({ chord: next, mood }); + next = chordStrategies.random['with mood']({ chord: next, mood }); return [root, next]; } // Falling more likely for release @@ -70,7 +70,7 @@ const diatom = (root, mood) => { const alteredRoot = alterMethod ? root[alterMethod]() : root; try { next = alteredRoot[method]().toChord(); - next = chordStrategies.random.withMood({ chord: next, mood }); + next = chordStrategies.random['with mood']({ chord: next, mood }); } catch (error) { console.log('error, method:', method, alteredRoot); const safeNote = new Chord(pick(startingNotes)); From 4a2a7b1ccbf19d4fe95b278b0d73a1accc2e7edd Mon Sep 17 00:00:00 2001 From: Jimmy MacGregor Date: Mon, 3 May 2021 21:18:57 +0100 Subject: [PATCH 4/6] select chord strategies --- .../segment-component/segment.js | 26 ++++++++---- .../segment-components/segment-container.js | 40 +++++++++++++----- src/music/segments/chords.js | 17 +++++++- src/music/segments/index.js | 42 +++++++++---------- 4 files changed, 84 insertions(+), 41 deletions(-) diff --git a/src/music/segment-components/segment-component/segment.js b/src/music/segment-components/segment-component/segment.js index bf318c4..767c1b7 100644 --- a/src/music/segment-components/segment-component/segment.js +++ b/src/music/segment-components/segment-component/segment.js @@ -6,8 +6,7 @@ import { MenuItem, InputLabel, TextField, - Typography, - ListSubheader + Typography } from '@material-ui/core'; import chordStrategies from '../../segments/chords'; import { Chord } from 'octavian'; @@ -15,12 +14,13 @@ import { RELEASE, TENSION } from '../../segments/constants'; const Segment = ({ color, - segmentType: { name, gridSize, root, repeats, mood }, + segmentType: { name, gridSize, root, repeats, mood, chordStrategy }, removeSegment, regenerateNotes, setRootNote, setMood, - setRepeats + setRepeats, + setChordStrategy }) => { return { try { const rootNote = new Chord(value); - regenerateNotes(mood, rootNote, repeats); + regenerateNotes(mood, rootNote, repeats, chordStrategy); } catch { console.warn(`Invalid root note: ${ value }`) } finally { @@ -57,7 +57,7 @@ const Segment = ({ onChange={ ({ target: { value }}) => { console.log('Changing value: ', value); try { - regenerateNotes(value, new Chord(root), repeats); + regenerateNotes(value, new Chord(root), repeats, chordStrategy); setMood(value); } catch { console.warn(`Invalid mood: ${ value }`) @@ -73,7 +73,7 @@ const Segment = ({ console.log('Changing repeats: ', value); try { if(value > 0) { - regenerateNotes(mood, new Chord(root), value); + regenerateNotes(mood, new Chord(root), value, chordStrategy); } } catch { console.warn(`Invalid repeat value: ${ value }`) @@ -87,7 +87,17 @@ const Segment = ({ Chord Strategy