236 lines
7.3 KiB
JavaScript
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
|