Add pages for Phonagen and GenerCommunes
This commit is contained in:
parent
6b9ec29344
commit
166dd9d522
4
main.rkt
4
main.rkt
|
@ -21,6 +21,8 @@
|
|||
("Generators" weblet pages:other-generators
|
||||
("ColorScheme" weblet pages:color-scheme-generator)
|
||||
("PictogrammicAdventurer" weblet pages:pictogrammic-adventurer)
|
||||
("Phonagen" weblet pages:phonagen)
|
||||
("GenerCommunes" weblet pages:gener-communes)
|
||||
)
|
||||
))
|
||||
; Sitemap
|
||||
|
@ -31,6 +33,8 @@
|
|||
("Miscellaneous Generators" "/Generators" #f
|
||||
("Color Scheme" "/Generators/ColorScheme" #f)
|
||||
("Pictogrammic Adventurer" "/Generators/PictogrammicAdventurer" #t)
|
||||
("Phonagen" "/Generators/Phonagen" #f)
|
||||
("Un Village en France" "/Generators/GenerCommunes" #f)
|
||||
)
|
||||
)
|
||||
; Webcontainer
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
(provide
|
||||
pages:other-generators
|
||||
pages:color-scheme-generator
|
||||
pages:pictogrammic-adventurer)
|
||||
pages:pictogrammic-adventurer
|
||||
pages:phonagen
|
||||
pages:gener-communes)
|
||||
|
||||
; About page on the other generators
|
||||
(define pages:other-generators
|
||||
|
@ -97,7 +99,54 @@
|
|||
("Home" . ,adventurer-living-place)
|
||||
("Destiny cards" . ,adventurer-cards)))))
|
||||
(section
|
||||
(h3 "About")
|
||||
(h3 "About this generator")
|
||||
(p "This generator makes adventurer characters whose statistics are represented by symbols and pictograms. " (br)
|
||||
"The font " (a ((href "http://users.teilar.gr/~g1951d/")) "Symbola") " is used to display the pictograms. "))
|
||||
)))))
|
||||
|
||||
(define pages:phonagen
|
||||
(pages:template
|
||||
#:title "Phonagen"
|
||||
#:author "Feufochmar"
|
||||
#:date "2019-11-10"
|
||||
#:stylesheets '("/css/feuforeve.css" "/css/phonagen.css")
|
||||
#:scripts '("/scripts/phonagen.js")
|
||||
#:on-load "phonagen.load('/data/phonagen.json');"
|
||||
#:content
|
||||
'(article
|
||||
(p "Select a word generator from the list and click generate.")
|
||||
(section ((id "phonagen")))
|
||||
(section
|
||||
(h3 "About this generator")
|
||||
(p
|
||||
"This generator creates words from a JSON representation of phonologies and word generators. "
|
||||
"This is an in-browser version of the generator used in the " (a ((href "/Floraverse/CharacterGenerator")) "Floraverse Character Generator") " for the character's name." (br)
|
||||
"It was made for the " (a ((href "https://itch.io/jam/procjam/rate/271994")) "ProcJam:Summer 2018") ". " (br)
|
||||
"The tools used to assemble the JSON representation and the source code of the in-browser generator is available through git: "
|
||||
(pre "git clone https://projects.feuforeve.fr/phonagen.git")))
|
||||
)))
|
||||
|
||||
(define pages:gener-communes
|
||||
(pages:template
|
||||
#:title "Un Village en France"
|
||||
#:author "Feufochmar"
|
||||
#:date "2019-11-10"
|
||||
#:stylesheets '("/css/feuforeve.css" "/css/gener-communes.css")
|
||||
#:scripts '("/scripts/gener-communes.js")
|
||||
#:on-load "genercommunes.load('/data/gener-communes.json')"
|
||||
#:content
|
||||
'(article
|
||||
(p "Select a departement from the list and click generate.")
|
||||
(section ((id "gener-communes")))
|
||||
(section
|
||||
(h3 "About this generator")
|
||||
(p
|
||||
"This generator was made for the " (a ((href "https://itch.io/jam/procjam/rate/511765")) "ProcJam 2019") ". " (br)
|
||||
"It creates municipality names from a JSON file that was generated after analysing the list of all french municipalities by departement. " (br)
|
||||
"The generator uses grammatical trees to represent the possible names, and fill them with either words taken from lists of generated using Markov chains. " (br)
|
||||
"The JSON file was generated by analysing the names to compute the possible trees and their probabilities, the distributions of words, and Markov chain generators. " (br)
|
||||
"For instance, a name like " (code "Saint-Pourçain-sur-Sioule") " was analysed as the tree " (code "(sur (Saint $MasculineName$) $RiverName$)") ", "
|
||||
"the word " (code "Pourçain") " as an element of the " (code "$MasculineName$") " word distribution, "
|
||||
"and the word " (code "Sioule") " as an input of the Markov chain generating river names. "
|
||||
))
|
||||
)))
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#commune-name {
|
||||
background-color: white;
|
||||
font-size: 300%;
|
||||
font-family: sans-serif;
|
||||
font-variant: small-caps;
|
||||
font-weight: bold;
|
||||
border-style: solid;
|
||||
border-radius: 30px;
|
||||
border-color: red;
|
||||
border-width: 20px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#phonagen {
|
||||
padding: 1ex;
|
||||
margin-top: 1ex;
|
||||
margin-bottom: 1ex;
|
||||
}
|
||||
|
||||
.main-transcription {
|
||||
font-size: 200%;
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,418 @@
|
|||
// Gener-Communes Web
|
||||
// Browser-based gerenator for gener-communes outputs
|
||||
|
||||
// Main object
|
||||
var genercommunes = {}
|
||||
|
||||
// Discrete distribution
|
||||
// The distribution object defines a discrete distribution
|
||||
genercommunes.Distribution = function () {
|
||||
this.total = 0 // Total number of elements in the distribution
|
||||
this.items = new Map() // Map of item -> number of elements
|
||||
}
|
||||
// Add 'occur' number of 'elem' elements into the distribution
|
||||
genercommunes.Distribution.prototype.addTo = function (elem, occur) {
|
||||
this.total += occur
|
||||
if (this.items.has(elem)) {
|
||||
this.items.set(elem, occur + this.items.get(elem))
|
||||
} else {
|
||||
this.items.set(elem, occur)
|
||||
}
|
||||
}
|
||||
// Pick a random element from the distribution
|
||||
genercommunes.Distribution.prototype.generate = function () {
|
||||
var idx = Math.floor(Math.random() * this.total)
|
||||
var acc = 0
|
||||
var pick = undefined
|
||||
for (var [elem, occur] of this.items)
|
||||
{
|
||||
acc += occur
|
||||
pick = elem
|
||||
if (acc > idx) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return pick
|
||||
}
|
||||
// Merge from another distribution
|
||||
genercommunes.Distribution.prototype.addFromDistribution = function (dist) {
|
||||
for (var [item, count] of dist.items) {
|
||||
this.addTo(item, count)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a distribution from the serialized model
|
||||
genercommunes.Distribution.parse = function (dist) {
|
||||
var ret = new genercommunes.Distribution()
|
||||
for (var elem of dist) {
|
||||
ret.addTo(elem.value, elem.count)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Chain generator
|
||||
// This generator makes an array of elements from a table of current chain -> next element
|
||||
genercommunes.ChainGenerator = function (order) {
|
||||
this.order = order
|
||||
this.nextItems = new Map()
|
||||
}
|
||||
// Populate the table of next elements of a given sequence from a description
|
||||
genercommunes.ChainGenerator.prototype.addSequence = function (sequence, distribution) {
|
||||
this.nextItems.set(sequence.toString(), distribution) // note: toString() when setting and getting to be able to compare the arrays
|
||||
}
|
||||
// Generate an array of elements
|
||||
genercommunes.ChainGenerator.prototype.generate = function () {
|
||||
var next = function (cg, current, output) {
|
||||
var nextElem = cg.nextItems.get(current.toString()).generate()
|
||||
if (nextElem === false) {
|
||||
return output
|
||||
} else {
|
||||
current.shift()
|
||||
current.push(nextElem)
|
||||
output.push(nextElem)
|
||||
return next(cg, current, output)
|
||||
}
|
||||
}
|
||||
var current = new Array()
|
||||
for (var i = 0; i < this.order; ++i) {
|
||||
current.push(false)
|
||||
}
|
||||
return next(this, current, new Array())
|
||||
}
|
||||
// Merge from a Chain Generator
|
||||
genercommunes.ChainGenerator.prototype.addFromChainGenerator = function (chainGen) {
|
||||
if (this.order === chainGen.order) {
|
||||
for (var [seq, dist] of chainGen.nextItems) {
|
||||
if (this.nextItems.has(seq)) {
|
||||
this.nextItems.get(seq).addFromDistribution(dist)
|
||||
} else {
|
||||
var newDist = new genercommunes.Distribution()
|
||||
newDist.addFromDistribution(dist)
|
||||
this.nextItems.set(seq, newDist)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw "Cannot merge ChainGenerator with different orders."
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a Chain generator from the serialized model
|
||||
genercommunes.ChainGenerator.parse = function (gen) {
|
||||
var ret = new genercommunes.ChainGenerator(gen.order)
|
||||
for (var next of gen.next) {
|
||||
ret.addSequence(next.sequence, genercommunes.Distribution.parse(next.distribution))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Departement
|
||||
genercommunes.Departement = function (id, name, localityOrder, areaOrder, riverOrder) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
// Distributions
|
||||
this.patterns = new genercommunes.Distribution()
|
||||
this.wordMasculine = new genercommunes.Distribution()
|
||||
this.wordFeminine = new genercommunes.Distribution()
|
||||
this.wordPlural = new genercommunes.Distribution()
|
||||
this.wordMasculinePlural = new genercommunes.Distribution()
|
||||
this.wordFemininePlural = new genercommunes.Distribution()
|
||||
this.personNameMasculine = new genercommunes.Distribution()
|
||||
this.personNameFeminine = new genercommunes.Distribution()
|
||||
// Chain generators
|
||||
this.localityName = new genercommunes.ChainGenerator(localityOrder)
|
||||
this.areaName = new genercommunes.ChainGenerator(areaOrder)
|
||||
this.riverName = new genercommunes.ChainGenerator(riverOrder)
|
||||
}
|
||||
|
||||
// Build from a serialized model
|
||||
genercommunes.Departement.parse = function (dep) {
|
||||
var ret = new genercommunes.Departement(dep.id, dep.name, 1, 1, 1)
|
||||
// Distributions
|
||||
ret.patterns = genercommunes.Distribution.parse(dep['patterns'])
|
||||
ret.wordMasculine = genercommunes.Distribution.parse(dep['word-masculine'])
|
||||
ret.wordFeminine = genercommunes.Distribution.parse(dep['word-feminine'])
|
||||
ret.wordPlural = genercommunes.Distribution.parse(dep['word-plural'])
|
||||
ret.wordMasculinePlural = genercommunes.Distribution.parse(dep['word-masculine-plural'])
|
||||
ret.wordFemininePlural = genercommunes.Distribution.parse(dep['word-feminine-plural'])
|
||||
ret.personNameMasculine = genercommunes.Distribution.parse(dep['person-name-masculine'])
|
||||
ret.personNameFeminine = genercommunes.Distribution.parse(dep['person-name-feminine'])
|
||||
// Chain generators
|
||||
ret.localityName = genercommunes.ChainGenerator.parse(dep['locality-name'])
|
||||
ret.areaName = genercommunes.ChainGenerator.parse(dep['area-name'])
|
||||
ret.riverName = genercommunes.ChainGenerator.parse(dep['river-name'])
|
||||
return ret
|
||||
}
|
||||
|
||||
// Merge from a departement
|
||||
genercommunes.Departement.prototype.addFromDepartement = function (dep) {
|
||||
this.patterns.addFromDistribution(dep.patterns)
|
||||
this.wordMasculine.addFromDistribution(dep.wordMasculine)
|
||||
this.wordFeminine.addFromDistribution(dep.wordFeminine)
|
||||
this.wordPlural.addFromDistribution(dep.wordPlural)
|
||||
this.wordMasculinePlural.addFromDistribution(dep.wordMasculinePlural)
|
||||
this.wordFemininePlural.addFromDistribution(dep.wordFemininePlural)
|
||||
this.personNameMasculine.addFromDistribution(dep.personNameMasculine)
|
||||
this.personNameFeminine.addFromDistribution(dep.personNameFeminine)
|
||||
this.localityName.addFromChainGenerator(dep.localityName)
|
||||
this.areaName.addFromChainGenerator(dep.areaName)
|
||||
this.riverName.addFromChainGenerator(dep.riverName)
|
||||
}
|
||||
|
||||
// Create from a map of departements
|
||||
genercommunes.Departement.fromMap = function (id, name, mapDepartements) {
|
||||
// Get the order of chain generators from the first departement of the map
|
||||
var departements = mapDepartements.values()
|
||||
var firstDep = departements.next().value
|
||||
// Build departement
|
||||
var ret = new genercommunes.Departement(id, name, firstDep.localityName.order, firstDep.areaName.order, firstDep.riverName.order)
|
||||
// fill the distributions
|
||||
for (var [depid, dep] of mapDepartements) {
|
||||
ret.addFromDepartement(dep)
|
||||
}
|
||||
// Return
|
||||
return ret
|
||||
}
|
||||
|
||||
// Format a generated word from a chain generator into a string
|
||||
genercommunes.formatWord = function (listChar) {
|
||||
var text = ''
|
||||
for (var char of listChar) {
|
||||
text += char
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// Replace the patterns
|
||||
// returns the input with all the pattern strings replaced by generated values
|
||||
// Recursive on arrays
|
||||
genercommunes.Departement.prototype.replacePatterns = function (pat) {
|
||||
if (Array.isArray(pat)) {
|
||||
return pat.map(x => this.replacePatterns(x))
|
||||
} else {
|
||||
switch (pat) {
|
||||
case '$WordMasculine$':
|
||||
return this.wordMasculine.generate()
|
||||
case '$WordFeminine$':
|
||||
return this.wordFeminine.generate()
|
||||
case '$WordPlural$':
|
||||
return this.wordPlural.generate()
|
||||
case '$WordMasculinePlural$':
|
||||
return this.wordMasculinePlural.generate()
|
||||
case '$WordFemininePlural$':
|
||||
return this.wordFemininePlural.generate()
|
||||
case '$PersonNameMasculine$':
|
||||
return this.personNameMasculine.generate()
|
||||
case '$PersonNameFeminine$':
|
||||
return this.personNameFeminine.generate()
|
||||
case '$LocalityName$':
|
||||
return genercommunes.formatWord(this.localityName.generate())
|
||||
case '$AreaName$':
|
||||
return genercommunes.formatWord(this.areaName.generate())
|
||||
case '$RiverName$':
|
||||
return genercommunes.formatWord(this.riverName.generate())
|
||||
default:
|
||||
return pat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a string starts with a vowel
|
||||
genercommunes.isStartingWithVowel = function (str) {
|
||||
var nfd = str.normalize('NFD'); // Normalize to remove the accents on the first character
|
||||
return ['A', 'E', 'H', 'I', 'O', 'U', 'Y'].includes(nfd.charAt(0)) // Note: the generated words start with an uppercase.
|
||||
}
|
||||
|
||||
// Transform an array tree of the patterns to a string
|
||||
genercommunes.Departement.prototype.treeToString = function (tree) {
|
||||
if (Array.isArray(tree)) {
|
||||
switch (tree[0]) {
|
||||
case 'el':
|
||||
case 'sans':
|
||||
case 'la':
|
||||
case 'les':
|
||||
case 'las':
|
||||
case 'los':
|
||||
case 'Saint':
|
||||
case 'San':
|
||||
case 'Sainte':
|
||||
case 'Santa':
|
||||
return tree[0] + '-' + this.treeToString(tree[1])
|
||||
case 'le':
|
||||
// elision possible
|
||||
var follow = this.treeToString(tree[1])
|
||||
if (genercommunes.isStartingWithVowel(follow)) {
|
||||
return "l'" + follow
|
||||
} else {
|
||||
return 'le-' + follow
|
||||
}
|
||||
case 'Santo':
|
||||
// elision possible
|
||||
var follow = this.treeToString(tree[1])
|
||||
if (genercommunes.isStartingWithVowel(follow)) {
|
||||
return "Sant'" + follow
|
||||
} else {
|
||||
return 'Santo-' + follow
|
||||
}
|
||||
case 'et':
|
||||
case 'sous':
|
||||
case 'lès':
|
||||
case 'lez':
|
||||
case 'près':
|
||||
case 'à':
|
||||
case 'en':
|
||||
case 'di':
|
||||
case 'sur':
|
||||
return this.treeToString(tree[1]) + '-' + tree[0] + '-' + this.treeToString(tree[2])
|
||||
case 'de':
|
||||
// elision possible
|
||||
var follow = this.treeToString(tree[2])
|
||||
if (genercommunes.isStartingWithVowel(follow)) {
|
||||
return this.treeToString(tree[1]) + "-d'" + follow
|
||||
} else {
|
||||
return this.treeToString(tree[1]) + "-de-" + follow
|
||||
}
|
||||
default:
|
||||
var str = ''
|
||||
var first = true
|
||||
for (var elem of tree) {
|
||||
if (!first) {
|
||||
str += '-'
|
||||
}
|
||||
first = false
|
||||
str += this.treeToString(elem)
|
||||
}
|
||||
return str
|
||||
}
|
||||
} else {
|
||||
return tree
|
||||
}
|
||||
}
|
||||
|
||||
// Apply elision rules
|
||||
genercommunes.applyElisions = function (str) {
|
||||
var ret = str
|
||||
// in the middle of the string
|
||||
ret = ret.replace(/-à-les-/gu, '-aux-')
|
||||
ret = ret.replace(/-de-los-/gu, '-dels-')
|
||||
ret = ret.replace(/-de-el-/gu, '-del-')
|
||||
ret = ret.replace(/-de-les-/gu, '-des-')
|
||||
ret = ret.replace(/-de-le-/gu, '-du-')
|
||||
ret = ret.replace(/-à-le-/gu, '-au-')
|
||||
// at the beginning of the string
|
||||
ret = ret.replace(/^la-/u, 'La ')
|
||||
ret = ret.replace(/^le-/u, 'Le ')
|
||||
ret = ret.replace(/^les-/u, 'Les ')
|
||||
ret = ret.replace(/^l'/u, "L'")
|
||||
ret = ret.replace(/^los-/u, 'Los ')
|
||||
//
|
||||
return ret
|
||||
}
|
||||
|
||||
// Generate a new name
|
||||
genercommunes.Departement.prototype.generateName = function () {
|
||||
// Pattern to use
|
||||
var nameArray = this.replacePatterns(this.patterns.generate())
|
||||
// Transform the tree to a string
|
||||
var nameStr = this.treeToString(nameArray)
|
||||
// Remove elisions & return
|
||||
return genercommunes.applyElisions(nameStr)
|
||||
}
|
||||
|
||||
// Region
|
||||
genercommunes.Region = function (id, name) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.departements = new Map()
|
||||
}
|
||||
|
||||
|
||||
// Build from a serialized model
|
||||
genercommunes.Region.parse = function (region) {
|
||||
var ret = new genercommunes.Region(region.id, region.name)
|
||||
for (var departement of region.departements) {
|
||||
ret.departements.set(departement.id, genercommunes.Departement.parse(departement))
|
||||
}
|
||||
// Add a pseudo-departement, for combining at region-level
|
||||
// Only if there is more than one departement
|
||||
if (ret.departements.size > 1) {
|
||||
ret.departements.set('0', genercommunes.Departement.fromMap('0', '(' + region.name + ')', ret.departements))
|
||||
}
|
||||
// Sort the map
|
||||
ret.departements = new Map([...ret.departements].sort())
|
||||
return ret
|
||||
}
|
||||
|
||||
// Model of the genercommunes generator
|
||||
genercommunes.model = {}
|
||||
|
||||
// Generate a name
|
||||
genercommunes.generateName = function () {
|
||||
var selector = document.getElementById('generator-selector')
|
||||
var generatorId = selector.options[selector.selectedIndex].value
|
||||
var splitId = generatorId.split(' ')
|
||||
var regId = splitId[0]
|
||||
var depId = splitId[1]
|
||||
var departement = genercommunes.model.regions.get(regId).departements.get(depId)
|
||||
document.getElementById('commune-name').innerHTML = departement.generateName()
|
||||
}
|
||||
|
||||
// Draw the genercommunes div
|
||||
genercommunes.drawDiv = function (model) {
|
||||
var contents = '<div id="genercommunes-output">'
|
||||
contents += '<label for="generator-selector">Choisissez un département</label><br/>'
|
||||
contents += '<select id="generator-selector">'
|
||||
for (var [regId, reg] of model.regions) {
|
||||
contents += '<optgroup label="' + reg.name + '">'
|
||||
for (var [depId, dep] of reg.departements) {
|
||||
contents += '<option value="' + regId + " " + depId + '">' + dep.name + '</option>'
|
||||
}
|
||||
contents += '</optgroup>'
|
||||
}
|
||||
contents += '</select>'
|
||||
contents += '<button onClick="genercommunes.generateName()">Générer</button>'
|
||||
contents += '<br/>'
|
||||
contents += '<span id="commune-name"> </span><br/></div>'
|
||||
document.getElementById('gener-communes').innerHTML = contents
|
||||
}
|
||||
|
||||
// Load the model
|
||||
genercommunes.loadModel = function (model) {
|
||||
// Parse the raw model
|
||||
// Regions
|
||||
genercommunes.model.regions = new Map()
|
||||
for (var region of model) {
|
||||
genercommunes.model.regions.set(region.id, genercommunes.Region.parse(region))
|
||||
}
|
||||
// Add a pseudo-region with a pseudo-departement combining all regions
|
||||
var pseudoReg = new genercommunes.Region('0', '(France)')
|
||||
// To get the orders of the Chain Generator, get the first departement of the first region
|
||||
var firstReg = genercommunes.model.regions.values().next().value
|
||||
var firstDep = firstReg.departements.values().next().value
|
||||
var pseudoDep = new genercommunes.Departement('0', '(France)', firstDep.localityName.order, firstDep.areaName.order, firstDep.riverName.order)
|
||||
for (var [regid, reg] of genercommunes.model.regions) {
|
||||
for (var [depid, dep] of reg.departements) {
|
||||
pseudoDep.addFromDepartement(dep)
|
||||
}
|
||||
}
|
||||
pseudoReg.departements.set(pseudoDep.id, pseudoDep)
|
||||
//
|
||||
genercommunes.model.regions.set(pseudoReg.id, pseudoReg)
|
||||
// Sort the map
|
||||
genercommunes.model.regions = new Map([...genercommunes.model.regions].sort())
|
||||
// Draw the genercommunes <div>
|
||||
genercommunes.drawDiv(genercommunes.model)
|
||||
// Generate a first name
|
||||
genercommunes.generateName()
|
||||
}
|
||||
|
||||
// Loading function for Gener-Communes
|
||||
genercommunes.load = function (jsonFile) {
|
||||
fetch(jsonFile)
|
||||
.then(function(response) {
|
||||
return response.json()
|
||||
})
|
||||
.then(genercommunes.loadModel)
|
||||
}
|
||||
|
||||
// Export info
|
||||
//module.exports = genercommunes
|
|
@ -0,0 +1,235 @@
|
|||
// Phonagen-web
|
||||
// Browser-based gerenator for phonagen outputs
|
||||
|
||||
// Main object
|
||||
var phonagen = {}
|
||||
|
||||
// Discrete distribution
|
||||
// The distribution object defines a discrete distribution
|
||||
phonagen.Distribution = function () {
|
||||
this.total = 0 // Total number of elements in the distribution
|
||||
this.items = new Map() // Map of item -> number of elements
|
||||
}
|
||||
// Add 'occur' number of 'elem' elements into the distribution
|
||||
phonagen.Distribution.prototype.addTo = function (elem, occur) {
|
||||
this.total += occur
|
||||
if (this.items.has(elem)) {
|
||||
this.items.set(elem, occur + this.items.get(elem))
|
||||
} else {
|
||||
this.items.set(elem, occur)
|
||||
}
|
||||
}
|
||||
// Pick a random element from the distribution
|
||||
phonagen.Distribution.prototype.pickFrom = function () {
|
||||
var idx = Math.floor(Math.random() * this.total)
|
||||
var acc = 0
|
||||
var pick = undefined
|
||||
for (var [elem, occur] of this.items)
|
||||
{
|
||||
acc += occur
|
||||
pick = elem
|
||||
if (acc > idx) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return pick
|
||||
}
|
||||
|
||||
// Chain generator
|
||||
// This generator makes an array of elements from a table of current chain -> next element
|
||||
phonagen.ChainGenerator = function (order) {
|
||||
this.order = order
|
||||
this.nextItems = new Map()
|
||||
}
|
||||
// Populate the table of next elements from a description
|
||||
phonagen.ChainGenerator.prototype.initializeFromDescription = function (description) {
|
||||
for (var elem of description) {
|
||||
var dist = new phonagen.Distribution()
|
||||
for (var next of elem['possible-outputs']) {
|
||||
dist.addTo(next.value, next.occurences)
|
||||
}
|
||||
this.nextItems.set(elem.input.toString(), dist) // note: toString() when setting and getting to be able to compare the arrays
|
||||
}
|
||||
}
|
||||
// Generate an array of elements
|
||||
phonagen.ChainGenerator.prototype.generate = function () {
|
||||
var next = function (cg, current, output) {
|
||||
var nextElem = cg.nextItems.get(current.toString()).pickFrom()
|
||||
if (nextElem === "") {
|
||||
return output
|
||||
} else {
|
||||
current.shift()
|
||||
current.push(nextElem)
|
||||
output.push(nextElem)
|
||||
return next(cg, current, output)
|
||||
}
|
||||
}
|
||||
var current = new Array()
|
||||
for (var i = 0; i < this.order; ++i) {
|
||||
current.push("")
|
||||
}
|
||||
return next(this, current, new Array())
|
||||
}
|
||||
|
||||
// Rule generator
|
||||
// This generator makes an array of elements from rules describing how to generate the array
|
||||
// Each rule is associated to a distribution of patterns indicating how to replace a rule identifier
|
||||
phonagen.RuleGenerator = function () {
|
||||
this.rules = new Map()
|
||||
}
|
||||
// Populate the rules table from a description
|
||||
phonagen.RuleGenerator.prototype.initializeFromDescription = function (description) {
|
||||
for (var rule of description) {
|
||||
var dist = new phonagen.Distribution()
|
||||
for (var pat of rule.distribution) {
|
||||
dist.addTo(pat.pattern, pat.occurences)
|
||||
}
|
||||
this.rules.set(rule.id, dist)
|
||||
}
|
||||
}
|
||||
// Generate an array of elements
|
||||
phonagen.RuleGenerator.prototype.generate = function () {
|
||||
var replacePattern = function (rules, input) {
|
||||
var output = new Array()
|
||||
for (var i of input) {
|
||||
if (rules.has(i)) {
|
||||
output = output.concat(replacePattern(rules, rules.get(i).pickFrom()))
|
||||
} else {
|
||||
output.push(i)
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
return replacePattern(this.rules, this.rules.get('word').pickFrom())
|
||||
}
|
||||
|
||||
// Model of the phonagen generator
|
||||
phonagen.model = {}
|
||||
|
||||
// Generate a word from the generator id
|
||||
phonagen.generateWord = function () {
|
||||
var selector = document.getElementById('generator-selector')
|
||||
var generatorId = selector.options[selector.selectedIndex].value
|
||||
var generator = phonagen.model.generators.get(generatorId)
|
||||
document.getElementById('phonagen-output').innerHTML = generator.generate()
|
||||
}
|
||||
|
||||
// Transform a list of phoneme ids to a formatted strings
|
||||
phonagen.formatWord = function (phonemes, phonology) {
|
||||
// Construct the different transcriptions
|
||||
var wordTranscriptions = new Map()
|
||||
for (var writing of phonology.transcriptions) {
|
||||
var word = ""
|
||||
for (var id of phonemes) {
|
||||
if (phonology.entries.has(id)) {
|
||||
word += phonology.entries.get(id)[writing]
|
||||
}
|
||||
}
|
||||
wordTranscriptions.set(writing, word)
|
||||
}
|
||||
// Construct what should be displayed
|
||||
var text = "<p>"
|
||||
text += "<span class=\"main-transcription\">" + wordTranscriptions.get(phonology.mainTranscription) + "</span><br/>"
|
||||
text += "Pronounciation: /" + wordTranscriptions.get("phoneme") +"/<br/>"
|
||||
for (var [writing, value] of wordTranscriptions) {
|
||||
if ( (writing !== "phoneme")
|
||||
&& (writing !== phonology.mainTranscription) ) {
|
||||
text += "Translitteration (" + writing + "): " + value + "<br/>"
|
||||
}
|
||||
}
|
||||
text += "</p>"
|
||||
return text
|
||||
}
|
||||
|
||||
// Make a rule generator
|
||||
phonagen.makeRuleBasedGenerator = function (rules, phonology) {
|
||||
var ruleGenerator = new phonagen.RuleGenerator()
|
||||
ruleGenerator.initializeFromDescription(rules)
|
||||
return function () {
|
||||
var phonemes = ruleGenerator.generate()
|
||||
return phonagen.formatWord(phonemes, phonology)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a chain generator
|
||||
phonagen.makeChainBasedGenerator = function (order, chains, phonology) {
|
||||
var chainGenerator = new phonagen.ChainGenerator(order)
|
||||
chainGenerator.initializeFromDescription(chains)
|
||||
return function () {
|
||||
var phonemes = chainGenerator.generate()
|
||||
return phonagen.formatWord(phonemes, phonology)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a raw phonology
|
||||
phonagen.parsePhonology = function (phon) {
|
||||
var phonology = {}
|
||||
phonology.description = phon.description
|
||||
phonology.transcriptions = phon.transcriptions
|
||||
phonology.mainTranscription = phon['main-transcription']
|
||||
phonology.entries = new Map()
|
||||
for (var entry of phon.entries) {
|
||||
phonology.entries.set(entry.id, entry)
|
||||
}
|
||||
return phonology
|
||||
}
|
||||
|
||||
// Parse a raw generator description
|
||||
phonagen.parseGenerator = function (gen, phonologies) {
|
||||
var generator = {}
|
||||
generator.description = gen.description
|
||||
generator.phonology = phonologies.get(gen.phonology)
|
||||
// Type
|
||||
if (gen.type === "rules") {
|
||||
generator.generate = phonagen.makeRuleBasedGenerator(gen.rules, generator.phonology)
|
||||
} else if (gen.type === "chains") {
|
||||
generator.generate = phonagen.makeChainBasedGenerator(gen.order, gen.chains, generator.phonology)
|
||||
} else {
|
||||
generator.generate = function () { return "Error: unsupported generator type: " + gen.type }
|
||||
}
|
||||
return generator
|
||||
}
|
||||
|
||||
// Draw the phonagen div
|
||||
phonagen.drawDiv = function (model) {
|
||||
var contents = ''
|
||||
contents += '<select id="generator-selector">'
|
||||
for (var [id, gen] of model.generators) {
|
||||
contents += '<option value="' + id + '">' + gen.description + '</option>'
|
||||
}
|
||||
contents += '</select>'
|
||||
contents += '<button onClick="phonagen.generateWord()">Generate</button>'
|
||||
contents += '<br/>'
|
||||
contents += '<div id="phonagen-output"><br/></div>'
|
||||
document.getElementById('phonagen').innerHTML = contents
|
||||
}
|
||||
|
||||
// Load the model
|
||||
phonagen.loadModel = function (model) {
|
||||
// Parse the raw model
|
||||
// Phonologies
|
||||
phonagen.model.phonologies = new Map()
|
||||
for (let phon of model.phonologies) {
|
||||
phonagen.model.phonologies.set(phon.id, phonagen.parsePhonology(phon))
|
||||
}
|
||||
// Generators
|
||||
phonagen.model.generators = new Map()
|
||||
for (let gen of model.generators) {
|
||||
phonagen.model.generators.set(gen.id, phonagen.parseGenerator(gen, phonagen.model.phonologies))
|
||||
}
|
||||
// Draw the phonagen <div>
|
||||
phonagen.drawDiv(phonagen.model)
|
||||
}
|
||||
|
||||
|
||||
// Loading function for Phonagen-htmljs
|
||||
phonagen.load = function (jsonFile) {
|
||||
fetch(jsonFile)
|
||||
.then(function(response) {
|
||||
return response.json()
|
||||
})
|
||||
.then(phonagen.loadModel)
|
||||
}
|
||||
|
||||
// Export info
|
||||
//module.exports = phonagen
|
Loading…
Reference in New Issue