diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2013-08-23 14:18:08 -0400 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2014-03-08 08:04:41 +0100 |
commit | 61d0583384b81de28498544ea3ec2e5c8eba42be (patch) | |
tree | 0901394a414674d70c82ee56cdfece1c7cce682a | |
parent | 62b10e383cc7439508e57751dbcdf0d8a617cf1a (diff) | |
download | rockbox-61d0583384b81de28498544ea3ec2e5c8eba42be.tar.gz rockbox-61d0583384b81de28498544ea3ec2e5c8eba42be.zip |
Greatly reduce volume-change zipper artifacts with SW volume.
Uses a cosine factor to smoothly shift the PCM level from the old level
to the new one over the length of a frame.
Implements indirect calls to PCM scaling function instead of testing
conditions on every callback, cleanly assigning a different call to
do the volume transition. The volume change call then assigns the final
scaling function.
Change-Id: If1004b92a91c5ca766dd0e4014ec274636e8ed26
Reviewed-on: http://gerrit.rockbox.org/763
Reviewed-by: Michael Sevakis <jethead71@rockbox.org>
Tested: Michael Sevakis <jethead71@rockbox.org>
-rw-r--r-- | firmware/export/pcm-internal.h | 1 | ||||
-rw-r--r-- | firmware/pcm.c | 4 | ||||
-rw-r--r-- | firmware/pcm_sw_volume.c | 131 |
3 files changed, 108 insertions, 28 deletions
diff --git a/firmware/export/pcm-internal.h b/firmware/export/pcm-internal.h index 2b73f64ef6..7670f99f04 100644 --- a/firmware/export/pcm-internal.h +++ b/firmware/export/pcm-internal.h | |||
@@ -57,6 +57,7 @@ void pcm_sw_volume_copy_buffer(void *dst, const void *src, size_t size); | |||
57 | #endif | 57 | #endif |
58 | #endif /* PCM_SW_VOLUME_UNBUFFERED */ | 58 | #endif /* PCM_SW_VOLUME_UNBUFFERED */ |
59 | 59 | ||
60 | void pcm_sync_pcm_factors(void); | ||
60 | #endif /* HAVE_SW_VOLUME_CONTROL */ | 61 | #endif /* HAVE_SW_VOLUME_CONTROL */ |
61 | 62 | ||
62 | #define PCM_SAMPLE_SIZE (2 * sizeof (int16_t)) | 63 | #define PCM_SAMPLE_SIZE (2 * sizeof (int16_t)) |
diff --git a/firmware/pcm.c b/firmware/pcm.c index 60ccdbd2fc..640bb7830f 100644 --- a/firmware/pcm.c +++ b/firmware/pcm.c | |||
@@ -111,6 +111,10 @@ void pcm_play_stop_int(void); | |||
111 | ** pcm_sw_volume.c **/ | 111 | ** pcm_sw_volume.c **/ |
112 | static inline void pcm_play_dma_start_int(const void *addr, size_t size) | 112 | static inline void pcm_play_dma_start_int(const void *addr, size_t size) |
113 | { | 113 | { |
114 | #ifdef HAVE_SW_VOLUME_CONTROL | ||
115 | /* Smoothed transition might not have happened so sync now */ | ||
116 | pcm_sync_pcm_factors(); | ||
117 | #endif | ||
114 | pcm_play_dma_start(addr, size); | 118 | pcm_play_dma_start(addr, size); |
115 | } | 119 | } |
116 | 120 | ||
diff --git a/firmware/pcm_sw_volume.c b/firmware/pcm_sw_volume.c index eb77fe0c6b..8b6c9220fd 100644 --- a/firmware/pcm_sw_volume.c +++ b/firmware/pcm_sw_volume.c | |||
@@ -35,62 +35,127 @@ static uint32_t prescale_factor = PCM_FACTOR_UNITY; | |||
35 | #endif /* AUDIOHW_HAVE_PRESCALER */ | 35 | #endif /* AUDIOHW_HAVE_PRESCALER */ |
36 | 36 | ||
37 | /* final pcm scaling factors */ | 37 | /* final pcm scaling factors */ |
38 | static uint32_t pcm_new_factor_l = 0, pcm_new_factor_r = 0; | ||
38 | static uint32_t pcm_factor_l = 0, pcm_factor_r = 0; | 39 | static uint32_t pcm_factor_l = 0, pcm_factor_r = 0; |
40 | static typeof (memcpy) *pcm_scaling_fn = NULL; | ||
39 | 41 | ||
40 | /*** | 42 | /*** |
41 | ** Volume scaling routine | 43 | ** Volume scaling routines |
42 | ** If unbuffered, called externally by pcm driver | 44 | ** If unbuffered, called externally by pcm driver |
43 | **/ | 45 | **/ |
44 | 46 | ||
45 | /* TODO: #include CPU-optimized routines and move this to /firmware/asm */ | 47 | /* TODO: #include CPU-optimized routines and move this to /firmware/asm */ |
46 | |||
47 | #if PCM_SW_VOLUME_FRACBITS <= 16 | 48 | #if PCM_SW_VOLUME_FRACBITS <= 16 |
48 | #define PCM_F_T int32_t | 49 | #define PCM_F_T int32_t |
49 | #else | 50 | #else |
50 | #define PCM_F_T int64_t /* Requires large integer math */ | 51 | #define PCM_F_T int64_t /* Requires large integer math */ |
51 | #endif /* PCM_SW_VOLUME_FRACBITS */ | 52 | #endif /* PCM_SW_VOLUME_FRACBITS */ |
52 | 53 | ||
54 | /* Scale and round sample by PCM factor */ | ||
53 | static inline int32_t pcm_scale_sample(PCM_F_T f, int32_t s) | 55 | static inline int32_t pcm_scale_sample(PCM_F_T f, int32_t s) |
54 | { | 56 | { |
55 | return (f * s + (PCM_F_T)PCM_FACTOR_UNITY/2) >> PCM_SW_VOLUME_FRACBITS; | 57 | return (f * s + (PCM_F_T)PCM_FACTOR_UNITY/2) >> PCM_SW_VOLUME_FRACBITS; |
56 | } | 58 | } |
57 | 59 | ||
58 | /* Copies buffer with volume scaling applied */ | 60 | /* Both UNITY, use direct copy */ |
61 | /* static void * memcpy(void *dst, const void *src, size_t size); */ | ||
62 | |||
63 | /* Either cut (both <= UNITY), no clipping needed */ | ||
64 | static void * pcm_scale_buffer_cut(void *dst, const void *src, size_t size) | ||
65 | { | ||
66 | int16_t *d = dst; | ||
67 | const int16_t *s = src; | ||
68 | uint32_t factor_l = pcm_factor_l, factor_r = pcm_factor_r; | ||
69 | |||
70 | while (size) | ||
71 | { | ||
72 | *d++ = pcm_scale_sample(factor_l, *s++); | ||
73 | *d++ = pcm_scale_sample(factor_r, *s++); | ||
74 | size -= PCM_SAMPLE_SIZE; | ||
75 | } | ||
76 | |||
77 | return dst; | ||
78 | } | ||
79 | |||
80 | /* Either boost (any > UNITY) requires clipping */ | ||
81 | static void * pcm_scale_buffer_boost(void *dst, const void *src, size_t size) | ||
82 | { | ||
83 | int16_t *d = dst; | ||
84 | const int16_t *s = src; | ||
85 | uint32_t factor_l = pcm_factor_l, factor_r = pcm_factor_r; | ||
86 | |||
87 | while (size) | ||
88 | { | ||
89 | *d++ = clip_sample_16(pcm_scale_sample(factor_l, *s++)); | ||
90 | *d++ = clip_sample_16(pcm_scale_sample(factor_r, *s++)); | ||
91 | size -= PCM_SAMPLE_SIZE; | ||
92 | } | ||
93 | |||
94 | return dst; | ||
95 | } | ||
96 | |||
97 | /* Transition the volume change smoothly across a frame */ | ||
98 | static void * pcm_scale_buffer_trans(void *dst, const void *src, size_t size) | ||
99 | { | ||
100 | int16_t *d = dst; | ||
101 | const int16_t *s = src; | ||
102 | uint32_t factor_l = pcm_factor_l, factor_r = pcm_factor_r; | ||
103 | |||
104 | /* Transition from the old value to the new value using an inverted cosinus | ||
105 | from PI..0 in order to minimize amplitude-modulated harmonics generation | ||
106 | (zipper effects). */ | ||
107 | uint32_t new_factor_l = pcm_new_factor_l; | ||
108 | uint32_t new_factor_r = pcm_new_factor_r; | ||
109 | |||
110 | int32_t diff_l = (int32_t)new_factor_l - (int32_t)factor_l; | ||
111 | int32_t diff_r = (int32_t)new_factor_r - (int32_t)factor_r; | ||
112 | |||
113 | for (size_t done = 0; done < size; done += PCM_SAMPLE_SIZE) | ||
114 | { | ||
115 | int32_t sweep = (1 << 14) - fp14_cos(180*done / size); /* 0.0..2.0 */ | ||
116 | uint32_t f_l = fp_mul(sweep, diff_l, 15) + factor_l; | ||
117 | uint32_t f_r = fp_mul(sweep, diff_r, 15) + factor_r; | ||
118 | *d++ = clip_sample_16(pcm_scale_sample(f_l, *s++)); | ||
119 | *d++ = clip_sample_16(pcm_scale_sample(f_r, *s++)); | ||
120 | } | ||
121 | |||
122 | /* Select steady-state operation */ | ||
123 | pcm_sync_pcm_factors(); | ||
124 | |||
125 | return dst; | ||
126 | } | ||
127 | |||
128 | /* Called by completion routine to scale the next buffer of samples */ | ||
59 | #ifndef PCM_SW_VOLUME_UNBUFFERED | 129 | #ifndef PCM_SW_VOLUME_UNBUFFERED |
60 | static inline | 130 | static inline |
61 | #endif | 131 | #endif |
62 | void pcm_sw_volume_copy_buffer(void *dst, const void *src, size_t size) | 132 | void pcm_sw_volume_copy_buffer(void *dst, const void *src, size_t size) |
63 | { | 133 | { |
64 | int16_t *d = dst; | 134 | pcm_scaling_fn(dst, src, size); |
65 | const int16_t *s = src; | 135 | } |
66 | uint32_t factor_l = pcm_factor_l; | ||
67 | uint32_t factor_r = pcm_factor_r; | ||
68 | 136 | ||
69 | if (factor_l == PCM_FACTOR_UNITY && factor_r == PCM_FACTOR_UNITY) | 137 | /* Assign the new scaling function for normal steady-state operation */ |
138 | void pcm_sync_pcm_factors(void) | ||
139 | { | ||
140 | uint32_t new_factor_l = pcm_new_factor_l; | ||
141 | uint32_t new_factor_r = pcm_new_factor_r; | ||
142 | |||
143 | pcm_factor_l = new_factor_l; | ||
144 | pcm_factor_r = new_factor_r; | ||
145 | |||
146 | if (new_factor_l == PCM_FACTOR_UNITY && | ||
147 | new_factor_r == PCM_FACTOR_UNITY) | ||
70 | { | 148 | { |
71 | /* Both unity */ | 149 | pcm_scaling_fn = memcpy; |
72 | memcpy(dst, src, size); | ||
73 | } | 150 | } |
74 | else if (LIKELY(factor_l <= PCM_FACTOR_UNITY && | 151 | else if (new_factor_l <= PCM_FACTOR_UNITY && |
75 | factor_r <= PCM_FACTOR_UNITY)) | 152 | new_factor_r <= PCM_FACTOR_UNITY) |
76 | { | 153 | { |
77 | /* Either cut, both <= UNITY */ | 154 | pcm_scaling_fn = pcm_scale_buffer_cut; |
78 | while (size) | ||
79 | { | ||
80 | *d++ = pcm_scale_sample(factor_l, *s++); | ||
81 | *d++ = pcm_scale_sample(factor_r, *s++); | ||
82 | size -= PCM_SAMPLE_SIZE; | ||
83 | } | ||
84 | } | 155 | } |
85 | else | 156 | else |
86 | { | 157 | { |
87 | /* Either positive gain, requires clipping */ | 158 | pcm_scaling_fn = pcm_scale_buffer_boost; |
88 | while (size) | ||
89 | { | ||
90 | *d++ = clip_sample_16(pcm_scale_sample(factor_l, *s++)); | ||
91 | *d++ = clip_sample_16(pcm_scale_sample(factor_r, *s++)); | ||
92 | size -= PCM_SAMPLE_SIZE; | ||
93 | } | ||
94 | } | 159 | } |
95 | } | 160 | } |
96 | 161 | ||
@@ -191,6 +256,9 @@ pcm_play_dma_status_callback_int(enum pcm_dma_status status) | |||
191 | /* Prefill double buffer and start pcm driver */ | 256 | /* Prefill double buffer and start pcm driver */ |
192 | static void start_pcm(bool reframe) | 257 | static void start_pcm(bool reframe) |
193 | { | 258 | { |
259 | /* Smoothed transition might not have happened so sync now */ | ||
260 | pcm_sync_pcm_factors(); | ||
261 | |||
194 | pcm_dbl_buf_num = 0; | 262 | pcm_dbl_buf_num = 0; |
195 | pcm_dbl_buf_size[0] = 0; | 263 | pcm_dbl_buf_size[0] = 0; |
196 | 264 | ||
@@ -272,8 +340,15 @@ static void pcm_sync_prescaler(void) | |||
272 | factor_l = fp_mul(prescale_factor, factor_l, PCM_SW_VOLUME_FRACBITS); | 340 | factor_l = fp_mul(prescale_factor, factor_l, PCM_SW_VOLUME_FRACBITS); |
273 | factor_r = fp_mul(prescale_factor, factor_r, PCM_SW_VOLUME_FRACBITS); | 341 | factor_r = fp_mul(prescale_factor, factor_r, PCM_SW_VOLUME_FRACBITS); |
274 | #endif | 342 | #endif |
275 | pcm_factor_l = MIN(factor_l, PCM_FACTOR_MAX); | 343 | pcm_play_lock(); |
276 | pcm_factor_r = MIN(factor_r, PCM_FACTOR_MAX); | 344 | |
345 | pcm_new_factor_l = MIN(factor_l, PCM_FACTOR_MAX); | ||
346 | pcm_new_factor_r = MIN(factor_r, PCM_FACTOR_MAX); | ||
347 | |||
348 | if (pcm_new_factor_l != pcm_factor_l || pcm_new_factor_r != pcm_factor_r) | ||
349 | pcm_scaling_fn = pcm_scale_buffer_trans; | ||
350 | |||
351 | pcm_play_unlock(); | ||
277 | } | 352 | } |
278 | 353 | ||
279 | #ifdef AUDIOHW_HAVE_PRESCALER | 354 | #ifdef AUDIOHW_HAVE_PRESCALER |