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