// Copyright (C) 2017-2018, Maxim Lihachev, // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, version 3. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // //TODO: Подписные йоты //ID поля ввода var entry_id = 'phrase'; //ID контейнера с примером фразы var example_id = 'example'; //Максимальная длина фразы var max_length = 35; //Описание грамматики var GRE = {}; GRE.clitics = { proclitics: /(^|[\s@])([οὁὃηἡε]|οι|αι|εν|εις|εξ|ει|ως|ου|ουκ|ουχ)\s+/gi, enclitics: /\s+(τὰ|τι|τις)[\s@]+./gi } GRE.accents = { acutus_long: [/([ήἥὤ]|εύ|ού|εί)/gi, "^", '\$1'], acutus_short: [/([άάἄέέἔἕἓἒίόὄήἥὤύ])/gi, "!", '\$1'], gravis_long: [/(ἢ|εὓ|αἴ|αὶ|αὶ)/gi, "V", '\$1'], gravis_short: [/([ὶὸὂὓὰ])/gi, "v", '\$1'], circumflexus: [/([ᾶἇῖῶὧᾦῆῇἦἧ]|οῖ|αῖ|οἶ|οῦ|εῖ)/gi, "~", '\$1 '], diphtongs: [/(οι|αι|αί|αὶ|ει|εί|εὶ|οὑ|ου|οῖ|αῖ|οἶ|οῦ|εῖ|ευ|εὐ|αὐ|αυ)/gi, "D", '\$1'] }; function getAllIndexes(arr, val) { var indexes = [], i; for(i = 0; i < arr.length; i++) if (arr[i] === val) indexes.push(i); return indexes; } //Функция разбиения списка на списки заданного размера function chunk(arr, n) { if ( !arr.length ) { return []; } return [ arr.slice(0, n) ].concat(chunk(arr.slice(n), n)); }; //Минимальная высота тона var minHeight = 4; //Максимальная высота тона var maxHeight = 9; var alphabet = { α: ["8", minHeight], ἀ: ["8", minHeight], ἁ: ["8", minHeight], ε: ["8", minHeight], ἐ: ["8", minHeight], ἑ: ["8", minHeight], η: ["q", minHeight], ἠ: ["q", minHeight], ἡ: ["q", minHeight], ι: ["8", minHeight], ἰ: ["8", minHeight], ἱ: ["8", minHeight], ο: ["8", minHeight], ὀ: ["8", minHeight], ὁ: ["8", minHeight], ὃ: ["8", minHeight], υ: ["8", minHeight], ὐ: ["8", minHeight], ὑ: ["8", minHeight], ω: ["q", minHeight], ὠ: ["q", minHeight], ὡ: ["q", minHeight], " ": ["hr", minHeight], "~": ["8", maxHeight], "^": ["8", maxHeight-1], "!": ["8", maxHeight], "v": ["8", minHeight], "V": ["q", minHeight], "d": ["8", minHeight], "D": ["q", minHeight] } //Длительности var duration = { 8: 1, q: 2, qr: 2, h: 4, hr: 4 } //Строй var scale = [ "c/4", "d/4", "e/4", "f/4", "g/4", "a/4", "b/4", "c/5", "d/5", "e/5", "e/5" ] //Тоны внутри допустимой квинты var allowed_scale = scale.slice(4,9); //Парсинг ударений function parseText(string) { //Греческий текст с выделенными ударениями let text = string.trim().toLowerCase(); //Слияние клитик с ударными словами while (text.match(GRE.clitics.enclitics) || text.match(GRE.clitics.proclitics)) { text = text.replace(GRE.clitics.enclitics, x => { return x.replace(/^\s+/, "@"); }); text = text.replace(GRE.clitics.proclitics, x => { return x.replace(/\s+$/, "@"); }); } //ῥ label_text = text.replace(/(ῥ)/, "\$1ᵡ"); //Йота подписная // label_text = label_text.replace(/(ῇ)/, "\$1₍ᵢ₎"); for (var accent in GRE.accents) { label_text = label_text.replace(GRE.accents[accent][0], GRE.accents[accent][2]); text = text.replace(GRE.accents[accent][0], GRE.accents[accent][1]); } //Удаление отметок клитик text = text.replace(/@/gi, ""); label_text = label_text.replace(/@/gi, ""); //Краткие дифтонги // text = text.replace(/αι |αί |αὶ |ει |εί |εὶ /gi, "d "); //Разбиение текста подписи на единицы label_text = label_text.match(new RegExp(GRE.accents.diphtongs[0].source + '|.', 'gi')); console.log(text); console.log(label_text); return [text, label_text]; } //Создание массива нот для VexFlow function makeNotes(text) { var notes = []; let beats = 0; //Получение разобранного текста и подготовленной подписи let [phrase, label_text] = parseText(text); //Первый знак ударения определяет исходное направление движения let accentos = { UPl: phrase.indexOf("^"), UPs: phrase.indexOf("!"), UPt: phrase.indexOf("~"), DNl: phrase.indexOf("V"), DNs: phrase.indexOf("v") }; let firstAccent = Object.keys(accentos).filter( f => accentos[f] != -1 ).sort( (l, r) => accentos[l] > accentos[r] )[0] || "UPl"; if (firstAccent.search("UP") != -1) { var flow = "up"; var pos = minHeight-1; } else { var flow = "down"; var pos = maxHeight-1; } //Если первая буква согласная, то начинаем выше на один тон if (accentos[firstAccent] != 0 && typeof alphabet[phrase[0]] == "undefined") { if (pos == minHeight-1) { pos++; } } console.log("POS:: " + pos); for (index in phrase) { let letter = phrase[index]; if (typeof alphabet[letter] != "undefined") { dur = alphabet[letter][0]; height = alphabet[letter][1]; pause = ""; if (letter != " ") { if (height > minHeight) { console.log("ACCENT"); pos = height; } if (height < minHeight) { flow = "up"; pos = minHeight; } else if (height >= maxHeight) { flow = "down"; pos--; } else if (height == minHeight) { pos = flow == "up" ? pos+1 : pos-1; } } } else { //ПАУЗЫ dur = "8"; pause = "r"; height = minHeight; console.log("N: " + letter); } console.log(letter + " :: " + " > " + duration[dur] + pause + " = " + height + "(" + pos + ")" + ":" + scale[pos]); beats += duration[dur]; if (letter == "^") { notes.push(new VF.StaveNote({clef: "treble", keys: [scale[--pos]], duration: 8 + pause })); notes.push(new VF.StaveNote({clef: "treble", keys: [scale[++pos]], duration: 8 + pause })); console.log(letter + " :: " + " > " + duration[dur] + pause + " = " + height + "(" + pos + ")" + ":" + scale[pos]); } else if (letter == "V") { notes.push(new VF.StaveNote({clef: "treble", keys: [scale[pos]], duration: 8 + pause })); notes.push(new VF.StaveNote({clef: "treble", keys: [scale[--pos]], duration: 8 + pause })); console.log(letter + " :: " + " > " + duration[dur] + pause + " = " + height + "(" + pos + ")" + ":" + scale[pos]); } else if (letter == "~") { notes.push(new VF.StaveNote({clef: "treble", keys: [scale[pos]], duration: "8" + pause })); notes.push(new VF.StaveNote({clef: "treble", keys: [scale[--pos]], duration: "8" + pause })); console.log(letter + " :: " + " > " + duration[dur] + pause + " = " + height + "(" + pos + ")" + ":" + scale[pos]); } else { //Безтоновые согласные if (dur == "8" && pause != "") { // dur = "1h"; dur = "2m"; pause = ""; } // let note = new VF.StaveNote({clef: "treble", keys: [scale[pos]], duration: dur + pause }); let note = new VF.StaveNote({clef: "treble", keys: [scale[pos]], duration: dur + pause }); console.log("SCALE: " + scale[pos] + ", DUR: " + dur + ", POS: " + pos); if (!allowed_scale.includes(scale[pos])) { //TODO: Не работает выделение цветом note.setKeyStyle(0, { fillStyle: 'darkred' }); // note.setStyle({ fillStyle: 'grey', strokeStyle: 'grey' }); } notes.push(note); } } return [beats, notes]; } //Параметры URL var URL = { opt: {}, set: function(text) { if (!text) { window.history.pushState({}, null, window.location.href.split('?')[0]); } else { window.history.pushState({}, null, window.location.href.split('?')[0]+'?query=' + text); } this.href = encodeURIComponent(window.location.href); }, parse: function() { var gy = window.location.search.substring(1).split("&"); gy.forEach(arg => { let ft = arg.split("="); this.opt[ft[0]] = this.opt[ft[0]] || decodeURIComponent(ft[1]); }); this.opt.href = encodeURIComponent(window.location.href); } }; //Отрисовка нотного стана function drawAccents() { if (document.getElementById(entry_id).value) { var text = document.getElementById(entry_id).value.substring(0, max_length); //Изменение адреса URL.set(text); } else { return; } VF = Vex.Flow; // Create an SVG renderer and attach it to the DIV element named "staff". var div = document.getElementById('staff'); div.innerHTML = ''; var renderer = new VF.Renderer(div, VF.Renderer.Backends.SVG); // Size our svg: var size = 830; renderer.resize(size, 200); // And get a drawing context: var context = renderer.getContext(); // Create a stave at position 10, 40 of width 400 on the canvas. var stave = new VF.Stave(-1, 40, size); // Connect it to the rendering context and draw! stave.setContext(context).draw(); //---------------------------------------------------------------------- var [beats, notes] = makeNotes(text); //Создание нотного стана var voice = new VF.Voice({num_beats: beats, beat_value: 8}); //Автоматическое объединение нот var beams = VF.Beam.generateBeams(notes, { groups: [new Vex.Flow.Fraction(3, 8)], groups: [new Vex.Flow.Fraction(5, 8)], groups: [new Vex.Flow.Fraction(7, 8)], groups: [new Vex.Flow.Fraction(9, 8)] }); var edges = [0]; getAllIndexes(notes.map(x => { return x.duration; }), "h").forEach (s => { edges.push(s-1); edges.push(s+1); }); edges.push(notes.length - 1); var ties = []; chunk(edges, 2).forEach (pair => { ties.push(new VF.StaveTie({ first_note: notes[pair[0]], last_note: notes[pair[1]], first_indices: [0], last_indices: [0] }) ); }); Vex.Flow.Formatter.FormatAndDraw(context, stave, notes); beams.forEach(function(b) {b.setContext(context).draw()}) //TODO ties.forEach(function(b) {b.setContext(context).draw()}); var svg = document.getElementsByTagName("svg")[0]; //Левая черта svg.removeChild(svg.childNodes[6]); //Правая черта svg.removeChild(svg.childNodes[5]); //Первая линейка svg.removeChild(svg.childNodes[4]); //Пятая линейка svg.removeChild(svg.childNodes[0]); //Выбор всех восьмых пауз //TODO: // 1m == 180 // 2m == 179 // 8r == 65 let uselessPauses = $("svg .vf-notehead path").filter( function() { return this.getAttribute("d").split(" ").length === 179; } ); //Закрашивание восьмых пауз синим uselessPauses.each(function() { this.setAttribute("fill", "slategrey"); }); uselessPauses.each(function() { this.setAttribute("style", "opacity: 0.4;"); }); // uselessPauses.remove(); //Выбор всех лиг let allTies = $("path").filter( function() { return this.getAttribute("d").split(" ").length === 6; } ); //Удаление штрихов нот 2m $(".vf-note:not(:has(.vf-flag))") .filter(function() { return this.childElementCount === 2 && this.children[1].children[0].getAttribute("d").split(" ").length == 179; }) .find(".vf-stem") .remove(); allTies.each(function() { this.setAttribute("fill", "slategrey"); }) allTies.each(function() { this.setAttribute("style", "opacity: 0.7;"); }) //----------------------------------- positions = notes.map(x => { return x.stem.x_end; }); for (var i in positions) { let label = document.createElementNS("http://www.w3.org/2000/svg","text"); label.setAttributeNS(null,"x",positions[i]-3); label.setAttributeNS(null,"y",170); label.setAttributeNS(null,"font-size","20"); label.textContent = label_text[i]; svg.appendChild(label); } } function showMailForm() { var x = document.getElementById("mailForm"); if (x.style.display === "none") { x.style.display = "block"; } else { x.style.display = "none"; } } function drawExample() { let TEXT = document.getElementById(example_id).text; document.getElementById(entry_id).value = TEXT; drawAccents(entry_id); } //Инициализация с разбором адресной строки function init(container) { URL.parse(); if (URL.opt.query) { document.getElementById(entry_id).value = URL.opt.query.replace(/\+/g," "); } drawAccents(); }