747 lines
27 KiB
GDScript
747 lines
27 KiB
GDScript
@tool
|
|
extends Node
|
|
|
|
const _SYMBOLS_TO_STRING: Dictionary[String, String] = {
|
|
"#": "sharp",
|
|
"@": "at",
|
|
"*": "star",
|
|
"$": "usd",
|
|
"%": "pcent",
|
|
"§": "para"
|
|
}
|
|
const BASE_PATH: String = "user://language_resources/"
|
|
const TRACING_DATA_FOLDER: String = "tracing_data/"
|
|
const LOOK_AND_LEARN_IMAGES: String = "/look_and_learn/images/"
|
|
const LOOK_AND_LEARN_SOUNDS: String = "/look_and_learn/sounds/"
|
|
const LOOK_AND_LEARN_VIDEOS: String = "/look_and_learn/video/"
|
|
const LANGUAGE_SOUNDS: String = "/language_sounds/"
|
|
const KALULU_FOLDER: String = "/kalulu/"
|
|
const ADDITIONAL_WORD_LIST_PATH: String = "word_list.csv"
|
|
const VIDEO_EXTENSION: String = ".ogv"
|
|
const IMAGE_EXTENSION: String = ".png"
|
|
const SOUND_EXTENSION: String = ".mp3"
|
|
|
|
var language: String:
|
|
set(value):
|
|
Log.trace("Database: set language from %s to %s" % [language, value])
|
|
language = value
|
|
db_path = get_language_folder() + "/language.db"
|
|
words_path = get_language_folder() + "/words/"
|
|
var db_path: String = get_language_folder() + "/language.db":
|
|
set(value):
|
|
db_path = value
|
|
if db:
|
|
db.path = db_path
|
|
db.foreign_keys = true
|
|
connect_to_db()
|
|
var words_path: String = get_language_folder() + "/words/"
|
|
var additional_word_list: Dictionary = {}
|
|
var is_open: bool = false
|
|
|
|
@onready var db: Object = (
|
|
ClassDB.instantiate(&"SQLite") if ClassDB.class_exists(&"SQLite") else null
|
|
)
|
|
|
|
|
|
func _exit_tree() -> void:
|
|
if db:
|
|
db.close_db()
|
|
|
|
|
|
func connect_to_db() -> void:
|
|
if not db:
|
|
Log.error("Database: SQLite extension is not available")
|
|
return
|
|
if is_open:
|
|
Log.trace("Database: Opening database while a connection is already opened. Closing the previous one.")
|
|
db.close_db()
|
|
is_open = false
|
|
if FileAccess.file_exists(db.path):
|
|
is_open = db.open_db()
|
|
if not is_open:
|
|
Log.error("Database: Database is not opened")
|
|
return
|
|
Log.info("Database: Database opened at %s" % ProjectSettings.globalize_path(db.path))
|
|
if db.get_error_message() != "" and db.get_error_message() != "not an error":
|
|
Log.warn("Database: Database just opened but already contains an error message: %s" % db.get_error_message())
|
|
else:
|
|
Log.warn("Database: Database file not found at %s" % db.path)
|
|
|
|
|
|
func get_additional_word_list_path() -> String:
|
|
return BASE_PATH.path_join(ADDITIONAL_WORD_LIST_PATH)
|
|
|
|
|
|
func load_additional_word_list() -> String:
|
|
additional_word_list.clear()
|
|
var word_list_path: String = get_additional_word_list_path()
|
|
if FileAccess.file_exists(word_list_path):
|
|
var file: FileAccess = FileAccess.open(word_list_path, FileAccess.READ)
|
|
var error: Error = FileAccess.get_open_error()
|
|
if error != OK:
|
|
Log.error("Database: Load additional word list: Cannot open file %s. Error: %s" % [word_list_path, error_string(error)])
|
|
return ""
|
|
if file == null:
|
|
Log.error("Database: Load additional word list: Cannot open file %s. File is null" % word_list_path)
|
|
return ""
|
|
var title_line: PackedStringArray = file.get_csv_line()
|
|
if (not "ORTHO" in title_line) or (not "PHON" in title_line) or (not "GPMATCH" in title_line):
|
|
var msg: String = "word list should have columns ORTHO, PHON and GPMATCH"
|
|
Log.error("Database: " + msg)
|
|
return msg
|
|
var ortho_index: int = title_line.find("ORTHO")
|
|
while not file.eof_reached():
|
|
var line: PackedStringArray = file.get_csv_line()
|
|
if line.size() != title_line.size():
|
|
return "Formatting error on: %s" % line
|
|
var data: Dictionary = {}
|
|
for index: int in range(line.size()):
|
|
data[title_line[index]] = line[index]
|
|
additional_word_list[line[ortho_index]] = data
|
|
file.close()
|
|
Log.info("Database: Loaded additional word list from %s (%d entries)" % [word_list_path, additional_word_list.size()])
|
|
else:
|
|
Log.warn("Database: Additional word list file not found: %s" % word_list_path)
|
|
return ""
|
|
|
|
|
|
func get_exercise_for_lesson(lesson_nb: int) -> Array[int]:
|
|
var query: String = "Select Exercise1, Exercise2, Exercise3 FROM LessonsExercises
|
|
INNER JOIN Lessons ON Lessons.ID = LessonsExercises.LessonID
|
|
WHERE LessonNB == " + str(lesson_nb)
|
|
db.query(query)
|
|
# Exercise* == 0 means "no minigame in this slot": drop zeros so size = count.
|
|
var result: Array[int] = []
|
|
for element: Dictionary in db.query_result:
|
|
for column: String in ["Exercise1", "Exercise2", "Exercise3"]:
|
|
var exercise_id: int = element[column] as int
|
|
if exercise_id > 0:
|
|
result.append(exercise_id)
|
|
return result
|
|
|
|
|
|
func get_gps_for_lesson(lesson_nb: int, distinct: bool, only_new: bool = false, only_vowels: bool = false, with_other_phonemes: bool = false, include_exceptions: bool = false) -> Array[Dictionary]:
|
|
|
|
var parameters: Array = []
|
|
var symbol: String = "<=" if not only_new else "=="
|
|
var query: String = "SELECT g.ID, g.Grapheme, g.Phoneme, "
|
|
|
|
if with_other_phonemes:
|
|
query += "(SELECT group_concat(Phoneme) FROM GPs g2
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = g2.ID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb " + symbol + " ?
|
|
WHERE g.Grapheme = g2.Grapheme AND g.Phoneme != g2.Phoneme)
|
|
AS OtherPhonemes, "
|
|
parameters.append(lesson_nb)
|
|
|
|
query += "Type, LessonNb FROM GPs g
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = g.ID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb " + symbol + " ?"
|
|
parameters.append(lesson_nb)
|
|
|
|
var conditions: Array = []
|
|
if not include_exceptions:
|
|
conditions.append("Exception = 0")
|
|
if only_vowels:
|
|
conditions.append("Type = 1")
|
|
|
|
if conditions.size() > 0:
|
|
query += " WHERE " + " AND ".join(conditions)
|
|
|
|
if distinct:
|
|
query += " GROUP BY Grapheme"
|
|
|
|
query += " ORDER BY LessonNb ASC"
|
|
|
|
db.query_with_bindings(query, parameters)
|
|
|
|
var result: Array[Dictionary] = db.query_result
|
|
|
|
if with_other_phonemes:
|
|
for gp: Dictionary in result:
|
|
if gp.OtherPhonemes:
|
|
var phonemes: String = gp.OtherPhonemes
|
|
gp.OtherPhonemes = phonemes.split(",")
|
|
|
|
return result
|
|
|
|
|
|
func get_gps_from_syllable(syllable_id: int) -> Array[Dictionary]:
|
|
db.query_with_bindings("SELECT GPs.ID, GPs.Grapheme, GPs.Phoneme, GPs.Type FROM Syllables INNER JOIN GPsInSyllables ON Syllables.ID = GPsInSyllables.SyllableID AND Syllables.ID=? INNER JOIN GPs WHERE GPs.ID = GPsInSyllables.GPID ORDER BY Position", [syllable_id])
|
|
return db.query_result
|
|
|
|
|
|
func get_gp_from_word(id: int) -> Array:
|
|
db.query_with_bindings("SELECT GPs.* FROM Words INNER JOIN GPsInWords ON Words.ID = GPsInWords.WordID AND Words.ID=? INNER JOIN GPs WHERE GPs.ID = GPsInWords.GPID ORDER BY Position", [id])
|
|
return db.query_result
|
|
|
|
|
|
func get_gps_from_sentence(sentence_id: int) -> Array[Dictionary]:
|
|
db.query_with_bindings("SELECT GPs.ID, GPs.Grapheme, GPs.Phoneme, GPs.Type, GPsInWords.WordID, GPsInWords.Position AS GPPosition, WordsInSentences.Position AS WordPosition FROM GPs
|
|
INNER JOIN GPsInWords ON GPsInWords.GPID = GPs.ID
|
|
INNER JOIN WordsInSentences ON WordsInSentences.WordID = GPsInWords.WordID
|
|
INNER JOIN Sentences ON Sentences.ID = WordsInSentences.SentenceID
|
|
WHERE Sentences.ID = ?
|
|
ORDER BY WordPosition ASC, GPPosition ASC", [sentence_id])
|
|
return db.query_result
|
|
|
|
|
|
func get_words_containing_grapheme(grapheme: String) -> Array[Dictionary]:
|
|
db.query_with_bindings("SELECT Word FROM Words INNER JOIN GPsInWords INNER JOIN GPs on Words.ID = GPsInWords.WordID AND GPs.Grapheme=? AND GPs.ID = GPsInWords.GPID", [grapheme])
|
|
return db.query_result
|
|
|
|
|
|
func get_word_id_from_text(text: String) -> int:
|
|
var clean_text: String = text.replace(".", "").replace(",", "")
|
|
db.query_with_bindings(
|
|
"SELECT ID FROM Words WHERE Word = ? COLLATE NOCASE;",
|
|
[clean_text]
|
|
)
|
|
if db.query_result.size() > 0:
|
|
if db.query_result[0].has("ID"):
|
|
return db.query_result[0].ID
|
|
# If not found, trying again with even cleaner text
|
|
clean_text = clean_text.replace("'", "")
|
|
db.query_with_bindings(
|
|
"SELECT ID FROM Words WHERE Word = ? COLLATE NOCASE;",
|
|
[clean_text]
|
|
)
|
|
if db.query_result.size() > 0:
|
|
if db.query_result[0].has("ID"):
|
|
return db.query_result[0].ID
|
|
Log.trace("Database: Word " + clean_text + " ID not found")
|
|
return -1
|
|
|
|
|
|
func get_syllables_for_lesson(lesson_nb: int, only_new: bool = false) -> Array[Dictionary]:
|
|
var query: String = "SELECT Syllables.ID, Syllables.Syllable as Grapheme, VerifiedCount.LessonNb FROM Syllables
|
|
INNER JOIN
|
|
(SELECT SyllableID, count() as Count FROM GPsInSyllables
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInSyllables.GPID
|
|
GROUP BY SyllableID
|
|
) TotalCount ON TotalCount.SyllableID = Syllables.ID
|
|
INNER JOIN
|
|
(SELECT SyllableID, count() as Count, max(LessonNb) AS LessonNb FROM GPsInSyllables
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInSyllables.GPID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb <= ?
|
|
GROUP BY SyllableID
|
|
) VerifiedCount ON VerifiedCount.SyllableID = Syllables.ID AND VerifiedCount.Count = TotalCount.Count"
|
|
if only_new:
|
|
query += " INNER JOIN GPsInSyllables ON GPsInSyllables.SyllableID = Syllables.ID
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInSyllables.GPID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb = ?"
|
|
db.query_with_bindings(query, [lesson_nb, lesson_nb])
|
|
else:
|
|
db.query_with_bindings(query, [lesson_nb])
|
|
|
|
var res: Array[Dictionary] = db.query_result
|
|
for syllable: Dictionary in res:
|
|
syllable.GPs = get_gps_from_syllable(syllable.ID as int)
|
|
var phonemes: Array[String] = []
|
|
for gp: Dictionary in syllable.GPs:
|
|
phonemes.append(gp.Phoneme)
|
|
syllable.Phoneme = "-".join(phonemes)
|
|
|
|
return res
|
|
|
|
|
|
func get_words_for_lesson(lesson_nb: int, only_new: bool = false, min_length: int = 2, max_length: int = 99, include_exception: bool = false) -> Array[Dictionary]:
|
|
var parameters: Array = []
|
|
var query: String = "SELECT DISTINCT Words.ID, Words.Word, Words.Exception, VerifiedCount.Count as GPsCount, MaxLessonNb as LessonNb, VerifiedCount.gpsid as GPs_IDs
|
|
FROM Words
|
|
INNER JOIN
|
|
(SELECT WordID, count() as Count FROM GPsInWords
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
GROUP BY WordID
|
|
) TotalCount ON TotalCount.WordID = Words.ID
|
|
INNER JOIN
|
|
(SELECT WordID, count() as Count, max(LessonNb) as MaxLessonNb, group_concat(GPsInWords.GPID) as gpsid FROM GPsInWords
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb <= ?
|
|
GROUP BY WordID
|
|
) VerifiedCount ON VerifiedCount.WordID = Words.ID AND VerifiedCount.Count = TotalCount.Count"
|
|
parameters.append(lesson_nb)
|
|
|
|
if only_new:
|
|
query += " INNER JOIN GPsInWords ON GPsInWords.WordID = Words.ID
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb = ?"
|
|
parameters.append(lesson_nb)
|
|
|
|
query += " WHERE TotalCount.Count <= ? and TotalCount.Count >= ?"
|
|
|
|
if not include_exception:
|
|
query += " and Words.Exception = false"
|
|
|
|
query += " ORDER BY LessonNb, GPsCount ASC"
|
|
parameters.append(max_length)
|
|
parameters.append(min_length)
|
|
|
|
db.query_with_bindings(query, parameters)
|
|
var res: Array[Dictionary] = db.query_result
|
|
|
|
# Parse the GPs IDs
|
|
for word: Dictionary in res:
|
|
var word_gps: Array = []
|
|
var word_gps_id: String = word.GPs_IDs
|
|
for gp_id: String in word_gps_id.split(","):
|
|
word_gps.append({ID = int(gp_id)})
|
|
word.GPs = word_gps
|
|
word.erase("GPs_IDs")
|
|
|
|
return res
|
|
|
|
|
|
func get_words_with_silent_gp_for_lesson(lesson_nb: int, only_new: bool = false, min_length: int = 2, max_length: int = 6) -> Array[Dictionary]:
|
|
var parameters: Array = []
|
|
var query: String = "SELECT Words.ID, Words.Word, MaxLessonNb as LessonNb
|
|
FROM Words
|
|
INNER JOIN
|
|
(SELECT WordID, count() as Count FROM GPsInWords
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
GROUP BY WordID
|
|
) TotalCount ON TotalCount.WordID = Words.ID
|
|
INNER JOIN
|
|
(SELECT WordID, count() as Count, max(LessonNb) as MaxLessonNb, group_concat(GPs.Phoneme) as gpphoneme FROM GPsInWords
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb <= ?
|
|
INNER JOIN GPs ON GPs.ID = GPsInWords.GPID
|
|
GROUP BY WordID
|
|
) VerifiedCount ON VerifiedCount.WordID = Words.ID AND VerifiedCount.Count = TotalCount.Count"
|
|
parameters.append(lesson_nb)
|
|
|
|
if only_new:
|
|
query += " INNER JOIN GPsInWords ON GPsInWords.WordID = Words.ID
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb = ?"
|
|
parameters.append(lesson_nb)
|
|
|
|
query += " WHERE TotalCount.Count <= ? and TotalCount.Count >= ?
|
|
AND VerifiedCount.gpphoneme LIKE '%#%'
|
|
ORDER BY LessonNb, VerifiedCount.Count ASC"
|
|
parameters.append(max_length)
|
|
parameters.append(min_length)
|
|
|
|
db.query_with_bindings(query, parameters)
|
|
return db.query_result
|
|
|
|
|
|
func get_sentences_by_lessons() -> Dictionary:
|
|
var sentences_by_lesson: Dictionary = {}
|
|
|
|
db.query("SELECT * FROM Sentences")
|
|
var sentences: Array[Dictionary] = db.query_result
|
|
|
|
for sentence: Dictionary in sentences:
|
|
var lesson_nb: int = get_min_lesson_for_sentence_id(sentence.ID as int)
|
|
var arr: Array = sentences_by_lesson.get(lesson_nb, [])
|
|
arr.append(sentence)
|
|
sentences_by_lesson[lesson_nb] = arr
|
|
return sentences_by_lesson
|
|
|
|
|
|
func get_sentences(p_lesson_nb: int, only_new: bool = false, sentences_by_lesson: Dictionary = {}) -> Array:
|
|
if sentences_by_lesson.is_empty():
|
|
sentences_by_lesson = get_sentences_by_lessons()
|
|
|
|
if only_new:
|
|
return sentences_by_lesson.get(p_lesson_nb, [])
|
|
|
|
var ret: Array = []
|
|
for index: int in range(p_lesson_nb):
|
|
ret.append_array(sentences_by_lesson.get(index, []) as Array)
|
|
return ret
|
|
|
|
|
|
func get_sentences_for_lesson(lesson_nb: int, min_length: int = 2, max_length:int = 5) -> Array[Dictionary]:
|
|
var query: String = "SELECT Sentences.*, VerifiedCount.MaxLessonNb AS LessonNb FROM Sentences
|
|
INNER JOIN
|
|
(SELECT SentenceID, count() as WordsCount FROM WordsInSentences
|
|
GROUP BY SentenceID
|
|
) WordCount ON WordCount.SentenceID = Sentences.ID
|
|
INNER JOIN
|
|
(SELECT SentenceID, count() as Count FROM GPsInWords
|
|
INNER JOIN WordsInSentences ON GPsInWords.WordID = WordsInSentences.WordID
|
|
INNER JOIN Sentences ON WordsInSentences.SentenceID = Sentences.ID
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
GROUP BY SentenceID
|
|
) TotalCount ON TotalCount.SentenceID = Sentences.ID
|
|
INNER JOIN
|
|
(SELECT SentenceID, count() as Count, max(LessonNb) as MaxLessonNb FROM GPsInWords
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
INNER JOIN WordsInSentences ON GPsInWords.WordID = WordsInSentences.WordID
|
|
INNER JOIN Sentences ON WordsInSentences.SentenceID = Sentences.ID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb <= ?
|
|
INNER JOIN GPs ON GPs.ID = GPsInWords.GPID
|
|
GROUP BY SentenceID
|
|
) VerifiedCount ON VerifiedCount.SentenceID = Sentences.ID AND VerifiedCount.Count = TotalCount.Count
|
|
WHERE Sentences.Exception = 0
|
|
AND WordsCount >=?
|
|
AND WordsCount <=?
|
|
ORDER BY LessonNb"
|
|
|
|
db.query_with_bindings(query, [lesson_nb, min_length, max_length])
|
|
return db.query_result
|
|
|
|
|
|
func get_sentences_for_lesson_with_silent_gps(lesson_nb: int, min_length: int = 2, max_length:int = 5) -> Array[Dictionary]:
|
|
var query: String = "SELECT Sentences.*, VerifiedCount.MaxLessonNb AS LessonNb FROM Sentences
|
|
INNER JOIN
|
|
(SELECT SentenceID, count() as WordsCount FROM WordsInSentences
|
|
GROUP BY SentenceID
|
|
) WordCount ON WordCount.SentenceID = Sentences.ID
|
|
INNER JOIN
|
|
(SELECT SentenceID, count() as Count FROM GPsInWords
|
|
INNER JOIN WordsInSentences ON GPsInWords.WordID = WordsInSentences.WordID
|
|
INNER JOIN Sentences ON WordsInSentences.SentenceID = Sentences.ID
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
GROUP BY SentenceID
|
|
) TotalCount ON TotalCount.SentenceID = Sentences.ID
|
|
INNER JOIN
|
|
(SELECT SentenceID, count() as Count, max(LessonNb) as MaxLessonNb, group_concat(GPs.Phoneme) as SentencePhoneme FROM GPsInWords
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
INNER JOIN WordsInSentences ON GPsInWords.WordID = WordsInSentences.WordID
|
|
INNER JOIN Sentences ON WordsInSentences.SentenceID = Sentences.ID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb <= ?
|
|
INNER JOIN GPs ON GPs.ID = GPsInWords.GPID
|
|
GROUP BY SentenceID
|
|
) VerifiedCount ON VerifiedCount.SentenceID = Sentences.ID AND VerifiedCount.Count = TotalCount.Count
|
|
WHERE Sentences.Exception = 0
|
|
AND WordsCount >=?
|
|
AND WordsCount <=?
|
|
AND VerifiedCount.SentencePhoneme LIKE '%#%'"
|
|
|
|
db.query_with_bindings(query, [lesson_nb, min_length, max_length])
|
|
return db.query_result
|
|
|
|
|
|
func get_pseudowords_for_lesson(p_lesson_nb: int) -> Array[Dictionary]:
|
|
var query: String = "SELECT * FROM Pseudowords
|
|
INNER JOIN Words ON Words.ID = Pseudowords.WordID
|
|
INNER JOIN
|
|
(SELECT WordID, count() as Count FROM GPsInWords
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
GROUP BY WordID
|
|
) TotalCount ON TotalCount.WordID = Words.ID
|
|
INNER JOIN
|
|
(SELECT WordID, count() as Count FROM GPsInWords
|
|
INNER JOIN GPsInLessons ON GPsInLessons.GPID = GPsInWords.GPID
|
|
INNER JOIN Lessons ON Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb <= ?
|
|
GROUP BY WordID
|
|
) VerifiedCount ON VerifiedCount.WordID = Words.ID AND VerifiedCount.Count = TotalCount.Count"
|
|
db.query_with_bindings(query, [p_lesson_nb])
|
|
return db.query_result
|
|
|
|
|
|
func get_distractors_for_grapheme(id: int, lesson_nb: int) -> Array[Dictionary]:
|
|
db.query_with_bindings("SELECT DISTINCT distractor.Grapheme, distractor.ID, distractor.Phoneme From GPs stimuli
|
|
INNER JOIN GPs distractor
|
|
INNER JOIN Lessons
|
|
INNER JOIN GPsInLessons
|
|
ON stimuli.ID=? AND distractor.type = stimuli.type
|
|
AND distractor.exception = false
|
|
AND distractor.Grapheme != stimuli.Grapheme
|
|
AND distractor.Phoneme != stimuli.Phoneme
|
|
AND CASE WHEN length(distractor.Grapheme) < length(stimuli.Grapheme)
|
|
THEN stimuli.Grapheme NOT LIKE distractor.Grapheme || '%'
|
|
ELSE true
|
|
END
|
|
AND GPsInLessons.GPID = distractor.ID
|
|
AND Lessons.ID = GPsInLessons.LessonID AND Lessons.LessonNb <= ?", [id, lesson_nb])
|
|
return db.query_result
|
|
|
|
|
|
func get_min_lesson_for_gp_id(gp_id: int) -> int:
|
|
db.query_with_bindings("SELECT Lessons.LessonNb as i FROM Lessons
|
|
INNER JOIN GPsInLessons ON Lessons.ID = GPsInLessons.LessonID AND GPsInLessons.GPID = ?", [gp_id])
|
|
if db.query_result.is_empty():
|
|
return -1
|
|
return db.query_result[0].i
|
|
|
|
|
|
func get_min_lesson_for_word_id(word_id: int) -> int:
|
|
db.query_with_bindings("SELECT * FROM Words
|
|
INNER JOIN GPsInWords ON GPsInWords.WordID=Words.ID
|
|
WHERE Words.ID=?
|
|
ORDER BY Position", [word_id])
|
|
var minimum: int = -1
|
|
for result: Dictionary in db.query_result:
|
|
var index: int = Database.get_min_lesson_for_gp_id(result.GPID as int)
|
|
if index < 0:
|
|
minimum = -1
|
|
break
|
|
minimum = maxi(minimum, index)
|
|
return minimum
|
|
|
|
|
|
func get_min_lesson_for_sentence_id(sentence_id: int) -> int:
|
|
db.query_with_bindings("SELECT MAX(Lessons.LessonNb) as i FROM Lessons
|
|
INNER JOIN GPsInLessons ON Lessons.ID = GPsInLessons.LessonID
|
|
INNER JOIN WordsInSentences ON WordsInSentences.SentenceID = ?
|
|
INNER JOIN GPsInWords ON GPsInWords.GPID = GPsInLessons.GPID AND GPsInWords.WordID = WordsInSentences.WordID", [sentence_id])
|
|
if not db.query_result[0].i:
|
|
return -1
|
|
return db.query_result[0].i
|
|
|
|
|
|
func get_words_in_sentence(sentence_id: int) -> Array[Dictionary]:
|
|
db.query_with_bindings("SELECT * FROM WordsInSentences
|
|
INNER JOIN Words ON Words.ID = WordsInSentences.WordID
|
|
WHERE SentenceID = ?
|
|
ORDER BY WordsInSentences.Position", [sentence_id])
|
|
return db.query_result
|
|
|
|
|
|
func get_words_in_sentence_for_integrity_check(sentence_id: int) -> Array[Dictionary]:
|
|
db.query_with_bindings(
|
|
"SELECT
|
|
Words.ID AS ID,
|
|
Words.Word AS Word,
|
|
WordsInSentences.Position AS WordPosition,
|
|
WordsInSentences.SentenceID AS SentenceID
|
|
FROM WordsInSentences
|
|
INNER JOIN Words ON Words.ID = WordsInSentences.WordID
|
|
WHERE WordsInSentences.SentenceID = ?
|
|
ORDER BY WordsInSentences.Position ASC",
|
|
[sentence_id]
|
|
)
|
|
return db.query_result
|
|
|
|
|
|
func get_lessons_count() -> int:
|
|
db.query("SELECT MAX(Lessons.LessonNb) as i FROM Lessons")
|
|
if db.query_result.is_empty() or not db.query_result[0].i:
|
|
return 0
|
|
return db.query_result[0].i
|
|
|
|
|
|
func get_audio_stream_for_path(path: String) -> AudioStream:
|
|
var full_path: String = BASE_PATH.path_join(language).path_join(path)
|
|
if not FileAccess.file_exists(full_path):
|
|
return null
|
|
return load(full_path)
|
|
|
|
|
|
func get_audio_stream_for_word(id: int) -> AudioStream:
|
|
var gps: Array = get_gp_from_word(id)
|
|
var file_name: String = _phoneme_to_string(gps[0].Phoneme as String)
|
|
for index: int in range(1, gps.size()):
|
|
var gp: Dictionary = gps[index]
|
|
file_name += "-" + _phoneme_to_string(gp.Phoneme as String)
|
|
file_name += ".mp3"
|
|
return load(words_path + file_name)
|
|
|
|
|
|
func get_audio_stream_for_phoneme(phoneme: String) -> AudioStream:
|
|
var phoneme_array: PackedStringArray = phoneme.split("-")
|
|
var path: String = words_path + _phoneme_to_string(phoneme_array[0])
|
|
for index: int in range(1, phoneme_array.size()):
|
|
path += "-" + _phoneme_to_string(phoneme_array[index])
|
|
path += ".mp3"
|
|
|
|
if FileAccess.file_exists(path) and ResourceLoader.exists(path):
|
|
return load(path)
|
|
Log.trace("Database: Audio stream not found for phoneme %s" % phoneme)
|
|
return null
|
|
|
|
|
|
func get_gp_look_and_learn_image(gp: Dictionary) -> Texture:
|
|
var path: String = get_gp_look_and_learn_image_path(gp)
|
|
if FileAccess.file_exists(path):
|
|
if ResourceLoader.exists(path):
|
|
return load(path)
|
|
else:
|
|
var image: Image = Image.load_from_file(path)
|
|
var texture: ImageTexture = ImageTexture.create_from_image(image)
|
|
return texture
|
|
|
|
Log.trace("Database: Look & Learn image not found for GP %s" % str(gp))
|
|
return null
|
|
|
|
|
|
func get_gp_look_and_learn_sound(gp: Dictionary) -> AudioStream:
|
|
var path: String = get_gp_look_and_learn_sound_path(gp)
|
|
if FileAccess.file_exists(path):
|
|
if ResourceLoader.exists(path):
|
|
return load(path)
|
|
else:
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
var error: Error = FileAccess.get_open_error()
|
|
if error != OK:
|
|
Log.error("Database: Get GP look and learn sound: Cannot open file %s. Error: %s" % [path, error_string(error)])
|
|
return null
|
|
if file == null:
|
|
Log.error("Database: Get GP look and learn sound: Cannot open file %s. File is null" % path)
|
|
return null
|
|
var sound: AudioStreamMP3 = AudioStreamMP3.new()
|
|
sound.data = file.get_buffer(file.get_length())
|
|
return sound
|
|
|
|
Log.trace("Database: Look & Learn sound not found for GP %s" % str(gp))
|
|
return null
|
|
|
|
|
|
func get_gp_look_and_learn_video(gp: Dictionary) -> VideoStream:
|
|
var path: String = get_gp_look_and_learn_video_path(gp)
|
|
if FileAccess.file_exists(path):
|
|
var video: VideoStream = load(path)
|
|
return video
|
|
|
|
Log.trace("Database: Look & Learn video not found for GP %s" % gp)
|
|
return null
|
|
|
|
|
|
func get_gp_name(gp: Dictionary) -> String:
|
|
var result: String = ""
|
|
if gp.has("Grapheme"):
|
|
result += gp["Grapheme"]
|
|
if gp.has("Phoneme"):
|
|
if result != "":
|
|
result += "-"
|
|
result += gp["Phoneme"]
|
|
return result
|
|
|
|
|
|
func get_language_folder() -> String:
|
|
return BASE_PATH + language
|
|
|
|
|
|
func get_language_sound_folder() -> String:
|
|
return get_language_folder() + LANGUAGE_SOUNDS
|
|
|
|
|
|
func get_gp_look_and_learn_image_path(gp: Dictionary) -> String:
|
|
return get_language_folder() + LOOK_AND_LEARN_IMAGES + get_gp_name(gp) + IMAGE_EXTENSION
|
|
|
|
|
|
func get_gp_look_and_learn_sound_path(gp: Dictionary) -> String:
|
|
return get_language_folder() + LOOK_AND_LEARN_SOUNDS + get_gp_name(gp) + SOUND_EXTENSION
|
|
|
|
|
|
func get_gp_look_and_learn_video_path(gp: Dictionary) -> String:
|
|
return get_language_folder() + LOOK_AND_LEARN_VIDEOS + get_gp_name(gp) + VIDEO_EXTENSION
|
|
|
|
|
|
func get_gp_sound_path(gp: Dictionary) -> String:
|
|
return get_language_sound_folder() + Database.get_gp_name(gp) + SOUND_EXTENSION
|
|
|
|
|
|
func get_syllable_sound_path(syllable: Dictionary) -> String:
|
|
return get_language_sound_folder() + syllable.Grapheme + SOUND_EXTENSION
|
|
|
|
|
|
func get_word_sound_path(word: Dictionary) -> String:
|
|
return get_language_sound_folder() + word.Word + SOUND_EXTENSION
|
|
|
|
|
|
func get_kalulu_speech_path(speech_category: String, speech_name: String) -> String:
|
|
return get_language_sound_folder() + KALULU_FOLDER + speech_category + "_" + speech_name + SOUND_EXTENSION
|
|
|
|
|
|
func load_external_sound(path: String) -> AudioStreamMP3:
|
|
Log.trace("Database: Load External Sound: %s" % path)
|
|
|
|
path = Utils.get_safe_file_path(path)
|
|
|
|
if not FileAccess.file_exists(path):
|
|
path = UnicodeNormalizer.to_nfd_basic(path)
|
|
Log.trace("Database: Load External Sound: File not found, retrying with Unicode NFD: %s" % path)
|
|
if not FileAccess.file_exists(path):
|
|
path = UnicodeNormalizer.to_nfd_extended(path)
|
|
Log.trace("Database: Load External Sound: File not found, retrying with Unicode NFD extended: %s" % path)
|
|
if not FileAccess.file_exists(path):
|
|
Log.error("Database: Load External Sound: File not found after normalization attempts: %s" % path)
|
|
return null
|
|
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
var error: Error = FileAccess.get_open_error()
|
|
if error != OK or file == null:
|
|
Log.error("Database: Load External Sound: Cannot open file %s. Error: %s" % [path, error_string(error)])
|
|
return null
|
|
|
|
var file_length: int = file.get_length()
|
|
if file_length <= 0:
|
|
Log.error("Database: Load External Sound: Invalid file length for %s" % path)
|
|
file.close()
|
|
return null
|
|
|
|
var data: PackedByteArray = file.get_buffer(file_length)
|
|
file.close()
|
|
|
|
if data.is_empty():
|
|
Log.error("Database: Load External Sound: Empty buffer read for %s" % path)
|
|
return null
|
|
|
|
var audio_stream: AudioStreamMP3 = AudioStreamMP3.new()
|
|
audio_stream.data = data
|
|
|
|
if not audio_stream or audio_stream == null:
|
|
Log.error("Database: Load External Sound: Failed to create AudioStreamMP3 for %s" % path)
|
|
return null
|
|
|
|
Log.trace("Database: Load External Sound: Loaded %s successfully (%d bytes)" % [path, file_length])
|
|
return audio_stream
|
|
|
|
|
|
func _phoneme_to_string(phoneme: String) -> String:
|
|
if _SYMBOLS_TO_STRING.has(phoneme):
|
|
return _SYMBOLS_TO_STRING[phoneme]
|
|
elif phoneme == phoneme.to_lower():
|
|
return phoneme
|
|
else:
|
|
return "cap." + phoneme.to_lower()
|
|
|
|
|
|
# Returns an array containing an int followed by an array of int
|
|
func _import_word_from_csv(ortho: String, gpmatch: String, is_word: bool = true) -> Array[Variant]:
|
|
var gp_list: PackedStringArray = gpmatch.trim_prefix("(").trim_suffix(")").split(".")
|
|
var gp_ids: Array[int] = []
|
|
for gp: String in gp_list:
|
|
var split: PackedStringArray = gp.split("-")
|
|
var grapheme: String = split[0]
|
|
var phoneme: String = split[1]
|
|
db.query_with_bindings("SELECT * FROM GPs WHERE Grapheme = ? AND Phoneme = ?", [grapheme, phoneme])
|
|
var gp_id: int = -1
|
|
if db.query_result.is_empty():
|
|
db.insert_row("GPs", {
|
|
Grapheme = grapheme,
|
|
Phoneme = phoneme,
|
|
})
|
|
gp_id = db.last_insert_rowid
|
|
else:
|
|
gp_id = db.query_result[0].ID
|
|
gp_ids.append(gp_id)
|
|
if is_word:
|
|
db.query_with_bindings("SELECT * FROM Words WHERE Word = ?", [ortho])
|
|
else:
|
|
db.query_with_bindings("SELECT * FROM Syllables WHERE Syllable = ?", [ortho])
|
|
var word_id: int = -1
|
|
if db.query_result.is_empty():
|
|
if is_word:
|
|
db.insert_row("Words", {
|
|
Word = ortho,
|
|
})
|
|
word_id = db.last_insert_rowid
|
|
for index: int in range(gp_ids.size()):
|
|
var gp_id: int = gp_ids[index]
|
|
db.insert_row("GPsInWords", {
|
|
WordID = word_id,
|
|
GPID = gp_id,
|
|
Position = index
|
|
})
|
|
else:
|
|
db.insert_row("Syllables", {
|
|
Syllable = ortho,
|
|
})
|
|
word_id = db.last_insert_rowid
|
|
for index: int in range(gp_ids.size()):
|
|
var gp_id: int = gp_ids[index]
|
|
db.insert_row("GPsInSyllables", {
|
|
SyllableID = word_id,
|
|
GPID = gp_id,
|
|
Position = index
|
|
})
|
|
else:
|
|
word_id = db.query_result[0].ID
|
|
return [word_id, gp_ids]
|