1 /*
2 Copyright (C) 2007 Ben Levitt
3 
4 This file is part of Traverso
5 
6 Traverso is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 
11 This program is distributed in the hope that it will be useful,
12 but 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
19 
20 */
21 
22 #include "WPAudioWriter.h"
23 
24 #include <QString>
25 #include "Utils.h"
26 
27 #include <cstdio>
28 
29 // Always put me below _all_ includes, this is needed
30 // in case we run with memory leak detection enabled!
31 #include "Debugger.h"
32 
33 
WPAudioWriter()34 WPAudioWriter::WPAudioWriter()
35  : AbstractAudioWriter()
36 {
37 	m_wp = 0;
38 	m_firstBlock = 0;
39 	m_firstBlockSize = 0;
40 	m_tmp_buffer = 0;
41 	m_tmpBufferSize = 0;
42 	m_configFlags = 0;
43 }
44 
45 
~WPAudioWriter()46 WPAudioWriter::~WPAudioWriter()
47 {
48 	if (m_wp) {
49 		close_private();
50 	}
51 	if (m_firstBlock) {
52 		delete [] m_firstBlock;
53 	}
54 }
55 
get_extension()56 const char* WPAudioWriter::get_extension()
57 {
58 	return ".wv";
59 }
60 
61 
set_format_attribute(const QString & key,const QString & value)62 bool WPAudioWriter::set_format_attribute(const QString& key, const QString& value)
63 {
64 	if (key == "quality") {
65 		// Clear quality before or-ing in the new quality value
66 		m_configFlags &= ~(CONFIG_FAST_FLAG | CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG);
67 
68 		if (value == "fast") {
69 			m_configFlags |= CONFIG_FAST_FLAG;
70 			return true;
71 		}
72 		else if (value == "high") {
73 			// CONFIG_HIGH_FLAG (default) ~ 1.5 times slower then FAST, ~ 20% extra compression then FAST
74 			m_configFlags |= CONFIG_HIGH_FLAG;
75 			return true;
76 		}
77 		else if (value == "very_high") {
78 			// CONFIG_VERY_HIGH_FLAG ~ 2 times slower then FAST, ~ 25 % extra compression then FAST
79 			m_configFlags |= CONFIG_HIGH_FLAG;
80 			m_configFlags |= CONFIG_VERY_HIGH_FLAG;
81 			return true;
82 		}
83 	}
84 
85 	if (key == "skip_wvx") {
86 		if (value == "true") {
87 			// This option reduces the storage of some floating-point data files by up to about 10% by eliminating some
88 			// information that has virtually no effect on the audio data. While this does technically make the compression
89 			// lossy, it retains all the advantages of floating point data (>600 dB of dynamic range, no clipping, and 25 bits
90 			// of resolution). This also affects large integer compression by limiting the resolution to 24 bits.
91 			m_configFlags |= CONFIG_SKIP_WVX;
92 			return true;
93 		}
94 		else if (value == "false") {
95 			m_configFlags &= ~CONFIG_SKIP_WVX;
96 			return true;
97 		}
98 	}
99 
100 	return false;
101 }
102 
103 
open_private()104 bool WPAudioWriter::open_private()
105 {
106 	m_file = fopen(m_fileName.toUtf8().data(), "wb");
107 	if (!m_file) {
108 		qWarning("Couldn't open file %s.", QS_C(m_fileName));
109 		return false;
110 	}
111 
112 	m_wp = WavpackOpenFileOutput(WPAudioWriter::write_block, (void *)this, NULL);
113 	if (!m_wp) {
114 		fclose(m_file);
115 		return false;
116 	}
117 
118 	memset (&m_config, 0, sizeof(m_config));
119 	m_config.bytes_per_sample = (m_sampleWidth == 1) ? 4 : m_sampleWidth/8;
120 	m_config.bits_per_sample = (m_sampleWidth == 1) ? 32 : m_sampleWidth;
121 	if (m_sampleWidth == 1) m_config.float_norm_exp = 127; // config->float_norm_exp,  select floating-point data (127 for +/-1.0)
122 	m_config.channel_mask = (m_channels == 2) ? 3 : 4; // Microsoft standard (mono = 4, stereo = 3)
123 	m_config.num_channels = m_channels;
124 	m_config.sample_rate = m_rate;
125 	m_config.flags = m_configFlags;
126 
127 	WavpackSetConfiguration(m_wp, &m_config, -1);
128 
129 	if (!WavpackPackInit(m_wp)) {
130 		fclose(m_file);
131 		WavpackCloseFile(m_wp);
132 		m_wp = 0;
133 		return false;
134 	}
135 
136 	m_firstBlock = 0;
137 	m_firstBlockSize = 0;
138 
139 	return true;
140 }
141 
142 
write_to_file(void * lpBuffer,uint32_t nNumberOfBytesToWrite,uint32_t * lpNumberOfBytesWritten)143 int WPAudioWriter::write_to_file(void *lpBuffer, uint32_t nNumberOfBytesToWrite, uint32_t *lpNumberOfBytesWritten)
144 {
145 	uint32_t bcount;
146 
147 	*lpNumberOfBytesWritten = 0;
148 
149 	while (nNumberOfBytesToWrite) {
150 		bcount = fwrite((uchar *) lpBuffer + *lpNumberOfBytesWritten, 1, nNumberOfBytesToWrite, m_file);
151 
152 		if (bcount) {
153 			*lpNumberOfBytesWritten += bcount;
154 			nNumberOfBytesToWrite -= bcount;
155 		}
156 		else {
157 			break;
158 		}
159 	}
160 	int err = ferror(m_file);
161 	return !err;
162 }
163 
164 
write_block(void * id,void * data,int32_t length)165 int WPAudioWriter::write_block(void *id, void *data, int32_t length)
166 {
167 	WPAudioWriter* writer = (WPAudioWriter*) id;
168 	uint32_t bcount;
169 
170 	if (writer && writer->m_file && data && length) {
171 		if (writer->m_firstBlock == 0) {
172 			writer->m_firstBlock = new char[length];
173 			memcpy(writer->m_firstBlock, data, length);
174 			writer->m_firstBlockSize = length;
175 		}
176 		if (!writer->write_to_file(data, (uint32_t)length, (uint32_t*)&bcount) || bcount != (uint32_t)length) {
177 			fclose(writer->m_file);
178 			writer->m_wp = 0;
179 			return false;
180 		}
181 	}
182 
183 	return true;
184 }
185 
186 
rewrite_first_block()187 bool WPAudioWriter::rewrite_first_block()
188 {
189 	if (!m_firstBlock || !m_file || !m_wp) {
190 		return false;
191 	}
192 	WavpackUpdateNumSamples (m_wp, m_firstBlock);
193 	if (fseek(m_file, 0, SEEK_SET) != 0) {
194 		return false;
195 	}
196 	if (!write_block(this, m_firstBlock, m_firstBlockSize)) {
197 		return false;
198 	}
199 
200 	return true;
201 }
202 
203 
write_private(void * buffer,nframes_t frameCount)204 nframes_t WPAudioWriter::write_private(void* buffer, nframes_t frameCount)
205 {
206 	// FIXME:
207 	// Instead of this block, add an option to gdither to leave each
208 	// 8bit or 16bit sample in a 0-padded, int32_t
209 	//
210 	if (m_sampleWidth > 1 && m_sampleWidth < 24) { // Not float, or 32bit int, or 24bit int
211 		if (frameCount > m_tmpBufferSize) {
212 			if (m_tmp_buffer) {
213 				delete [] m_tmp_buffer;
214 			}
215 			m_tmp_buffer = new int32_t[frameCount * m_channels];
216 			m_tmpBufferSize = frameCount;
217 		}
218 		for (nframes_t s = 0; s < frameCount * m_channels; s++) {
219 			switch (m_sampleWidth) {
220 				case 8:
221 					m_tmp_buffer[s] = ((int8_t*)buffer)[s];
222 					break;
223 				case 16:
224 					m_tmp_buffer[s] = ((int16_t*)buffer)[s];
225 					break;
226 				default:
227 					// Less than 24 bit, but not 8 or 16 ?  This won't end well...
228 					break;
229 			}
230 		}
231 		if (WavpackPackSamples(m_wp, m_tmp_buffer, frameCount) == false) {
232 			return 0;
233 		}
234 		return frameCount;
235 	}
236 
237 	if (WavpackPackSamples(m_wp, (int32_t *)buffer, frameCount) == false) {
238 		return 0;
239 	}
240 	return frameCount;
241 }
242 
243 
close_private()244 bool WPAudioWriter::close_private()
245 {
246 	bool success = true;
247 
248 	if (WavpackFlushSamples(m_wp) == false) {
249 		success = false;
250 	}
251 	if (rewrite_first_block() == false) {
252 		success = false;
253 	}
254 
255 	WavpackCloseFile(m_wp);
256 	m_wp = 0;
257 
258 	fclose(m_file);
259 	m_file = 0;
260 
261 	if (m_tmp_buffer) {
262 		delete [] m_tmp_buffer;
263 		m_tmp_buffer = 0;
264 	}
265 	m_tmpBufferSize = 0;
266 
267 	if (m_firstBlock) {
268 		delete [] m_firstBlock;
269 		m_firstBlock = 0;
270 		m_firstBlockSize = 0;
271 	}
272 
273 	return success;
274 }
275 
276