Replace manual option handling by use of glib
[lcid-xwax.git] / track.c
blob1f9dd324bbeb95870bb271c63540ca5ae5dd6e6d
1 /*
2 * Copyright (C) 2008 Mark Hills <mark@pogo.org.uk>
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License version 2 for more details.
13 * You should have received a copy of the GNU General Public License
14 * version 2 along with this program; if not, write to the Free
15 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16 * MA 02110-1301, USA.
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <signal.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/poll.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <math.h>
32 #include "track.h"
33 #include "track_cache.h"
35 #define LOCK(tr) pthread_mutex_lock(&(tr)->mx)
36 #define UNLOCK(tr) pthread_mutex_unlock(&(tr)->mx)
38 static guint average_blocks = 0;
39 static guint block_allocation_count = 0;
41 static guint get_average_blocks(const guint currentBlockCount)
43 guint new_block_allocation_count = block_allocation_count + 1;
44 average_blocks = ((float)block_allocation_count / (float)new_block_allocation_count * (float)(average_blocks)) + ((float)currentBlockCount / (float)new_block_allocation_count);
45 block_allocation_count = new_block_allocation_count;
46 return average_blocks;
49 /* Start the importer process. On completion, pid and fd are set */
51 static int start_import(struct track_t *tr, const char *path)
53 tr->cache = cached_track_init(path);
55 //try to reserve cache file
56 tr->cache_read = cached_track_read(tr->cache);
58 if(tr->cache_read)
59 tr->fd = cache_read_get_fd(tr->cache_read);
60 else
62 int pstdout[2];
64 if(pipe(pstdout) == -1) {
65 perror("pipe");
66 return -1;
69 tr->pid = vfork();
71 if(tr->pid < 0) {
72 perror("vfork");
73 return -1;
75 } else if(tr->pid == 0) { /* child */
77 close(pstdout[0]);
78 dup2(pstdout[1], STDOUT_FILENO);
79 close(pstdout[1]);
81 if(execl(tr->importer, "import", path, NULL) == -1) {
82 perror("execl");
83 exit(-1);
84 return 0;
88 close(pstdout[1]);
89 tr->fd = pstdout[0];
90 if(fcntl(tr->fd, F_SETFL, O_NONBLOCK) == -1) {
91 perror("fcntl");
92 return -1;
96 tr->bytes = 0;
97 //tr->length = 0;
98 tr->ppm = 0;
99 tr->overview = 0;
100 tr->eof = 0;
102 return 0;
105 guint track_get_used_blocks(const struct track_t* tr)
107 //double cast is necessary here because it
108 //otherwise converts the argument to int
109 return ceil((double)tr->bytes / BYTES_PER_PCM_BLOCK);
112 guint track_get_last_block_used_bytes(const struct track_t* tr)
114 return tr->bytes % BYTES_PER_PCM_BLOCK;
117 guint track_block_get_used_bytes(const struct track_t* tr, const guint index)
119 guint count = track_get_used_blocks(tr);
121 if(count < (index + 1))
122 return 0;
123 else if(count == (index + 1))
124 return tr->bytes - (index * BYTES_PER_PCM_BLOCK);
125 else
126 return BYTES_PER_PCM_BLOCK;
129 void track_blocks_shrink(struct track_t* tr, const guint newSize)
131 //new size contains an invalid value
132 if(newSize >= tr->blocks)
133 return;
135 for(guint index = tr->blocks - 1; index > newSize - 1; index--)
137 g_slice_free(struct track_block_t, track_get_block(tr, index));
140 g_ptr_array_remove_range(tr->block, newSize, tr->blocks - newSize);
142 fprintf(
143 stderr,
144 "Deallocated track blocks (freed %d using %zu bytes).\n",
145 tr->blocks - newSize,
146 (tr->blocks - newSize) * sizeof(struct track_block_t));
148 tr->blocks = newSize;
151 /* Conclude the importer process. To be called whether the importer
152 * was aborted or completed successfully */
154 static int stop_import(struct track_t *tr)
156 int status;
158 if(close(tr->fd) == -1) {
159 perror("close");
160 return -1;
163 if(tr->cache_read)
165 cache_read_destroy(tr->cache_read);
166 tr->cache_read = NULL;
167 cached_track_destroy(tr->cache);
168 tr->cache = NULL;
170 status = 0;
172 else
174 if(waitpid(tr->pid, &status, 0) == -1) {
175 perror("waitpid");
176 return -1;
179 fprintf(stderr, "Track importer exited with status %d.\n", status);
181 return 0;
184 guint track_get_sample_count(const struct track_t* tr)
186 return tr->bytes / BYTES_PER_SAMPLE;
189 /* Read the next block of data from the file. Return -1 when an error
190 * occurs and requires our attention, 1 if there is no more data to be
191 * read, otherwise zero. */
193 static int read_from_pipe(struct track_t *tr)
195 int r, ls;
196 size_t m;
197 unsigned short v;
198 unsigned int s, w;
199 struct track_block_t *block;
201 /* Check whether we need to allocate a new block */
203 if(tr->bytes >= tr->blocks * BYTES_PER_PCM_BLOCK) {
204 block = g_slice_new(struct track_block_t);
206 if(!block) {
207 perror("malloc");
208 return -1;
211 tr->blocks++;
212 g_ptr_array_add(tr->block, block);
214 fprintf(stderr, "Allocated new track block (%d at %zu bytes).\n",
215 tr->blocks, tr->bytes);
218 /* Load in audio to the end of the current block. We've just
219 * allocated a new one if needed, so no possibility of read()
220 * returning zero, except for EOF */
222 block = track_get_block(tr, tr->bytes / BYTES_PER_PCM_BLOCK);
224 if(tr->cache_read)
225 r = cache_read_block(tr->cache_read, block->pcm, TRACK_BLOCK_PCM_LENGTH);
226 else
228 guint used = track_get_last_block_used_bytes(tr);
230 r = read(tr->fd, (char*)block->pcm + used,
231 BYTES_PER_PCM_BLOCK - used);
234 if(r == -1) {
235 if(errno == EAGAIN)
236 return 0;
237 perror("read");
238 return -1;
240 } else if(r == 0) {
241 m = BYTES_PER_PCM_BLOCK * tr->blocks / 1024;
242 fprintf(stderr, "Track memory %zuKb PCM, %zuKb PPM, %zuKb overview.\n",
243 m, m / TRACK_PPM_RES, m / TRACK_OVERVIEW_RES);
245 return 1;
248 /* Meter the audio which has just been read */
250 for(s = track_get_sample_count(tr); s < (tr->bytes + r) / BYTES_PER_SAMPLE; s++) {
251 ls = s % TRACK_BLOCK_SAMPLES;
253 v = (abs(block->pcm[ls * TRACK_CHANNELS])
254 + abs(block->pcm[ls * TRACK_CHANNELS]));
256 /* PPM-style fast meter approximation */
258 if(v > tr->ppm)
259 tr->ppm += (v - tr->ppm) >> 3;
260 else
261 tr->ppm -= (tr->ppm - v) >> 9;
263 block->ppm[ls / TRACK_PPM_RES] = tr->ppm >> 8;
265 /* Update the slow-metering overview. Fixed point arithmetic
266 * going on here */
268 w = v << 16;
270 if(w > tr->overview)
271 tr->overview += (w - tr->overview) >> 8;
272 else
273 tr->overview -= (tr->overview - w) >> 17;
275 block->overview[ls / TRACK_OVERVIEW_RES] = tr->overview >> 24;
278 tr->bytes += r;
280 return 0;
284 void track_init(struct track_t *tr, const char *importer)
286 tr->importer = importer;
287 tr->pid = 0;
289 tr->artist = NULL;
290 tr->title = NULL;
291 tr->name = NULL;
292 tr->cache = NULL;
293 tr->cache_read = NULL;
295 tr->blocks = 0;
296 tr->block = g_ptr_array_new();
297 tr->bytes = 0;
298 //tr->length = 0;
300 pthread_mutex_init(&tr->mx, 0); /* always returns zero */
302 tr->status = TRACK_STATUS_VALID;
305 /* Destroy this track from memory, and any child process */
307 int track_clear(struct track_t *tr)
310 int n;
313 /* Force a cleanup of whichever state we are in */
315 if(tr->status > TRACK_STATUS_VALID) {
316 if(tr->status == TRACK_STATUS_IMPORTING) {
317 if(!tr->cache_read && kill(tr->pid, SIGKILL) == -1) {
318 perror("kill");
319 return -1;
322 if(stop_import(tr) == -1)
323 return -1;
326 track_blocks_shrink(tr, 0);
328 g_ptr_array_free(tr->block, FALSE);
330 if(pthread_mutex_destroy(&tr->mx) == EBUSY) {
331 fprintf(stderr, "Track busy on destroy.\n");
332 return -1;
335 cached_track_destroy(tr->cache);
336 tr->cache = NULL;
338 return 0;
342 /* Return the number of file descriptors which should be watched for
343 * this track, and fill pe */
345 int track_pollfd(struct track_t *tr, struct pollfd *pe)
347 int r;
349 LOCK(tr);
351 if(tr->status == TRACK_STATUS_IMPORTING && !tr->eof) {
352 pe->fd = tr->fd;
353 pe->revents = 0;
354 pe->events = POLLIN | POLLHUP | POLLERR;
355 tr->pe = pe;
356 r = 1;
357 } else {
358 tr->pe = NULL;
359 r = 0;
362 UNLOCK(tr);
363 return r;
367 /* Handle any activity on this track, whatever the current state */
369 int track_handle(struct track_t *tr)
371 int r;
373 /* Only one thread is allowed to call this function, and it owns
374 * the poll entry */
376 if(!tr->pe || !tr->pe->revents)
377 return 0;
379 LOCK(tr);
381 if(tr->status == TRACK_STATUS_IMPORTING && !tr->eof) {
382 r = read_from_pipe(tr);
383 if(r != 0)
385 if(r > 0)
386 cached_track_write(tr->cache, tr);
388 guint currentUsedBlocks = track_get_used_blocks(tr);
390 //currently we have more blocks allocated
391 //than are in use
392 if(currentUsedBlocks < tr->blocks)
394 guint averageUsedBlocks = get_average_blocks(track_get_used_blocks(tr));
395 track_blocks_shrink(tr, MAX(averageUsedBlocks, currentUsedBlocks));
398 tr->eof = 1;
402 UNLOCK(tr);
403 return 0;
407 /* A request to begin importing a new track. Can be called when the
408 * track is in any state */
410 int track_import(struct track_t *tr, const char *path)
412 int r;
414 LOCK(tr);
416 /* Abort any running import process */
418 if(tr->status != TRACK_STATUS_VALID) {
419 if(tr->status == TRACK_STATUS_IMPORTING) {
420 if(!tr->cache_read && kill(tr->pid, SIGTERM) == -1) {
421 perror("kill");
422 UNLOCK(tr);
423 return -1;
426 if(stop_import(tr) == -1) {
427 UNLOCK(tr);
428 return -1;
432 /* Start the new import process */
434 r = start_import(tr, path);
435 if(r < 0) {
436 UNLOCK(tr);
437 return -1;
439 tr->status = TRACK_STATUS_IMPORTING;
441 UNLOCK(tr);
443 rig_awaken(tr->rig);
445 return 0;
449 /* Do any waiting for completion of importer process if needed */
451 int track_wait(struct track_t *tr)
453 LOCK(tr);
455 if(tr->status == TRACK_STATUS_IMPORTING && tr->eof) {
456 stop_import(tr);
457 tr->status = TRACK_STATUS_VALID;
460 UNLOCK(tr);
461 return 0;