a467b4ca006e7f84049bdd3b87cf5e5c1140a4ad
[flac.git] / src / plugin_winamp2 / in_flac.c
1 /* in_flac - Winamp2 FLAC input plugin\r
2  * Copyright (C) 2000,2001,2002,2003,2004,2005,2006,2007  Josh Coalson\r
3  *\r
4  * This library is free software; you can redistribute it and/or\r
5  * modify it under the terms of the GNU Lesser General Public\r
6  * License as published by the Free Software Foundation; either\r
7  * version 2.1 of the License, or (at your option) any later version.\r
8  *\r
9  * This library is distributed in the hope that it will be useful,\r
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
12  * Lesser General Public License for more details.\r
13  *\r
14  * You should have received a copy of the GNU Lesser General Public\r
15  * License along with this library; if not, write to the Free Software\r
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\r
17  */\r
18 \r
19 #if HAVE_CONFIG_H\r
20 #  include <config.h>\r
21 #endif\r
22 \r
23 #include <windows.h>\r
24 #include <limits.h> /* for INT_MAX */\r
25 #include <stdio.h>\r
26 \r
27 #include "winamp2/in2.h"\r
28 #include "configure.h"\r
29 #include "infobox.h"\r
30 #include "tagz.h"\r
31 \r
32 #define PLUGIN_VERSION          "1.1.3"\r
33 \r
34 static In_Module mod_;                      /* the input module (declared near the bottom of this file) */\r
35 static char lastfn_[MAX_PATH];              /* currently playing file (used for getting info on the current file) */\r
36 flac_config_t flac_cfg;\r
37 \r
38 static stream_data_struct stream_data_;\r
39 static int paused;\r
40 static FLAC__StreamDecoder *decoder_;\r
41 static char sample_buffer_[SAMPLES_PER_WRITE * FLAC_PLUGIN__MAX_SUPPORTED_CHANNELS * (24/8) * 2];\r
42 /* (24/8) for max bytes per sample, and 2 for DSPs */\r
43 \r
44 static HANDLE thread_handle = NULL;         /* the handle to the decode thread */\r
45 static DWORD WINAPI DecodeThread(void *b);  /* the decode thread procedure */\r
46 \r
47 /*\r
48  *  init/quit\r
49  */\r
50 \r
51 static void init()\r
52 {\r
53         decoder_ = FLAC__stream_decoder_new();\r
54         strcpy(lastfn_, "");\r
55 \r
56         InitConfig();\r
57         ReadConfig();\r
58         InitInfobox();\r
59 }\r
60 \r
61 static void quit()\r
62 {\r
63         WriteConfig();\r
64         DeinitInfobox();\r
65         FLAC_plugin__decoder_delete(decoder_);\r
66         decoder_ = 0;\r
67 }\r
68 \r
69 /*\r
70  *  open/close\r
71  */\r
72 \r
73 static int isourfile(char *fn) { return 0; }\r
74 \r
75 static int play(char *fn)\r
76 {\r
77         LONGLONG filesize;\r
78         DWORD thread_id;\r
79         int   maxlatency;\r
80         /* checks */\r
81         if (decoder_ == 0) return 1;\r
82         if (!(filesize = FileSize(fn))) return -1;\r
83         /* init decoder */\r
84         if (!FLAC_plugin__decoder_init(decoder_, fn, filesize, &stream_data_, &flac_cfg.output))\r
85                 return 1;\r
86         strcpy(lastfn_, fn);\r
87         /* open output */\r
88         maxlatency = mod_.outMod->Open(stream_data_.sample_rate, stream_data_.channels, stream_data_.output_bits_per_sample, -1, -1);\r
89         if (maxlatency < 0)\r
90         {\r
91                 FLAC_plugin__decoder_finish(decoder_);\r
92                 return 1;\r
93         }\r
94         /* set defaults */\r
95         mod_.outMod->SetVolume(-666);\r
96         mod_.outMod->SetPan(0);\r
97         /* initialize vis stuff */\r
98         mod_.SAVSAInit(maxlatency, stream_data_.sample_rate);\r
99         mod_.VSASetInfo(stream_data_.sample_rate, stream_data_.channels);\r
100         /* set info */\r
101         mod_.SetInfo(stream_data_.average_bps, stream_data_.sample_rate/1000, stream_data_.channels, 1);\r
102         /* start playing thread */\r
103         paused = 0;\r
104         thread_handle = CreateThread(NULL, 0, DecodeThread, NULL, 0, &thread_id);\r
105         if (!thread_handle)     return 1;\r
106 \r
107         return 0;\r
108 }\r
109 \r
110 static void stop()\r
111 {\r
112         if (thread_handle)\r
113         {\r
114                 stream_data_.is_playing = false;\r
115                 if (WaitForSingleObject(thread_handle, 2000) == WAIT_TIMEOUT)\r
116                 {\r
117                         FLAC_plugin__show_error("Error while stopping decoding thread.");\r
118                         TerminateThread(thread_handle, 0);\r
119                 }\r
120                 CloseHandle(thread_handle);\r
121                 thread_handle = NULL;\r
122         }\r
123 \r
124         FLAC_plugin__decoder_finish(decoder_);\r
125         mod_.outMod->Close();\r
126         mod_.SAVSADeInit();\r
127 }\r
128 \r
129 /*\r
130  *  play control\r
131  */\r
132 \r
133 static void pause()\r
134 {\r
135         paused = 1;\r
136         mod_.outMod->Pause(1);\r
137 }\r
138 \r
139 static void unpause()\r
140 {\r
141         paused = 0;\r
142         mod_.outMod->Pause(0);\r
143 }\r
144 \r
145 static int ispaused()\r
146 {\r
147         return paused;\r
148 }\r
149 \r
150 static int getlength()\r
151 {\r
152         return stream_data_.length_in_msec;\r
153 }\r
154 \r
155 static int getoutputtime()\r
156 {\r
157         return mod_.outMod->GetOutputTime();\r
158 }\r
159 \r
160 static void setoutputtime(int time_in_ms)\r
161 {\r
162         stream_data_.seek_to = time_in_ms;\r
163 }\r
164 \r
165 static void setvolume(int volume)\r
166 {\r
167         mod_.outMod->SetVolume(volume);\r
168 }\r
169 \r
170 static void setpan(int pan)\r
171 {\r
172         mod_.outMod->SetPan(pan);\r
173 }\r
174 \r
175 static void eq_set(int on, char data[10], int preamp) {}\r
176 \r
177 /*\r
178  *  playing loop\r
179  */\r
180 \r
181 static void do_vis(char *data, int nch, int resolution, int position, unsigned samples)\r
182 {\r
183         static char vis_buffer[SAMPLES_PER_WRITE * FLAC_PLUGIN__MAX_SUPPORTED_CHANNELS];\r
184         char *ptr;\r
185         int size, count;\r
186 \r
187         /*\r
188          * Winamp visuals may have problems accepting sample sizes larger than\r
189          * 16 bits, so we reduce the sample size here if necessary.\r
190          */\r
191 \r
192         switch(resolution) {\r
193                 case 32:\r
194                 case 24:\r
195                         size  = resolution / 8;\r
196                         count = samples * nch;\r
197                         data += size - 1;\r
198 \r
199                         ptr = vis_buffer;\r
200                         while(count--) {\r
201                                 *ptr++ = data[0] ^ 0x80;\r
202                                 data += size;\r
203                         }\r
204 \r
205                         data = vis_buffer;\r
206                         resolution = 8;\r
207                         /* fall through */\r
208                 case 16:\r
209                 case 8:\r
210                         mod_.SAAddPCMData(data, nch, resolution, position);\r
211                         mod_.VSAAddPCMData(data, nch, resolution, position);\r
212         }\r
213 }\r
214 \r
215 static DWORD WINAPI DecodeThread(void *unused)\r
216 {\r
217         const unsigned channels = stream_data_.channels;\r
218         const unsigned bits_per_sample = stream_data_.bits_per_sample;\r
219         const unsigned target_bps = stream_data_.output_bits_per_sample;\r
220         const unsigned sample_rate = stream_data_.sample_rate;\r
221         const unsigned fact = channels * (target_bps/8);\r
222 \r
223         while (stream_data_.is_playing)\r
224         {\r
225                 /* seek needed */\r
226                 if (stream_data_.seek_to != -1)\r
227                 {\r
228                         const int pos = FLAC_plugin__seek(decoder_, &stream_data_);\r
229                         if (pos != -1) mod_.outMod->Flush(pos);\r
230                 }\r
231                 /* stream ended */\r
232                 else if (stream_data_.eof)\r
233                 {\r
234                         if (!mod_.outMod->IsPlaying())\r
235                         {\r
236                                 PostMessage(mod_.hMainWindow, WM_WA_MPEG_EOF, 0, 0);\r
237                                 return 0;\r
238                         }\r
239                         Sleep(10);\r
240                 }\r
241                 /* decode */\r
242                 else\r
243                 {\r
244                         /* decode samples */\r
245                         int bytes = FLAC_plugin__decode(decoder_, &stream_data_, sample_buffer_);\r
246                         const int n = bytes / fact;\r
247                         /* visualization */\r
248                         do_vis(sample_buffer_, channels, target_bps, mod_.outMod->GetWrittenTime(), n);\r
249                         /* dsp */\r
250                         if (mod_.dsp_isactive())\r
251                                 bytes = mod_.dsp_dosamples((short*)sample_buffer_, n, target_bps, channels, sample_rate) * fact;\r
252                         /* output */\r
253                         while (mod_.outMod->CanWrite()<bytes && stream_data_.is_playing && stream_data_.seek_to==-1)\r
254                                 Sleep(20);\r
255                         if (stream_data_.is_playing && stream_data_.seek_to==-1)\r
256                                 mod_.outMod->Write(sample_buffer_, bytes);\r
257                         /* show bitrate */\r
258                         if (flac_cfg.display.show_bps)\r
259                         {\r
260                                 const int rate = FLAC_plugin__get_rate(mod_.outMod->GetWrittenTime(), mod_.outMod->GetOutputTime(), &stream_data_);\r
261                                 if (rate) mod_.SetInfo(rate/1000, stream_data_.sample_rate/1000, stream_data_.channels, 1);\r
262                         }\r
263                 }\r
264         }\r
265 \r
266         return 0;\r
267 }\r
268 \r
269 /*\r
270  *  title formatting\r
271  */\r
272 \r
273 static T_CHAR *get_tag(const T_CHAR *tag, void *param)\r
274 {\r
275         FLAC__StreamMetadata *tags = (FLAC__StreamMetadata*)param;\r
276         char *tagname, *p;\r
277         T_CHAR *val;\r
278 \r
279         if (!tag)\r
280                 return 0;\r
281         /* Vorbis comment names must be ASCII, so convert 'tag' first */\r
282         tagname = malloc(wcslen(tag)+1);\r
283         for(p=tagname;*tag;) {\r
284                 if(*tag > 0x7d) {\r
285                         free(tagname);\r
286                         return 0;\r
287                 }\r
288                 else\r
289                         *p++ = (char)(*tag++);\r
290         }\r
291         *p++ = '\0';\r
292         /* now get it */\r
293         val = FLAC_plugin__tags_get_tag_ucs2(tags, tagname);\r
294         free(tagname);\r
295         /* some "user friendly cheavats" */\r
296         if (!val)\r
297         {\r
298                 if (!wcsicmp(tag, L"ARTIST"))\r
299                 {\r
300                         val = FLAC_plugin__tags_get_tag_ucs2(tags, "PERFORMER");\r
301                         if (!val) val = FLAC_plugin__tags_get_tag_ucs2(tags, "COMPOSER");\r
302                 }\r
303                 else if (!wcsicmp(tag, L"YEAR") || !wcsicmp(tag, L"DATE"))\r
304                 {\r
305                         val = FLAC_plugin__tags_get_tag_ucs2(tags, "YEAR_RECORDED");\r
306                         if (!val) val = FLAC_plugin__tags_get_tag_ucs2(tags, "YEAR_PERFORMED");\r
307                 }\r
308         }\r
309 \r
310         return val;\r
311 }\r
312 \r
313 static void free_tag(T_CHAR *tag, void *param)\r
314 {\r
315         (void)param;\r
316         free(tag);\r
317 }\r
318 \r
319 static void format_title(const char *filename, WCHAR *title, unsigned max_size)\r
320 {\r
321         FLAC__StreamMetadata *tags;\r
322 \r
323         ReadTags(filename, &tags, /*forDisplay=*/true);\r
324 \r
325         tagz_format(flac_cfg.title.tag_format_w, get_tag, free_tag, tags, title, max_size);\r
326 \r
327         FLAC_plugin__tags_destroy(&tags);\r
328 }\r
329 \r
330 static void getfileinfo(char *filename, char *title, int *length_in_msec)\r
331 {\r
332         FLAC__StreamMetadata streaminfo;\r
333 \r
334         if (!filename || !*filename) {\r
335                 filename = lastfn_;\r
336                 if (length_in_msec) {\r
337                         *length_in_msec = stream_data_.length_in_msec;\r
338                         length_in_msec = 0;    /* force skip in following code */\r
339                 }\r
340         }\r
341 \r
342         if (!FLAC__metadata_get_streaminfo(filename, &streaminfo)) {\r
343                 if (length_in_msec)\r
344                         *length_in_msec = -1;\r
345                 return;\r
346         }\r
347 \r
348         if (title) {\r
349                 static WCHAR buffer[400];\r
350                 format_title(filename, buffer, 400);\r
351                 WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, buffer, -1, title, 400, NULL, NULL);\r
352         }\r
353 \r
354         if (length_in_msec) {\r
355                 /* with VC++ you have to spoon feed it the casting from uint64->int64->double */\r
356                 FLAC__uint64 l = (FLAC__uint64)((double)(FLAC__int64)streaminfo.data.stream_info.total_samples / (double)streaminfo.data.stream_info.sample_rate * 1000.0 + 0.5);\r
357                 if (l > INT_MAX)\r
358                         l = INT_MAX;\r
359                 *length_in_msec = (int)l;\r
360         }\r
361 }\r
362 \r
363 /*\r
364  *  interface\r
365  */\r
366 \r
367 void FLAC_plugin__show_error(const char *message,...)\r
368 {\r
369         char foo[512];\r
370         va_list args;\r
371         va_start(args, message);\r
372         vsprintf(foo, message, args);\r
373         va_end(args);\r
374         MessageBox(mod_.hMainWindow, foo, "FLAC Plug-in Error", MB_ICONSTOP);\r
375 }\r
376 \r
377 static void about(HWND hwndParent)\r
378 {\r
379         MessageBox(hwndParent, "Winamp2 FLAC Plugin v"PLUGIN_VERSION"\nby Josh Coalson and X-Fixer\n\nuses libFLAC "VERSION"\nSee http://flac.sourceforge.net/\n", "About FLAC Plugin", MB_ICONINFORMATION);\r
380 }\r
381 \r
382 static void config(HWND hwndParent)\r
383 {\r
384         if (DoConfig(mod_.hDllInstance, hwndParent))\r
385                 WriteConfig();\r
386 }\r
387 \r
388 static int infobox(char *fn, HWND hwnd)\r
389 {\r
390         DoInfoBox(mod_.hDllInstance, hwnd, fn);\r
391         return 0;\r
392 }\r
393 \r
394 /*\r
395  *  exported stuff\r
396  */\r
397 \r
398 static In_Module mod_ =\r
399 {\r
400         IN_VER,\r
401         "FLAC Decoder v" PLUGIN_VERSION,\r
402         0,                                    /* hMainWindow */\r
403         0,                                    /* hDllInstance */\r
404         "FLAC\0FLAC Audio File (*.FLAC)\0",\r
405         1,                                    /* is_seekable */\r
406         1,                                    /* uses output */\r
407         config,\r
408         about,\r
409         init,\r
410         quit,\r
411         getfileinfo,\r
412         infobox,\r
413         isourfile,\r
414         play,\r
415         pause,\r
416         unpause,\r
417         ispaused,\r
418         stop,\r
419 \r
420         getlength,\r
421         getoutputtime,\r
422         setoutputtime,\r
423 \r
424         setvolume,\r
425         setpan,\r
426 \r
427         0,0,0,0,0,0,0,0,0,                    /* vis stuff */\r
428         0,0,                                  /* dsp */\r
429         eq_set,\r
430         NULL,                                 /* setinfo */\r
431         0                                     /* out_mod */\r
432 };\r
433 \r
434 __declspec(dllexport) In_Module *winampGetInModule2()\r
435 {\r
436         return &mod_;\r
437 }\r
438 \r
439 BOOL WINAPI _DllMainCRTStartup(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)\r
440 {\r
441         return TRUE;\r
442 }\r