tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ProjectSearchIndex.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
15{
16 juce::String word;
18
19 IndexedWord (juce::InputStream& in) : word (in.readString())
20 {
21 auto numIDs = in.readShort();
22 ids.resize ((size_t) numIDs);
23 in.read (ids.data(), (int) sizeof (int) * numIDs);
24 }
25
26 IndexedWord (const juce::String& w, int id) : word (w)
27 {
28 ids.push_back (id);
29 }
30
31 void writeToStream (juce::OutputStream& out)
32 {
33 out.writeString (word);
34 out.writeShort ((short) ids.size());
35 out.write (ids.data(), ids.size() * sizeof (int));
36 }
37
38 void addID (int id)
39 {
40 for (auto i : ids)
41 if (i == id)
42 return;
43
44 if (ids.size() > 32766)
45 return;
46
47 ids.push_back (id);
48 }
49
51};
52
53//==============================================================================
54ProjectSearchIndex::ProjectSearchIndex (Project& p) : project (p)
55{
56}
57
58static bool isNoiseWord (const juce::String& word)
59{
60 return word == "a"
61 || word == "the"
62 || word == "of"
63 || word == "to"
64 || word == "for"
65 || word == "from"
66 || word == "and"
67 || word == "in"
68 || word == "on"
69 || word == "at"
70 || word == "by"
71 || word == "or"
72 || word == "some"
73 || word == "into"
74 || word == "onto"
75 || word == "as"
76 || word == "it"
77 || word == "is"
78 || word == "few"
79 || word == "are"
80 || word == "if"
81 || word == "like"
82 || word == "then"
83 || word == "that"
84 || word == "this"
85 || word == "not"
86 || word == "but";
87}
88
89void ProjectSearchIndex::addClip (const ProjectItem::Ptr& item)
90{
91 if (item != nullptr)
92 {
93 for (auto newWord : item->getSearchTokens())
94 {
95 auto word = newWord.toLowerCase().retainCharacters ("abcdefghijklmnopqrstuvwxyz0123456789");
96
97 if (! (word.isEmpty() || isNoiseWord (word)))
98 {
99 if (auto w = findWordMatch (word))
100 {
101 w->addID (item->getID().getItemID());
102 }
103 else
104 {
105 index.add (new IndexedWord (word, item->getID().getItemID()));
106
107 std::sort (index.begin(), index.end(),
108 [] (IndexedWord* a, IndexedWord* b) { return a->word < b->word; });
109 }
110 }
111 }
112 }
113}
114
115void ProjectSearchIndex::writeToStream (juce::OutputStream& out)
116{
117 out.writeInt (index.size());
118
119 for (auto&& i : index)
120 i->writeToStream (out);
121}
122
123void ProjectSearchIndex::readFromStream (juce::InputStream& in)
124{
125 index.clear();
126
127 for (int i = in.readInt(); --i >= 0;)
128 index.add (new IndexedWord (in));
129}
130
131IndexedWord* ProjectSearchIndex::findWordMatch (const juce::String& word) const
132{
133 int start = 0;
134 int end = index.size();
135
136 for (;;)
137 {
138 if (start >= end)
139 break;
140
141 auto w = index.getUnchecked (start);
142
143 if (word == w->word)
144 return w;
145
146 const int halfway = (start + end) >> 1;
147
148 if (halfway == start)
149 break;
150
151 if (word.compare (index[halfway]->word) >= 0)
152 start = halfway;
153 else
154 end = halfway;
155 }
156
157 return {};
158}
159
160void ProjectSearchIndex::findMatches (SearchOperation& search, juce::Array<ProjectItemID>& results)
161{
162 for (auto& res : search.getMatches (*this))
163 results.add (ProjectItemID (res, project.getProjectID()));
164}
165
166
167//==============================================================================
168SearchOperation::SearchOperation (SearchOperation* o1, SearchOperation* o2) : in1 (o1), in2 (o2)
169{
170}
171
172SearchOperation::~SearchOperation()
173{
174}
175
176//==============================================================================
178{
179 WordMatchOperation (const juce::String& w) : word (w.toLowerCase().trim()) {}
180
181 juce::Array<int> getMatches (ProjectSearchIndex& psi) override
182 {
183 juce::Array<int> found;
184
185 if (auto w = psi.findWordMatch (word))
186 for (auto id : w->ids)
187 found.add (id);
188
189 return found;
190 }
191
192 juce::String word;
193};
194
196{
198
199 juce::Array<int> getMatches (ProjectSearchIndex& psi) override
200 {
201 auto i1 = in1->getMatches (psi);
202 auto i2 = in2->getMatches (psi);
203
204 if (i1.isEmpty())
205 return i2;
206
207 if (i2.isEmpty())
208 return i1;
209
210 for (int i = i2.size(); --i >= 0;)
211 i1.addIfNotAlreadyThere (i2[i]);
212
213 return i1;
214 }
215};
216
218{
220
221 juce::Array<int> getMatches (ProjectSearchIndex& psi) override
222 {
223 auto i1 = in1->getMatches (psi);
224
225 if (i1.isEmpty())
226 return i1;
227
228 auto i2 = in2->getMatches (psi);
229
230 if (i2.isEmpty())
231 return i2;
232
233 i1.removeValuesNotIn (i2);
234 return i1;
235 }
236};
237
239{
240 NotOperation (SearchOperation* in) : SearchOperation (in, nullptr) {}
241
242 juce::Array<int> getMatches (ProjectSearchIndex& psi)
243 {
245
246 i1 = psi.project.getAllItemIDs();
247 i1.removeValuesIn (in1->getMatches (psi));
248
249 return i1;
250 }
251};
252
254{
255 juce::Array<int> getMatches (ProjectSearchIndex&) override
256 {
257 return {};
258 }
259};
260
261//==============================================================================
262inline SearchOperation* createPluralOptions (juce::String s)
263{
265
266 if (s.length() > 2 && ! (s.endsWith ("a") || s.endsWith ("i") || s.endsWith ("u")))
267 {
268 if (s.endsWith ("e") && ! (s.endsWith ("ee") || s.endsWith ("ie")))
269 {
270 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (1) + "ing"));
271 }
272 else
273 {
274 if (s.endsWith ("ss") || ! s.endsWith ("s"))
275 {
276 c = new OrOperation (c, new WordMatchOperation (s + "ing"));
277 }
278 else
279 {
280 if (s.endsWith ("es"))
281 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (2) + "ing"));
282 else
283 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (1) + "ing"));
284 }
285 }
286 }
287
288 if (s.endsWith ("ing") && s.length() > 4)
289 {
290 s = s.dropLastCharacters (3);
291 c = new OrOperation (c, new WordMatchOperation (s));
292 c = new OrOperation (c, new WordMatchOperation (s + "e"));
293 }
294
295 if (s.endsWith ("s"))
296 {
297 if (s.endsWith ("shes") || s.endsWith ("ches"))
298 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (2)));
299 else if (s.endsWith ("ses"))
300 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (2)));
301 else if (s.endsWith ("ss"))
302 return c;
303 else if (s.endsWith ("ies"))
304 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (3) + "y"));
305 else if (s.endsWith ("ves"))
306 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (3) + "f"));
307 else
308 c = new OrOperation (new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (1))),
309 new WordMatchOperation (s + "es"));
310 }
311 else if (s.endsWith ("f"))
312 {
313 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (1) + "ves"));
314 }
315 else if (s.endsWith ("y"))
316 {
317 c = new OrOperation (c, new WordMatchOperation (s.dropLastCharacters (1) + "ies"));
318 }
319 else if (s.endsWith ("sh") || s.endsWith ("ch"))
320 {
321 c = new OrOperation (c, new WordMatchOperation (s + "es"));
322 }
323 else
324 {
325 c = new OrOperation (c, new WordMatchOperation (s + "s"));
326 }
327
328 return c;
329}
330
331inline SearchOperation* multipleWordMatch (const juce::String& s)
332{
334 a.addTokens (s, false);
335
336 SearchOperation* c = nullptr;
337
338 for (int i = a.size(); --i >= 0;)
339 {
340 if (c == nullptr)
341 c = new WordMatchOperation (a[i]);
342 else
343 c = new AndOperation (c, new WordMatchOperation (a[i]));
344 }
345
346 return c;
347}
348
349inline SearchOperation* createCondition (const juce::StringArray& words, int start, int length)
350{
351 if (length == 0)
352 return {};
353
354 if (length == 1)
355 {
356 if (words[start] == TRANS("All"))
357 return new NotOperation (new FalseOperation());
358
359 return createPluralOptions (words[start]);
360 }
361
362 if (length > 1 && words[start] == TRANS("Not"))
363 return new NotOperation (createCondition (words, start + 1, length - 1));
364
365 if (length > 2)
366 {
367 auto c = createCondition (words, start, 1);
368
369 if (words[start + 1] == TRANS("Or"))
370 return new OrOperation (c, createCondition (words, start + 2, length - 2));
371
372 if (words[start + 1] == TRANS("And"))
373 return new AndOperation (c, createCondition (words, start + 2, length - 2));
374
375 if (words[start + 1] == TRANS("But")
376 && words[start + 2] == TRANS("Not")
377 && length > 3)
378 return new AndOperation (c, new NotOperation (createCondition (words, start + 3, length - 3)));
379
380 if (words[start + 1] == TRANS("Not"))
381 return new AndOperation (c, new NotOperation (createCondition (words, start + 2, length - 2)));
382 }
383
384 return new AndOperation (createCondition (words, start, 1),
385 createCondition (words, start + 1, length - 1));
386}
387
388
390{
391 auto k = keywords.toLowerCase()
392 .replace ("-", " " + TRANS("Not") + " ")
393 .replace ("+", " " + TRANS("And") + " ")
394 .retainCharacters (juce::CharPointer_UTF8 ("abcdefghijklmnopqrstuvwxyz0123456789\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4\xc3\xa5\xc3\xa6\xc3\xa7\xc3\xa8\xc3\xa9\xc3\xaa\xc3\xab\xc3\xac\xc3\xad\xc3\xae\xc3\xaf\xc3\xb0\xc3\xb1\xc3\xb2\xc3\xb3\xc3\xb4\xc3\xb5\xc3\xb6\xc3\xb8\xc3\xb9\xc3\xba\xc3\xbb\xc3\xbc\xc3\xbd\xc3\xbf\xc3\x9f"))
395 .trim();
396
397 juce::StringArray words;
398 words.addTokens (k, false);
399 words.trim();
400 words.removeEmptyStrings();
401
402 for (int i = words.size(); --i >= 0;)
403 if (isNoiseWord (words[i])
404 && words[i] != TRANS("Not")
405 && words[i] != TRANS("Or")
406 && words[i] != TRANS("And"))
407 words.remove (i);
408
409 if (auto sc = createCondition (words, 0, words.size()))
410 return sc;
411
412 return new FalseOperation();
413}
414
415}} // namespace tracktion { inline namespace engine
void removeValuesIn(const OtherArrayType &otherArray)
void add(const ElementType &newElement)
virtual short readShort()
virtual int read(void *destBuffer, int maxBytesToRead)=0
virtual int readInt()
virtual String readString()
virtual bool write(const void *dataToWrite, size_t numberOfBytes)=0
virtual bool writeShort(short value)
virtual bool writeInt(int value)
virtual bool writeString(const String &text)
void removeEmptyStrings(bool removeWhitespaceStrings=true)
int size() const noexcept
void remove(int index)
int addTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
int length() const noexcept
String trim() const
String retainCharacters(StringRef charactersToRetain) const
String dropLastCharacters(int numberToDrop) const
String toLowerCase() const
String replace(StringRef stringToReplace, StringRef stringToInsertInstead, bool ignoreCase=false) const
bool endsWith(StringRef text) const noexcept
int compare(const String &other) const noexcept
T data(T... args)
T end(T... args)
#define TRANS(stringLiteral)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
SearchOperation * createSearchForKeywords(const juce::String &keywords)
Turns a keyword string into a search condition tree.
T push_back(T... args)
T resize(T... args)
T search(T... args)
T size(T... args)
T sort(T... args)