1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /***************************************************************************
3 * drumkit_creator.cc
4 *
5 * Thu Jan 12 18:51:34 CET 2017
6 * Copyright 2017 Andr� Nusser
7 * andre.nusser@googlemail.com
8 ****************************************************************************/
9
10 /*
11 * This file is part of DrumGizmo.
12 *
13 * DrumGizmo is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
17 *
18 * DrumGizmo is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with DrumGizmo; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
26 */
27 #include "drumkit_creator.h"
28
29 #include <unistd.h>
30
31 #include "../src/random.h"
32
33 #include <sndfile.h>
34
35 #include <iostream>
36 #include <fstream>
37 #include <cstdlib>
38 #include <algorithm>
39
40 #ifdef _WIN32
41 #define WIN32_LEAN_AND_MEAN
42 #include <windows.h>
43 #endif
44
~DrumkitCreator()45 DrumkitCreator::~DrumkitCreator()
46 {
47 for (const auto& file: created_files)
48 {
49 auto error = unlink(file.c_str());
50
51 if (error) {
52 std::cerr << "File could not be deleted in DrumkitCreator destructor"
53 << std::endl;
54 }
55 }
56
57 for (const auto& dir: created_directories)
58 {
59 #ifndef _WIN32
60 auto error = rmdir(dir.c_str());
61
62 if (error) {
63 std::cerr << "Directory could not be deleted in DrumkitCreator destructor"
64 << std::endl;
65 }
66 #else
67 RemoveDirectory(dir.c_str());
68 #endif
69 }
70 }
71
create(const DrumkitData & data)72 std::string DrumkitCreator::create(const DrumkitData& data)
73 {
74 std::string drumkit_filename;
75
76 if (is_valid(data))
77 {
78 auto dir = createTemporaryDirectory("drumkit");
79
80 for (const auto& wav_info: data.wav_infos) {
81 createWav(wav_info, data.number_of_channels, dir);
82 }
83
84 for (const auto& instrument: data.instruments) {
85 createInstrument(instrument, data.number_of_channels, dir);
86 }
87
88 drumkit_filename = createDrumkitFile(data, dir);
89 }
90 else
91 {
92 throw "DrumkitData not valid";
93 }
94
95 return drumkit_filename;
96 }
97
createWav(const WavInfo & wav_info,std::size_t number_of_channels,const std::string & dir)98 void DrumkitCreator::createWav(const WavInfo& wav_info, std::size_t number_of_channels, const std::string& dir)
99 {
100 SF_INFO sfinfo;
101 sfinfo.samplerate = 44100;
102 sfinfo.channels = number_of_channels;
103 sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
104
105 std::string filename = dir + "/" + wav_info.filename;
106 auto sndfile = sf_open(filename.c_str(), SFM_WRITE, &sfinfo);
107 if (!sndfile) {
108 throw "The wav file could not be created";
109 }
110
111 created_files.push_back(filename);
112
113 auto data_vec = createData(wav_info, number_of_channels);
114 sf_write_raw(sndfile, data_vec.data(), 2*data_vec.size());
115
116 sf_write_sync(sndfile);
117 sf_close(sndfile);
118 }
119
createStdKit(const std::string & name)120 std::string DrumkitCreator::createStdKit(const std::string& name)
121 {
122 std::vector<WavInfo> wav_infos = {
123 WavInfo("1011.wav", 1, 0x1110),
124 WavInfo("2122.wav", 1, 0x2221)
125 };
126
127 std::vector<Audiofile> audiofiles1(4, Audiofile{&wav_infos.front(), 1});
128 std::vector<Audiofile> audiofiles2(4, Audiofile{&wav_infos.back(), 1});
129
130 std::vector<SampleData> sample_data1{{"stroke1", std::move(audiofiles1)}};
131 std::vector<SampleData> sample_data2{{"stroke1", std::move(audiofiles2)}};
132
133 std::vector<InstrumentData> instruments = {
134 InstrumentData{"instr1", "instr1.xml", std::move(sample_data1)},
135 InstrumentData{"instr2", "instr2.xml", std::move(sample_data2)}
136 };
137
138 auto kit_data = DrumkitData{name, 4, instruments, wav_infos};
139
140 return create(kit_data);
141 }
142
createSmallKit(const std::string & name)143 std::string DrumkitCreator::createSmallKit(const std::string& name)
144 {
145 std::vector<WavInfo> wav_infos = {
146 WavInfo("small_instr.wav", 549833)
147 };
148
149 std::vector<Audiofile> audiofiles;
150 for (std::size_t i = 0; i < 13; ++i) {
151 audiofiles.push_back({&wav_infos.front(), i+1});
152 }
153
154 std::vector<SampleData> sample_data{{"stroke1", std::move(audiofiles)}};
155
156 std::vector<InstrumentData> instruments = {
157 InstrumentData{"small_instr", "small_instr.xml", std::move(sample_data)}
158 };
159
160 auto kit_data = DrumkitData{name, 13, instruments, wav_infos};
161
162 return create(kit_data);
163 }
164
createHugeKit(const std::string & name)165 std::string DrumkitCreator::createHugeKit(const std::string& name)
166 {
167 std::vector<WavInfo> wav_infos = {
168 WavInfo("huge_instr.wav", 549833)
169 };
170
171 std::vector<Audiofile> audiofiles;
172 for (std::size_t i = 0; i < 13; ++i) {
173 audiofiles.push_back({&wav_infos.front(), i+1});
174 }
175
176 std::vector<SampleData> sample_data;
177 for (std::size_t i = 0; i < 50; ++i) {
178 sample_data.push_back({"stroke" + std::to_string(i), audiofiles});
179 }
180
181 std::vector<InstrumentData> instruments;
182 for (std::size_t i = 0; i < 50; ++i) {
183 instruments.push_back({"huge_instr" + std::to_string(i),
184 "huge_instr.xml",
185 sample_data});
186 }
187
188 auto kit_data = DrumkitData{name, 13, instruments, wav_infos};
189
190 return create(kit_data);
191 }
192
createSingleChannelWav(const std::string & name)193 std::string DrumkitCreator::createSingleChannelWav(const std::string& name)
194 {
195 auto dir = createTemporaryDirectory("wavfiles");
196
197 auto wav_info = WavInfo(name, 173516);
198 createWav(wav_info,
199 1,
200 dir);
201
202 return dir + "/" + name;
203 }
204
createMultiChannelWav(const std::string & name)205 std::string DrumkitCreator::createMultiChannelWav(const std::string& name)
206 {
207 auto dir = createTemporaryDirectory("wavfiles");
208
209 auto wav_info = WavInfo(name, 549833);
210 createWav(wav_info,
211 13,
212 dir);
213
214 return dir + "/" + name;
215 }
216
create0000Wav(const std::string & name)217 std::string DrumkitCreator::create0000Wav(const std::string& name)
218 {
219 auto dir = createTemporaryDirectory("wavfiles");
220
221 auto wav_info = WavInfo(name, 1, 0x0000);
222 createWav(wav_info,
223 1,
224 dir);
225
226 return dir + "/" + name;
227 }
228
createStdMidimap(const std::string & name)229 std::string DrumkitCreator::createStdMidimap(const std::string& name)
230 {
231 auto dir = createTemporaryDirectory("midimap");
232
233 std::string content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
234 "<midimap>\n"
235 "<map note=\"1\" instr=\"instr1\"/>\n"
236 "<map note=\"2\" instr=\"instr2\"/>\n"
237 "</midimap>\n";
238
239 std::string filename = dir + "/" + name + ".xml";
240 std::ofstream file;
241 file.open(filename);
242 if (file.is_open())
243 {
244 created_files.push_back(filename);
245 file << content;
246 }
247 else
248 {
249 throw "File could not be opened";
250 }
251 file.close();
252
253 return filename;
254 }
255
256 //
257 // private member functions
258 //
259
is_valid(const DrumkitData & data)260 bool DrumkitCreator::is_valid(const DrumkitData& data)
261 {
262 // TODO Check the consistency of the data.
263 return true;
264 }
265
createTemporaryDirectory(const std::string & name)266 std::string DrumkitCreator::createTemporaryDirectory(const std::string& name)
267 {
268 #ifndef _WIN32
269 std::string dir_template = "/tmp/drumgizmo_" + name + "XXXXXX";
270 const auto dir_name = mkdtemp(&dir_template[0]);
271 #else
272 char temp_dir[MAX_PATH];
273 char dir_name[MAX_PATH];
274 GetTempPath(sizeof(temp_dir), temp_dir);
275 GetTempFileName(temp_dir, name.c_str(), 0, dir_name);
276 CreateDirectory(dir_name, 0);
277 #endif
278 if (dir_name) {
279 created_directories.push_back(dir_name);
280 return std::string(dir_name);
281 }
282
283 return "";
284 }
285
createData(const WavInfo & wav_info,std::size_t number_of_channels)286 auto DrumkitCreator::createData(const WavInfo& wav_info, std::size_t number_of_channels) -> std::vector<Sample>
287 {
288 std::vector<Sample> data_vec(number_of_channels * wav_info.length, wav_info.sample);
289 if (wav_info.is_random)
290 {
291 Random rand(42); // Fix the seed to make it reproducable.
292 int lower_bound = std::numeric_limits<Sample>::min();
293 int upper_bound = std::numeric_limits<Sample>::max();
294
295 std::generate(data_vec.begin(),
296 data_vec.end(),
297 [&](){ return Sample(rand.intInRange(lower_bound, upper_bound)); });
298 }
299
300 return data_vec;
301 }
302
createInstrument(const InstrumentData & data,std::size_t number_of_channels,const std::string & dir)303 void DrumkitCreator::createInstrument(const InstrumentData& data, std::size_t number_of_channels,
304 const std::string& dir)
305 {
306 std::string prefix = "<?xml version='1.0' encoding='UTF-8'?>\n"
307 "<instrument name=\"" + data.name + "\" version=\"2.0\">\n"
308 " <samples>\n";
309 // FIXME sampleref
310 std::string postfix = " </samples>\n</instrument>\n";
311
312 std::string samples;
313 float power = 1.0f;
314 for (const auto& sample: data.sample_data) {
315 samples += "<sample name=\"" + sample.name + "\" power=\"" + std::to_string(power) + "\">\n";
316 power += 0.1f;
317
318 for (std::size_t i = 0; i < sample.audiofiles.size(); ++i)
319 {
320 const auto& audiofile = sample.audiofiles[i];
321 samples += "<audiofile channel=\"ch" + std::to_string(i) + "\" file=\""
322 + audiofile.wav_info->filename + "\" filechannel=\""
323 + std::to_string(audiofile.filechannel) + "\"/>\n";
324 }
325 samples += "</sample>\n";
326 }
327
328 // Write to file
329 std::string filename = dir + "/" + data.filename;
330 std::ofstream file;
331 file.open(filename);
332 if (file.is_open())
333 {
334 created_files.push_back(filename);
335 file << prefix + samples + postfix;
336 }
337 else
338 {
339 throw "File could not be opened";
340 }
341 file.close();
342 }
343
createDrumkitFile(const DrumkitData & data,const std::string & dir)344 std::string DrumkitCreator::createDrumkitFile(const DrumkitData& data, const std::string& dir)
345 {
346 // Pre- and postfix string
347 std::string prefix = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
348 "<drumkit name=\"" + data.name + "\" version=\"2.0\""
349 " description=\"An drumkit generated by the drumkit_generator\">\n";
350 std::string postfix = "</drumkit>\n";
351
352 // Channel string
353 std::string channels;
354 channels += "<channels>\n";
355 for (std::size_t i = 0; i < data.number_of_channels; ++i)
356 {
357 channels += "<channel name=\"ch" + std::to_string(i) + "\"/>\n";
358 }
359 channels += "</channels>\n";
360
361 // Instrument string
362 std::string instruments;
363 instruments += "<instruments>\n";
364 for (const auto& instrument: data.instruments)
365 {
366 instruments += "<instrument name=\"" + instrument.name + "\" file=\"" + instrument.filename + "\">\n";
367 for (std::size_t i = 0; i < data.number_of_channels; ++i)
368 {
369 std::string i_str = std::to_string(i);
370 instruments += "<channelmap in=\"ch" + i_str + "\" out=\"ch" + i_str + "\"/>\n";
371 }
372 instruments += "</instrument>\n";
373 }
374 instruments += "</instruments>\n";
375
376 // Write everything to a drumkit file
377 std::string filename = dir + "/" + data.name + ".xml";
378 std::ofstream file;
379 file.open(filename);
380 if (file.is_open())
381 {
382 created_files.push_back(filename);
383 file << prefix + channels + instruments + postfix;
384 }
385 else
386 {
387 throw "File could not be opened";
388 }
389 file.close();
390
391 return filename;
392 }
393