diff --git a/configs/webpack/dev.js b/configs/webpack/dev.js index 56fbbee..450c1f0 100644 --- a/configs/webpack/dev.js +++ b/configs/webpack/dev.js @@ -23,6 +23,7 @@ module.exports = merge(commonConfig, { alias: { 'react-dom': '@hot-loader/react-dom', }, + symlinks: false, // To use local packages (e.g., @magenta/music), turn off symlink resolution https://github.com/webpack/webpack/issues/811 }, entry: [ 'react-hot-loader/patch', // activate HMR for React diff --git a/package.json b/package.json index 6de0719..47480ad 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "webpack-merge": "^4.2.1" }, "dependencies": { - "@magenta/music": "^1.8.0", + "@magenta/music": "file:.yalc/@magenta/music", "@material-ui/core": "^4.0.0", "@material-ui/icons": "^4.0.0", "@material-ui/lab": "^4.0.0-alpha.13", diff --git a/src/components/sequences.tsx b/src/components/sequences.tsx index 7659ec9..5db52b7 100644 --- a/src/components/sequences.tsx +++ b/src/components/sequences.tsx @@ -17,16 +17,18 @@ import React from 'react'; import { style } from 'typestyle'; import { observer } from 'mobx-react'; import Button from '@material-ui/core/Button'; +import Typography from '@material-ui/core/Typography'; +import Slider from '@material-ui/core/Slider'; import FormControl from '@material-ui/core/FormControl'; import Select from '@material-ui/core/Select'; import MenuItem from '@material-ui/core/MenuItem'; import FormHelperText from '@material-ui/core/FormHelperText'; import { MusicNote } from '@material-ui/icons'; -import { engine, sequences, layout, Note } from '../core'; +import { editor, engine, sequences, layout, Note } from '../core'; import { Sequence } from './sequence'; -import { MAX_PITCH, MIN_PITCH, TOTAL_SIXTEENTHS } from '../core/constants'; +import { MAX_PITCH, MIN_PITCH, TOTAL_SIXTEENTHS, RefineOnOriginal } from '../core/constants'; function getPitchRange(noteSequences: Note[][]) { let minPitch = MAX_PITCH; @@ -56,10 +58,48 @@ function getPositionRange(noteSequences: Note[][]) { return [minPosition, maxPosition]; } +const horizontalSliderStyle = style({ + marginLeft: 20, + marginRight: 20, +}); + +const surprisingSliderMarks = [ + { + value: 0.01, + label: "Ordinary" + }, + { + value: 0.99, + label: "Surprising" + } +]; + +// limit, middle, end +const refineSliderMarks = [0, 2, 4].map(value => { + return { + value: value, + label: refineSliderTextOptions(value) + }; +}); + + +function refineSliderTextOptions(value: number) { + if (value === RefineOnOriginal.VerySimilarNotes) { + return "Similar"; + } + else if (value === RefineOnOriginal.NoRefinement) { + return "Independent"; + } + else if (value === RefineOnOriginal.VeryDifferentNotes) { + return "Different"; + } +} + export interface SequencesProps {} @observer export class Sequences extends React.Component { + renderSequences() { const noteSequences = sequences.candidateSequences; @@ -108,6 +148,33 @@ export class Sequences extends React.Component { ); } + renderRefineOnOriginal() { + return ( +
+ + get _____ to in mask + +
+ { + if (newValue!== null) { + sequences.refineOnOriginalStrategy = Number(newValue); + } + }} + getAriaValueText={refineSliderTextOptions} + aria-labelledby="refine-on-original-slider-restrict" + step={1} + valueLabelDisplay="off" + marks={refineSliderMarks} + min={0} + max={4} + /> +
+
+ ); + } + render() { const harmonizeEnabled = engine.isModelLoaded && !engine.isWorking; @@ -119,6 +186,7 @@ export class Sequences extends React.Component { }); const showCandidateSequences = sequences.candidateSequences.length > 0; + const showRefineOnOriginal = editor.getMaskedSequence.length > 0; return (
@@ -149,6 +217,23 @@ export class Sequences extends React.Component { n sequences +
+ { + if (newValue !== null) { + sequences.temperature = Number(newValue); + } + }} + aria-labelledby="temperature-slider-restrict" + step={0.01} + valueLabelDisplay="auto" + marks={surprisingSliderMarks} + min={0.01} + max={0.99} + /> +
+ {showRefineOnOriginal && this.renderRefineOnOriginal()} {showCandidateSequences && this.renderSequences()}
); diff --git a/src/core/constants.ts b/src/core/constants.ts index 6e32622..6e5b8aa 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -51,3 +51,11 @@ export const PIANO_ROLL_WIDTH = 24; export const NOTE_HEIGHT = 12; export const MASK_LANE_HEIGHT = 20; export const TIMELINE_HEIGHT = 20; + +export enum RefineOnOriginal { + VerySimilarNotes = 0, + SimilarNotes, + NoRefinement, + DifferentNotes, + VeryDifferentNotes +} \ No newline at end of file diff --git a/src/core/editor.ts b/src/core/editor.ts index 0fd77f4..5fbe235 100644 --- a/src/core/editor.ts +++ b/src/core/editor.ts @@ -321,7 +321,7 @@ class Editor { return false; } - getMaskedSequence() { + @computed get getMaskedSequence() { const notes = this.allNotes; const maskedSequence: NoteSequence = []; diff --git a/src/core/engine.ts b/src/core/engine.ts index de8b3d8..111b8a3 100644 --- a/src/core/engine.ts +++ b/src/core/engine.ts @@ -30,6 +30,7 @@ import { SOUNDFONT_URL, MODEL_URL, TOTAL_SIXTEENTHS, + RefineOnOriginal } from './constants'; interface InfillMask { @@ -207,14 +208,39 @@ class Engine { } const nHarmonizations = sequences.nSequencesToGenerate; + const temperature = sequences.temperature; + let discourageNotes; + let nudgeFactor; + if (sequences.refineOnOriginalStrategy === RefineOnOriginal.SimilarNotes) { + discourageNotes = false; + // 1 translates to a 1:3 ratio + nudgeFactor = 1; + } + else if (sequences.refineOnOriginalStrategy === RefineOnOriginal.VerySimilarNotes) { + discourageNotes = false; + // 2 translates to a 1:12 ratio + nudgeFactor = 2 + } + else if (sequences.refineOnOriginalStrategy === RefineOnOriginal.DifferentNotes) { + discourageNotes = true; + nudgeFactor = 1; + } + else if (sequences.refineOnOriginalStrategy === RefineOnOriginal.VeryDifferentNotes) { + discourageNotes = true; + nudgeFactor = 2; + } + + console.log(`generating...\n temperature = ${temperature} | discourageNotes = ${discourageNotes} | nudgeFactor = ${nudgeFactor}`); const outputSequences: NoteSequence[] = []; for (let i = 0; i < nHarmonizations; i++) { const inputNotes = [...editor.allNotes]; const sequence = this.getMagentaNoteSequence(inputNotes); const infillMask = this.getInfillMask(); const results = await this.model.infill(sequence, { - temperature: 0.99, + temperature, infillMask, + discourageNotes, + nudgeFactor, }); const outputSequence = fromMagentaSequence( @@ -237,7 +263,7 @@ class Engine { } // Now, set the first candidate sequence to be the original, masked sequence - const maskedSequence = editor.getMaskedSequence(); + const maskedSequence = editor.getMaskedSequence; editor.removeCandidateNoteSequence(maskedSequence); sequences.addCandidateSequences([maskedSequence, ...outputSequences]); diff --git a/src/core/sequences.ts b/src/core/sequences.ts index 2de5171..0a6fcf5 100644 --- a/src/core/sequences.ts +++ b/src/core/sequences.ts @@ -17,9 +17,12 @@ import { observable } from 'mobx'; import { NoteSequence } from './note'; import editor from './editor'; import engine from './engine'; +import { RefineOnOriginal } from './constants'; export class Sequences { @observable nSequencesToGenerate = 2; + @observable temperature = 0.99; + @observable refineOnOriginalStrategy = RefineOnOriginal.NoRefinement; @observable generatedSequences: NoteSequence[][] = []; @observable candidateSequences: NoteSequence[] = [];