Compare commits

...

6 Commits

9 changed files with 129 additions and 30 deletions

View File

@ -22,7 +22,7 @@ function invertPair(whostr, dialect) {
} }
} }
function labelForBeats(beats) { export function labelForBeats(beats) {
if (beats % 16 == 0) if (beats % 16 == 0)
switch (beats / 16) { switch (beats / 16) {
case 0: case 0:

View File

@ -7,7 +7,15 @@
// language construct for defining dance moves // language construct for defining dance moves
// and related support functions for dealing with figures // and related support functions for dealing with figures
import { formalParamIsDancers } from "./param.js"
import { libfigureObjectCopy, throw_up } from "./util.js" import { libfigureObjectCopy, throw_up } from "./util.js"
import {
FLATTEN_FORMAT_HTML,
FLATTEN_FORMAT_SAFE_TEXT,
FLATTEN_FORMAT_UNSAFE_TEXT,
Words,
words,
} from "./words.js";
// always freshly allocated // always freshly allocated
function newFigure(optional_progression) { function newFigure(optional_progression) {
@ -18,7 +26,7 @@ function newFigure(optional_progression) {
return m return m
} }
function figureBeats(f) { export function figureBeats(f) {
var defaultBeats = 8 var defaultBeats = 8
if (!f.move) return defaultBeats if (!f.move) return defaultBeats
var idx = find_parameter_index_by_name("beats", parameters(f.move)) var idx = find_parameter_index_by_name("beats", parameters(f.move))
@ -106,7 +114,7 @@ function find_parameter_index_by_name(name, parameters) {
// ================ // ================
function parameter_strings(move, parameter_values, dialect) { export function parameter_strings(move, parameter_values, dialect) {
return parameter_strings_or_words(move, parameter_values, dialect, false) return parameter_strings_or_words(move, parameter_values, dialect, false)
} }
@ -173,7 +181,7 @@ function heyLengthSubstitution(hey_length, dialect) {
var moveSubstitutionPercentSRegexp = / *%S */g var moveSubstitutionPercentSRegexp = / *%S */g
function moveSubstitution(move_term, dialect) { export function moveSubstitution(move_term, dialect) {
var sub = moveSubstitutionWithEscape(move_term, dialect) var sub = moveSubstitutionWithEscape(move_term, dialect)
return sub.replace(moveSubstitutionPercentSRegexp, " ").trim() return sub.replace(moveSubstitutionPercentSRegexp, " ").trim()
} }

View File

@ -13,7 +13,10 @@ import {
defineRelatedMove2Way, defineRelatedMove2Way,
goodBeatsMinMaxFn, goodBeatsMinMaxFn,
goodBeatsMinFn, goodBeatsMinFn,
moveSubstitution,
parameter_strings,
} from "./define-figure.js" } from "./define-figure.js"
import { comma, words } from "./words.js";
//////////////////////////////////////////////// ////////////////////////////////////////////////
// ALLEMANDE // // ALLEMANDE //

View File

@ -8,6 +8,8 @@
// Properties of moves (strings). // Properties of moves (strings).
// Few dependencies to the rest of the system. // Few dependencies to the rest of the system.
import { deAliasMove } from "./define-figure.js";
var moveCaresAboutRotationsHash = { var moveCaresAboutRotationsHash = {
"do si do": true, "do si do": true,
allemande: true, allemande: true,
@ -17,7 +19,7 @@ var moveCaresAboutRotationsHash = {
"mad robin": true, "mad robin": true,
} }
// it now seems to me that this should be defined by defineFigure -dm 03-07-2017 // it now seems to me that this should be defined by defineFigure -dm 03-07-2017
function moveCaresAboutRotations(move) { export function moveCaresAboutRotations(move) {
return moveCaresAboutRotationsHash[deAliasMove(move)] return moveCaresAboutRotationsHash[deAliasMove(move)]
} }
@ -29,7 +31,7 @@ var moveCaresAboutPlacesHash = {
"box circulate": true, "box circulate": true,
} }
// it now seems to me that this should be defined by defineFigure -dm 03-07-2017 // it now seems to me that this should be defined by defineFigure -dm 03-07-2017
function moveCaresAboutPlaces(move) { export function moveCaresAboutPlaces(move) {
return moveCaresAboutPlacesHash[deAliasMove(move)] return moveCaresAboutPlacesHash[deAliasMove(move)]
} }
@ -72,7 +74,7 @@ export const degreesToWords = function(degrees, optional_move) {
return degrees.toString() + " degrees" return degrees.toString() + " degrees"
} }
function degreesToRotations(degrees) { export function degreesToRotations(degrees) {
if (degrees) { if (degrees) {
return degrees2rotations[degrees] || degrees.toString() + " degrees" return degrees2rotations[degrees] || degrees.toString() + " degrees"
} else { } else {
@ -80,7 +82,7 @@ function degreesToRotations(degrees) {
} }
} }
function degreesToPlaces(degrees) { export function degreesToPlaces(degrees) {
if (degrees) { if (degrees) {
return degrees2places[degrees] || degrees.toString() + " degrees" return degrees2places[degrees] || degrees.toString() + " degrees"
} else { } else {

View File

@ -11,7 +11,13 @@
// //
import { throw_up } from "./util.js" import { throw_up } from "./util.js"
import { chooser } from "./chooser.js" import { chooser, dancerMenuForChooser } from "./chooser.js"
import {
degreesToPlaces,
degreesToRotations,
moveCaresAboutPlaces,
moveCaresAboutRotations,
} from "./move.js";
const __params = {} const __params = {}
@ -453,7 +459,7 @@ defineParam("lead_dancer_l1", {
ui: "chooser_dancer", ui: "chooser_dancer",
}) })
function formalParamIsDancers(param) { export function formalParamIsDancers(param) {
// harder to maintain implementation: // harder to maintain implementation:
// return ['who', 'who2', 'whom', 'lead'].indexOf(param.name) >= 0; // return ['who', 'who2', 'whom', 'lead'].indexOf(param.name) >= 0;
return !!dancerMenuForChooser(param.ui) return !!dancerMenuForChooser(param.ui)

View File

@ -6,7 +6,7 @@ import {
import { dancers } from "./chooser.js" import { dancers } from "./chooser.js"
import { moves } from "./define-figure.js" import { moves } from "./define-figure.js"
function Words(arr) { export function Words(arr) {
this.arr = arr this.arr = arr
} }

View File

@ -77,6 +77,15 @@
background-color: hsl(180, 90%, 70%); background-color: hsl(180, 90%, 70%);
} }
.move {
list-style-type: none;
cursor: pointer;
}
.currentMove {
background-color: yellow;
list-style-type: '▶';
}
.source { .source {
font-size: 0.8em; font-size: 0.8em;
position: absolute; position: absolute;

View File

@ -536,7 +536,7 @@ function moveAsLowLevelMoves(move: Move, startingPos: Map<DancerIdentity, Semant
function danceAsLowLevelMoves(moves: Move[], startingPos: Map<DancerIdentity, SemanticPosition>): Map<DancerIdentity, LowLevelMove[]> { function danceAsLowLevelMoves(moves: Move[], startingPos: Map<DancerIdentity, SemanticPosition>): Map<DancerIdentity, LowLevelMove[]> {
const res = new Map<DancerIdentity, LowLevelMove[]>([...startingPos.keys()].map(id => [id, []])); const res = new Map<DancerIdentity, LowLevelMove[]>([...startingPos.keys()].map(id => [id, []]));
let currentPos = startingPos; let currentPos = new Map<DancerIdentity, SemanticPosition>(startingPos);
for (const move of moves) { for (const move of moves) {
const newMoves = moveAsLowLevelMoves(move, currentPos); const newMoves = moveAsLowLevelMoves(move, currentPos);
for (const [id, newMoveList] of newMoves.entries()) { for (const [id, newMoveList] of newMoves.entries()) {

View File

@ -2,8 +2,10 @@ import * as animation from "./animation.js";
import * as interpreter from "./interpreter.js"; import * as interpreter from "./interpreter.js";
import * as renderer from "./renderer.js"; import * as renderer from "./renderer.js";
import { DancerIdentity } from "./danceCommon.js"; import { DancerIdentity } from "./danceCommon.js";
import { LibFigureDance, Move } from "./libfigureMapper.js"; import { LibFigureDance, LibFigureMove, Move } from "./libfigureMapper.js";
import { animateLowLevelMove, LowLevelMove } from "./lowLevelMove.js"; import { animateLowLevelMove, LowLevelMove } from "./lowLevelMove.js";
import { figureBeats, figureToHtml } from "./libfigure/define-figure.js";
import { labelForBeats } from "./libfigure/dance.js";
const body = document.querySelector('body')!; const body = document.querySelector('body')!;
@ -79,6 +81,10 @@ progressionLabel.htmlFor = 'progression';
const playButton = document.createElement('button'); const playButton = document.createElement('button');
playButton.innerText = "Play"; playButton.innerText = "Play";
const movesList = document.createElement('ul');
wrapperDiv.appendChild(movesList);
wrapperDiv.appendChild(document.createElement('br'));
wrapperDiv.appendChild(bpmSelector); wrapperDiv.appendChild(bpmSelector);
wrapperDiv.appendChild(bpmLabel); wrapperDiv.appendChild(bpmLabel);
wrapperDiv.appendChild(playButton); wrapperDiv.appendChild(playButton);
@ -89,14 +95,25 @@ wrapperDiv.appendChild(document.createElement('br'));
wrapperDiv.appendChild(beatSliderLabel); wrapperDiv.appendChild(beatSliderLabel);
wrapperDiv.appendChild(beatDisplay); wrapperDiv.appendChild(beatDisplay);
beatSlider.addEventListener('input', (ev) => { function drawAtCurrentBeat() {
r.drawSetsWithTrails(beatSlider.valueAsNumber, progressionSelector.valueAsNumber); r.drawSetsWithTrails(beatSlider.valueAsNumber, progressionSelector.valueAsNumber);
const currentMoves = movesList.getElementsByClassName('currentMove');
for (let i = 0; i < currentMoves.length; i++) {
currentMoves[i].classList.remove('currentMove');
}
movesList.getElementsByClassName('moveForBeat_' + Math.floor(beatSlider.valueAsNumber))[0]
.classList.add('currentMove');
}
beatSlider.addEventListener('input', (ev) => {
drawAtCurrentBeat();
beatDisplay.innerText = beatSlider.valueAsNumber.toFixed(1); beatDisplay.innerText = beatSlider.valueAsNumber.toFixed(1);
restartAnimation(false); restartAnimation(false);
}); });
progressionSelector.addEventListener('input', (ev) => { progressionSelector.addEventListener('input', (ev) => {
r.drawSetsWithTrails(beatSlider.valueAsNumber, progressionSelector.valueAsNumber); drawAtCurrentBeat();
restartAnimation(false); restartAnimation(false);
}); });
@ -131,7 +148,61 @@ playButton.addEventListener('click', (ev) => {
}); });
bpmSelector.addEventListener('change', (ev) => { bpmSelector.addEventListener('change', (ev) => {
restartAnimation(false); restartAnimation(false);
}) });
// Two Hearts in Time by Isaac Banner. Selected arbitrarily.
const exampleDance: LibFigureDance = [{ "parameter_values": [true, 8], "move": "petronella" }, { "parameter_values": [true, 8], "move": "petronella" }, { "parameter_values": ["neighbors", "balance", 16], "move": "swing" }, { "parameter_values": ["ladles", true, 540, 8], "move": "allemande" }, { "parameter_values": ["partners", "none", 8], "move": "swing" }, { "parameter_values": ["gentlespoons", 360, 6], "move": "mad robin" }, { "parameter_values": [true, 270, 6], "move": "circle" }, { "parameter_values": ["partners", 4], "move": "California twirl", "progression": 1 }];
const dialect = {
moves: {},
dancers: {
ladle: "robin",
ladles: "robins",
gentlespoon: "lark",
gentlespoons: "larks",
"first ladle": "first robin",
"second ladle": "second robin",
"first gentlespoon": "first lark",
"second gentlespoon": "second lark",
},
};
let dance: LibFigureDance;
function buildMovesList() {
removeAllChildNodes(movesList);
let currentBeat: number = 0;
let lastItem: HTMLLIElement | undefined;
for (const figure of dance) {
const startBeat = currentBeat;
currentBeat += figureBeats(figure);
const moveItem = document.createElement('li');
moveItem.innerHTML = "[" + labelForBeats(Math.floor(startBeat / 16) * 16) + " "
+ "(" + startBeat + "-" + currentBeat + ")] "
+ figureToHtml(figure, dialect);
moveItem.classList.add('move');
for (let beat = startBeat; beat < currentBeat; beat++) {
moveItem.classList.add('moveForBeat_' + beat);
}
if (startBeat === 0) moveItem.classList.add('currentMove');
moveItem.addEventListener('click', (ev) => {
setBeat(startBeat);
restartAnimation(false);
});
lastItem = moveItem;
movesList.appendChild(moveItem);
}
if (lastItem) {
lastItem.classList.add('moveForBeat_' + currentBeat);
}
}
function setBeat(beat: number) {
beatSlider.value = beat.toString();
beatDisplay.innerText = beat.toFixed(1);
drawAtCurrentBeat();
}
function playAnimation(bpm: number, start: number, end: number) { function playAnimation(bpm: number, start: number, end: number) {
const startTime = Date.now(); const startTime = Date.now();
const msPerBeat = (60 * 1000) / bpm; const msPerBeat = (60 * 1000) / bpm;
@ -153,10 +224,7 @@ function playAnimation(bpm: number, start: number, end: number) {
changedProgression = true; changedProgression = true;
} }
beatSlider.value = beat.toString(); setBeat(beat);
beatDisplay.innerText = beat.toFixed(1);
r.drawSetsWithTrails(beat, progressionSelector.valueAsNumber);
if (changedProgression) { if (changedProgression) {
restartAnimation(true); restartAnimation(true);
return; return;
@ -167,9 +235,6 @@ function playAnimation(bpm: number, start: number, end: number) {
anim(); anim();
} }
// Two Hearts in Time by Isaac Banner. Selected arbitrarily.
const exampleDance: LibFigureDance = [{ "parameter_values": [true, 8], "move": "petronella" }, { "parameter_values": [true, 8], "move": "petronella" }, { "parameter_values": ["neighbors", "balance", 16], "move": "swing" }, { "parameter_values": ["ladles", true, 540, 8], "move": "allemande" }, { "parameter_values": ["partners", "none", 8], "move": "swing" }, { "parameter_values": ["gentlespoons", 360, 6], "move": "mad robin" }, { "parameter_values": [true, 270, 6], "move": "circle" }, { "parameter_values": ["partners", 4], "move": "California twirl", "progression": 1 }];
const danceJsonArea = document.createElement('textarea'); const danceJsonArea = document.createElement('textarea');
danceJsonArea.value = JSON.stringify(exampleDance, undefined, 2); danceJsonArea.value = JSON.stringify(exampleDance, undefined, 2);
danceJsonArea.rows = 15; danceJsonArea.rows = 15;
@ -180,8 +245,9 @@ wrapperDiv.appendChild(document.createElement('br'));
wrapperDiv.appendChild(danceJsonArea); wrapperDiv.appendChild(danceJsonArea);
wrapperDiv.appendChild(loadDanceButton); wrapperDiv.appendChild(loadDanceButton);
loadDanceButton.addEventListener('click', (ev) => { const table = document.createElement('table');
const dance: LibFigureDance = JSON.parse(danceJsonArea.value); function loadDance() {
dance = JSON.parse(danceJsonArea.value);
r.animation = interpreter.loadDance(dance); r.animation = interpreter.loadDance(dance);
if (cancelAnim !== undefined) { if (cancelAnim !== undefined) {
cancelAnimationFrame(cancelAnim); cancelAnimationFrame(cancelAnim);
@ -192,7 +258,10 @@ loadDanceButton.addEventListener('click', (ev) => {
beatDisplay.innerText = '0.0'; beatDisplay.innerText = '0.0';
r.drawSetsWithTrails(0); r.drawSetsWithTrails(0);
buildDebugTable(); buildDebugTable();
}); buildMovesList();
}
loadDanceButton.addEventListener('click', loadDance);
loadDance();
function createJsonCell(content: any, rowSpan?: number, id?: DancerIdentity) { function createJsonCell(content: any, rowSpan?: number, id?: DancerIdentity) {
const cell = document.createElement('td'); const cell = document.createElement('td');
@ -247,13 +316,15 @@ showDebugLabel.htmlFor = 'showDebug';
body.appendChild(showDebug); body.appendChild(showDebug);
body.appendChild(showDebugLabel); body.appendChild(showDebugLabel);
const table = document.createElement('table');
table.id = 'debug' table.id = 'debug'
function buildDebugTable() { function removeAllChildNodes(html: HTMLElement) {
while (table.childNodes.length > 0) { while (html.childNodes.length > 0) {
table.removeChild(table.childNodes[table.childNodes.length - 1]); html.removeChild(html.childNodes[html.childNodes.length - 1]);
} }
}
function buildDebugTable() {
removeAllChildNodes(table);
const headerRow = document.createElement('tr'); const headerRow = document.createElement('tr');
const roles = [DancerIdentity.OnesLark, DancerIdentity.OnesRobin, const roles = [DancerIdentity.OnesLark, DancerIdentity.OnesRobin,