phonagen/web/phonagen.js

236 lines
7.3 KiB
JavaScript

// 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