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