02744dd63402db88c7630324b7d371ff00785301
[spectmorph.git] / lib / sminstrument.cc
1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2
3 #include "sminstrument.hh"
4 #include "smpugixml.hh"
5 #include "smzip.hh"
6
7 #include <map>
8 #include <memory>
9
10 using namespace SpectMorph;
11
12 using pugi::xml_document;
13 using pugi::xml_node;
14 using pugi::xml_writer;
15
16 using std::string;
17 using std::vector;
18 using std::map;
19
20 /* ------------- Sample::Shared -------------*/
21
22 // this class should never modify any data after construction
23 //  -> we can share it between different threads
24
25 Sample::Shared::Shared (const WavData& wav_data) :
26   m_wav_data (wav_data)
27 {
28   m_wav_data_hash = sha1_hash ((const guchar *) &wav_data.samples()[0], sizeof (float) * wav_data.samples().size());
29 }
30
31 string
32 Sample::Shared::wav_data_hash() const
33 {
34   return m_wav_data_hash;
35 }
36
37 const WavData&
38 SpectMorph::Sample::Shared::wav_data() const
39 {
40   return m_wav_data;
41 }
42
43 /* ------------- Sample -------------*/
44 Sample::Sample (Instrument *inst, const WavData& wav_data) :
45   instrument (inst),
46   m_shared (new Sample::Shared (wav_data))
47 {
48 }
49
50 void
51 Sample::set_marker (MarkerType marker_type, double value)
52 {
53   marker_map[marker_type] = value;
54   instrument->marker_changed();
55 }
56
57 double
58 Sample::get_marker (MarkerType marker_type) const
59 {
60   auto it = marker_map.find (marker_type);
61   if (it != marker_map.end())
62     return it->second;
63   return -1;
64 }
65
66 int
67 Sample::midi_note() const
68 {
69   return m_midi_note;
70 }
71
72 void
73 Sample::set_midi_note (int note)
74 {
75   m_midi_note = note;
76
77   instrument->update_order();
78 }
79
80 Sample::Loop
81 Sample::loop () const
82 {
83   return m_loop;
84 }
85
86 void
87 Sample::set_loop (Loop loop)
88 {
89   m_loop = loop;
90   instrument->marker_changed();
91 }
92
93 const WavData&
94 Sample::wav_data() const
95 {
96   return m_shared->wav_data();
97 }
98
99 string
100 Sample::wav_data_hash() const
101 {
102   return m_shared->wav_data_hash();
103 }
104
105 Sample::SharedP
106 Sample::shared() const
107 {
108   return m_shared;
109 }
110
111 /* ------------- Instrument -------------*/
112
113 Instrument::Instrument()
114 {
115 }
116
117 Sample *
118 Instrument::add_sample (const string& filename)
119 {
120   /* try loading file */
121   WavData wav_data;
122   if (!wav_data.load_mono (filename))
123     return nullptr;
124
125   /* new sample will be selected */
126   m_selected = samples.size();
127
128   Sample *sample = new Sample (this, wav_data);
129   samples.emplace_back (sample);
130   sample->filename  = filename;
131   sample->short_name = gen_short_name (filename);
132
133   sample->set_marker (MARKER_CLIP_START, 0.0 * 1000.0 * wav_data.samples().size() / wav_data.mix_freq());
134   sample->set_marker (MARKER_CLIP_END, 1.0 * 1000.0 * wav_data.samples().size() / wav_data.mix_freq());
135   sample->set_marker (MARKER_LOOP_START, 0.4 * 1000.0 * wav_data.samples().size() / wav_data.mix_freq());
136   sample->set_marker (MARKER_LOOP_END, 0.6 * 1000.0 * wav_data.samples().size() / wav_data.mix_freq());
137
138   signal_samples_changed();
139
140   return sample;
141 }
142
143 size_t
144 Instrument::size() const
145 {
146   return samples.size();
147 }
148
149 string
150 Instrument::name() const
151 {
152   return m_name;
153 }
154
155 Sample *
156 Instrument::sample (size_t n) const
157 {
158   if (n < samples.size())
159     return samples[n].get();
160   else
161     return nullptr;
162 }
163
164 int
165 Instrument::selected() const
166 {
167   return m_selected;
168 }
169
170 void
171 Instrument::set_selected (int sel)
172 {
173   m_selected = sel;
174
175   signal_samples_changed();
176 }
177
178 void
179 Instrument::load (const string& filename)
180 {
181   samples.clear();
182
183   char *basename = g_path_get_basename (filename.c_str());
184   m_name = basename;
185   g_free (basename);
186
187   xml_document doc;
188   doc.load_file (filename.c_str());
189   xml_node inst_node = doc.child ("instrument");
190   for (xml_node sample_node : inst_node.children ("sample"))
191     {
192       string filename = sample_node.attribute ("filename").value();
193       int midi_note = atoi (sample_node.attribute ("midi_note").value());
194       if (midi_note == 0) /* default */
195         midi_note = 69;
196
197       /* try loading file */
198       WavData wav_data;
199       if (wav_data.load_mono (filename))
200         {
201           Sample *sample = new Sample (this, wav_data);
202           samples.emplace_back (sample);
203           sample->filename  = filename;
204           sample->short_name = gen_short_name (filename);
205           sample->set_midi_note (midi_note);
206
207           xml_node clip_node = sample_node.child ("clip");
208           if (clip_node)
209             {
210               sample->set_marker (MARKER_CLIP_START, sm_atof (clip_node.attribute ("start").value()));
211               sample->set_marker (MARKER_CLIP_END, sm_atof (clip_node.attribute ("end").value()));
212             }
213           xml_node loop_node = sample_node.child ("loop");
214           if (loop_node)
215             {
216               string loop_type = loop_node.attribute ("type").value();
217               if (loop_type == "forward")
218                 sample->set_loop (Sample::Loop::FORWARD);
219               if (loop_type == "ping-pong")
220                 sample->set_loop (Sample::Loop::PING_PONG);
221               if (loop_type == "single-frame")
222                 sample->set_loop (Sample::Loop::SINGLE_FRAME);
223
224               sample->set_marker (MARKER_LOOP_START, sm_atof (loop_node.attribute ("start").value()));
225               sample->set_marker (MARKER_LOOP_END, sm_atof (loop_node.attribute ("end").value()));
226             }
227         }
228     }
229   // auto tune
230   m_auto_tune.enabled = false; // default
231   xml_node auto_tune_node = inst_node.child ("auto_tune");
232   if (auto_tune_node)
233     {
234       string method = auto_tune_node.attribute ("method").value();
235
236       if (method == "simple")
237         {
238           m_auto_tune.method = AutoTune::SIMPLE;
239           m_auto_tune.enabled = true;
240         }
241       else if (method == "all_frames")
242         {
243           m_auto_tune.method   = AutoTune::ALL_FRAMES;
244           m_auto_tune.partials = atoi (auto_tune_node.attribute ("partials").value());
245           m_auto_tune.enabled  = true;
246         }
247       else if (method == "smooth")
248         {
249           m_auto_tune.method   = AutoTune::SMOOTH;
250           m_auto_tune.partials = atoi (auto_tune_node.attribute ("partials").value());
251           m_auto_tune.time     = sm_atof (auto_tune_node.attribute ("time").value());
252           m_auto_tune.amount   = sm_atof (auto_tune_node.attribute ("amount").value());
253           m_auto_tune.enabled  = true;
254         }
255     }
256
257   // auto volume
258   m_auto_volume.enabled = false;
259   xml_node auto_volume_node = inst_node.child ("auto_volume");
260   if (auto_volume_node)
261     {
262       string method = auto_volume_node.attribute ("method").value();
263
264       if (method == "from_loop")
265         {
266           m_auto_volume.method  = AutoVolume::FROM_LOOP;
267           m_auto_volume.enabled = true;
268         }
269       else if (method == "global")
270         {
271           m_auto_volume.method  = AutoVolume::GLOBAL;
272           m_auto_volume.gain    = sm_atof (auto_volume_node.attribute ("gain").value());
273           m_auto_volume.enabled = true;
274         }
275       else
276         {
277           fprintf (stderr, "unknown auto volume method: %s\n", method.c_str());
278         }
279     }
280   m_encoder_config = EncoderConfig(); // reset
281   for (xml_node enc_node : inst_node.children ("encoder_config"))
282     {
283       EncoderEntry entry;
284       entry.param = enc_node.attribute ("param").value();
285
286       if (entry.param != "")
287         {
288           entry.value = enc_node.attribute ("value").value();
289           m_encoder_config.entries.push_back (entry);
290         }
291     }
292
293   /* select first sample if possible */
294   if (samples.empty())
295     m_selected = -1;
296   else
297     m_selected = 0;
298   signal_samples_changed();
299 }
300
301 static bool
302 ends_with (const std::string& str, const std::string& suffix)
303 {
304   /* if suffix is .wav, match foo.wav, foo.WAV, foo.Wav, ... */
305   return str.size() >= suffix.size() &&
306          std::equal (str.end() - suffix.size(), str.end(), suffix.begin(),
307                      [] (char c1, char c2) -> bool { return tolower (c1) == tolower (c2);});
308 }
309
310 static bool
311 no_case_equal (const string& s1, const string& s2)
312 {
313   if (s1.size() != s2.size())
314     return false;
315
316   return std::equal (s1.begin(), s1.end(), s2.begin(),
317                      [] (char c1, char c2) -> bool { return tolower (c1) == tolower (c2);});
318 }
319
320 string
321 Instrument::gen_short_name (const string& filename)
322 {
323   char *gbasename = g_path_get_basename (filename.c_str());
324   string basename = gbasename;
325   g_free (gbasename);
326
327   for (auto ext : vector<string> { ".wav", ".flac", ".ogg", ".aiff" })
328     if (ends_with (basename, ext))
329       {
330         basename.resize (basename.size() - ext.size());
331       }
332   for (int i = 1; ; i++)
333     {
334       string short_name = basename;
335       if (i > 1)
336         short_name += string_printf ("-%d", i);
337
338       bool   used = false;
339
340       for (auto& sample : samples)
341         {
342           /* some filesystems are case insensitive, so we avoid short names only differ in case */
343           if (no_case_equal (sample->short_name, short_name))
344             used = true;
345         }
346
347       if (!used)
348         {
349           return short_name;
350         }
351     }
352 }
353
354 void
355 Instrument::save (const string& filename, bool zip)
356 {
357   xml_document doc;
358   xml_node inst_node = doc.append_child ("instrument");
359   for (auto& sample : samples)
360     {
361       xml_node sample_node = inst_node.append_child ("sample");
362       if (zip)
363         sample_node.append_attribute ("filename").set_value ((sample->short_name + ".wav").c_str());
364       else
365         sample_node.append_attribute ("filename").set_value (sample->filename.c_str());
366       sample_node.append_attribute ("midi_note").set_value (sample->midi_note());
367
368       xml_node clip_node = sample_node.append_child ("clip");
369       clip_node.append_attribute ("start") = string_printf ("%.3f", sample->get_marker (MARKER_CLIP_START)).c_str();
370       clip_node.append_attribute ("end") = string_printf ("%.3f", sample->get_marker (MARKER_CLIP_END)).c_str();
371
372       if (sample->loop() != Sample::Loop::NONE)
373         {
374           string loop_type;
375
376           if (sample->loop() == Sample::Loop::FORWARD)
377             loop_type = "forward";
378           if (sample->loop() == Sample::Loop::PING_PONG)
379             loop_type = "ping-pong";
380           if (sample->loop() == Sample::Loop::SINGLE_FRAME)
381             loop_type = "single-frame";
382
383           xml_node loop_node = sample_node.append_child ("loop");
384           loop_node.append_attribute ("type") = loop_type.c_str();
385           loop_node.append_attribute ("start") = string_printf ("%.3f", sample->get_marker (MARKER_LOOP_START)).c_str();
386           loop_node.append_attribute ("end") = string_printf ("%.3f", sample->get_marker (MARKER_LOOP_END)).c_str();
387         }
388     }
389   if (m_auto_tune.enabled)
390     {
391       xml_node auto_tune_node = inst_node.append_child ("auto_tune");
392       if (m_auto_tune.method == AutoTune::SIMPLE)
393         {
394           auto_tune_node.append_attribute ("method").set_value ("simple");
395         }
396       else if (m_auto_tune.method == AutoTune::ALL_FRAMES)
397         {
398           auto_tune_node.append_attribute ("method").set_value ("all_frames");
399           auto_tune_node.append_attribute ("partials") = m_auto_tune.partials;
400         }
401       else if (m_auto_tune.method == AutoTune::SMOOTH)
402         {
403           auto_tune_node.append_attribute ("method").set_value ("smooth");
404           auto_tune_node.append_attribute ("partials") = m_auto_tune.partials;
405           auto_tune_node.append_attribute ("time")     = string_printf ("%.1f", m_auto_tune.time).c_str();
406           auto_tune_node.append_attribute ("amount")   = string_printf ("%.1f", m_auto_tune.amount).c_str();
407         }
408     }
409   if (m_auto_volume.enabled)
410     {
411       xml_node auto_volume_node = inst_node.append_child ("auto_volume");
412       if (m_auto_volume.method == AutoVolume::FROM_LOOP)
413         {
414           auto_volume_node.append_attribute ("method").set_value ("from_loop");
415         }
416       if (m_auto_volume.method == AutoVolume::GLOBAL)
417         {
418           auto_volume_node.append_attribute ("method").set_value ("global");
419           auto_volume_node.append_attribute ("gain") = string_printf ("%.1f", m_auto_volume.gain).c_str();
420         }
421     }
422   for (auto entry : m_encoder_config.entries)
423     {
424       xml_node conf_node = inst_node.append_child ("encoder_config");
425       conf_node.append_attribute ("param").set_value (entry.param.c_str());
426       conf_node.append_attribute ("value").set_value (entry.value.c_str());
427     }
428   if (zip)
429     {
430       class VectorOut : public xml_writer
431       {
432       public:
433         vector<unsigned char> vec;
434         void
435         write (const void* data, size_t size) override
436         {
437           const unsigned char *d = (const unsigned char *) data;
438
439           vec.insert (vec.end(), d, d + size);
440         }
441       } out;
442       doc.save (out);
443
444       ZipWriter writer (filename);
445       writer.add ("instrument.xml", out.vec);
446       for (size_t i = 0; i < samples.size(); i++)
447         {
448           /* we make a deep copy here, because save() is non-const */
449           WavData wav_data (samples[i]->wav_data().samples(),
450                             samples[i]->wav_data().n_channels(),
451                             samples[i]->wav_data().mix_freq(),
452                             samples[i]->wav_data().bit_depth());
453
454           vector<unsigned char> wav_file_vec;
455           wav_data.save (wav_file_vec);
456           writer.add (samples[i]->short_name + ".wav", wav_file_vec);
457         }
458     }
459   else
460     {
461       doc.save_file (filename.c_str());
462     }
463 }
464
465 void
466 Instrument::update_order()
467 {
468   Sample *ssample = sample (selected());
469   using SUPtr = std::unique_ptr<Sample>;
470   sort (samples.begin(), samples.end(),
471     [](const SUPtr& s1, const SUPtr& s2) {
472       if (s1->midi_note() > s2->midi_note())
473         return true;
474       if (s1->midi_note() < s2->midi_note())
475         return false;
476       return s1->filename < s2->filename;
477     });
478
479   for (size_t n = 0; n < samples.size(); n++)
480     if (samples[n].get() == ssample)
481       m_selected = n;
482
483   signal_samples_changed();
484 }
485
486 void
487 Instrument::marker_changed()
488 {
489   signal_marker_changed();
490 }
491
492 Instrument::AutoVolume
493 Instrument::auto_volume() const
494 {
495   return m_auto_volume;
496 }
497
498 void
499 Instrument::set_auto_volume (const AutoVolume& new_value)
500 {
501   m_auto_volume = new_value;
502
503   signal_global_changed();
504 }
505
506 Instrument::AutoTune
507 Instrument::auto_tune() const
508 {
509   return m_auto_tune;
510 }
511
512 void
513 Instrument::set_auto_tune (const AutoTune& new_value)
514 {
515   m_auto_tune = new_value;
516
517   signal_global_changed();
518 }
519
520 Instrument::EncoderConfig
521 Instrument::encoder_config() const
522 {
523   return m_encoder_config;
524 }
525
526 void
527 Instrument::set_encoder_config (const EncoderConfig& new_value)
528 {
529   m_encoder_config = new_value;
530
531   signal_global_changed();
532 }