1 /*
2  * Copyright (C) 2002 - David W. Durham
3  *
4  * This file is part of ReZound, an audio editing application.
5  *
6  * ReZound is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published
8  * by the Free Software Foundation; either version 2 of the License,
9  * or (at your option) any later version.
10  *
11  * ReZound is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19  */
20 
21 /* Later I should support loading and saving the user notes and cues, etc.. but I'm not even
22  * done defining my own, and the libFLAC++ documentation is a little hard to follow about metadata
23  * so I'll ommit this for now
24  */
25 
26 #include "CFLACSoundTranslator.h"
27 
28 #if defined(HAVE_LIBFLACPP)
29 
30 #include <unistd.h>
31 
32 #include <string>
33 #include <stdexcept>
34 #include <utility>
35 
36 #include <FLAC++/all.h>
37 
38 #include <istring>
39 #include <CPath.h>
40 #include <TAutoBuffer.h>
41 
42 #include "CSound.h"
43 #include "AStatusComm.h"
44 
45 CFLACSoundTranslator::CFLACSoundTranslator()
46 {
47 }
48 
49 CFLACSoundTranslator::~CFLACSoundTranslator()
50 {
51 }
52 
53 
54 
55 
56 class MyFLACDecoderFile : public FLAC::Decoder::File
57 {
58 public:
59 	#define REALLOC_FILE_SIZE (1024*1024/4)
60 
61 	MyFLACDecoderFile(const string _filename,CSound *_sound) :
62 		File(),
63 
64 		filename(_filename),
65 		sound(_sound),
66 
67 		statusBar(_("Loading Sound"),0,CPath(filename).getSize(false),true),
68 
69 		sampleRate(0),
70 		channelCount(0),
71 		bitRate(0),
72 
73 		pos(0)
74 	{
75 		for(unsigned t=0;t<MAX_CHANNELS;t++)
76 			accessers[t]=NULL;
77 
78 		//set_filename(filename.c_str());
79 
80 		set_metadata_ignore_all();
81 		//set_metadata_respond(FLAC__METADATA_TYPE_VORBIS_COMMENT);
82 		//set_metadata_respond(FLAC__METADATA_TYPE_CUESHEET);
83 
84 		FLAC__StreamDecoderInitStatus s=init(filename.c_str());
85 		if(s!=FLAC__STREAM_DECODER_INIT_STATUS_OK)
86 			throw runtime_error(string(__func__)+" -- "+FLAC__StreamDecoderInitStatusString[s]);
87 	}
88 
89 	virtual ~MyFLACDecoderFile()
90 	{
91 		finish();
92 
93 		// remove space we didn't need to add (because we're adding in chunks)
94 		sound->removeSpace(pos,sound->getLength()-pos);
95 
96 		for(unsigned t=0;t<channelCount;t++)
97 			delete accessers[t];
98 	}
99 
100 protected:
101 
102 	::FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[])
103 	{
104 		if(sound->isEmpty())
105 		{
106 #warning an exception thrown from here causes an abort because it is running in a thread??? why? I dunno, might want to talk to the FLAC guys
107 			sampleRate=get_sample_rate();
108 			channelCount=get_channels();
109 			bitRate=get_bits_per_sample();
110 
111 			#warning I need to translate from different bit rates that might come back from get_bits_per_sample ... I am not sure what they would be
112 			//printf("br %d\n",bitRate);
113 			//printf("cc %d\n",channelCount);
114 			//printf("sr %d\n",sampleRate);
115 			if(bitRate!=16 && bitRate!=24 && bitRate!=32)
116 			{
117 				Error("unhandled bitrate! (data will be incorrect): "+istring(bitRate));
118 			}
119 
120 			if(channelCount<=0 || channelCount>MAX_CHANNELS) // ??? could just ignore the extra channels
121 				throw runtime_error(string(__func__)+" -- invalid number of channels in audio file: "+istring(channelCount)+" -- you could simply increase MAX_CHANNELS in CSound.h");
122 
123 			if(sampleRate<100 || sampleRate>197000)
124 				throw runtime_error(string(__func__)+" -- an unlikely sample rate of "+istring(sampleRate));
125 
126 			sound->createWorkingPoolFile(filename,sampleRate,channelCount,REALLOC_FILE_SIZE);
127 
128 			for(unsigned t=0;t<channelCount;t++)
129 				accessers[t]=new CRezPoolAccesser(sound->getAudio(t));
130 		}
131 
132 
133 		const sample_pos_t sampleframes_read=frame->header.blocksize;
134 
135 		if((pos+sampleframes_read)>sound->getLength())
136 			sound->addSpace(sound->getLength(),REALLOC_FILE_SIZE);
137 
138 		for(unsigned i=0;i<channelCount;i++)
139 		{
140 			const FLAC__int32 *src=buffer[i];
141 			CRezPoolAccesser &dest=*(accessers[i]);
142 
143 			if(bitRate==16)
144 			{
145 				for(unsigned t=0;t<sampleframes_read;t++)
146 					// the FLAC__int32 src seems to actually be 16bit (maybe this changes depending on the file?)
147 					dest[pos+t]=convert_sample<int16_t,sample_t>(src[t]);
148 			}
149 			else if(bitRate==24)
150 			{
151 				for(unsigned t=0;t<sampleframes_read;t++)
152 				{
153 					int24_t sd;
154 					sd.set(src[t]);
155 					dest[pos+t]=convert_sample<int24_t,sample_t>(sd);
156 				}
157 			}
158 			else if(bitRate==32)
159 			{
160 				for(unsigned t=0;t<sampleframes_read;t++)
161 					dest[pos+t]=convert_sample<int32_t,sample_t>(src[t]);
162 			}
163 			else
164 			{ // warned user already
165 			}
166 		}
167 
168 		pos+=sampleframes_read;
169 
170 
171 		// update status bar and detect user cancel
172 		FLAC__uint64 filePosition;
173 		FLAC__stream_decoder_get_decode_position(decoder_, &filePosition);
174 		return statusBar.update(filePosition) ? FLAC__STREAM_DECODER_WRITE_STATUS_ABORT : FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
175 	}
176 
177 	void metadata_callback(const ::FLAC__StreamMetadata *metadata)
178 	{
179 		/*
180 		if(sound->isEmpty())
181 		{
182 			printf("ignoring metadata (until we know the parameters)\n");
183 			return;
184 		}
185 
186 		printf("accepting metadata\n");
187 		*/
188 	}
189 
190 	void error_callback(::FLAC__StreamDecoderErrorStatus status)
191 	{
192 		/*
193 		printf("error\n");
194 		*/
195 	}
196 
197 
198 private:
199 
200 	const string filename;
201 	CSound *sound;
202 
203 	CStatusBar statusBar;
204 
205 	unsigned sampleRate;
206 	unsigned channelCount;
207 	unsigned bitRate;
208 
209 
210 	sample_pos_t pos;
211 
212 	CRezPoolAccesser *accessers[MAX_CHANNELS];
213 };
214 
215 bool CFLACSoundTranslator::onLoadSound(const string filename,CSound *sound) const
216 {
217 	MyFLACDecoderFile f(filename,sound);
218 	return f.process_until_end_of_stream();
219 }
220 
221 
222 
223 
224 
225 class MyFLACEncoderFile : public FLAC::Encoder::File
226 {
227 public:
228 	CStatusBar statusBar;
229 	bool cancelled;
230 
231 	MyFLACEncoderFile(sample_pos_t saveLength) :
232 		File(),
233 		statusBar(_("Saving Sound"),0,saveLength,true),
234 		cancelled(false)
235 	{
236 	}
237 
238 	virtual ~MyFLACEncoderFile()
239 	{
240 	}
241 
242 protected:
243 	void progress_callback(FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate)
244 	{
245 		cancelled|=statusBar.update(samples_written);
246 	}
247 };
248 
249 bool CFLACSoundTranslator::onSaveSound(const string filename,const CSound *sound,const sample_pos_t saveStart,const sample_pos_t saveLength,bool useLastUserPrefs) const
250 {
251 	int bitRate=0;
252 
253 	if(sound->getCueCount()>0 || sound->getUserNotes()!="")
254 	{
255 		if(Question(_("Saving user notes or cues is unimplemented for FLAC\nDo you wish to continue?"),yesnoQues)!=yesAns)
256 			return false;
257 	}
258 
259 	MyFLACEncoderFile f(saveLength);
260 
261 	//f.set_filename(filename.c_str());
262 
263 	f.set_channels(sound->getChannelCount());
264 
265 	/* ??? needs to be a user choice */
266 	f.set_bits_per_sample(16);
267 	bitRate=16;
268 
269 	f.set_sample_rate(sound->getSampleRate());
270 
271 	// calling set_do_mid_side_stereo set_loose_mid_side_stereo   requires the sound to have only 2 channels
272 	// maybe call set_loose_mid_side_stereo if it makes for better compression ???
273 
274 	//f.set_metadata(...) // ??? to do to set cues and user notes, etc
275 
276 
277 	FLAC__StreamEncoderInitStatus s=f.init(filename.c_str());
278 	if(s==FLAC__STREAM_ENCODER_INIT_STATUS_OK)
279 	{
280 		#define BUFFER_SIZE 65536
281 		TAutoBuffer<FLAC__int32> buffers[MAX_CHANNELS];
282 		FLAC__int32 *_buffers[MAX_CHANNELS];
283 
284 		for(unsigned t=0;t<sound->getChannelCount();t++)
285 		{
286 			buffers[t].setSize(BUFFER_SIZE);
287 			_buffers[t]=buffers[t]; // get point of buffer[t] to be able to pass to f.process()
288 		}
289 
290 
291 		sample_pos_t pos=0;
292 		while(pos<saveLength)
293 		{
294 			const sample_pos_t len=min((sample_pos_t)BUFFER_SIZE,saveLength-pos);
295 			for(unsigned i=0;i<sound->getChannelCount();i++)
296 			{
297 				const CRezPoolAccesser src=sound->getAudio(i);
298 				FLAC__int32 *dest=buffers[i];
299 
300 				if(bitRate==16)
301 				{
302 					for(sample_pos_t t=0;t<len;t++)
303 						dest[t]=convert_sample<sample_t,int16_t>(src[t+pos+saveStart]);
304 				}
305 				else
306 					throw runtime_error(string(__func__)+" -- internal error -- unhandled bitRate: "+istring(bitRate));
307 			}
308 
309 			if(!f.process(_buffers,len))
310 			{
311 				const int errNO=errno;
312 				f.finish();
313 				throw runtime_error(string(__func__)+" -- error writing FLAC file -- "+strerror(errNO));
314 			}
315 
316 			if(f.cancelled)
317 			{
318 				f.finish();
319 				unlink(filename.c_str());
320 				return false;
321 			}
322 
323 			pos+=len;
324 		}
325 
326 		f.finish();
327 
328 		return true;
329 	}
330 	else
331 		throw runtime_error(string(__func__)+" -- error creating FLAC encoder -- "+FLAC__StreamEncoderInitStatusString[s]);
332 
333 }
334 
335 
336 bool CFLACSoundTranslator::handlesExtension(const string extension,const string filename) const
337 {
338 	return extension=="flac" || extension=="fla";
339 }
340 
341 bool CFLACSoundTranslator::supportsFormat(const string filename) const
342 {
343 	// implementing this would not be convenient right now, I wish there were a simple function for it in the API
344 	return false;
345 }
346 
347 const vector<string> CFLACSoundTranslator::getFormatNames() const
348 {
349 	vector<string> names;
350 
351 	names.push_back("FLAC");
352 
353 	return names;
354 }
355 
356 const vector<vector<string> > CFLACSoundTranslator::getFormatFileMasks() const
357 {
358 	vector<vector<string> > list;
359 	vector<string> fileMasks;
360 
361 	fileMasks.clear();
362 	fileMasks.push_back("*.flac");
363 	fileMasks.push_back("*.fla");
364 	list.push_back(fileMasks);
365 
366 	return list;
367 }
368 
369 #endif // HAVE_LIBFLACPP
370