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.
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,
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
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
,
71 signed short a
, b
, *pa
, *pb
, *pcm_s
;
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 */
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
);
96 if(sb
>= 0 && sb
< track_get_sample_count(tr
))
97 pb
= track_get_sample(tr
, sb
);
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
)
118 pl
->target_position
= -1;
119 pl
->last_difference
= 0;
122 pl
->sync_pitch
= 1.0;
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
)
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
)
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
)
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
;
176 pl
->target_pitch
= 0.0;
178 /* If we can read an absolute time from the timecode, then use it */
181 pl
->target_position
= -1;
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
;
198 /* Return to the zero of the track */
200 int player_recue(struct player_t
*pl
)
202 pl
->offset
= pl
->position
;
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
)
215 if(sync_to_timecode(pl
) == -1)
216 player_disconnect_timecoder(pl
);
219 if(pl
->target_position
== -1)
220 pl
->sync_pitch
= 1.0;
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 */
228 pl
->offset
+= pl
->target_position
- pl
->position
;
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)
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
;
283 void player_connect_track(struct player_t
*pl
, struct track_t
*tr
)