Replace manual option handling by use of glib
[lcid-xwax.git] / player.c
bloba4baa3c31bb7402dfd9f1fa2c836e791b7008ba9
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 <math.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
25 #include "device.h"
26 #include "player.h"
27 #include "track.h"
28 #include "timecoder.h"
31 /* Bend playback speed to compensate for the difference between our
32 * current position and that given by the timecode */
34 #define SYNC_TIME (PLAYER_RATE / 2) /* time taken to reach sync */
35 #define SYNC_PITCH 0.05 /* don't sync at low pitches */
38 /* If the difference between our current position and that given by
39 * the timecode is greater than this value, recover by jumping
40 * straight to the position given by the timecode. */
42 #define SKIP_THRESHOLD (PLAYER_RATE / 8) /* before dropping audio */
45 /* Smooth the pitch returned from the timecoder. Smooth it too much
46 * and we end up having to use sync to bend in line with the
47 * timecode. Smooth too little and there will be audible
48 * distortion. */
50 #define SMOOTHING (128 / DEVICE_FRAME) /* result value is in frames */
53 /* The base volume level. A value of 1.0 leaves no headroom to play
54 * louder when the record is going faster than 1.0. */
56 #define VOLUME (7.0/8)
59 #define SQ(x) ((x)*(x))
62 /* Build a block of PCM audio, resampled from the track. Always builds
63 * 'frame' samples, and returns the number of samples to advance the
64 * track position by. This is just a basic resampler which has
65 * particular problems where pitch > 1.0. */
67 static double build_pcm(signed short *pcm, int frame, const struct track_t *tr,
68 double position, float pitch, float start_vol,
69 float end_vol)
71 signed short a, b, *pa, *pb, *pcm_s;
72 int s, c, sa, sb;
73 double sample;
74 float f, vol;
76 for(s = 0; s < frame; s++) {
77 sample = position + (double)pitch * s;
78 pcm_s = pcm + s * PLAYER_CHANNELS;
80 /* Calculate the pcm samples which sample falls
81 * inbetween. sample can be positive or negative */
83 sa = (int)sample;
84 if(sample < 0.0)
85 sa--;
86 sb = sa + 1;
87 f = sample - sa;
89 vol = start_vol + ((end_vol - start_vol) * s / frame);
91 if(sa >= 0 && sa < track_get_sample_count(tr))
92 pa = track_get_sample(tr, sa);
93 else
94 pa = NULL;
96 if(sb >= 0 && sb < track_get_sample_count(tr))
97 pb = track_get_sample(tr, sb);
98 else
99 pb = NULL;
101 for(c = 0; c < PLAYER_CHANNELS; c++) {
102 a = pa ? *(pa + c) : 0;
103 b = pb ? *(pb + c) : 0;
104 *(pcm_s + c) = vol * ((1.0 - f) * a + f * b);
108 return (double)pitch * frame;
112 void player_init(struct player_t *pl)
114 pl->reconnect = 0;
116 pl->position = 0.0;
117 pl->offset = 0;
118 pl->target_position = -1;
119 pl->last_difference = 0;
121 pl->pitch = 1.0;
122 pl->sync_pitch = 1.0;
123 pl->volume = 0.0;
125 pl->track = NULL;
126 pl->timecoder = NULL;
130 void player_clear(struct player_t *pl)
135 void player_connect_timecoder(struct player_t *pl, struct timecoder_t *tc)
137 pl->timecoder = tc;
138 pl->reconnect = 1;
139 pl->safe = timecoder_get_safe(tc);
143 void player_disconnect_timecoder(struct player_t *pl)
145 pl->timecoder = NULL;
149 /* Synchronise this player to the timecode. This function calls
150 * timecoder_get_pitch() and should be the only place which does for
151 * any given timecoder_t */
153 static int sync_to_timecode(struct player_t *pl)
155 float pitch;
156 unsigned int tcpos;
157 signed int timecode;
158 int when, alive, pitch_unavailable;
160 timecode = timecoder_get_position(pl->timecoder, &when);
161 alive = timecoder_get_alive(pl->timecoder);
162 pitch_unavailable = timecoder_get_pitch(pl->timecoder, &pitch);
164 /* Instruct the caller to disconnect the timecoder if the needle
165 * is outside the 'safe' zone of the record */
167 if(timecode != -1 && timecode > pl->safe)
168 return -1;
170 /* If the timecoder is alive and can tell us a current pitch based
171 * on the sine wave, then use it */
173 if(alive && !pitch_unavailable)
174 pl->target_pitch = pitch;
175 else if(!alive)
176 pl->target_pitch = 0.0;
178 /* If we can read an absolute time from the timecode, then use it */
180 if(timecode == -1)
181 pl->target_position = -1;
183 else {
184 tcpos = (long long)timecode * TIMECODER_RATE
185 / timecoder_get_resolution(pl->timecoder);
187 pl->target_position = tcpos + pl->target_pitch * when;
190 /* Apply filtering in every sync cycle */
192 pl->pitch = (pl->pitch * (SMOOTHING - 1) + pl->target_pitch) / SMOOTHING;
194 return 0;
198 /* Return to the zero of the track */
200 int player_recue(struct player_t *pl)
202 pl->offset = pl->position;
203 return 0;
207 /* Get a block of PCM audio data to send to the soundcard. */
209 int player_collect(struct player_t *pl, signed short *pcm, int samples)
211 double diff;
212 float target_volume;
214 if(pl->timecoder) {
215 if(sync_to_timecode(pl) == -1)
216 player_disconnect_timecoder(pl);
219 if(pl->target_position == -1)
220 pl->sync_pitch = 1.0;
221 else {
223 /* If reconnection has been requested, move the logical record
224 * on the vinyl so that the current position is right under
225 * the needle, and continue */
227 if(pl->reconnect) {
228 pl->offset += pl->target_position - pl->position;
229 pl->reconnect = 0;
232 /* Calculate the pitch compensation required to get us back on
233 * track with the absolute timecode position */
235 diff = pl->position - pl->target_position;
236 pl->last_difference = diff; /* to print in user interface */
238 if(fabs(diff) > SKIP_THRESHOLD) {
240 /* Jump the track to the time */
242 pl->position = pl->target_position;
243 pl->sync_pitch = 1.0;
245 fprintf(stderr, "Seek to new position %.0lf.\n", pl->position);
247 } else if(fabs(pl->pitch) > SYNC_PITCH) {
249 /* Pull the track into line. It wouldn't suprise me if
250 * there is a mistake in here. A simplification of
252 * move = samples * pl->pitch;
253 * end = diff - diff * samples / SYNC_TIME;
254 * pl->sync_pitch = (move + end - diff) / move; */
256 /* Divide by near-zero caught by pl->pitch > SYNC_PITCH */
258 pl->sync_pitch = 1 - diff / (SYNC_TIME * pl->pitch);
261 /* Acknowledge that we've accounted for the target position */
263 pl->target_position = -1;
266 target_volume = fabs(pl->pitch) * VOLUME;
267 if(target_volume > 1.0)
268 target_volume = 1.0;
270 /* Sync pitch is applied post-filtering */
272 pl->position += build_pcm(pcm, samples, pl->track,
273 pl->position - pl->offset,
274 pl->pitch * pl->sync_pitch,
275 pl->volume, target_volume);
277 pl->volume = target_volume;
279 return 0;
283 void player_connect_track(struct player_t *pl, struct track_t *tr)
285 pl->track = tr;