1
+ /*
2
+ * Copyright (C) 2020 The Android Open Source Project
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ package curves
17
+
18
+ import java.util.*
19
+
20
+ /* *
21
+ * This generates variable frequency oscillation curves
22
+ *
23
+ *
24
+ */
25
+ class Cycles {
26
+ private var mPeriod = floatArrayOf()
27
+ private var mPosition = floatArrayOf()
28
+ private lateinit var mArea: FloatArray
29
+ private var mCustomType: String? = null
30
+ private var mCustomCurve: MonoSpline ? = null
31
+ private var mType = 0
32
+ private var mPI2 = Math .PI .toFloat() * 2
33
+ private var mNormalized = false
34
+ override fun toString (): String {
35
+ return " pos =" + Arrays .toString(mPosition) + " period=" + Arrays .toString(mPeriod)
36
+ }
37
+
38
+ // @TODO: add description
39
+ fun setType (type : Int , customType : String ) {
40
+ mType = type
41
+ mCustomType = customType
42
+ if (mCustomType != null ) {
43
+ mCustomCurve = buildWave(customType)
44
+ }
45
+ }
46
+
47
+ // @TODO: add description
48
+ fun addPoint (position : Float , period : Float ) {
49
+ val len = mPeriod.size + 1
50
+ var j = Arrays .binarySearch(mPosition, position)
51
+ if (j < 0 ) {
52
+ j = - j - 1
53
+ }
54
+ mPosition = Arrays .copyOf(mPosition, len)
55
+ mPeriod = Arrays .copyOf(mPeriod, len)
56
+ mArea = FloatArray (len)
57
+ System .arraycopy(mPosition, j, mPosition, j + 1 , len - j - 1 )
58
+ mPosition[j] = position
59
+ mPeriod[j] = period
60
+ mNormalized = false
61
+ }
62
+
63
+ /* *
64
+ * After adding point every thing must be normalized
65
+ */
66
+ fun normalize () {
67
+ var totalArea = 0f
68
+ var totalCount = 0f
69
+ for (i in mPeriod.indices) {
70
+ totalCount + = mPeriod[i]
71
+ }
72
+ for (i in 1 until mPeriod.size) {
73
+ val h = (mPeriod[i - 1 ] + mPeriod[i]) / 2
74
+ val w = mPosition[i] - mPosition[i - 1 ]
75
+ totalArea = totalArea + w * h
76
+ }
77
+ // scale periods to normalize it
78
+ for (i in mPeriod.indices) {
79
+ mPeriod[i] * = (totalCount / totalArea)
80
+ }
81
+ mArea[0 ] = 0f
82
+ for (i in 1 until mPeriod.size) {
83
+ val h = (mPeriod[i - 1 ] + mPeriod[i]) / 2
84
+ val w = mPosition[i] - mPosition[i - 1 ]
85
+ mArea[i] = mArea[i - 1 ] + w * h
86
+ }
87
+ mNormalized = true
88
+ }
89
+
90
+ fun getP (time : Float ): Float {
91
+ var time = time
92
+ if (time < 0 ) {
93
+ time = 0f
94
+ } else if (time > 1 ) {
95
+ time = 1f
96
+ }
97
+ var index = Arrays .binarySearch(mPosition, time)
98
+ var p = 0f
99
+ if (index > 0 ) {
100
+ p = 1f
101
+ } else if (index != 0 ) {
102
+ index = - index - 1
103
+ val t = time
104
+ val m = ((mPeriod[index] - mPeriod[index - 1 ])
105
+ / (mPosition[index] - mPosition[index - 1 ]))
106
+ p = mArea[index - 1 ] + (mPeriod[index - 1 ] - m * mPosition[index - 1 ]) * (t - mPosition[index - 1 ]) + m * (t * t - mPosition[index - 1 ] * mPosition[index - 1 ]) / 2
107
+ }
108
+ return p
109
+ }
110
+
111
+ // @TODO: add description
112
+ fun getValue (time : Float , phase : Float ): Float {
113
+ val angle = phase + getP(time) // angle is / by 360
114
+ return when (mType) {
115
+ SIN_WAVE -> Math .sin((mPI2 * angle).toDouble()).toFloat()
116
+ SQUARE_WAVE -> Math .signum(0.5 - angle % 1 ).toFloat()
117
+ TRIANGLE_WAVE -> 1 - Math .abs((angle * 4 + 1 ) % 4 - 2 )
118
+ SAW_WAVE -> (angle * 2 + 1 ) % 2 - 1
119
+ REVERSE_SAW_WAVE -> 1 - (angle * 2 + 1 ) % 2
120
+ COS_WAVE -> Math .cos((mPI2 * (phase + angle)).toDouble()).toFloat()
121
+ BOUNCE -> {
122
+ val x = 1 - Math .abs(angle * 4 % 4 - 2 )
123
+ 1 - x * x
124
+ }
125
+
126
+ CUSTOM -> mCustomCurve!! .getPos((angle % 1 ), 0 )
127
+ else -> Math .sin((mPI2 * angle).toDouble()).toFloat()
128
+ }
129
+ }
130
+
131
+ fun getDP (time : Float ): Float {
132
+ var time = time
133
+ if (time <= 0 ) {
134
+ time = 0.00001f
135
+ } else if (time >= 1 ) {
136
+ time = .999999f
137
+ }
138
+ var index = Arrays .binarySearch(mPosition, time)
139
+ var p = 0f
140
+ if (index > 0 ) {
141
+ return 0f
142
+ }
143
+ if (index != 0 ) {
144
+ index = - index - 1
145
+ val t = time
146
+ val m = ((mPeriod[index] - mPeriod[index - 1 ])
147
+ / (mPosition[index] - mPosition[index - 1 ]))
148
+ p = m * t + (mPeriod[index - 1 ] - m * mPosition[index - 1 ])
149
+ }
150
+ return p
151
+ }
152
+
153
+ // @TODO: add description
154
+ fun getSlope (time : Float , phase : Float , dphase : Float ): Float {
155
+ val angle = phase + getP(time)
156
+ val dangle_dtime = getDP(time) + dphase
157
+ return when (mType) {
158
+ SIN_WAVE -> (mPI2 * dangle_dtime * Math .cos((mPI2 * angle).toDouble())).toFloat()
159
+ SQUARE_WAVE -> 0f
160
+ TRIANGLE_WAVE -> 4 * dangle_dtime * Math .signum((angle * 4 + 3 ) % 4 - 2 )
161
+ SAW_WAVE -> dangle_dtime * 2
162
+ REVERSE_SAW_WAVE -> - dangle_dtime * 2
163
+ COS_WAVE -> (- mPI2 * dangle_dtime * Math .sin((mPI2 * angle).toDouble())).toFloat()
164
+ BOUNCE -> 4 * dangle_dtime * ((angle * 4 + 2 ) % 4 - 2 )
165
+ CUSTOM -> mCustomCurve!! .getSlope((angle % 1 ), 0 )
166
+ else -> (mPI2 * dangle_dtime * Math .cos((mPI2 * angle).toDouble())).toFloat()
167
+ }
168
+ }
169
+
170
+ companion object {
171
+ var TAG = " Oscillator"
172
+ const val SIN_WAVE = 0 // theses must line up with attributes
173
+ const val SQUARE_WAVE = 1
174
+ const val TRIANGLE_WAVE = 2
175
+ const val SAW_WAVE = 3
176
+ const val REVERSE_SAW_WAVE = 4
177
+ const val COS_WAVE = 5
178
+ const val BOUNCE = 6
179
+ const val CUSTOM = 7
180
+
181
+ /* *
182
+ * This builds a monotonic spline to be used as a wave function
183
+ */
184
+ fun buildWave (configString : String ): MonoSpline {
185
+ // done this way for efficiency
186
+ val values = FloatArray (configString.length / 2 )
187
+ var start = configString.indexOf(' (' ) + 1
188
+ var off1 = configString.indexOf(' ,' , start)
189
+ var count = 0
190
+ while (off1 != - 1 ) {
191
+ val tmp = configString.substring(start, off1).trim { it <= ' ' }
192
+ values[count++ ] = tmp.toFloat()
193
+ off1 = configString.indexOf(' ,' , off1 + 1 .also { start = it })
194
+ }
195
+ off1 = configString.indexOf(' )' , start)
196
+ val tmp = configString.substring(start, off1).trim { it <= ' ' }
197
+ values[count++ ] = tmp.toFloat()
198
+ return buildWave(Arrays .copyOf(values, count))
199
+ }
200
+
201
+ private fun buildWave (values : FloatArray ): MonoSpline {
202
+ val length = values.size * 3 - 2
203
+ val len = values.size - 1
204
+ val gap = 1.0f / len
205
+ val points = ArrayList <FloatArray >(length)
206
+ val time = FloatArray (length)
207
+ for (i in values.indices) {
208
+ val v = values[i]
209
+ points[i + len][0 ] = v
210
+ time[i + len] = i * gap
211
+ if (i > 0 ) {
212
+ points[i + len * 2 ][0 ] = v + 1
213
+ time[i + len * 2 ] = i * gap + 1
214
+ points[i - 1 ][0 ] = v - 1 - gap
215
+ time[i - 1 ] = i * gap + - 1 - gap
216
+ }
217
+ }
218
+ return MonoSpline (time, points)
219
+ }
220
+ }
221
+ }
0 commit comments