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