1 /*
2  * Copyright (c) 2012 Tim van der Molen <tim@kariliq.nl>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 /*
18  * Note that the WavPack API uses the term "sample" to refer to "sample per
19  * channel" (i.e. a frame).
20  */
21 
22 #include <limits.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include <wavpack/wavpack.h>
28 
29 #include "../siren.h"
30 
31 #define IP_WAVPACK_BUFSIZE	2048	/* Buffer size, in frames */
32 #define IP_WAVPACK_ERRSTRLEN	80	/* As per the documentation */
33 
34 struct ip_wavpack_ipdata {
35 	WavpackContext	*wpc;
36 	int		 float_samples;
37 	int32_t		*buf;
38 	uint32_t	 bufidx;	/* Current sample */
39 	uint32_t	 buflen;	/* Buffer length, in samples */
40 };
41 
42 static void		 ip_wavpack_close(struct track *);
43 static void		 ip_wavpack_get_metadata(struct track *);
44 static int		 ip_wavpack_get_position(struct track *,
45 			    unsigned int *);
46 static char		*ip_wavpack_get_tag_item(WavpackContext *,
47 			    const char *);
48 static int		 ip_wavpack_open(struct track *);
49 static int		 ip_wavpack_read(struct track *,
50 			    struct sample_buffer *);
51 static void		 ip_wavpack_seek(struct track *, unsigned int);
52 
53 static const char	*ip_wavpack_extensions[] = { "wv", NULL };
54 
55 const struct ip		 ip = {
56 	"wavpack",
57 	IP_PRIORITY_WAVPACK,
58 	ip_wavpack_extensions,
59 	ip_wavpack_close,
60 	ip_wavpack_get_metadata,
61 	ip_wavpack_get_position,
62 	NULL,
63 	ip_wavpack_open,
64 	ip_wavpack_read,
65 	ip_wavpack_seek
66 };
67 
68 static void
ip_wavpack_close(struct track * t)69 ip_wavpack_close(struct track *t)
70 {
71 	struct ip_wavpack_ipdata *ipd;
72 
73 	ipd = t->ipdata;
74 	WavpackCloseFile(ipd->wpc);
75 	free(ipd->buf);
76 	free(ipd);
77 }
78 
79 static void
ip_wavpack_get_metadata(struct track * t)80 ip_wavpack_get_metadata(struct track *t)
81 {
82 	WavpackContext	*wpc;
83 	uint32_t	 nframes, rate;
84 	char		 errstr[IP_WAVPACK_ERRSTRLEN], *val;
85 
86 	wpc = WavpackOpenFileInput(t->path, errstr, OPEN_TAGS, 0);
87 	if (wpc == NULL) {
88 		LOG_ERRX("WavpackOpenFileInput: %s: %s", t->path, errstr);
89 		msg_errx("%s: Cannot open track: %s", t->path, errstr);
90 		return;
91 	}
92 
93 	t->album = ip_wavpack_get_tag_item(wpc, "album");
94 	t->artist = ip_wavpack_get_tag_item(wpc, "artist");
95 	t->comment = ip_wavpack_get_tag_item(wpc, "comment");
96 	t->date = ip_wavpack_get_tag_item(wpc, "year");
97 	t->genre = ip_wavpack_get_tag_item(wpc, "genre");
98 	t->title = ip_wavpack_get_tag_item(wpc, "title");
99 
100 	val = ip_wavpack_get_tag_item(wpc, "track");
101 	if (val != NULL) {
102 		track_split_tag(val, &t->tracknumber, &t->tracktotal);
103 		free(val);
104 	}
105 
106 	/*
107 	 * APEv2 doesn't have a standard key for the disc number. Try the
108 	 * "disc" and "part" keys.
109 	 */
110 	val = ip_wavpack_get_tag_item(wpc, "disc");
111 	if (val == NULL)
112 		val = ip_wavpack_get_tag_item(wpc, "part");
113 	if (val != NULL) {
114 		track_split_tag(val, &t->discnumber, &t->disctotal);
115 		free(val);
116 	}
117 
118 	/*
119 	 * APEv2 doesn't have a standard key for the album artist. Try the
120 	 * "albumartist" and "album artist" keys.
121 	 */
122 	t->albumartist = ip_wavpack_get_tag_item(wpc, "albumartist");
123 	if (t->albumartist == NULL)
124 		t->albumartist = ip_wavpack_get_tag_item(wpc, "album artist");
125 
126 	nframes = WavpackGetNumSamples(wpc);
127 	rate = WavpackGetSampleRate(wpc);
128 	if (nframes != (uint32_t)-1 && rate != 0)
129 		t->duration = nframes / rate;
130 
131 	WavpackCloseFile(wpc);
132 }
133 
134 static int
ip_wavpack_get_position(struct track * t,unsigned int * pos)135 ip_wavpack_get_position(struct track *t, unsigned int *pos)
136 {
137 	struct ip_wavpack_ipdata	*ipd;
138 	uint32_t			 curframe;
139 
140 	ipd = t->ipdata;
141 
142 	curframe = WavpackGetSampleIndex(ipd->wpc);
143 	*pos = curframe / t->format.rate;
144 	return 0;
145 }
146 
147 /*
148  * Return the value of the APEv2 or ID3v1 tag item with the specified key.
149  */
150 static char *
ip_wavpack_get_tag_item(WavpackContext * wpc,const char * key)151 ip_wavpack_get_tag_item(WavpackContext *wpc, const char *key)
152 {
153 	int	 len;
154 	char	*value;
155 
156 	len = WavpackGetTagItem(wpc, key, NULL, 0);
157 	if (len <= 0)
158 		value = NULL;
159 	else {
160 		if (len < INT_MAX)
161 			len++;
162 		value = xmalloc(len);
163 		WavpackGetTagItem(wpc, key, value, len);
164 	}
165 
166 	return value;
167 }
168 
169 static int
ip_wavpack_open(struct track * t)170 ip_wavpack_open(struct track *t)
171 {
172 	struct ip_wavpack_ipdata	*ipd;
173 	WavpackContext			*wpc;
174 	int				 float_samples;
175 	char				 errstr[IP_WAVPACK_ERRSTRLEN];
176 
177 	wpc = WavpackOpenFileInput(t->path, errstr, OPEN_NORMALIZE | OPEN_WVC,
178 	    0);
179 	if (wpc == NULL) {
180 		LOG_ERRX("WavpackOpenFileInput: %s: %s", t->path, errstr);
181 		msg_errx("%s: Cannot open track: %s", t->path, errstr);
182 		return -1;
183 	}
184 
185 	float_samples = WavpackGetMode(wpc) & MODE_FLOAT;
186 	if (float_samples)
187 		t->format.nbits = 16;
188 	else
189 		/*
190 		 * WavPack aligns samples whose bit depth is not a multiple of
191 		 * 8 on the MSB rather than the LSB. Therefore, determine the
192 		 * bit depth from the number of bytes per sample.
193 		 */
194 		t->format.nbits = 8 * WavpackGetBytesPerSample(wpc);
195 
196 	t->format.nchannels = WavpackGetNumChannels(wpc);
197 	t->format.rate = WavpackGetSampleRate(wpc);
198 
199 	ipd = xmalloc(sizeof *ipd);
200 	ipd->wpc = wpc;
201 	ipd->float_samples = float_samples;
202 	ipd->bufidx = 0;
203 	ipd->buflen = 0;
204 	ipd->buf = xreallocarray(NULL,
205 	    IP_WAVPACK_BUFSIZE * t->format.nchannels, sizeof *ipd->buf);
206 
207 	t->ipdata = ipd;
208 	return 0;
209 }
210 
211 static int
ip_wavpack_read(struct track * t,struct sample_buffer * sb)212 ip_wavpack_read(struct track *t, struct sample_buffer *sb)
213 {
214 	struct ip_wavpack_ipdata	*ipd;
215 	uint32_t			 ret;
216 	float				 f;
217 
218 	ipd = t->ipdata;
219 
220 	for (sb->len_s = 0; sb->len_s < sb->size_s; sb->len_s++) {
221 		if (ipd->bufidx == ipd->buflen) {
222 			ret = WavpackUnpackSamples(ipd->wpc, ipd->buf,
223 			    IP_WAVPACK_BUFSIZE);
224 			if (ret == 0)
225 				/* EOF reached. */
226 				break;
227 			ipd->buflen = ret * t->format.nchannels;
228 			ipd->bufidx = 0;
229 		}
230 
231 		if (!ipd->float_samples)
232 			switch (sb->nbytes) {
233 			case 1:
234 				sb->data1[sb->len_s] = ipd->buf[ipd->bufidx];
235 				break;
236 			case 2:
237 				sb->data2[sb->len_s] = ipd->buf[ipd->bufidx];
238 				break;
239 			case 4:
240 				sb->data4[sb->len_s] = ipd->buf[ipd->bufidx];
241 				break;
242 			}
243 		else {
244 			/* We assume floats use IEEE 754 representation. */
245 			f = *(float *)&ipd->buf[ipd->bufidx] * -INT16_MIN;
246 			if (f < INT16_MIN)
247 				sb->data2[sb->len_s] = INT16_MIN;
248 			else if (f > INT16_MAX)
249 				sb->data2[sb->len_s] = INT16_MAX;
250 			else
251 				sb->data2[sb->len_s] = f;
252 		}
253 
254 		ipd->bufidx++;
255 	}
256 
257 	sb->len_b = sb->len_s * sb->nbytes;
258 	return sb->len_s != 0;
259 }
260 
261 static void
ip_wavpack_seek(struct track * t,unsigned int sec)262 ip_wavpack_seek(struct track *t, unsigned int sec)
263 {
264 	struct ip_wavpack_ipdata	*ipd;
265 	uint32_t			 frame;
266 
267 	ipd = t->ipdata;
268 	frame = sec * t->format.rate;
269 	if (!WavpackSeekSample(ipd->wpc, frame)) {
270 		LOG_ERRX("WavpackSeekSample: %s: %s", t->path,
271 		    WavpackGetErrorMessage(ipd->wpc));
272 		msg_errx("Cannot seek: %s", WavpackGetErrorMessage(ipd->wpc));
273 	}
274 }
275