Krotos Modules 3
Loading...
Searching...
No Matches
TextToPreset.cpp
Go to the documentation of this file.
1#include "TextToPreset.h"
2
3#include <openssl/evp.h>
4#include <openssl/err.h>
5
7namespace krotos
8{
10 {
11 File ggmlDataFile = File(krotos::utils::StringsIntoPath(AssetManager::getPluginDirectory().getFullPathName(),
12 "ttpResources", modelFileName));
13
14 if (ggmlDataFile.existsAsFile())
15 {
17 }
18 // TODO: set a priority?
19 startThread();
20}
21
22TextToPreset::~TextToPreset() { stopThread(4000); }
23
25{
26 if (!threadShouldExit())
27 {
29 }
30}
31
33{
34 // TODO: check nothing missing here
36 m_presetNames.clear();
37 m_presetData.clear();
38 m_kNN.clear();
39}
40
41bool TextToPreset::processFileList(Array<File>& filesToProcess)
42{
43 if (m_modelFileAvailable == false)
44 {
45 return false;
46 }
47
48 ScopedLock sl(m_cs);
49
50 // clear previously stored data
51 clear();
52
53 for (File& file : filesToProcess)
54 {
55 auto success = calculateEmbeddingsForPresetFile(file);
56 if (!success || Thread::currentThreadShouldExit())
57 {
58 return false;
59 }
60 }
61
62 // build KD-Tree index so we can search
63 if (!m_presetNames.empty())
64 {
66 }
67
68 // append - save embeddings to file
69 const auto appendedDataToEmbeddingsFile = appendDataToEmbeddingsFile();
70
71 return appendedDataToEmbeddingsFile;
72}
73
75{
76 // map preset name with preset path name
77 if (!presetFile.exists())
78 {
79 return false;
80 }
81
82 m_presetFile = presetFile;
83 m_presetName = m_presetFile.getFileName();
84
85 m_presetNameAndPresetPathMap.insert(std::pair<std::string, std::string>(
86 m_presetName.toStdString(), m_presetFile.getFullPathName().toStdString()));
87
88 // create the preset value tree from the preset file
90
91 // prepare sample paths and sample file names containers for the new training session
92 m_samplePaths.clear();
93 m_sampleFilenames.clear();
94
95 // get the names of all the assets included in the preset
96 getPresetAssetNames(preset);
97
98 // split asset names to tags
99 auto success = assetNamesToTags(true);
100
101 return success;
102}
103
104bool TextToPreset::assetNamesToTags(bool eliminateDuplicates)
105{
106 // clear preset tags
107 m_presetTags.clear();
108
109 String presetTagsString;
110
111 // if we already have the tags available, skip extracting them
112 if (m_presetData.count(m_presetName) > 0)
113 {
115 presetTagsString = m_presetTags.joinIntoString(", ");
116 }
117 else
118 {
119 // extract tags from filenames
120 for (auto assetName : m_sampleFilenames)
121 {
122 if (Thread::currentThreadShouldExit())
123 {
124 return false;
125 }
126
127 // break asset name to tokens
128 StringArray assetNameTokens;
129 assetNameTokens.addTokens(assetName, StringRef(delimiters), StringRef("'"));
130
131 // for each token we got see if it belongs to the exclude list
132 for (auto token : assetNameTokens)
133 {
134 if (token.isEmpty() == false && token.containsOnly(ttpNumericalMetadata) == false)
135 {
136 // if it does not, we got a tag we can use
137 if (excludeTerms.contains(token.toLowerCase()) == false)
138 {
139 if (token == "FOLEYFeet")
140 token = "FOLYFeet"; // fix for typo in our factory assets
141
142 // if token is valid UCS CatID
143 if (m_UCS->isValid(token))
144 {
145 // unpack token into UCS Category SubCategory
146 // e.g. "MECHLock" -> "MECHANICAL, LOCK"
147 token = m_UCS->getCategorySubCategory(token);
148 token = token.removeCharacters(",");
149 }
150 // so lets add it to the tags array
151 m_presetTags.add(token);
152 }
153 }
154 }
155 }
156
157#if AUDIO_METADATA_TAGS
158 // also parse the asset wav files, look for extra metadata
159 // and if there is any push them in the m_presetTags array as well
160 for (auto assetPath : m_samplePaths)
161 {
162 if (Thread::currentThreadShouldExit())
163 {
164 return false;
165 }
166 // convert path to platform path format if needed
167 assetPath = AssetManager::convertFilePathString(assetPath);
168
169 // create format manager and format reader
170 AudioFormatManager formatManager;
171 formatManager.registerBasicFormats();
172 std::unique_ptr<AudioFormatReader> reader;
173
174 // copes with . and ..
175 if (assetPath.startsWith(".") == true)
176 {
177 continue;
178 }
179
180 // asset files are inclided as .wav in preset files, see if they exist as such
181 // if not look for kaf equivalents.
182 File assetFile(assetPath);
183 if (assetFile.exists() == false)
184 {
185 assetPath = assetPath.upToFirstOccurrenceOf(".wav", false, true) + ".kaf";
186 // copes with . and ..
187 if (assetPath.startsWith(".") == true)
188 {
189 continue;
190 }
191 assetFile = File(assetPath);
192 }
193
194 // analyze wav
195 if (assetFile.hasFileExtension(kafFileExtension) == false)
196 {
197 // new format reader
198 reader.reset(formatManager.createReaderFor(AssetManager::convertFilePath(assetPath)));
199 if (reader)
200 {
201 for (String key : reader->metadataValues.getAllKeys())
202 {
203 if (key == String(wavMetadataFieldInFile))
204 {
205 auto description = reader->metadataValues.getValue(key, "");
206 StringArray tokens;
207 tokens.addTokens(description, ",", StringRef(""));
208 for (auto token : tokens)
209 {
210 // tokens should not be empty or contain any of the terms below, or the file name of the
211 // preset which is included multiple times in wav metadata for several files
212 if (token.isEmpty() == false && token.containsOnly(ttpNumericalMetadata) == false &&
213 token.containsIgnoreCase("krt") == false &&
214 token.containsIgnoreCase("krotos") == false && token.startsWith("\\") == false &&
215 token.contains(m_presetFile.getFileNameWithoutExtension().toLowerCase()) == false)
216 {
217 if (!excludeTerms.contains(token.toLowerCase()))
218 {
219 if (token.startsWith(" "))
220 {
221 token = token.fromFirstOccurrenceOf(" ", false, true);
222 }
223 m_presetTags.add(token);
224 }
225 }
226 }
227 }
228 }
229 }
230 }
231 else
232 {
233 std::array<unsigned char, BUF_SIZE> m_sampleData;
234
235 if (assetFile.existsAsFile() == false)
236 {
237 continue;
238 }
239
240 std::ifstream inputFile(assetPath.toStdString(), std::ios::binary);
241 if (!inputFile)
242 {
243 continue;
244 }
245
246 // memory block pre-allocated to size of file
247 int64 assetFileSize = assetFile.getSize();
248 std::unique_ptr<juce::MemoryBlock> outputDataBlock =
249 std::make_unique<juce::MemoryBlock>(assetFile.getSize());
250
251 juce::MemoryOutputStream outputDataStream(*outputDataBlock, false);
252
253 std::vector<unsigned char> buffer(BUFFER_SIZE);
254 std::vector<unsigned char> plaintext(BUFFER_SIZE + EVP_MAX_BLOCK_LENGTH);
255
256 std::vector<unsigned char> iv(IV_SIZE);
257 inputFile.read(reinterpret_cast<char*>(iv.data()), IV_SIZE);
258
259 // encrytion key
260 std::string encKey("5128bd6c2bc42ed6835d9d24ee0e7200cc8aeae68e18e9f1ea3f6cd52cc082a8");
261
262 // create hex for ecryption key
263 getSampleMetadata(encKey, m_data);
264
265 // Create cipher context
266 EVP_CIPHER_CTX* ctx;
267 if (!(ctx = EVP_CIPHER_CTX_new()))
268 {
269 jassertfalse;
270 }
271
272 // Initialise AES 256 CBC cipher
273 if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, m_data, iv.data()) != 1)
274 {
275 jassertfalse;
276 }
277 auto* thread = Thread::getCurrentThread();
278 // Decrypt data
279 int len;
280 int totalBytesProcessed = 0;
281 while (inputFile.read(reinterpret_cast<char*>(buffer.data()), buffer.size()))
282 {
283 auto bytesRead = inputFile.gcount();
284
285 if (thread && thread->threadShouldExit())
286 {
287 return false;
288 }
289
290 if (EVP_DecryptUpdate(ctx, plaintext.data(), &len, buffer.data(), static_cast<int>(bytesRead)) != 1)
291 {
292 jassertfalse;
293 }
294
295 outputDataStream.write(reinterpret_cast<char*>(plaintext.data()), len);
296 totalBytesProcessed += len;
297 }
298
299 // Handle remaining data
300 auto remainingBytes = inputFile.gcount();
301 if (remainingBytes > 0)
302 {
303 if (EVP_DecryptUpdate(ctx, plaintext.data(), &len, buffer.data(),
304 static_cast<int>(remainingBytes)) != 1)
305 jassertfalse;
306
307 outputDataStream.write(reinterpret_cast<char*>(plaintext.data()), len);
308 }
309 outputDataStream.flush();
310 totalBytesProcessed += len;
311 EVP_CIPHER_CTX_free(ctx);
312
313 std::unique_ptr<MemoryInputStream> audioDataStream =
314 std::make_unique<MemoryInputStream>(*outputDataBlock, true);
315
316 reader.reset(formatManager.createReaderFor(std::move(audioDataStream)));
317
318 // TODO: create function for it ?
319 if (reader)
320 {
321 for (String key : reader->metadataValues.getAllKeys())
322 {
323 if (key == String(wavMetadataFieldInFile))
324 {
325 auto description = reader->metadataValues.getValue(key, "");
326 StringArray tokens;
327 tokens.addTokens(description, ",", StringRef(""));
328 for (auto token : tokens)
329 {
330 // tokens should not be empty or contain any of the terms below, or the file name of the
331 // preset which is included multiple times in wav metadata for several files
332 if (token.isEmpty() == false && token.containsOnly(ttpNumericalMetadata) == false &&
333 token.containsIgnoreCase("krt") == false &&
334 token.containsIgnoreCase("krotos") == false && token.startsWith("\\") == false &&
335 token.containsAnyOf(m_presetName.toLowerCase()) == false)
336 {
337 if (!excludeTerms.contains(token.toLowerCase()))
338 {
339 if (token.startsWith(" "))
340 {
341 token = token.fromFirstOccurrenceOf(" ", false, true);
342 }
343 m_presetTags.add(token);
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351 }
352#endif
353
354#if PRESET_FILENAME_TAGS
355 // split preset name if necessary and push the tokens created into m_presetTags
356 StringArray presetNameTokens;
357 presetNameTokens.addTokens(m_presetFile.getFileNameWithoutExtension(), " _", StringRef(""));
358 for (auto& presetNameToken : presetNameTokens)
359 {
360 if (presetNameToken.isEmpty() == false && presetNameToken.containsOnly(ttpNumericalMetadata) == false)
361 {
362 if (!excludeTerms.contains(presetNameToken.toLowerCase()))
363 m_presetTags.add(presetNameToken);
364 }
365 }
366#endif
367
368 // add preset name with its tags, and
369 // append current preset and its tags to the preset tags value tree
370 // eliminate duplicates (optional)
371 presetTagsString = m_presetTags.joinIntoString(", ");
372 if (eliminateDuplicates == true)
373 {
374 presetTagsString.clear();
375 // Create a container for unique words
376 StringArray uniqueTags;
377
378 // Iterate through the tags and add unique ones to the container
379 for (auto tag : m_presetTags)
380 {
381 tag = tag.toLowerCase();
382 if (!uniqueTags.contains(tag))
383 {
384 uniqueTags.add(tag);
385 }
386 }
387
388 // Join the unique words to create a new string
389 m_presetTags = uniqueTags;
390 presetTagsString = uniqueTags.joinIntoString(", ");
391 }
392 }
393
394 if (Thread::currentThreadShouldExit())
395 {
396 return false;
397 }
398
399 // load previously calculated embeddings if they are available
400 std::vector<float> embeddings;
401 if (m_presetData.count(m_presetName) > 0)
402 {
403 embeddings = m_presetData[m_presetName].embeddings;
404 }
405 else
406 {
407 embeddings = m_sentenceTransformer.encode(presetTagsString.toStdString());
408 }
409
410 // update map
411 Data data(m_presetTags, embeddings);
413
414 // store embeddings in kNN after calculation
415 m_presetNames.push_back(m_presetName);
416 m_kNN.addDatasetItem(embeddings);
417
418 return true;
419}
420
422{
423 searchForTagAndProperty(root, Identifier("CUSTOMPARAM"), Identifier("SamplePaths"));
424}
425
426void TextToPreset::searchForTagAndProperty(ValueTree& root, Identifier tag, Identifier property)
427{
428 int index = 0;
429 ValueTree child;
430 int numChildrean = root.getNumChildren();
431
432 while ((child = root.getChild(index)).isValid())
433 {
434 if (child.hasType(tag))
435 {
436 if (child.getProperty(Identifier("id")).toString() == property.toString())
437 {
438 String samplePath = child.getPropertyAsValue(Identifier("value"), nullptr).toString();
439 if (samplePath.isNotEmpty())
440 {
441 // samplePath may contain more than one paths and we need to compensate for it
442 m_treatedAssetPaths.clear();
443 m_treatedAssetPaths.addTokens(samplePath, assetPathsDelimiter, StringRef(""));
444 for (auto& currentSamplePath : m_treatedAssetPaths)
445 {
446 auto convertPath = AssetManager::convertFilePath(currentSamplePath);
447 auto dynamicPath = convertPath.getFullPathName().fromFirstOccurrenceOf(AssetManager::getAssetDirectory().getFullPathName(), true, true);
448 auto resultingCurrentSamplePath = File::addTrailingSeparator(AssetManager::readFactorySamplesPath()) + dynamicPath;
449 String sampleName = File(resultingCurrentSamplePath).getFileName();
450 m_samplePaths.add(resultingCurrentSamplePath);
451 m_sampleFilenames.add(sampleName);
452 }
453 }
454 return;
455 }
456 else
457 {
458 searchForTagAndProperty(child, tag, property);
459 }
460 }
461 else
462 {
463 searchForTagAndProperty(child, tag, property);
464 }
465 index++;
466 }
467 return;
468}
469
470StringArray TextToPreset::combineSearch(const String& searchTerm, const StringArray& classicResults, const StringArray& ttpResults, std::map<String, std::vector<float>>& embeddingsCache, const size_t& maxResults)
471{
473 {
474 return classicResults;
475 }
476
477 StringArray combinedResults;
478 std::vector<std::pair<String, float>> scoredResults;
479 std::vector<std::vector<float>> combinedEmbeddings;
480 std::vector<String> combinedNames;
481
482 // Reserve capacity for the maximum number of results
483 scoredResults.reserve(maxResults);
484 combinedEmbeddings.reserve(maxResults);
485 combinedNames.reserve(maxResults);
486
487 // Get the query embedding
488 auto queryEmbedding = m_sentenceTransformer.encode(searchTerm.toStdString());
489
490 // Lambda to get embeddings with caching
491 auto getEmbedding = [&embeddingsCache, this](const String& result) -> std::vector<float> {
492 if (embeddingsCache.find(result) == embeddingsCache.end())
493 {
494 embeddingsCache[result] = m_sentenceTransformer.encode(result.toStdString());
495 }
496 return embeddingsCache[result];
497 };
498
499 // Encode classic search results and add to combined lists
500 for (const auto& result : classicResults)
501 {
502 if (combinedEmbeddings.size() >= maxResults) break;
503 combinedEmbeddings.push_back(getEmbedding(result));
504 combinedNames.push_back(result);
505 }
506
507 // Encode TTP results and add to combined lists
508 for (const auto& result : ttpResults)
509 {
510 if (combinedEmbeddings.size() >= maxResults) break;
511 combinedEmbeddings.push_back(getEmbedding(result));
512 combinedNames.push_back(result);
513 }
514
515 // Compute similarity scores for combined results
516 for (size_t i = 0; i < combinedNames.size(); ++i)
517 {
518 float similarity = computeSimilarity(queryEmbedding, combinedEmbeddings[i]);
519 scoredResults.push_back({combinedNames[i], similarity});
520 }
521
522 // Partial sort to get the top maxResults
523 std::partial_sort(scoredResults.begin(), scoredResults.begin() + std::min(scoredResults.size(), maxResults), scoredResults.end(), [](const auto& a, const auto& b) {
524 return a.second > b.second;
525 });
526
527 // Collect sorted results
528 for (size_t i = 0; i < std::min(scoredResults.size(), maxResults); ++i)
529 {
530 combinedResults.add(scoredResults[i].first);
531 }
532
533 return combinedResults;
534}
535
536float TextToPreset::computeSimilarity(const std::vector<float>& queryEmbedding, const std::vector<float>& resultEmbedding) const
537{
538 float dotProduct = std::inner_product(queryEmbedding.begin(), queryEmbedding.end(), resultEmbedding.begin(), 0.0f);
539 float queryNorm = std::sqrt(std::inner_product(queryEmbedding.begin(), queryEmbedding.end(), queryEmbedding.begin(), 0.0f));
540 float resultNorm = std::sqrt(std::inner_product(resultEmbedding.begin(), resultEmbedding.end(), resultEmbedding.begin(), 0.0f));
541
542 // Prevent divide by zero
543 float denominator = std::max(queryNorm * resultNorm, 1e-8f);
544
545 return dotProduct / denominator;
546}
547
548StringArray TextToPreset::findClosestPresets(String searchTerm, int maxNumPresetsToFind) const
549{
550 StringArray closestPresets;
551 if (!m_presetNames.empty())
552 {
553 ScopedLock sl(m_cs);
554 auto queryEmbedding = m_sentenceTransformer.encode(searchTerm.toStdString());
555
556 // embeddings are normalised so search uses cosine similarity
557 auto indices = m_kNN.knnQuery(queryEmbedding, maxNumPresetsToFind);
558 for (auto index : indices)
559 {
560 closestPresets.add(m_presetNames[index]);
561 }
562 }
563 return closestPresets;
564}
565
566template <class T> String TextToPreset::stringify(std::vector<T> numbers)
567{
568 String stringWithNumbers;
569 for (auto& number : numbers)
570 {
571 stringWithNumbers.append(String(number) + String(", "), 100);
572 }
573 return stringWithNumbers;
574}
575
576std::vector<float> TextToPreset::destringify(String stringWithNumbers)
577{
578 auto values = StringArray::fromTokens(stringWithNumbers, ",", "");
579 std::vector<float> numbers;
580 for (auto value : values)
581 {
582 if (value.containsNonWhitespaceChars())
583 {
584 numbers.push_back(value.getFloatValue());
585 }
586 }
587 return numbers;
588}
589
590File TextToPreset::createFilePath(String filename)
591{
592 return utils::StringsIntoPath(AssetManager::getPluginDirectory().getFullPathName(), "ttpResources", filename);
593}
594
596{
597 DynamicObject* dataset = new DynamicObject();
598 Array<var> rows;
599
600 for (const auto& element : m_presetData)
601 {
602 const auto presetName = element.first;
603 const auto tags = element.second.tags;
604 const auto embeddings = element.second.embeddings;
605
606 DynamicObject* metadata = new DynamicObject();
607 metadata->setProperty(presetNameField, var(presetName));
608
609 Array<var> tagsArray;
610 for (auto value : tags)
611 tagsArray.add(value);
612 metadata->setProperty(tagsNameField, tagsArray);
613
614 Array<var> embeddingsArray;
615 for (auto value : embeddings)
616 embeddingsArray.add(value);
617 metadata->setProperty(embeddingsNameField, embeddingsArray);
618
619 rows.add(metadata);
620 metadata = nullptr;
621 }
622 dataset->setProperty("PresetsTagEmbeddings", rows);
623
624 FileOutputStream stream(file);
625 if (stream.openedOk())
626 {
627 // overwrite an existing file
628 stream.setPosition(0);
629 stream.truncate();
630 JSON::writeToStream(stream, dataset);
631 return true;
632 }
633
634 return false;
635}
636
638{
639 if (file.existsAsFile() == false)
640 {
641 return false;
642 }
643
644 clear();
645
646 var parsedJson;
647 const Result jsonParseResult = JSON::parse(file.loadFileAsString(), parsedJson);
648
649 if (jsonParseResult.failed())
650 {
651 jassertfalse;
652 return false;
653 }
654
655 var presetTagEmbeddings = parsedJson.getProperty(Identifier("PresetsTagEmbeddings"), 0);
656
657 const auto resultSize = presetTagEmbeddings.size();
658 for (int i = 0; i < resultSize; ++i)
659 {
660 auto presetName = presetTagEmbeddings[i].getProperty(Identifier(presetNameField), 0).toString();
661 var tagsArray = presetTagEmbeddings[i].getProperty(Identifier(tagsNameField), 0);
662 var embeddingsArray = presetTagEmbeddings[i].getProperty(Identifier(embeddingsNameField), 0);
663
664 StringArray tags;
665 for (int j = 0; j < tagsArray.size(); ++j)
666 tags.add(tagsArray[j]);
667
668 std::vector<float> embeddings;
669 for (int j = 0; j < embeddingsArray.size(); ++j)
670 embeddings.push_back(embeddingsArray[j]);
671
672 m_kNN.addDatasetItem(embeddings);
673
674 Data data(tags, embeddings);
675
676 m_presetData.insert(std::make_pair(presetName, data));
677 m_presetNames.push_back(presetName);
678 }
679
681
682 return true;
683}
684
685bool TextToPreset::appendDataToEmbeddingsFile(/*CriticalSection& cs*/)
686{
687 // clear kNN it will have its indices rebuild after all data is merged
688 m_kNN.clear();
689 // load existing embedings
690 auto embeddingsFile = createFilePath(embeddingsFileName);
691
692 std::map<String, Data> presetData;
693 std::vector<String> presetNames;
694
695 if (embeddingsFile.existsAsFile())
696 {
697 auto json = JSON::parse(embeddingsFile);
698 var result = json.getProperty(Identifier("PresetsTagEmbeddings"), 0);
699 for (int i = 0; i < result.size(); ++i)
700 {
701 auto presetName = result[i].getProperty(Identifier(presetNameField), 0).toString();
702 var tagsArray = result[i].getProperty(Identifier(tagsNameField), 0);
703 var embeddingsArray = result[i].getProperty(Identifier(embeddingsNameField), 0);
704
705 StringArray tags;
706 for (int j = 0; j < tagsArray.size(); ++j)
707 {
708 tags.add(tagsArray[j]);
709 }
710
711 std::vector<float> embeddings;
712 for (int j = 0; j < embeddingsArray.size(); ++j)
713 {
714 embeddings.push_back(embeddingsArray[j]);
715 }
716
717 Data data(tags, embeddings);
718 presetData[presetName] = data;
719 presetNames.push_back(presetName);
720 }
721 }
722 else
723 {
724 embeddingsFile.create();
725 }
726
727 // append new ones to the data
728 for (const auto& element : m_presetData)
729 {
730 const auto presetName = element.first;
731 const auto tags = element.second.tags;
732 const auto embeddings = element.second.embeddings;
733 Data data(tags, embeddings);
734 presetData[presetName] = data;
735 presetNames.push_back(presetName);
736 }
737
738 // write the new dict to the embeddings file
739 DynamicObject* dataset = new DynamicObject();
740 Array<var> rows;
741
742 for (const auto& element : presetData)
743 {
744 const auto presetName = element.first;
745 const auto tags = element.second.tags;
746 const auto embeddings = element.second.embeddings;
747
748 DynamicObject* metadata = new DynamicObject();
749 metadata->setProperty(presetNameField, var(presetName));
750
751 Array<var> tagsArray;
752 for (auto value : tags)
753 tagsArray.add(value);
754 metadata->setProperty(tagsNameField, tagsArray);
755
756 Array<var> embeddingsArray;
757 for (auto value : embeddings)
758 embeddingsArray.add(value);
759 metadata->setProperty(embeddingsNameField, embeddingsArray);
760
761 rows.add(metadata);
762 metadata = nullptr;
763 }
764 dataset->setProperty("PresetsTagEmbeddings", rows);
765
766 // need to have all data loaded and updated in order to perform search
767 m_presetData = presetData;
768 m_presetNames = presetNames;
769
770 TemporaryFile tempEmbeddingsFile(embeddingsFile, TemporaryFile::useHiddenFile);
771 if (auto stream = tempEmbeddingsFile.getFile().createOutputStream())
772 {
773 // overwrite an existing file
774 stream->setPosition(0);
775 stream->truncate();
776 JSON::writeToStream(*stream, dataset);
777 stream->flush();
778
779 // close the output stream, so we can switch out the temp file.
780 stream.reset();
781
782 const bool success = tempEmbeddingsFile.overwriteTargetFileWithTemporary();
783
784 if (!success)
785 {
786 // Failed to overwrite!
787 jassertfalse;
788 return false;
789 }
790 }
791 else
792 {
793 return false;
794 }
795
796 readFromFile(embeddingsFile);
797
798 return true;
799}
800
802{
803 ScopedLock sl(m_cs);
804 // load tags/embeddings if available
805 auto presetTagEmbeddingsPath = createFilePath(embeddingsFileName);
806 if (presetTagEmbeddingsPath.existsAsFile())
807 {
808 // clear previously stored data
809 readFromFile(presetTagEmbeddingsPath);
810
811 // signal to stop the calling thread if it is the Analysis Thread
812 Thread* currentThread = getCurrentThread();
813 if (currentThread != nullptr)
814 {
815 if (currentThread->getThreadName() == ttpThreadName)
816 {
817 signalThreadShouldExit();
818 }
819 }
820 }
821}
822
823StringArray TextToPreset::performSearch(String query)
824{
825 if (m_modelFileAvailable == false)
826 {
827 return StringArray();
828 }
829
830 // get search term
831 String searchTerm = query;
832
833 StringArray presetRecommendations;
834 bool search = !isThreadRunning();
835 if (search)
836 {
837 // TODO: investigate what should be the maximum and if this exceeds the available presets number is a crash risk
838 int maxNumResults = 10;
839 presetRecommendations = findClosestPresets(searchTerm, maxNumResults);
840 }
841 return presetRecommendations;
842}
843
844DirectoryWatcher::DirectoryWatcher(const File& dirToWatch) : m_directory(dirToWatch)
845{
846 thread.addTimeSliceClient(this);
847 thread.startThread();
848}
849
851{
852 // Signal thread to exit first so removeTimeSliceClient can return asap.
853 thread.signalThreadShouldExit();
854 thread.notify();
855
856 // Calling stopThread before removeTimeSliceClient can result in a deadlock if the timeout expires and the thread
857 // is killed whilst it's still executing. This is because removeTimeSliceClient doesn't return until the thread
858 // finishes whatever it's doing. If it's killed, the lock is never released and the call will never return. By
859 // signalling the thread to exit first, it should return at the next possible opportunity.
860 thread.removeTimeSliceClient(this);
861
862 bool threadExitedSafely = thread.stopThread(2000);
863 if (!threadExitedSafely)
864 {
865 jassertfalse;
866 }
867}
868
870{
871 // scan and load all preset files to a file array
873
874 // load embeddings file and load all preset file names into a StringArray
876
877 if (file.existsAsFile())
878 {
879 auto json = JSON::parse(file);
880 var result = json.getProperty(Identifier("PresetsTagEmbeddings"), 0);
881
882 for (int i = 0; i < result.size(); ++i)
883 {
884 if (thread.threadShouldExit())
885 {
886 return false;
887 }
888
889 auto presetName = result[i].getProperty(Identifier(presetNameField), 0).toString();
890 m_presetsWithEmbeddings.add(presetName);
891 }
892 }
893
894 if (thread.threadShouldExit())
895 {
896 return false;
897 }
898
899 // for every preset file loaded, check if there is available embeddings
900 // if not add it to an array that will be processed by TTP module
901 for (File& file : m_presetFilesArray)
902 {
903 if (thread.threadShouldExit())
904 {
905 return false;
906 }
907
908 // file is new and has no embeddings, so ..
909 String filenameToCheck = file.getFileName();
910
911 if (m_presetsWithEmbeddings.contains(filenameToCheck) == false && file.getFileExtension() == ".ksp")
912 {
913 m_presetFilesToProcess.add(file);
914 }
915 }
916
917 if (thread.threadShouldExit())
918 {
919 return false;
920 }
921
922 // calculate embeddings for new presets
923 const auto fileProcessingSuccessful = m_textToPresetSharedInstance.m_textToPreset->processFileList(m_presetFilesToProcess);
924
925 return fileProcessingSuccessful;
926}
927
928void DirectoryWatcher::getAllPresetFiles(const File& directory, Array<File>& fileArray)
929{
930 DirectoryIterator iter(directory, true, "*", File::findFilesAndDirectories);
931 while (iter.next())
932 {
933 auto file = iter.getFile();
934 if (file.isDirectory())
935 {
936 getAllPresetFiles(file, fileArray);
937 }
938 else
939 {
940 fileArray.add(file);
941 }
942 }
943}
944
946{
947 String dirModificationDate = m_directory.getLastModificationTime().toString(true, true);
948 String lastRecordedModificationDate;
949
950 File textEmbeddingsStatusFile = m_textToPresetSharedInstance.m_textToPreset->createFilePath(ttpStatusFileName);
951
952 File ggmlDataFile = File(utils::StringsIntoPath(AssetManager::getPluginDirectory().getFullPathName(),
953 "ttpResources", modelFileName));
954
955 if (ggmlDataFile.existsAsFile() == false || thread.threadShouldExit())
956 {
957 return 5000;
958 }
959
960 // trigger ttp update if there is no status file
961 if (textEmbeddingsStatusFile.exists() == false)
962 {
963 textEmbeddingsStatusFile.create();
964
965 m_dirModificationDate = dirModificationDate;
966 bool success = handleContentUpdate();
967
968 if (success)
969 {
970 DynamicObject* ttpStatus = new DynamicObject();
971 var ttpStatusObj(ttpStatus);
972 ttpStatus->setProperty(ttpStatusDateProperty, var(m_dirModificationDate));
973 FileOutputStream stream(textEmbeddingsStatusFile);
974 if (stream.openedOk())
975 {
976 stream.setPosition(0);
977 stream.truncate();
978 JSON::writeToStream(stream, ttpStatus);
979 }
980 }
981 else
982 {
983 DBG("TTP embeddings update aborted");
984 textEmbeddingsStatusFile.deleteFile();
985 }
986 }
987 else
988 {
989 // parse ttp status file
990 auto ttpStatusJSON = JSON::parse(textEmbeddingsStatusFile);
991
992 // get date modified
993 m_dirModificationDate = ttpStatusJSON.getProperty(Identifier(ttpStatusDateProperty), String()).toString();
994
995 // get embeddings file
996 File embeddingsFile = m_textToPresetSharedInstance.m_textToPreset->createFilePath(embeddingsFileName);
997
998 // if it differs or if embeddings file does not exist (deleted probably) trigger ttp update
999 if ((dirModificationDate != m_dirModificationDate || embeddingsFile.exists() == false) &&
1000 !thread.threadShouldExit())
1001 {
1002
1003 m_dirModificationDate = dirModificationDate;
1004 bool success = handleContentUpdate();
1005 if (success)
1006 {
1007 DynamicObject* ttpStatus = new DynamicObject();
1008 ttpStatus->setProperty(ttpStatusDateProperty, var(m_dirModificationDate));
1009 FileOutputStream stream(textEmbeddingsStatusFile);
1010 if (stream.openedOk())
1011 {
1012 stream.setPosition(0);
1013 stream.truncate();
1014 JSON::writeToStream(stream, ttpStatus);
1015 }
1016 }
1017 }
1018 }
1019 return 5000;
1020}
1021} // namespace krotos
static File getPluginDirectory()
Definition AssetManager.cpp:392
static File convertFilePath(const String &)
Definition AssetManager.cpp:604
static String convertFilePathString(const String &)
Definition AssetManager.cpp:583
static File getAssetDirectory()
Definition AssetManager.cpp:383
static String readFactorySamplesPath()
Definition AssetManager.cpp:112
int useTimeSlice() override
timeslice thread checks for modufication dir date and handles it accordingly
Definition TextToPreset.cpp:945
File m_directory
Definition TextToPreset.h:250
bool handleContentUpdate()
handles content update and triggers ttp embeddings calculation accordingly
Definition TextToPreset.cpp:869
DirectoryWatcher(const File &dirToWatch)
Definition TextToPreset.cpp:844
void getAllPresetFiles(const File &directory, Array< File > &fileArray)
gets all presets under a directory recursivelly
Definition TextToPreset.cpp:928
String m_dirModificationDate
Definition TextToPreset.h:247
Array< File > m_presetFilesToProcess
Definition TextToPreset.h:262
~DirectoryWatcher() override
Definition TextToPreset.cpp:850
TextToPresetShared m_textToPresetSharedInstance
Definition TextToPreset.h:253
TimeSliceThread thread
Definition TextToPreset.h:241
StringArray m_presetsWithEmbeddings
Definition TextToPreset.h:259
Array< File > m_presetFilesArray
Definition TextToPreset.h:256
void buildIndex()
Definition KDTreeND.cpp:6
std::vector< std::size_t > knnQuery(const std::vector< float > &x, std::size_t k) const
Definition KDTreeND.cpp:18
void addDatasetItem(const std::vector< float > &x)
Definition KDTreeND.cpp:4
void clear()
Definition KDTreeND.h:37
static ValueTree getValueTreeFromFile(const File &presetFile)
Definition PresetManager.cpp:154
std::vector< float > encode(std::string sentence) const
Definition SentenceTransformer.cpp:49
bool calculateEmbeddingsForPresetFile(File presetFile)
create text embeddings file for the given preset file
Definition TextToPreset.cpp:74
unsigned char m_data[BUFFER_SIZE]
Definition TextToPreset.h:206
String assetPathsDelimiter
Definition TextToPreset.h:182
~TextToPreset()
Definition TextToPreset.cpp:22
bool processFileList(Array< File > &filesToProcess)
processes a file list to create text embeddings
Definition TextToPreset.cpp:41
KDTreeND m_kNN
Definition TextToPreset.h:197
StringArray m_sampleFilenames
Definition TextToPreset.h:157
bool m_modelFileAvailable
Definition TextToPreset.h:209
StringArray m_samplePaths
Definition TextToPreset.h:154
TextToPreset()
Definition TextToPreset.cpp:9
File m_presetFile
Definition TextToPreset.h:160
StringArray m_presetTags
Definition TextToPreset.h:166
StringArray excludeTerms
Definition TextToPreset.h:175
SentenceTransformer m_sentenceTransformer
Definition TextToPreset.h:191
StringArray combineSearch(const String &searchTerm, const StringArray &classicResults, const StringArray &ttpResults, std::map< String, std::vector< float > > &embeddingsCache, const size_t &maxResults=25)
combine multiple StringArrays, megre and rerank the items based on cosine similarity
Definition TextToPreset.cpp:470
void searchForTagAndProperty(ValueTree &root, Identifier tag, Identifier property)
this will recursivelly search in a preset ValueTree structure for a given idendtifier and property na...
Definition TextToPreset.cpp:426
std::vector< float > destringify(String stringWithNumbers)
destrignifies embeddings
Definition TextToPreset.cpp:576
bool readFromFile(File file)
load embeddings from file
Definition TextToPreset.cpp:637
String delimiters
Definition TextToPreset.h:188
void getPresetAssetNames(ValueTree &root)
will get preset asset paths and names to the m_samplePaths and m_sampleFilenames arrays
Definition TextToPreset.cpp:421
std::vector< String > m_presetNames
Definition TextToPreset.h:200
void clear()
clears internal data
Definition TextToPreset.cpp:32
void loadEmbeddings()
will load embeddings data from file to all modules that need it
Definition TextToPreset.cpp:801
StringArray performSearch(String query)
performs search based on text embeddings and KNN
Definition TextToPreset.cpp:823
float computeSimilarity(const std::vector< float > &queryEmbedding, const std::vector< float > &resultEmbedding) const
compute cosine similarity based of result to query
Definition TextToPreset.cpp:536
String m_presetName
Definition TextToPreset.h:163
std::map< String, Data > m_presetData
Definition TextToPreset.h:169
std::map< std::string, std::string > m_presetNameAndPresetPathMap
Definition TextToPreset.h:172
StringArray m_treatedAssetPaths
Definition TextToPreset.h:185
String stringify(std::vector< T > numbers)
Definition TextToPreset.cpp:566
SharedResourcePointer< UniversalCategorySystem > m_UCS
Definition TextToPreset.h:194
bool appendDataToEmbeddingsFile()
appends data to the main embeddings file
Definition TextToPreset.cpp:685
bool assetNamesToTags(bool eliminateDuplicates)
Splits the asset names, in respect to the excluded terms, and pushes the remaining terms into a new s...
Definition TextToPreset.cpp:104
void run() override
Definition TextToPreset.cpp:24
File createFilePath(String filename)
creates the path for the embeddings file
Definition TextToPreset.cpp:590
bool saveToFile(File file)
saves embeddings to file
Definition TextToPreset.cpp:595
void getSampleMetadata(const std::string &input, unsigned char *byteArray)
Definition TextToPreset.h:93
CriticalSection m_cs
Definition TextToPreset.h:203
StringArray findClosestPresets(String searchTerm, int maxNumPresetsToFind) const
returns a String array that contains K closest presets,
Definition TextToPreset.cpp:548
SharedResourcePointer< TextToPreset > m_textToPreset
Definition TextToPreset.h:216
String StringsIntoPath(Args... args)
Joins multiple string arguments into a path string.
Definition helpers.h:25
Definition AirAbsorptionFilter.cpp:2
constexpr size_t IV_SIZE
Definition TextToPreset.h:21
constexpr char tagsNameField[]
Definition TextToPreset.h:16
constexpr char ttpStatusDateProperty[]
Definition TextToPreset.h:11
const String kafFileExtension("kaf")
constexpr char ttpNumericalMetadata[]
Definition TextToPreset.h:12
constexpr char embeddingsNameField[]
Definition TextToPreset.h:15
constexpr char wavMetadataFieldInFile[]
Definition TextToPreset.h:13
constexpr char modelFileName[]
Definition SentenceTransformer.h:7
constexpr char ttpStatusFileName[]
Definition TextToPreset.h:10
constexpr char presetNameField[]
Definition TextToPreset.h:14
static const String ttpThreadName
Definition TextToPreset.h:17
constexpr char embeddingsFileName[]
Definition TextToPreset.h:9
constexpr size_t BUFFER_SIZE
Definition TextToPreset.h:20
Definition TextToPreset.h:144