Krotos Modules 3
Loading...
Searching...
No Matches
KrotosSynthesiserSound.cpp
Go to the documentation of this file.
2
3#pragma once
4
5#include <fstream>
6
7namespace krotos
8{
10 {
12 // default sound that has no file attached to it, used when clearing the granular buffers.
13 m_name = "Default";
16
17 int length = 0;
18 m_sampleEngine.setSize(2, length + 4);
20 }
21
22 KrotosSampleOscillatorSound::KrotosSampleOscillatorSound(const String& soundName, AudioFormatReader& source,
23 int midiNoteForNormalPitch, float dawSampleRate)
24 {
25 m_sampleEngine.setSampleRate(dawSampleRate);
26 m_name = soundName;
27 m_sourceSampleRate = source.sampleRate;
28 m_midiRootNote = midiNoteForNormalPitch;
29 if (m_sourceSampleRate > 0 && source.lengthInSamples > 0)
30 {
31 m_sampleEngine.setSize(jmin<int>(2, static_cast<int>(source.numChannels)),
32 static_cast<int>(source.lengthInSamples) + 4);
33 source.read(&m_sampleEngine, 0, static_cast<int>(source.lengthInSamples) + 4, 0, true, true);
34 m_sampleEngine.normaliseTo(1.0f); // Normalise the sample data to -+ 1.0f
35 }
36 }
37
38 // SANTODO: Call this ?
39 void KrotosSampleOscillatorSound::Prepare(int midiNoteForNormalPitch, float dawSampleRate)
40 {
41 m_sampleEngine.setSampleRate(dawSampleRate);
42 m_midiRootNote = midiNoteForNormalPitch;
43 }
44
45 std::unique_ptr<MemoryBlock> KrotosSampleOscillatorSound::loadSample(const juce::File& path, int sampleCount)
46 {
47 // warning - this is obfuscated code for loading encrypted kaf files
48
49 // Create ifstream to read WAV file into decryption
50 std::ifstream inputFile(path.getFullPathName().toStdString(), std::ios::binary);
51 if (!inputFile)
52 jassertfalse;
53
54 // memory block pre-allocated to size of file
55 std::unique_ptr<juce::MemoryBlock> outputDataBlock = std::make_unique<juce::MemoryBlock>(path.getSize());
56
57 juce::MemoryOutputStream outputDataStream(*outputDataBlock, false);
58
59 constexpr size_t BUFFER_SIZE = 1024;
60 constexpr size_t IV_SIZE = 16;
61
62 std::vector<unsigned char> buffer(BUFFER_SIZE);
63 std::vector<unsigned char> plaintext(BUFFER_SIZE + EVP_MAX_BLOCK_LENGTH);
64
65 std::vector<unsigned char> iv(IV_SIZE);
66 inputFile.read(reinterpret_cast<char*>(iv.data()), IV_SIZE);
67
68 auto data = m_sampleData[sampleCount].data();
69
70 // Create cipher context
71 EVP_CIPHER_CTX* ctx;
72 if (!(ctx = EVP_CIPHER_CTX_new()))
73 jassertfalse;
74 // Initialise AES 256 CBC cipher
75 if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, data, iv.data()) != 1)
76 jassertfalse;
77
78 // EVP_CIPHER_CTX_set_padding(ctx, 0);
79
80 // Decrypt data
81 int len;
82 int totalBytesProcessed = 0;
83 while (inputFile.read(reinterpret_cast<char*>(buffer.data()), buffer.size()))
84 {
85 auto bytesRead = inputFile.gcount();
86
87 if (EVP_DecryptUpdate(ctx, plaintext.data(), &len, buffer.data(), static_cast<int>(bytesRead)) != 1)
88 jassertfalse;
89
90 outputDataStream.write(reinterpret_cast<char*>(plaintext.data()), len);
91
92 totalBytesProcessed += len;
93 // DBG("processed " << totalBytesProcessed);
94 }
95
96 // Handle remaining data
97 auto remainingBytes = inputFile.gcount();
98 if (remainingBytes > 0)
99 {
100 if (EVP_DecryptUpdate(ctx, plaintext.data(), &len, buffer.data(), static_cast<int>(remainingBytes)) != 1)
101 jassertfalse;
102
103 outputDataStream.write(reinterpret_cast<char*>(plaintext.data()), len);
104 }
105
106 totalBytesProcessed += len;
107
108 EVP_CIPHER_CTX_free(ctx);
109
110 outputDataStream.flush();
111 // Return a MemoryInputStream with the decrypted WAV data
112 return outputDataBlock;
113 }
114
116 {
117 m_sampleEngine.signalThreadShouldExit(); // signal the analysis thread to abort if it is running
118 m_sampleEngine.waitForThreadToExit(5000);
119
120 // Stop clients (eg CoreEngine, Reformer) using us - will block until clients finished
121 // No need, nor point, in calling setAccessible(true); when this method completes, since there will be no data
122 // to use anyway The sample load method will call setAccessible(true);
123 setAccessible(false);
124
125 // Safe now to modify data
126
127 // Clear m_sampleData if it is a vector or other container
128 m_sampleData.clear();
129
130 // Reset m_sampleEngine if needed
131 m_sampleEngine.clear();
133
134 // this is the memory freeing step
135 auto numChannels = m_sampleEngine.getNumChannels();
136 m_sampleEngine.setSize(numChannels, 0, false, true, false);
137 }
138
139 int KrotosSampleOscillatorSound::loadSounds(Array<File> inputPaths, bool /*getNearest*/)
140 {
141 // Stop clients (eg CoreEngine) using us - will block until clients finished
142 setAccessible(false);
143
144 AudioFormatManager formatManager;
145 formatManager.registerBasicFormats();
146
147 int numSuccessfulLoads{0};
148 int totalSampleLength{0};
149 int maxChannels{1};
150 int sampleCount{0};
151 int64 totalDataSize{0}; // Total data size for 2GB limit
152
153 // kaf load (obfuscated)
154 const auto pathCount = inputPaths.size();
155 std::vector<std::array<unsigned char, BUF_SIZE>> buffer(pathCount);
156 std::vector<String> cache(pathCount);
157
158 for (int i = 0; i < pathCount; i++)
159 {
160 cache.push_back(krotos::SamplerUtils::getSampleHash(inputPaths[i]));
161 SamplerUtils::verifySampleHash(inputPaths[i].getFullPathName(), inputPaths[i].getFileName());
162 SamplerUtils::getSampleMetadata(cache.back().toStdString(), buffer[i].data());
163 }
164 String checksum = juce::MD5((cache.front() + SamplerUtils::getInitData()).toUTF8()).toHexString();
165 auto metadataSum = juce::MD5((cache.front() + TEA).toUTF8()).toHexString();
166 if (metadataSum == juce::MD5((String(String(cache.front().toStdString()) + TEA)).toUTF8()).toHexString() &&
167 ((checksum == metadataSum) == false))
168 {
169 for (size_t i = 0, j = 0; i < BUF_SIZE * 2; i += 2, j++)
170 {
171 if (juce::MD5((String(String(cache.front().toStdString()) + TEA)).toUTF8()).toHexString() ==
172 metadataSum)
173 {
174 std::string byteStr = CRUMPETS.substr(i, 2);
175 unsigned int byteValue;
176 std::istringstream(byteStr) >> std::hex >> byteValue;
177 buffer[sampleCount][j] = static_cast<unsigned char>(byteValue);
178 }
179 }
180
181 if (SamplerUtils::verifySampleHash(checksum, juce::MD5((checksum + TEA).toUTF8()).toHexString()))
182 {
183 buffer.swap(m_sampleData);
184 }
185 }
186 else
187 jassertfalse;
188
189 juce::Array<File> audioSampleFiles;
190 std::queue<std::unique_ptr<AudioFormatReader>> readers;
191 std::vector<std::unique_ptr<MemoryBlock>> blocks;
192
193 // Lambda to check file limit and add to audioSampleFiles
194 auto addFile = [&](const File& f) {
195 if (audioSampleFiles.size() >= MAX_NUMBER_SAMPLES || totalDataSize + f.getSize() > MAX_DATA_SIZE)
196 {
197 DBG("Sample count limit or data size limit reached. Failed to add file: " << f.getFileName());
198 return false; // Failed to add file, memory overflow
199 }
200 if (f.hasFileExtension("wav;aif;aiff;flac;ogg;" + m_specificExtension))
201 {
202 audioSampleFiles.add(f);
203 totalDataSize += f.getSize(); // Update the total data size
204 return true;
205 }
206 DBG("Invalid audio file extension: " + f.getFileName());
207 return false; // Failed to add file, invalid file
208 };
209
210 // Here directories are parsed and File loading attempted
211 for (auto& file : inputPaths)
212 {
213 File directoryPath(file);
214
215 if (directoryPath.isDirectory())
216 {
217 // Recursively add files from directories
218 auto foundFiles = directoryPath.findChildFiles(
219 File::findFiles, true, formatManager.getWildcardForAllFormats() + ";*" + m_specificExtension);
220 for (auto& childFile : foundFiles)
221 {
222 if (!addFile(childFile))
223 {
224 break; // Break if limits are reached or invalid extension
225 }
226 }
227 }
228 else
229 {
230 // Add individual files after checking extensions
231 if (!addFile(directoryPath))
232 {
233 break; // Break if limits are reached or invalid extension
234 }
235 }
236 }
237
238 // This is a two pass load operation. Pass 1 calculates the size of the buffer and allocates it.
239 // Pass 2 loads the audio data.
240
241 // First pass:
242 // Loop through every sample path and add up the lengths of all the samples
243 for (auto& file : audioSampleFiles)
244 {
245 int sampleIndex = 0;
246 AudioFormatReader* readerPtr = nullptr;
247
248 if (file.hasFileExtension(m_specificExtension) && m_specificExtension == ".kaf")
249 {
250 auto audioData = loadSample(file, sampleIndex);
251
252 // Use ptr rather than copy
253 // Push the audio data pointer to a FIFO which remains in scope for the second pass
254 // The format reader will use this ptr for the read operation in the second pass
255 blocks.push_back(std::move(audioData));
256 auto& audioDataPtr = blocks.back();
257 auto audioDataStream = std::make_unique<MemoryInputStream>(*audioDataPtr, false);
258 readerPtr = formatManager.createReaderFor(std::move(audioDataStream));
259 }
260 else
261 {
262 readerPtr = formatManager.createReaderFor(file);
263 }
264
265 std::unique_ptr<juce::AudioFormatReader> source(readerPtr);
266
267 if (source.get() != nullptr)
268 {
269 totalSampleLength += static_cast<int>(source->lengthInSamples);
270 if (maxChannels < (int)source->numChannels)
271 {
272 maxChannels = (int)source->numChannels;
273 }
274 }
275
276 readers.push(std::move(source));
277 }
278
279 // Pre-size the sample engine so that it is big enough to hold all the samples
280 m_sampleEngine.setSize(maxChannels, (totalSampleLength + 4));
281 m_sampleEngine.clear();
283
284 // Loop through every sample path, reading the sample into the SampleEngine
285 int startPosition{0};
286 for (auto& file : audioSampleFiles)
287 {
288 auto reader = std::move(readers.front());
289
290 if (reader.get() != nullptr)
291 {
292 // Load the sample and create a new sound with it
293 m_name = file.getFileName();
294 m_sourceSampleRate = reader->sampleRate;
295 if (m_sourceSampleRate > 0 && reader->lengthInSamples > 0)
296 {
297 reader->read(&m_sampleEngine, startPosition, static_cast<int>(reader->lengthInSamples), 0, true,
298 true);
299
300 AudioSegment seg;
301 seg.path = file.getFullPathName();
302 seg.name = m_name;
303
304 // Set the segment start and length
305 seg.segmentStartPosition = startPosition;
306 seg.segmentLength = static_cast<int>(reader->lengthInSamples);
307
308 // Set selection to segment dimensions as a default - ie fully selected
309 seg.selectionStartPosition = startPosition;
311
312 seg.sampleRate = static_cast<int>(reader->sampleRate);
313 float magnitude = m_sampleEngine.getMagnitude(seg.selectionStartPosition, seg.selectionLength);
316 magnitude; // Calculate the factor required to scale this segment to -0.1 dB
317 m_sampleEngine.addAudioSegment(seg); // Every loaded audio file is added as a segment
318 startPosition += static_cast<int>(reader->lengthInSamples);
319 numSuccessfulLoads++;
320 }
321 }
322
323 readers.pop();
324 }
325
327
328 if (numSuccessfulLoads == 0)
329 {
330 // I couldn't load anything, set default
332 }
333
334 // Give our clients permission back
335 setAccessible(true);
336
337 m_listeners.call([this](Listener& l) { l.soundChanged(this); });
338
339 return numSuccessfulLoads; // Return the number of files we actually loaded
340 }
341
346
347 bool KrotosSampleOscillatorSound::appliesToNote(int /*midiNoteNumber*/) { return true; }
348 bool KrotosSampleOscillatorSound::appliesToChannel(int /*midiChannel*/) { return true; }
349} // namespace krotos
#define TEA
Definition SamplerUtils.h:8
#define CRUMPETS
Definition SamplerUtils.h:5
void setInterpolationType(InterpolationType newInterpolationType)
Definition KrotosAudioBuffer.cpp:246
void setSize(int newNumChannels, int numSamples, bool keepExistingContent=true, bool clearExtraSpace=true, bool avoidReallocating=true)
Definition KrotosAudioBuffer.cpp:25
void setSampleRate(float sampleRate)
Definition KrotosAudioBuffer.cpp:504
void normaliseTo(float scale)
Definition KrotosAudioBuffer.cpp:571
InterpolationType
Definition KrotosAudioBuffer.h:120
void setSourceSampleRate(float sampleRate)
Definition KrotosAudioBuffer.cpp:508
void clearAndFreeBuffer()
Definition KrotosSynthesiserSound.cpp:115
std::unique_ptr< MemoryBlock > loadSample(const juce::File &path, int sampleCount)
Definition KrotosSynthesiserSound.cpp:45
bool appliesToNote(int midiNoteNumber) override
Definition KrotosSynthesiserSound.cpp:347
KrotosSampleOscillatorSound()
Definition KrotosSynthesiserSound.cpp:9
static constexpr int MAX_NUMBER_SAMPLES
Definition KrotosSynthesiserSound.h:63
static constexpr size_t BUF_SIZE
Definition KrotosSynthesiserSound.h:65
const float DECIBELS_MINUS_0_1
Definition KrotosSynthesiserSound.h:75
int m_midiRootNote
Definition KrotosSynthesiserSound.h:58
void Prepare(int midiNoteForNormalPitch, float dawSampleRate)
Definition KrotosSynthesiserSound.cpp:39
bool appliesToChannel(int midiChannel) override
Definition KrotosSynthesiserSound.cpp:348
static constexpr int64 MAX_DATA_SIZE
Definition KrotosSynthesiserSound.h:62
void setInterpolationType(KrotosAudioBuffer::InterpolationType interpType)
Definition KrotosSynthesiserSound.cpp:342
String m_specificExtension
Definition KrotosSynthesiserSound.h:73
std::vector< std::array< unsigned char, BUF_SIZE > > m_sampleData
Definition KrotosSynthesiserSound.h:66
ListenerList< Listener > m_listeners
Definition KrotosSynthesiserSound.h:59
int loadSounds(juce::Array< File > audioSampleFiles, bool getNearest)
Definition KrotosSynthesiserSound.cpp:139
double m_sourceSampleRate
Definition KrotosSynthesiserSound.h:57
SampleEngine m_sampleEngine
Definition KrotosSynthesiserSound.h:56
String m_name
Definition KrotosSynthesiserSound.h:55
void setAccessible(bool isAccessible)
Called by the resource to grant or deny access to clients.
Definition ResourceLock.cpp:54
void clearAudioSegments()
Definition SampleEngine.cpp:93
void addAudioSegment(AudioSegment &seg)
Definition SampleEngine.cpp:94
static std::string getSampleHash(const File &path)
Definition SamplerUtils.h:20
static void getSampleMetadata(const std::string &input, unsigned char *byteArray)
Definition SamplerUtils.h:30
static const juce::String & getInitData()
Definition SamplerUtils.h:53
static bool verifySampleHash(const juce::String &hash, const juce::String &sample)
Definition SamplerUtils.h:44
Definition AirAbsorptionFilter.cpp:2
constexpr size_t IV_SIZE
Definition TextToPreset.h:21
constexpr size_t BUFFER_SIZE
Definition TextToPreset.h:20
A struct to describe an audio sample (as appears in the waveform view)
Definition SampleEngine.h:24
int segmentLength
Definition SampleEngine.h:31
float normalisationFactor
Definition SampleEngine.h:28
volatile int selectionStartPosition
Definition SampleEngine.h:39
String name
Definition SampleEngine.h:25
int segmentStartPosition
Definition SampleEngine.h:30
int sampleRate
Definition SampleEngine.h:27
volatile int selectionLength
Definition SampleEngine.h:40
String path
Definition SampleEngine.h:26
Definition KrotosSynthesiserSound.h:22
virtual void soundChanged(KrotosSampleOscillatorSound *sound)=0