LIB: support enabled attribute for inst encoder config
[spectmorph.git] / lib / sminstenccache.cc
1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2
3 #include "sminstenccache.hh"
4 #include "smbinbuffer.hh"
5 #include "sminstencoder.hh"
6 #include "smmmapin.hh"
7 #include "smmemout.hh"
8
9 #include <mutex>
10
11 #include <assert.h>
12
13 using namespace SpectMorph;
14
15 using std::string;
16 using std::vector;
17 using std::map;
18
19 static string
20 tmpfile (const string& filename)
21 {
22   return sm_get_user_dir (USER_DIR_DATA) + "/" + filename;
23 }
24
25 InstEncCache::InstEncCache()
26 {
27 }
28
29 InstEncCache*
30 InstEncCache::the()
31 {
32   static InstEncCache *instance = NULL;
33   if (!instance)
34     instance = new InstEncCache;
35
36   return instance;
37 }
38
39 void
40 InstEncCache::cache_save (const string& key)
41 {
42   const CacheData& cache_data = cache[key];
43
44   BinBuffer buffer;
45
46   buffer.write_start ("SpectMorphCache");
47   buffer.write_string (cache_data.version.c_str());
48   buffer.write_int (cache_data.data.size());
49   buffer.write_string (sha1_hash (&cache_data.data[0], cache_data.data.size()).c_str());
50   buffer.write_end();
51
52   string out_filename = tmpfile (key);
53   FILE *outf = fopen (out_filename.c_str(), "wb");
54   if (outf)
55     {
56       for (auto ch : buffer.to_string())
57         fputc (ch, outf);
58       fputc (0, outf);
59       for (auto ch : cache_data.data)
60         fputc (ch, outf);
61       fclose (outf);
62     }
63 }
64
65 void
66 InstEncCache::cache_try_load (const string& cache_key, const string& need_version)
67 {
68   GenericIn *in_file = GenericIn::open (tmpfile (cache_key));
69
70   if (!in_file)  // no cache entry
71     return;
72
73   // read header (till zero char)
74   string header_str;
75   int ch;
76   while ((ch = in_file->get_byte()) > 0)
77     header_str += char (ch);
78
79   BinBuffer buffer;
80   buffer.from_string (header_str);
81
82   string type       = buffer.read_start_inplace();
83   string version    = buffer.read_string_inplace();
84   int    data_size  = buffer.read_int();
85   string data_hash  = buffer.read_string_inplace();
86
87   if (version == need_version)
88     {
89       vector<unsigned char> data (data_size);
90       if (in_file->read (&data[0], data.size()) == data_size)
91         {
92           string load_data_hash = sha1_hash (&data[0], data.size());
93           if (load_data_hash == data_hash)
94             {
95               cache[cache_key].version = version;
96               cache[cache_key].data    = std::move (data);
97             }
98         }
99     }
100   delete in_file;
101 }
102
103 static string
104 mk_version (const string& wav_data_hash, int midi_note, int iclipstart, int iclipend, Instrument::EncoderConfig& cfg)
105 {
106   /* create one single string that lists all the dependencies for the cache entry;
107    * hash it to get a compact representation of the "version"
108    */
109   string depends;
110
111   depends += wav_data_hash + "\n";
112   depends += string_printf ("%d\n", midi_note);
113   depends += string_printf ("%d\n", iclipstart);
114   depends += string_printf ("%d\n", iclipend);
115   if (cfg.enabled)
116     {
117       for (auto entry : cfg.entries)
118         depends += entry.param + "=" + entry.value + "\n";
119     }
120
121   return sha1_hash (depends);
122 }
123
124 Audio *
125 InstEncCache::encode (const string& inst_name, const WavData& wav_data, const string& wav_data_hash, int midi_note, int iclipstart, int iclipend, Instrument::EncoderConfig& cfg)
126 {
127   std::lock_guard<std::mutex> lg (cache_mutex); // more optimal range possible
128
129   string cache_key = string_printf ("%s_%d", inst_name.c_str(), midi_note);
130
131   string version = mk_version (wav_data_hash, midi_note, iclipstart, iclipend, cfg);
132
133   if (cache[cache_key].version != version)
134     {
135       cache_try_load (cache_key, version);
136     }
137   if (cache[cache_key].version == version) // cache hit (in memory)
138     {
139       vector<unsigned char>& data = cache[cache_key].data;
140
141       GenericIn *in = MMapIn::open_mem (&data[0], &data[data.size()]);
142       Audio     *audio = new Audio;
143       bool       load_ok = (audio->load (in) == Error::NONE);
144
145       delete in;
146
147       if (load_ok)
148         return audio;
149
150       delete audio;
151       return nullptr;
152     }
153
154   /* clip sample */
155   vector<float> clipped_samples = wav_data.samples();
156
157   /* sanity checks for clipping boundaries */
158   iclipend   = sm_bound<int> (0, iclipend,  clipped_samples.size());
159   iclipstart = sm_bound<int> (0, iclipstart, iclipend);
160
161   /* do the clipping */
162   clipped_samples.erase (clipped_samples.begin() + iclipend, clipped_samples.end());
163   clipped_samples.erase (clipped_samples.begin(), clipped_samples.begin() + iclipstart);
164
165   WavData wav_data_clipped (clipped_samples, 1, wav_data.mix_freq(), wav_data.bit_depth());
166
167   InstEncoder enc;
168   Audio *audio = enc.encode (wav_data_clipped, midi_note, cfg);
169
170   vector<unsigned char> data;
171   MemOut                audio_mem_out (&data);
172
173   audio->save (&audio_mem_out);
174
175   cache[cache_key].version = version;
176   cache[cache_key].data    = data;
177
178   cache_save (cache_key);
179
180   return audio;
181 }
182
183 void
184 InstEncCache::clear()
185 {
186   std::lock_guard<std::mutex> lg (cache_mutex);
187
188   cache.clear();
189 }