1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chromecast/media/base/slew_volume.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <cstring>
10
11 #include "base/check_op.h"
12 #include "media/base/vector_math.h"
13
14 namespace {
15
16 // The time to slew from current volume to target volume.
17 const int kMaxSlewTimeMs = 100;
18 const int kDefaultSampleRate = 44100;
19
20 } // namespace
21
22 struct FMACTraits {
ProcessBulkDataFMACTraits23 static void ProcessBulkData(const float* src,
24 float volume,
25 int frames,
26 float* dest) {
27 ::media::vector_math::FMAC(src, volume, frames, dest);
28 }
29
ProcessSingleDatumFMACTraits30 static void ProcessSingleDatum(const float* src, float volume, float* dest) {
31 (*dest) += (*src) * volume;
32 }
33
ProcessZeroVolumeFMACTraits34 static void ProcessZeroVolume(const float* src, int frames, float* dest) {}
35
ProcessUnityVolumeFMACTraits36 static void ProcessUnityVolume(const float* src, int frames, float* dest) {
37 ProcessBulkData(src, 1.0, frames, dest);
38 }
39 };
40
41 struct FMULTraits {
ProcessBulkDataFMULTraits42 static void ProcessBulkData(const float* src,
43 float volume,
44 int frames,
45 float* dest) {
46 ::media::vector_math::FMUL(src, volume, frames, dest);
47 }
48
ProcessSingleDatumFMULTraits49 static void ProcessSingleDatum(const float* src, float volume, float* dest) {
50 (*dest) = (*src) * volume;
51 }
52
ProcessZeroVolumeFMULTraits53 static void ProcessZeroVolume(const float* src, int frames, float* dest) {
54 std::memset(dest, 0, frames * sizeof(*dest));
55 }
56
ProcessUnityVolumeFMULTraits57 static void ProcessUnityVolume(const float* src, int frames, float* dest) {
58 if (src == dest) {
59 return;
60 }
61 std::memcpy(dest, src, frames * sizeof(*dest));
62 }
63 };
64
65 namespace chromecast {
66 namespace media {
67
SlewVolume()68 SlewVolume::SlewVolume() : SlewVolume(kMaxSlewTimeMs) {}
69
SlewVolume(int max_slew_time_ms)70 SlewVolume::SlewVolume(int max_slew_time_ms)
71 : SlewVolume(max_slew_time_ms, false) {}
72
SlewVolume(int max_slew_time_ms,bool use_cosine_slew)73 SlewVolume::SlewVolume(int max_slew_time_ms, bool use_cosine_slew)
74 : sample_rate_(kDefaultSampleRate),
75 max_slew_time_ms_(max_slew_time_ms),
76 max_slew_per_sample_(1000.0 / (max_slew_time_ms_ * sample_rate_)),
77 use_cosine_slew_(use_cosine_slew) {}
78
SetSampleRate(int sample_rate)79 void SlewVolume::SetSampleRate(int sample_rate) {
80 CHECK_GT(sample_rate, 0);
81
82 sample_rate_ = sample_rate;
83 SetVolume(volume_scale_);
84 }
85
SetVolume(double volume_scale)86 void SlewVolume::SetVolume(double volume_scale) {
87 volume_scale_ = volume_scale;
88 if (interrupted_) {
89 current_volume_ = volume_scale_;
90 last_starting_volume_ = current_volume_;
91 }
92
93 // Slew rate should be volume_to_slew / slew_time / sample_rate, but use a
94 // minimum volume_to_slew of 0.1 to avoid very small slew per sample.
95 double volume_diff =
96 std::max(0.1, std::fabs(volume_scale_ - current_volume_));
97 max_slew_per_sample_ =
98 volume_diff * 1000.0 / (max_slew_time_ms_ * sample_rate_);
99
100 if (use_cosine_slew_) {
101 // Set initial state for cosine slew. Cosine fading always lasts
102 // max_slew_time_ms_.
103 slew_counter_ = max_slew_time_ms_ * 0.001 * sample_rate_;
104 slew_angle_ = sin(M_PI / slew_counter_);
105 slew_offset_ = (current_volume_ + volume_scale_) * 0.5;
106 slew_cos_ = (current_volume_ - volume_scale_) * 0.5;
107 slew_sin_ = 0.0;
108 }
109 }
110
LastBufferMaxMultiplier()111 float SlewVolume::LastBufferMaxMultiplier() {
112 return std::max(current_volume_, last_starting_volume_);
113 }
114
SetMaxSlewTimeMs(int max_slew_time_ms)115 void SlewVolume::SetMaxSlewTimeMs(int max_slew_time_ms) {
116 CHECK_GE(max_slew_time_ms, 0);
117
118 max_slew_time_ms_ = max_slew_time_ms;
119 }
120
Interrupted()121 void SlewVolume::Interrupted() {
122 interrupted_ = true;
123 current_volume_ = volume_scale_;
124 }
125
ProcessFMAC(bool repeat_transition,const float * src,int frames,int channels,float * dest)126 void SlewVolume::ProcessFMAC(bool repeat_transition,
127 const float* src,
128 int frames,
129 int channels,
130 float* dest) {
131 ProcessData<FMACTraits>(repeat_transition, src, frames, channels, dest);
132 }
133
ProcessFMUL(bool repeat_transition,const float * src,int frames,int channels,float * dest)134 void SlewVolume::ProcessFMUL(bool repeat_transition,
135 const float* src,
136 int frames,
137 int channels,
138 float* dest) {
139 ProcessData<FMULTraits>(repeat_transition, src, frames, channels, dest);
140 }
141
142 template <typename Traits>
ProcessData(bool repeat_transition,const float * src,int frames,int channels,float * dest)143 void SlewVolume::ProcessData(bool repeat_transition,
144 const float* src,
145 int frames,
146 int channels,
147 float* dest) {
148 DCHECK(src);
149 DCHECK(dest);
150 // Ensure |src| and |dest| are 16-byte aligned.
151 DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(src) &
152 (::media::vector_math::kRequiredAlignment - 1));
153 DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(dest) &
154 (::media::vector_math::kRequiredAlignment - 1));
155
156 if (!frames) {
157 return;
158 }
159
160 interrupted_ = false;
161 if (repeat_transition) {
162 current_volume_ = last_starting_volume_;
163 } else {
164 last_starting_volume_ = current_volume_;
165 }
166
167 if (current_volume_ == volume_scale_) {
168 if (current_volume_ == 0.0) {
169 Traits::ProcessZeroVolume(src, frames * channels, dest);
170 return;
171 }
172 if (current_volume_ == 1.0) {
173 Traits::ProcessUnityVolume(src, frames * channels, dest);
174 return;
175 }
176 Traits::ProcessBulkData(src, current_volume_, frames * channels, dest);
177 return;
178 }
179
180 if (use_cosine_slew_) {
181 int slew_frames = std::min(slew_counter_, frames);
182 frames -= slew_frames;
183 slew_counter_ -= slew_frames;
184 for (; slew_frames > 0; --slew_frames) {
185 slew_cos_ -= slew_sin_ * slew_angle_;
186 slew_sin_ += slew_cos_ * slew_angle_;
187 current_volume_ = std::min(1.0, std::max(0.0, slew_offset_ + slew_cos_));
188 for (int i = 0; i < channels; ++i) {
189 Traits::ProcessSingleDatum(src, current_volume_, dest);
190 ++src;
191 ++dest;
192 }
193 }
194 if (!slew_counter_) {
195 current_volume_ = volume_scale_;
196 }
197 } else if (current_volume_ < volume_scale_) {
198 do {
199 for (int i = 0; i < channels; ++i) {
200 Traits::ProcessSingleDatum(src, current_volume_, dest);
201 ++src;
202 ++dest;
203 }
204 --frames;
205 current_volume_ += max_slew_per_sample_;
206 } while (current_volume_ < volume_scale_ && frames);
207 current_volume_ = std::min(current_volume_, volume_scale_);
208 } else { // current_volume_ > volume_scale_
209 do {
210 for (int i = 0; i < channels; ++i) {
211 Traits::ProcessSingleDatum(src, current_volume_, dest);
212 ++src;
213 ++dest;
214 }
215 --frames;
216 current_volume_ -= max_slew_per_sample_;
217 } while (current_volume_ > volume_scale_ && frames);
218 current_volume_ = std::max(current_volume_, volume_scale_);
219 }
220 while (frames && (reinterpret_cast<uintptr_t>(src) &
221 (::media::vector_math::kRequiredAlignment - 1))) {
222 for (int i = 0; i < channels; ++i) {
223 Traits::ProcessSingleDatum(src, current_volume_, dest);
224 ++src;
225 ++dest;
226 }
227 --frames;
228 }
229 if (!frames) {
230 return;
231 }
232 Traits::ProcessBulkData(src, current_volume_, frames * channels, dest);
233 }
234
235 } // namespace media
236 } // namespace chromecast
237