Public
Edited
Jan 24, 2024
1 fork
ramble – development
Replaceable Quatrains
Insert cell
Insert cell
Insert cell
Insert cell
affinities = {
while (true) {
yield Promises.delay(1000).then(
() =>
`Origin: ${affinity(ORIGIN, true)}%, Target: ${affinity(
TARGET,
true
)}%, ${state.outgoing ? "outgoing" : "incoming"} leg no: ${
mutable legCount + (mutable legCount >= state.maxLegs ? 0 : 1)
} of ${state.maxLegs}, domain: ${state.domain.toUpperCase()}`
);
}
}
Insert cell
state = ({
domain: ORIGIN, // values are ORIGIN or TARGET
prevDomain: ORIGIN,
outgoing: true, // are we going out or coming back
histories: { display: originHistory, buffer: targetHistory }, // history arrays for each cell
maxReplacements: 100, // when to start returning; was 160
delay: 100, // millis between updates; was 100
maxLegs: 10
})
Insert cell
DEBUG = false
// if (DEBUG) "Origin" & "Target" are added many times to originText & targetText
// and item 8 = "Origin"/"Target" is often picked for replacement
Insert cell
targetWords.filter((word) => replaceableWord(word)) // .join(" ") JUST A SNIPPET
Insert cell
affinity = (domain, strict = false) => {
// strict = true provides percentages for only words that are replaceable && also differ
// in originWords and targetWords (filtering out the replaceable words that they share)
let supplyWords = domain == ORIGIN ? originWords : targetWords;
let sharedWords;
if (strict)
sharedWords = originWords.filter((word, idx) => targetWords[idx] == word);
//
let condition = strict
? (word) => replaceableWord(word) && !sharedWords.includes(word)
: (word) => replaceableWord(word);
let numberOfReplaceables = supplyWords.reduce(
(acc, word, idx) => (acc += condition(word) ? 1 : 0),
0
);
//
condition = strict
? (word, idx) =>
word == supplyWords[idx] &&
replaceableWord(word) &&
!sharedWords.includes(word)
: (word, idx) => word == supplyWords[idx] && replaceableWord(word);
let numberOfMatches = wordsDisplay.reduce(
(acc, word, idx) => (acc += condition(word, idx) ? 1 : 0),
// (acc += word == supplyWords[idx] && replaceableWord(word) ? 1 : 0),
0
);
// returning a rounded percentage
return Math.round((numberOfMatches / numberOfReplaceables) * 100);
}
Insert cell
embedAutoPlay = placeHolderSequence()
Insert cell
mutable withEpigraph = false // if false, puts system in debug mode
Insert cell
Insert cell
Insert cell
timeToRead = (word, basetime = 150) => {
const syltime = basetime / 2; // most significant accumulator: these milliseconds per syllable
if (RiTa.isPunct(word)) {
return word.match(/[\.\?!]/) ? basetime * 3 : basetime * 1.5;
}
let syls = RiTa.syllables(word); // array of syllables
let time = basetime + syls.split("/").length * syltime; // syls * basic unit
time += syls.split(/[\/-]/).length * (syltime / 12); // add 1/12 of a syltime for each phoneme
time += word.match(/[,;:—]/) ? basetime : 0; // add basetime to a punctuated word
time += word.match(/[\.\?!]/) ? basetime * 2 : 0; // or add more for the end of a period
return time;
}
Insert cell
"thisis".match(/[aeiouy]/g)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// viewof controls = startStop() // for debugging: toggle word replacing
Insert cell
updater = {
while (updating) {
// mutable domain (toggling back and forth between words and wordsOfTarget)
// is now passed to update()
await Promises.delay(state.delay).then(update());
yield replacements();
}
console.log(
`COMPLETED last leg in "${state.domain}" after leg no. ${mutable legCount}`
);
}
Insert cell
Insert cell
Insert cell
Insert cell
replacements = () => {
// number of replacements made
return wordsDisplay.reduce(
(acc, w, i) => (acc += state.histories.display[i].length - 1),
0
);
// return state.domain == ORIGIN
// ? originWords.reduce((acc, w, i) => (acc += state.history[i].length - 1), 0)
// : targetWords.reduce(
// (acc, w, i) => (acc += state.historyOfTarget[i].length - 1),
// 0
// );
}
Insert cell
// choose a random word and reverse one timestep
restore = () => {
// let next = { display: "", buffer: "" };
// let word = { display: "", buffer: "" };
let word;
let choices = wordsDisplay
.map((next, idx) => ({ next, idx }))
.filter((o) => state.histories.display[o.idx].length > 1);

// if (state.domain == ORIGIN) {
// choices = originWords
// .map((next, idx) => ({ next, idx }))
// .filter((o) => state.history[o.idx].length > 1);
// } else {
// choices = targetWords
// .map((next, idx) => ({ next, idx }))
// .filter((o) => state.historyOfTarget[o.idx].length > 1);
// }

if (!choices.length) return; // nothing to do

// pick a changed word to step back
// idx and pos are taken to be "in sync" across domains of both words and wordsOfTarget
let { next, idx } = RiTa.random(choices);
// next.display = nextWord;
let pos = partsOfSpeech[idx];
word = wordsDisplay[idx];
// let word = state.domain == ORIGIN ? originWords[idx] : originWords[idx];
let hist = state.histories.display[idx];
// let hist =
// state.domain == ORIGIN ? state.history[idx] : state.historyOfTarget[idx];

next = hist.pop(); // select last from history
// do replacement
wordsDisplay[idx] = next;
// if ((state.domain = ORIGIN)) {
// originWords[idx] = next;
// } else {
// originWords[idx] = next;
// }

if (hist.length === 1 && next !== hist[0]) {
hist.push(hist[0]); // hack for last incoming
return;
}
return { next, idx, pos, word };
}
Insert cell
replaceableWord = (word) => {
let r = true;
if (word.length < 3) r = false; // len >= 3
if (stopWords.includes(word)) r = false; // preserve some grammatical words
return r;
}
Insert cell
update = () => {
if (!updating) return;

let pos, idx;
let next = { display: "", buffer: "" };
let word = { display: "", buffer: "" };
let wordsLength = wordsDisplay.length;

// ramble on
if (state.outgoing) {
let r = Math.floor(Math.random() * wordsLength);

// loop from a random spot
for (let i = r; i < wordsLength + r; i++) {
// if (DEBUG) "Origin" & "Target" are added many times to originText & targetText
// and item 8 = "Origin"/"Target" is often picked for replacement
idx = DEBUG
? RiTa.evaluate(`(${i % wordsLength} [20] | 8)`)
: i % wordsLength;

word.display = wordsDisplay[idx].toLowerCase();
word.buffer = wordsBuffer[idx].toLowerCase();
// for (let prop in word) {
// word[prop] = state.words[prop][idx].toLowerCase();
// }

if (!replaceableWord(word.display) || !replaceableWord(word.buffer))
continue;
// if (word.length < 4) continue; // len >= 4
// if (stopWords.includes(word)) continue; // preserve some grammatical words
// let reject = false;
// for (let prop in word) {
// if (word[prop].length < 4) reject = true; // len >= 4
// // preserve some grammatical words
// if (stopWords.includes(word[prop])) reject = true;
// }
// if (reject) continue;

pos = partsOfSpeech[idx];

// we are going to create histories for both words and wordsOfTarget
// regardless of where we are actually rambling out from
// find related words
let similar = { display: [], buffer: [] };
for (let prop in next) {
let rhymes = RiTa.rhymes(word[prop], { pos });
let sounds = RiTa.soundsLike(word[prop], { pos });
let spells = RiTa.spellsLike(word[prop], { pos });
let theseSimilars = [...rhymes, ...sounds, ...spells];
similar[prop] = theseSimilars;
}
let reject = false;
for (let prop in similar) {
// only words with 2 or more similars
if (similar[prop].length < 2) reject = true;
}
if (reject) continue;

// pick random similars
for (let prop in next) {
next[prop] = RiTa.random(similar[prop]);
}
reject = false;
for (let prop in next) {
if (next[prop].length < 3) reject = true; // skip if not >= 3 letters long
if (next[prop].includes(word[prop]) || word[prop].includes(next[prop]))
reject = true; // skip substrings
if (ignores.includes(next[prop])) reject = true;
}
if (reject) continue;

for (let prop in next) {
if (/[A-Z]/.test(wordsDisplay[idx][0]))
next[prop] = RiTa.capitalize(next[prop]); // keep capitals
if (/[A-Z]/.test(wordsBuffer[idx][0]))
next[prop] = RiTa.capitalize(next[prop]); // keep capitals
}

// word = state.domain == "origin" ? originWords[idx] : targetWords[idx];

wordsDisplay[idx] = next.display;
wordsBuffer[idx] = next.buffer;

for (let prop in next) {
state.histories[prop][idx].push(next[prop]);
}
// originWords[idx] = nexts[0]; // do replacement
// targetWords[idx] = nexts[1]; // do replacement in target
// state.history[idx].push(nexts[0]); // add to history
// state.historyOfTarget[idx].push(nexts[1]); // add to historyOfTarget

// next = state.domain == "origin" ? nexts[0] : nexts[1];
next = next.display;
word = word.display;
mutable replacementCount++;
break; // done
}
} else {
// ramble back
let data = restore();
if (!data) {
// also means that replacements() <= 0
return;
}
mutable replacementCount--;
[next, idx, pos, word] = Object.values(data);
}

updateDOM(next, idx);
// DOM updated, here's the report ...
let numDisplay = wordsDisplay.reduce(
(acc, w, i) => (acc += state.histories.display[i].length - 1),
0
);
let numBuffer = wordsBuffer.reduce(
(acc, w, i) => (acc += state.histories.buffer[i].length - 1),
0
);
console.log(
`${numDisplay}) @${idx} ${word} -> ${next} [${pos}] numBuffer: ${numBuffer} replacementCount: ${mutable replacementCount}`
);
// ... now update state
updateState();
}
Insert cell
updateDOM = (next, idx) => {
// update the one span that has changed
let ele = document.querySelector(`#w${idx}`);
if (!ele) {
console.warn("NO ELE FOR IDX: " + idx);
return; // shouldn't happen
}

// assumes puntuation is the same in both (state.)domains
ele.innerText = next + (RiTa.isPunct(wordsDisplay[idx + 1]) ? "" : " ");

// highlight updates as they happen
if (!withEpigraph) {
ele.style.backgroundColor =
state.histories.display[idx][0] === next
? "#fff" // original
: state.outgoing
? "#fbb" // outgoing
: "#bbf"; // incoming
}
}
Insert cell
mutable legCount = 0
Insert cell
Insert cell
Insert cell
updateState = () => {
let changes = replacements();
if (
// changes >= state.maxReplacements ||
state.outgoing &&
mutable replacementCount >= state.maxReplacements
) {
if (++mutable legCount >= state.maxLegs) {
mutable updating = false;
return changes;
}
console.log(
`changes: ${changes}: about to HEAD BACK in "${
state.domain
}" after leg no. ${mutable legCount}\n\n`
);
state.outgoing = false;
// currently, we only change domain after the initial ramble out
state.domain = TARGET;
//
if (state.domain != state.prevDomain) {
swapHistories();
state.prevDomain = state.domain;
// TODO! at the time of this swap; all of the words that were changed in the outgoing leg
// should be successively replaced, with state.delay between each change and
// in random order, so that the display actually shows the last words of the buffered history!
// Currently this does *not* happen:
// e.g. a word having only initial+1 items in its history is immediately changed to
// the inital word in the buffered history!
}
//
} else if (
// (updating && changes <= 0) ||
!state.outgoing &&
mutable replacementCount <= 0
) {
if (++mutable legCount >= state.maxLegs) {
mutable updating = false;
return changes;
}
console.log(
`changes: ${changes}: about to SET OUT in "${
state.domain
}" after leg no. ${mutable legCount}\n\n`
);
mutable outCount = 0;
state.outgoing = true;
// state.domain = ORIGIN; // TESTING
if (state.domain != state.prevDomain) {
swapHistories();
state.prevDomain = state.domain;
}
}
return changes;
}
Insert cell
swapHistories = () => {
let temp = state.histories.buffer;
state.histories.buffer = state.histories.display;
state.histories.display = temp;
}
Insert cell
Insert cell
Insert cell
Insert cell
diffWords = targetWords.filter((word, idx) => word !== originWords[idx])
Insert cell
lightWords = originWords.filter((word, idx) => word === "light")
Insert cell
wordsDisplay = [...originWords]
Insert cell
wordsBuffer = [...targetWords]
Insert cell
Insert cell
originText = {
let text =
"by the time the light has faded, as the last of the reddish gold illumination comes to rest, then imperceptibly spreads out over the moss and floor of the woods on the westerly facing lakeside slopes, you or I will have set out on several of yet more circuits at every time and in all directions, before or after this or that circadian, usually diurnal, event on mildly rambling familiar walks, as if these exertions might be journeys of adventure whereas always our gestures, guided by paths, are also more like traces of universal daily ritual: just before or with the dawn, after a morning dip, in anticipation of breakfast, whenever the fish are still biting, as and when the industrious creatures are building their nests and shelters, after our own trials of work, while the birds still sing, in quiet moments after lunch, most particularly after dinner, at sunset, to escape, to avoid being found, to seem to be lost right here in this place where you or I have always wanted to be and where we might sometimes now or then have discovered some singular hidden beauty, or one another, or stumbled and injured ourselves beyond the hearing and call of other voices, or met with other danger, animal or inhuman, the one tearing and rending and opening up the darkness within us to bleed, yet we suppress any sound that might have expressed the terror and passion and horror and pain so that I or you may continue on this ramble, this before or after walk, and still return; or the other, the quiet evacuation of the light, the way, as we have kept on walking, it falls on us and removes us from existence since in any case we are all but never there, always merely passing through and by and over the moss, under the limbs of the evergreens, beside the lake, within the sound of its lapping waves, annihilated, gone, quite gone, now simply gone and, in being or walking in these ways, giving up all living light for settled, hearth held fire in its place, returned";
return DEBUG ? text.replaceAll(",", ", Origin,") : text;
}
Insert cell
targetText = {
let text =
"by the time the light has faded, as the last of the reddish gold illumination comes to rest, then imperceptibly spreads out over the dust and rubble of the craters on the easterly facing bankside heights, you or I will have rushed out on several of yet more circuits at every time and in all directions, before or after this or that violent, usually nocturnal, event on desperately hurried unfamiliar flights, as if these panics might be movements of desire whereas always our gestures, constrained by obstacles, are also more like scars of universal daily terror: just before or with the dawn, after a morning prayer, in anticipation of hunger, while the neighbors are still breathing, as and when the diligent authorities are marshaling their cronies and thugs, after our own trials of loss, while the mortars still fall, in quiet moments after shock, most particularly after curfew, at sunset, to escape, to avoid being found, to seem to be lost right here in this place where you or I have always wanted to be and where we might sometimes now or then have discovered some singular hidden beauty, or one another, or stumbled and injured ourselves beyond the hearing and call of other voices, or met with other danger, venal or military, the one tearing and rending and opening up the darkness within us to bleed, yet we suppress any sound that might have expressed the terror and longing and horror and pain so that I or you may continue on this expedition, this before or after assault, and still return; or the other, the quiet evacuation of the light, the way, as we have kept on struggling, it falls on us and removes us from existence since in any case we are all but never there, always merely passing through and by and over the dust, within the shadows of our ruins, beneath the wall, within the razor of its coiled wire, annihilated, gone, quite gone, now simply gone and, in being or advancing in these ways, giving up all living light for unsettled, heart felt fire in our veins, exiled";
return DEBUG ? text.replaceAll(",", ", Target,") : text;
}
Insert cell
Insert cell
Insert cell
Insert cell
mutable skipEpigraph = false
Insert cell
Insert cell
Insert cell
css = html`<style>
.container {
width: 80vw;
}

.epigraph {
font-style: italic;
font-size: 3.5vw;
}

.none {
display: none;
}

.readerWindow {
width: 70vw;
text-align: center;
}

.text {
opacity: .3;
transition: opacity .5s ease-in-out;
}

.text.visible {
opacity: 1;
}
</style>`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
laob_notebook_css
Insert cell
<div><span id="first">this</span><span id="mid">and</span><span id="last"></div>
Insert cell
document.getElementById("last").nextSibling ==
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more