├── .gitignore ├── LICENSE ├── README.md ├── _exp.go ├── _exp_test.go ├── delay.go ├── delay_test.go ├── dispatch.go ├── dispatch_test.go ├── envel.go ├── envel_test.go ├── example ├── bass │ └── main.go ├── exp │ └── main.go ├── oscil │ └── main.go ├── piano │ ├── .gitignore │ ├── AndroidManifest.xml │ ├── README.md │ ├── assets │ │ ├── basic-frag.glsl │ │ ├── basic-vert.glsl │ │ └── material │ │ │ ├── glyphs.png │ │ │ ├── material-icons-black-mdpi.png │ │ │ └── source-code-pro-glyphs-sdf.png │ ├── key.go │ ├── main.go │ └── waveform.go ├── plugin │ ├── README.md │ ├── main.go │ └── reverb │ │ └── main.go └── rhythm │ └── main.go ├── filter.go ├── filter_test.go ├── freeze.go ├── freeze_test.go ├── gain.go ├── go.mod ├── go.sum ├── instrument.go ├── mixer.go ├── mixer_test.go ├── notes.go ├── notes_test.go ├── oscil.go ├── oscil_test.go ├── pan.go ├── pan_test.go ├── player.go ├── plot_test.go ├── ring.go ├── snd.go └── snd_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#*\# 3 | .\#* 4 | plots/ 5 | *.prof 6 | *.test 7 | *.so 8 | *.log 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Daniel Skinner 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Package snd [![GoDoc](https://godoc.org/dasa.cc/snd?status.svg)](https://godoc.org/dasa.cc/snd) 2 | 3 | Package snd provides methods and types for sound processing and synthesis. 4 | 5 | ``` 6 | go get dasa.cc/snd 7 | ``` 8 | 9 | ## Windows 10 | 11 | Tested with msys2. Additional setup required due to how gomobile currently links to openal on windows. This should go away in the future stepping away from gomobile's exp/al package. 12 | 13 | ``` 14 | pacman -S mingw-w64-x86_64-openal 15 | cd /mingw64/lib 16 | cp libopenal.a libOpenAL32.a 17 | cp libopenal.dll.a libOpenAL32.dll.a 18 | ``` 19 | 20 | ## Tests 21 | 22 | In addition to regular unit tests, there are plot tests that produce images 23 | saved to a plots/ folder. This depends on package gonum/plot and requires an 24 | additional tag flag to enable as follows: 25 | 26 | ``` 27 | go get github.com/gonum/plot 28 | go test -tags plot 29 | ``` 30 | 31 | ## SndObj 32 | 33 | This package was very much inspired by Victor Lazzarini's [SndObj Library](http://sndobj.sourceforge.net/) 34 | for which I spent countless hours enjoying, and without it I may never have come to program. 35 | -------------------------------------------------------------------------------- /_exp.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "math" 4 | 5 | // TODO reading below 6 | // http://wilsonminesco.com/16bitMathTables/ 7 | // https://en.wikipedia.org/wiki/Binary_scaling#Binary_angles 8 | 9 | /* 10 | 11 | Sampling 12 | 13 | F(nₖT) is a sampling function. 14 | 15 | n = {0, ..., k-1}, for integer values of k 16 | 17 | sample period 18 | TODO consider appending *2π here and elsewhere as relevant instead of shoehorning in last minute 19 | T = 1/fₛ 20 | 21 | sample frequency 22 | fₛ = 1/T 23 | 24 | max sample frequency without error ... 25 | α = max(F(n₁T), ..., F(nₖ-₁T)) 26 | this is not correct, samples do not describe max frequency without error 27 | because F(n) is not guaranteed to ever return any particular value. 28 | better might be to say that α is the max fₛ allowed by a system for 29 | normalized values. So ... 30 | 31 | all possible sampling frequencies 32 | S = {f₁, ..., fₛ}, set of integers 33 | 34 | selection from S 35 | α = S[s] 36 | 37 | minimum sample period without error 38 | t = 1/α 39 | 40 | let f equal any other selection from S 41 | 42 | let e represent error frequency 43 | 44 | r = max(fₛ, α) mod min(fₛ, α) 45 | for r != 0, e > 0 46 | e = (max(fₛ, α)-r) / max(fₛ, α) 47 | 48 | Example 49 | fₛ = 2 50 | α = 3 51 | r = 3 mod 2 = 1 52 | e = (3-1)/3 = 2/3 53 | 54 | So the error frequency is 2/3. This can be seen in the following: 55 | 56 | n = {1, 2, 3, 4, 5, 6} 57 | T = 1/fₛ = 1/2 58 | nT*2π = {π, 2π, 3π, 4π, 5π, 6π} 59 | 60 | t = 1/α = 1/3 61 | nt*2π = {2π/3, 4π/3, 2π, 8π/3, 10π/3, 4π} // !!! is this appropriate? to sample the same n and then compare? 62 | 63 | So α is stepping by 2/3 while fₛ is stepping by 1. These are not aligned. 64 | 65 | t() = {2π/3, 4π/3, 2π, 8π/3, 10π/3, 4π} 66 | F() = { π, 2π, 3π, 4π, 5π, 6π} 67 | 68 | t() = {2/3, 4/3, 6/3, 8/3, 10/3, 12/3} 69 | F() = {1/1, 2/1, 3/1, 4/1, 5/1, 6/1} 70 | 71 | So where the numerators at t[i] and F[i] are not evenly divisible, this means 72 | the F[i] result is out of phase. The result in this case is the angular frequency. 73 | The frequency is not incorrect, it's just in the wrong place, so its an error with 74 | the angular velocity. Perhaps unintuitively, the fix for this is to change angular 75 | frequency which in effect is actually making the sample period no longer a constant, 76 | b/c we are not changing it arbitrarily, we are setting it from an angular velocity 77 | calculated in a higher bandwidth than available in fₛ. 78 | 79 | Theory, if fₛ is scaled upward so that it aligns with α, the additional bandwidth 80 | allows us to calculate the appropriate angular frequency for that phase. In addition, 81 | we can locate the out-of-phase angular frequency to determine exactly how far out of phase 82 | it was. 83 | 84 | 1 2 3 4 5 6 85 | t() = -•-•-•-•-•-•------ 86 | F() = --•--•--•--•--•--• 87 | 88 | well, these results actually scale linearly so right now, not much to see here. 89 | maybe in an effort to simplify using values 2 and 3 instead of 65535,etc, i picked a bad example? 90 | */ 91 | 92 | /* 93 | the use of complex would allow for non-constant periods as the actual phase is part of the number 94 | 95 | TODO review use of complex numbers 96 | http://scipp.ucsc.edu/~johnson/phys160/ComplexNumbers.pdf // saved to ~/Documents/ 97 | Complex numbers are commonly used in electrical engineering, as well as in 98 | physics. In general they are used when some quantity has a phase as well as a 99 | magnitude. Such a situation occurs when one deals with sinusoidal oscillating voltage 100 | and current (other examples in physics include optics, where wave interference is 101 | important, and quantum mechanical wave 102 | functions). I want to emphasize that 103 | complex numbers are used to make 104 | calculations easier! 105 | */ 106 | 107 | /* 108 | TODO read about error function! looks fun 109 | https://en.wikipedia.org/wiki/Error_function 110 | */ 111 | 112 | type Frequency uint64 113 | 114 | // TODO somehow need to map 1Hz to 2*Pi 115 | func (f Frequency) Angular() Radian2 { 116 | return Radian2(f) * RPiTwo 117 | } 118 | 119 | // TODO maybe drop Giga? and add one below nano? Or ditch nanohertz for tetra 120 | // using uint16 rotations uint64 for Radians gives the max value of 121 | // 562,949,953,421,311 122 | // that's five groupings that can be supported 123 | // 44,100Hz is average 124 | // 192,000Hz is hidef for sure, 4 times 48kHz 125 | // 352,800Hz is extreme, 8 times the amount of 44,100 126 | // 5,644,800Hz is highest listed frequency 127 | // 128 | // I think 192kHz should be target so we want space for two times that, 384kHz. 129 | // But basically that means we can go to 999kHz, its just the first group goes to kHz. 130 | // 131 | // This should basically have a one-to-one mapping to Radian, or something like that 132 | // 1Hz = 2Pi rads 133 | // 134 | // These "round" numbers aren't really useful b/c they don't map to Radian at all, 135 | // at least Radian2 136 | const ( 137 | Nanohertz Frequency = 1 138 | Microhertz = 1000 * Nanohertz 139 | Millihertz = 1000 * Microhertz 140 | HHertz = 1000 * Millihertz 141 | Kilohertz = 1000 * HHertz 142 | Megahertz = 1000 * Kilohertz 143 | Gigahertz = 1000 * Megahertz // caps out at 18.44GHz 144 | // Tetrahertz = 1000 * Gigahertz 145 | ) 146 | 147 | // 148 | // s = sample function 149 | // n = integer value 150 | // 151 | // http://wilsonminesco.com/16bitMathTables/ 152 | // https://en.wikipedia.org/wiki/Binary_scaling#Binary_angles 153 | // 154 | // list all alias errors (also see More examples subheading): 155 | // https://en.wikipedia.org/wiki/Aliasing#Sampling_sinusoidal_functions 156 | // 157 | // For precision, i may want to work in something besides hertz, like millihertz, etc 158 | // 159 | 160 | type Radian uint64 161 | 162 | // func (rad Radian) Hertz() float64 { 163 | // return 164 | // } 165 | 166 | func (rad Radian) Degrees() float64 { 167 | return float64(rad) / math.MaxUint64 * 360 168 | } 169 | 170 | func (rad Radian) Float64() float64 { 171 | return float64(rad) / math.MaxUint64 * twopi 172 | // 2*Pi equals zero so it can't be done like this 173 | // return float64(rad*2*Pi) / math.MaxUint64 174 | } 175 | 176 | // func (rad Radian) String() string { 177 | // return fmt.Sprintf("%vπ/%v", rad, rad) 178 | // } 179 | 180 | // TODO I may want to consider mapping this to first 16 bits to allow for knowing number of rotations. 181 | // That would allow this to exactly represent hertz but I need to figure out what the max hertz would be 182 | const ( 183 | PiOverTwo Radian = 0x4000000000000000 // 90 184 | Pi Radian = 0x8000000000000000 // 180 185 | PiThreeOverTwo Radian = 0xC000000000000000 // 270 186 | PiTwo Radian = 0 187 | OneRadian Radian = 0x28BE60DB93910600 188 | ) 189 | 190 | /* 191 | Rationale for use of Radian Type 192 | 193 | I started thinking of tracking progress by the phase. Phase can mean initial offset from zero-point of cycle, but can also mean fraction of a cycle that has elapsed. 194 | 195 | For a discrete-time signal, this is the integer number of periods that has elapsed, but phase represents infinite precision and can be used when talking about continuous-time signals. So periods are a countable subset of phase which is the set of all real numbers in [0..1]. 196 | 197 | Due to this, phase also acts as a normalized value, and much of this package works with normalized values. 198 | 199 | For example, if rendering a 440Hz sine wave @ 44.1kHz for 5 seconds, an ideal algorithm should work with a normalized frequency to fill a concrete amount of memory. In this case, 44.1kHz is a cycle, and 440Hz is a phase value of that cycle so the normalized frequency is 440/44100. To play this back as intended, using an oscillator as a clock we can define a cycle as 5 seconds and the sample rate of 44.1kHz as the phase, which equates to 1 second. Setting the oscillators frequency to 1/5 will play the samples back in the intended timing. 200 | 201 | These are all normalized values but this is also starting to stretch the definition of phase towards something seemingly arbitrary (though it's quite fitting). Enter, the radian. 202 | 203 | The radian IS arbitrary. The radian is a dimensionless unit. The radian is perfectly suitable for use with a continuous-time signal thanks to pi. 204 | 205 | Phase can be expressed as radians. But why would we? 206 | 207 | When dealing with precision, float64 can lead to errors. Currently, normalized values are expressed as float64 in range [0..1]. In the case of expressing a sample period as a phase value, and then needing to accumulate that value, this can lead to error. This means that depending on the intended use, thought needs to be given to when and where it might be appropriate to use float64 and where it might not be. This can affect design decisions and is another encumberance that demands considering. 208 | 209 | Instead, I'm proposing use of the radian type expressed as a uint64. A yet-to-be determined number of least significant bits (minimum int16) can be used as a rotary, with constants for π/2, π, 3π/2, and 2π respectively as 0x40, 0x80, 0xC0, and 0x001 (padded as necessary for intended bits used). 210 | 211 | A 16bit rotary allows expressing a max ~281.474THz that advances by ~15.258mHz with each increment. 212 | 213 | Question: How does this avoid issues like expressing 1/3? This would be 2π/3 rads which does not have a direct correlary. 214 | 215 | If outputing int16 to the hardware, then the max precision of the hardware is ~15.258μHz anyway. Considering the value 1/3, when this doesn't match and gets rounded to the nearest phase, 216 | 217 | --- 218 | 219 | I was reflecting on what makes radians work so well because, in effect, to produce valid output I'd need to multiply the radian value by the reciprocal sample rate also given in radians. This cancels out the value of Pi. 220 | 221 | So whats the point? The Pi doesn't even do anything and if i were to stick with ints, I'm effectively just working with rationals. 222 | 223 | It's a mindset. It's a reminder. Rendering the floating point value of a radian and working with that doesn't actually make sense because Pi extends infinitely. 224 | 225 | Working in radians doesn't provide any tangible benefit over working with rationals computationally. But considering the value of Pi does force you to consider that you're working with an infinite set. Working in radians is a constant reminder of that. 226 | */ 227 | 228 | // Having radian as int16 to store rotations works but is not particularly precise. Makes 2Pi multiplication nice though. 229 | type Radian2 uint64 230 | 231 | const MaxHertz = 0xFFFFFFFFFFFF0000 232 | const MaxHertz2 = 0xFFFFFFFF00000000 233 | const MaxHertz3 = 0xFFFF000000000000 234 | 235 | func (rad Radian2) Hertz() uint64 { 236 | return uint64(rad / RPiTwo) 237 | // return float64(rad) / float64(RPiTwo) 238 | } 239 | 240 | func (rad Radian2) Degrees() float64 { 241 | return float64(rad) / float64(RPiTwo) * 360 242 | } 243 | 244 | func (rad Radian2) Float64() float64 { 245 | return float64(rad) / math.MaxUint16 * twopi 246 | // 2*Pi equals zero so it can't be done like this 247 | // return float64(rad*2*Pi) / math.MaxUint64 248 | } 249 | 250 | const ( 251 | RPiOverTwo Radian2 = 0x4000 // 90 252 | RPi Radian2 = 0x8000 // 180 253 | RPiThreeOverTwo Radian2 = 0xC000 // 270 254 | RPiTwo Radian2 = 0x10000 255 | ROneRadian Radian2 = 0x28BE // this would accumulate error pretty sure so ditch it 256 | ) 257 | 258 | type System struct { 259 | stub 260 | // TODO ins should be some generalization as it could be 261 | // a Discrete or a file. Maybe use Sampler? Or some kind of Reader? 262 | // Out is always Discrete. 263 | ins []Discrete 264 | out Discrete 265 | sr float64 266 | } 267 | 268 | func (sys *System) Channels() int { return len(sys.ins) } 269 | func (sys *System) SampleRate() float64 { return sys.sr } 270 | func (sys *System) Samples() Discrete { return sys.out } 271 | 272 | func (sys *System) Prepare(h uint64) {} 273 | 274 | // TODO define type Radian 275 | // TODO Radian type? 276 | 277 | // TODO rename Signal? due to the potential need for multiple methods 278 | type Sampler interface { 279 | // Sample reads values from src at the sampling frequency f into dst. 280 | // 281 | // If sampling a Discrete, set the sampling frequency to (desiredSampleRate*sourceSampleRate)/len(src). 282 | // 283 | // If building a lookup table, let f = len(dst). 284 | // 285 | // If samples are intended to be played back in sequence, provide normalized frequency 286 | // against output sample rate; e.g. sample four seconds of 440Hz sine wave at 44.1kHz 287 | // 288 | // r := 44100.0 289 | // t := 4.0 290 | // out := make(Discrete, r*t) // allocate four seconds of space 291 | // out.Sample(SineFunc, 440/r, 0) // sample 440Hz sine wave 292 | // 293 | // To play samples in realtime, use an oscillator as clock. 294 | // 295 | // osc := NewOscil(out, 1/t, nil) 296 | // 297 | // TODO instead of frequency argument, should it just be period? Should probably reference 298 | // it as normalized frequency. Definitely don't call it "rate". 299 | // If the frequency is called norm, what's the phase called? Essentially only fractional part 300 | // of phase is ever used so it's also normalized. 301 | // Maybe just call it period and mention that is synonymous with norm-freq. 302 | // Period is a more obvious coralary to phase. 303 | Sample(src Continuous, interval, phase float64) float64 304 | } 305 | 306 | // TODO maybe not ... 307 | // TODO biggest reason for having a Reader.Read type is so that multiple oscillators 308 | // being modulated by this are done so with the same signal and not in a time-invariant way. 309 | // TODO but perhaps instead just make it a method on Discrete that takes tc uint64 and hardware sample-rate 310 | // type Reader interface { 311 | // Read returns successive samples from the reader. Implementations of Read should not panic if the 312 | // underlying data is exhausted. 313 | // Read() float64 314 | // } 315 | 316 | /* ************************** */ 317 | 318 | // TODO reading below 319 | // http://wilsonminesco.com/16bitMathTables/ 320 | // https://en.wikipedia.org/wiki/Binary_scaling#Binary_angles 321 | 322 | type Buf struct { 323 | Discrete 324 | // phase float64 325 | i int // phase numerator 326 | } 327 | 328 | // Reader interface Read doesn't work if used by multiple ... oh well, official nail in the coffin 329 | func (buf *Buf) Read() float64 { 330 | // x := buf.Index(buf.phase) 331 | // buf.phase += 1 / float64(len(buf.Discrete)) 332 | x := buf.Index(float64(buf.i) / float64(len(buf.Discrete))) 333 | buf.i++ 334 | return x 335 | } 336 | 337 | // TODO remove when no longer needed 338 | // func phaseErr(c int, phase float64) (newphase, e float64) { 339 | // var ( 340 | // r = 44100.0 341 | // f = 441.0 342 | // p = f / r 343 | // n = 1 344 | // ) 345 | 346 | // cphase := float64(c*n) / (r / f) 347 | // e = (cphase - phase) / p 348 | 349 | // for i := 0; i < n; i++ { 350 | // phase += p 351 | // } 352 | // return phase, e 353 | // } 354 | -------------------------------------------------------------------------------- /_exp_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | ) 8 | 9 | func TestRadian(t *testing.T) { 10 | 11 | var rad Radian 12 | const n = 10 13 | onethird := Radian(math.MaxUint64 / 3.) 14 | for i := 1; i < n; i++ { 15 | rad += onethird + onethird + onethird 16 | 17 | fmt.Println(rad.Degrees()) 18 | fmt.Println(math.MaxUint64 - rad) 19 | fmt.Println() 20 | 21 | // if rad.Degrees() != 360 { 22 | // t.Fatalf("failed on iteration %v", i) 23 | // } 24 | } 25 | 26 | // var rad Radian2 27 | 28 | // fmt.Println(RPiTwo % 3) 29 | // x := float64(RPiTwo) / 3.0 30 | 31 | // onethird := Radian2(x) 32 | // fmt.Println(onethird) 33 | // for i := 0; i < 10; i++ { 34 | // rad += onethird 35 | // fmt.Println(rad.Degrees()) 36 | // } 37 | 38 | // var z Radian2 39 | 40 | // const n = 44100 41 | // for i := 1; i < n; i++ { 42 | // z += RPiTwo 43 | // if uint64(z)%uint64(RPiTwo) != 0 { 44 | // t.Fatalf("%v: mod failed for %v", i, z) 45 | // } 46 | // if uint64(i) != z.Hertz() { 47 | // t.Fatalf("%v: hertz failed for %v", i, z) 48 | // } 49 | // t.Log(z.Degrees(), z.Hertz(), uint64(z)%uint64(RPiTwo)) 50 | // } 51 | 52 | // z += 1 53 | // t.Log(z.Degrees(), z.Hertz(), uint64(z)%uint64(RPiTwo)) 54 | 55 | // x := uint64(math.MaxUint64) 56 | // for ; x%uint64(RPiTwo) != 0; x-- { 57 | // } 58 | // t.Log(x % uint64(RPiTwo)) 59 | // t.Logf("0x%016X", x) 60 | 61 | // THz GHz MHz kHz Hz 62 | // 281,474,976,710,655 63 | // t.Log(Radian2(MaxHertz).Hertz()) 64 | 65 | // 281,474,976,645,120 66 | // t.Log(Radian2(MaxHertz2).Hertz()) 67 | // 4,294,967,295 68 | // t.Log(MaxHertz2 / 0x100000000) 69 | // t.Log(MaxHertz3 / 0x1000000000000) 70 | 71 | // mHz μHz nHz pHz fHz 72 | // 000000000.000,000,000,232,830,64365386963e-10 73 | // t.Log(1. / (1. + math.MaxUint32)) 74 | 75 | // t.Log(RPiTwo) 76 | 77 | // t.Log(2. / 65536) 78 | 79 | // 0.000,015,258,789,0625 80 | 81 | // t.Log(RPi.Degrees()) 82 | // t.Log(math.MaxUint64 / RPi) 83 | 84 | // x := 2 * RPi 85 | // t.Logf("0x%016X", x) 86 | 87 | // 562,949,953,421,311 88 | 89 | // a := 750 * Millihertz 90 | // r := a.Angular() 91 | // t.Log(r.Degrees() / 360.0 / float64(HHertz)) 92 | 93 | // r2 := Radian(a) 94 | // t.Log(r2) 95 | // t.Log(r2.Degrees()) 96 | } 97 | -------------------------------------------------------------------------------- /delay.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | // bufc implements a circular buffer. 9 | type bufc struct { 10 | xs []float64 11 | r, w int 12 | } 13 | 14 | // newbufc returns buffer of length n with read offset r. 15 | func newbufc(n int, r int) *bufc { return &bufc{make([]float64, n), r, 0} } 16 | 17 | func (b *bufc) read() (x float64) { 18 | x = b.xs[b.r] 19 | b.r++ 20 | if b.r == len(b.xs) { 21 | b.r = 0 22 | } 23 | return 24 | } 25 | 26 | // readat returns x at r and next read position. 27 | func (b *bufc) readat(r int) (x float64, n int) { 28 | n = r 29 | x = b.xs[n] 30 | n++ 31 | if n == len(b.xs) { 32 | n = 0 33 | } 34 | return 35 | } 36 | 37 | func (b *bufc) write(x float64) (end bool) { 38 | b.xs[b.w] = x 39 | b.w++ 40 | end = b.w == len(b.xs) 41 | if end { 42 | b.w = 0 43 | } 44 | return 45 | } 46 | 47 | // Dtof converts time duration to approximate number of representative frames. 48 | func Dtof(d time.Duration, sr float64) (f int) { 49 | return int(float64(d) / float64(time.Second) * sr) 50 | } 51 | 52 | // Ftod converts f, number of frames, to approximate time duration. 53 | func Ftod(f int, sr float64) (d time.Duration) { 54 | return time.Duration(float64(f) / sr * float64(time.Second)) 55 | } 56 | 57 | // Delay represents a signal delayed by a given duration. 58 | type Delay struct { 59 | *mono 60 | line *bufc 61 | } 62 | 63 | // NewDelay returns Delay with sample buffer of a length approximated by d. 64 | func NewDelay(d time.Duration, in Sound) *Delay { 65 | return &Delay{newmono(in), newbufc(Dtof(d, in.SampleRate()), 1)} 66 | } 67 | 68 | func (dly *Delay) Prepare(uint64) { 69 | for i := range dly.out { 70 | if dly.off { 71 | dly.out[i] = 0 72 | } else { 73 | dly.out[i] = dly.line.read() 74 | dly.line.write(dly.in.Index(i)) 75 | } 76 | } 77 | } 78 | 79 | // Tap is a tapped delay line, essentially a shorter delay within a larger one. 80 | // 81 | // TODO consider some type of method on Delay instead of a separate type. 82 | // For example, Tap intentionally does not expose dly via Inputs() so why is it 83 | // its own type? Conversely, that'd make Delay a mixer of sorts. 84 | type Tap struct { 85 | *mono 86 | dly *Delay 87 | r int 88 | } 89 | 90 | func NewTap(d time.Duration, in *Delay) *Tap { 91 | f := Dtof(d, in.SampleRate()) 92 | n := len(in.line.xs) 93 | if f >= n { 94 | f = n - 1 95 | } 96 | // TODO this would fall out of sync if toggled off 97 | // separate from Delay suggesting a more intrinsic design 98 | // is required here. 99 | // 100 | // get ahead of the delay's write position 101 | r := in.line.w + (n - f) 102 | if r >= n { 103 | r -= n 104 | } 105 | return &Tap{newmono(nil), in, r} 106 | } 107 | 108 | func (tap *Tap) Prepare(uint64) { 109 | for i := range tap.out { 110 | if tap.off { 111 | tap.out[i] = 0 112 | } else { 113 | tap.out[i], tap.r = tap.dly.line.readat(tap.r) 114 | } 115 | } 116 | } 117 | 118 | // Comb adds a delayed version of a signal onto itself. 119 | type Comb struct { 120 | *mono 121 | line *bufc 122 | gain float64 123 | } 124 | 125 | func NewComb(gain float64, d time.Duration, in Sound) *Comb { 126 | return &Comb{newmono(in), newbufc(Dtof(d, in.SampleRate()), 1), gain} 127 | } 128 | 129 | func (cmb *Comb) Prepare(uint64) { 130 | for i := range cmb.out { 131 | if cmb.off { 132 | cmb.out[i] = 0 133 | } else { 134 | cmb.out[i] = cmb.line.read() 135 | cmb.line.write(cmb.in.Index(i) + cmb.out[i]*cmb.gain) 136 | } 137 | } 138 | } 139 | 140 | // Loop records a signal by a given duration, repeating the recording 141 | // in subsequent playback. 142 | // 143 | // TODO cross-fade 144 | type Loop struct { 145 | *mono 146 | line *bufc 147 | rec bool 148 | 149 | syncrec bool 150 | sync int 151 | syncpos int 152 | } 153 | 154 | // NewLoop returns a Loop with sample buffer of a length approximated by d. 155 | func NewLoop(d time.Duration, in Sound) *Loop { 156 | return &Loop{newmono(in), newbufc(Dtof(d, in.SampleRate()), 0), false, false, 0, 0} 157 | } 158 | 159 | // NewLoopFrames return a Loop with sample buffer of length nframes. 160 | func NewLoopFrames(nframes int, in Sound) *Loop { 161 | return &Loop{newmono(in), newbufc(nframes, 0), false, false, 0, 0} 162 | } 163 | 164 | func (lp *Loop) SetBPM(bpm BPM) { 165 | lp.sync = Dtof(bpm.Dur(), lp.SampleRate()) 166 | } 167 | 168 | func (lp *Loop) Syncing() bool { return lp.syncrec } 169 | 170 | func (lp *Loop) Recording() bool { return lp.rec } 171 | 172 | func (lp *Loop) Record() { 173 | // TODO may want to replace lp.line with new instance instead? 174 | // otherwise there will be left over data in buffer 175 | // if adding Stop() method, unless that's desireable? 176 | lp.line.w = 0 177 | if lp.sync == 0 { 178 | lp.rec = true 179 | } else { 180 | lp.syncpos = lp.sync 181 | lp.syncrec = true 182 | } 183 | } 184 | 185 | func (lp *Loop) Stop() { 186 | lp.rec = false 187 | lp.syncrec = false 188 | lp.syncpos = 0 189 | lp.line.r = 0 190 | } 191 | 192 | func (lp *Loop) Prepare(tc uint64) { 193 | nf := tc * uint64(len(lp.out)) 194 | for i := range lp.out { 195 | lp.out[i] = 0 196 | if !lp.off { 197 | if lp.syncrec { 198 | if ((nf + uint64(i)) % uint64(lp.sync)) == 0 { 199 | lp.syncrec = false 200 | lp.rec = true 201 | } else { 202 | lp.syncpos-- 203 | if lp.syncpos == 0 { 204 | log.Println("Failed to start loop.") 205 | lp.syncrec = false 206 | } 207 | } 208 | } 209 | 210 | if lp.rec { 211 | if done := lp.line.write(lp.in.Index(i)); done { 212 | // lp.rec = false 213 | // lp.line.r = 0 214 | lp.Stop() 215 | } 216 | } else { 217 | lp.out[i] = lp.line.read() 218 | } 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /delay_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestBufc(t *testing.T) { 9 | for _, td := range []struct{ n, r int }{ 10 | {10, 1}, 11 | {4, 2}, 12 | {7, 3}, 13 | {5, 4}, 14 | } { 15 | n, r := td.n, td.r 16 | buf := newbufc(n, r) 17 | for i := 1; i <= 100; i++ { 18 | if buf.write(float64(i)) && i%n != 0 { 19 | t.Fatalf("buf write incorrectly signaled end [i=%v]", i) 20 | } 21 | if 0 > buf.w || buf.w >= n { 22 | t.Fatalf("buf write position out of bounds %+v", buf) 23 | } 24 | 25 | if x := buf.read(); i <= (n-r) && x != 0 { 26 | t.Fatalf("buf read %v during first pass, want 0 [i=%v] %+v", x, i, buf) 27 | } else if w := i - n + r; i > (n-r) && int(x) != w { 28 | t.Fatalf("buf read %v, want %v [i=%v] %+v", x, w, i, buf) 29 | } 30 | if 0 > buf.r || buf.r >= n { 31 | t.Fatalf("buf read position out of bounds %+v", buf) 32 | } 33 | } 34 | } 35 | } 36 | 37 | func TestDtof(t *testing.T) { 38 | sr := 44100.0 39 | eps := time.Duration(1 / sr * float64(time.Second)) // 1Hz as time.Duration 40 | d := 75 * time.Millisecond 41 | x := Ftod(Dtof(d, sr), sr) 42 | if diff := d - x; diff > eps { 43 | t.Fatalf("%s greater than epsilon %s", diff, eps) 44 | } 45 | } 46 | 47 | func BenchmarkDelay(b *testing.B) { 48 | dly := NewDelay(100*time.Millisecond, newunit()) 49 | b.ReportAllocs() 50 | b.ResetTimer() 51 | for n := 0; n < b.N; n++ { 52 | dly.Prepare(uint64(n)) 53 | } 54 | } 55 | 56 | func BenchmarkDelayTap(b *testing.B) { 57 | dly := NewDelay(100*time.Millisecond, newunit()) 58 | tap := NewTap(50*time.Millisecond, dly) 59 | b.ReportAllocs() 60 | b.ResetTimer() 61 | for n := 0; n < b.N; n++ { 62 | dly.Prepare(uint64(n)) 63 | tap.Prepare(uint64(n)) 64 | } 65 | } 66 | 67 | func BenchmarkComb(b *testing.B) { 68 | cmb := NewComb(0.8, 100*time.Millisecond, newunit()) 69 | b.ReportAllocs() 70 | b.ResetTimer() 71 | for n := 0; n < b.N; n++ { 72 | cmb.Prepare(uint64(n)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /dispatch.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | // TODO most of this probably doesn't need to be exposed 9 | // but those details can be worked out once additional audio 10 | // drivers are supported. 11 | 12 | type Dispatcher struct{ sync.WaitGroup } 13 | 14 | // Dispatch blocks until all inputs are prepared. 15 | func (dp *Dispatcher) Dispatch(tc uint64, inps ...*Input) { 16 | wt := inps[0].wt 17 | for _, inp := range inps { 18 | if inp.wt != wt { 19 | dp.Wait() 20 | wt = inp.wt 21 | } 22 | dp.Add(1) 23 | go func(sd Sound, tc uint64) { 24 | sd.Prepare(tc) 25 | dp.Done() 26 | }(inp.sd, tc) 27 | } 28 | dp.Wait() 29 | } 30 | 31 | type Input struct { 32 | sd Sound 33 | wt int 34 | } 35 | 36 | type ByWT []*Input 37 | 38 | func (a ByWT) Len() int { return len(a) } 39 | func (a ByWT) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 40 | func (a ByWT) Less(i, j int) bool { return a[i].wt > a[j].wt } 41 | 42 | func (a ByWT) Slice() (sl [][]*Input) { 43 | if len(a) == 0 { 44 | return nil 45 | } 46 | wt := a[0].wt 47 | i := 0 48 | for j, p := range a { 49 | if p.wt != wt { 50 | sl = append(sl, a[i:j]) 51 | i = j 52 | wt = p.wt 53 | } 54 | } 55 | return append(sl, a[i:]) 56 | } 57 | 58 | func GetInputs(sd Sound) []*Input { 59 | inps := []*Input{{sd, 0}} 60 | getinputs(sd, 1, &inps) 61 | sort.Sort(ByWT(inps)) 62 | return inps 63 | } 64 | 65 | // TODO janky func 66 | func getinputs(sd Sound, wt int, out *[]*Input) { 67 | for _, in := range sd.Inputs() { 68 | if in == nil { // TODO for !realtime || in.IsOff() { 69 | continue 70 | } 71 | at := -1 72 | for i, p := range *out { 73 | if p.sd == in { 74 | if p.wt >= wt { 75 | return // object has or will be traversed on different path 76 | } 77 | at = i 78 | break 79 | } 80 | } 81 | if at != -1 { 82 | (*out)[at].sd = in 83 | (*out)[at].wt = wt 84 | } else { 85 | *out = append(*out, &Input{in, wt}) 86 | } 87 | getinputs(in, wt+1, out) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /dispatch_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "testing" 4 | 5 | func BenchmarkDispatchSerial(b *testing.B) { 6 | sd := mksound() 7 | inps := GetInputs(sd) 8 | 9 | b.ReportAllocs() 10 | b.ResetTimer() 11 | for n := 0; n < b.N; n++ { 12 | tc := uint64(n) 13 | for _, inp := range inps { 14 | inp.sd.Prepare(tc) 15 | } 16 | } 17 | } 18 | 19 | func TestDispatch(t *testing.T) { 20 | sd := mksound() 21 | inps := GetInputs(sd) 22 | 23 | tc := uint64(1) 24 | dp := new(Dispatcher) 25 | 26 | // TODO better test than "does it hang?" 27 | dp.Dispatch(tc, inps...) 28 | } 29 | 30 | func BenchmarkDispatch(b *testing.B) { 31 | sd := mksound() 32 | inps := GetInputs(sd) 33 | 34 | dp := new(Dispatcher) 35 | 36 | b.ReportAllocs() 37 | b.ResetTimer() 38 | 39 | for n := 0; n < b.N; n++ { 40 | dp.Dispatch(uint64(n), inps...) 41 | } 42 | } 43 | 44 | func TestGetInputs(t *testing.T) { 45 | sd := mksound() 46 | inps := GetInputs(sd) 47 | if len(inps) <= 1 { 48 | t.Fatal("getinps did not produce a result") 49 | } 50 | last := inps[0].wt 51 | for i, inp := range inps { 52 | if inp.wt > last { 53 | t.Fatal("inputs are not sorted highest to lowest") 54 | } 55 | last = inp.wt 56 | t.Log(i, inp) 57 | } 58 | } 59 | 60 | func BenchmarkGetInputs(b *testing.B) { 61 | sd := mksound() 62 | var inps []*Input 63 | b.ReportAllocs() 64 | b.ResetTimer() 65 | for n := 0; n < b.N; n++ { 66 | inps = GetInputs(sd) 67 | } 68 | _ = inps 69 | } 70 | 71 | func TestByWTSlice(t *testing.T) { 72 | sd := mksound() 73 | inps := GetInputs(sd) 74 | sl := ByWT(inps).Slice() 75 | 76 | want := len(inps) 77 | total := 0 78 | for i, p := range sl { 79 | total += len(p) 80 | t.Log(i, len(p)) 81 | } 82 | if total != want { 83 | t.Fatalf("Have length %v, want %v", total, want) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /envel.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "time" 5 | 6 | "dasa.cc/signal" 7 | ) 8 | 9 | func ExpDrive() signal.Discrete { 10 | sig := signal.ExpDecay() 11 | sig.Reverse() 12 | return sig 13 | } 14 | 15 | func LinearDrive() signal.Discrete { 16 | sig := make(signal.Discrete, 1024) 17 | sig.Sample(func(t float64) float64 { return t }, 1./1024, 0) 18 | return sig 19 | } 20 | 21 | // TODO rename ... 22 | type timed struct { 23 | sig signal.Discrete 24 | nfr float64 25 | } 26 | 27 | func newtimed(sig signal.Discrete, nfr int) *timed { 28 | return &timed{sig, float64(nfr)} 29 | } 30 | 31 | // TODO look into exposing this 32 | type seq struct { 33 | *mono 34 | tms []*timed 35 | r int 36 | pn float64 37 | 38 | lk int 39 | } 40 | 41 | func newseq(in Sound) *seq { 42 | return &seq{mono: newmono(in), lk: -1} 43 | } 44 | 45 | func (sq *seq) Prepare(uint64) { 46 | for i := range sq.out { 47 | tm := sq.tms[sq.r] 48 | if sq.off { 49 | sq.out[i] = 0 50 | } else if sq.in == nil { 51 | sq.out[i] = tm.sig.At(sq.pn / tm.nfr) 52 | } else { 53 | sq.out[i] = tm.sig.At(sq.pn/tm.nfr) * sq.in.Index(i) 54 | } 55 | 56 | // TODO finicky 57 | if sq.lk == sq.r { 58 | continue 59 | } 60 | 61 | sq.pn++ 62 | if sq.pn >= tm.nfr { 63 | sq.pn = 0 64 | sq.r++ 65 | if sq.r == len(sq.tms) { 66 | sq.r = 0 67 | } 68 | } 69 | } 70 | } 71 | 72 | // TODO reimplement sustain functionality 73 | type ADSR struct { 74 | *seq 75 | sustaining bool 76 | } 77 | 78 | func NewADSR(attack, decay, sustain, release time.Duration, susamp, maxamp float64, in Sound) *ADSR { 79 | adsr := &ADSR{newseq(in), false} 80 | sr := adsr.SampleRate() 81 | 82 | atksig := LinearDrive() 83 | atksig.NormalizeRange(0, maxamp) 84 | atk := newtimed(atksig, Dtof(attack, sr)) 85 | 86 | // dcysig := LinearDecay() 87 | dcysig := signal.ExpDecay() 88 | dcysig.NormalizeRange(maxamp, susamp) 89 | dcy := newtimed(dcysig, Dtof(decay, sr)) 90 | 91 | sus := newtimed(signal.Discrete{susamp, susamp}, Dtof(sustain, sr)) 92 | 93 | relsig := signal.ExpDecay() 94 | relsig.NormalizeRange(susamp, 0) 95 | rel := newtimed(relsig, Dtof(release, sr)) 96 | 97 | adsr.tms = []*timed{atk, dcy, sus, rel} 98 | return adsr 99 | } 100 | 101 | func (adsr *ADSR) Dur() time.Duration { 102 | var n int 103 | for _, tm := range adsr.tms { 104 | n += int(tm.nfr) 105 | } 106 | return Ftod(n, adsr.SampleRate()) 107 | } 108 | 109 | // Restart resets envelope to start from attack period. 110 | func (adsr *ADSR) Restart() { adsr.r, adsr.pn, adsr.lk = 0, 0, -1 } 111 | 112 | // Sustain locks envelope when sustain period is reached. 113 | func (adsr *ADSR) Sustain() { 114 | adsr.sustaining = true 115 | adsr.lk = 2 116 | } 117 | 118 | // Release immediately releases envelope from anywhere and starts release period. 119 | func (adsr *ADSR) Release() (ok bool) { 120 | adsr.sustaining = false 121 | adsr.lk = -1 122 | if ok = adsr.r < 3; ok { 123 | adsr.r, adsr.pn = 3, 0 124 | } 125 | return 126 | } 127 | 128 | type Damp struct { 129 | *mono 130 | sig signal.Discrete 131 | i, n float64 132 | } 133 | 134 | func NewDamp(d time.Duration, in Sound) *Damp { 135 | sd := newmono(in) 136 | return &Damp{ 137 | mono: sd, 138 | sig: signal.ExpDecay(), 139 | n: float64(Dtof(d, sd.SampleRate())), 140 | } 141 | } 142 | 143 | func (dmp *Damp) Prepare(uint64) { 144 | for i := range dmp.out { 145 | if dmp.off { 146 | dmp.out[i] = 0 147 | } else if dmp.in == nil { 148 | dmp.out[i] = dmp.sig.At(dmp.i / dmp.n) 149 | } else { 150 | dmp.out[i] = dmp.in.Index(i) * dmp.sig.At(dmp.i/dmp.n) 151 | } 152 | dmp.i++ 153 | if dmp.i == dmp.n { 154 | dmp.i = 0 155 | } 156 | } 157 | } 158 | 159 | type Drive struct { 160 | *mono 161 | sig signal.Discrete 162 | i, n float64 163 | } 164 | 165 | func NewDrive(d time.Duration, in Sound) *Drive { 166 | sd := newmono(in) 167 | return &Drive{ 168 | mono: sd, 169 | sig: ExpDrive(), 170 | n: float64(Dtof(d, sd.SampleRate())), 171 | } 172 | } 173 | 174 | func (drv *Drive) Prepare(uint64) { 175 | for i := range drv.out { 176 | if drv.off { 177 | drv.out[i] = 0 178 | } else if drv.in == nil { 179 | drv.out[i] = drv.sig.At(drv.i / drv.n) 180 | } else { 181 | drv.out[i] = drv.in.Index(i) * drv.sig.At(drv.i/drv.n) 182 | } 183 | drv.i++ 184 | if drv.i == drv.n { 185 | drv.i = 0 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /envel_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func BenchmarkADSR(b *testing.B) { 9 | ms := time.Millisecond 10 | env := NewADSR(5*ms, 10*ms, 15*ms, 20*ms, 0.7, 1, nil) 11 | b.ReportAllocs() 12 | b.ResetTimer() 13 | for n := 0; n < b.N; n++ { 14 | env.Prepare(uint64(n)) 15 | } 16 | } 17 | 18 | func BenchmarkDamp(b *testing.B) { 19 | env := NewDamp(50*time.Millisecond, newunit()) 20 | b.ReportAllocs() 21 | b.ResetTimer() 22 | for n := 0; n < b.N; n++ { 23 | env.Prepare(uint64(n)) 24 | } 25 | } 26 | 27 | func BenchmarkDrive(b *testing.B) { 28 | env := NewDrive(50*time.Millisecond, newunit()) 29 | b.ReportAllocs() 30 | b.ResetTimer() 31 | for n := 0; n < b.N; n++ { 32 | env.Prepare(uint64(n)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/bass/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math" 6 | "time" 7 | 8 | "dasa.cc/signal" 9 | "dasa.cc/snd" 10 | "dasa.cc/snd/al" 11 | ) 12 | 13 | var ( 14 | notes = snd.EqualTempermant(12, 440, 48) 15 | 16 | freq0, freq1 float64 17 | freq2, freq3 float64 18 | 19 | freq4, freq5 float64 20 | freq6, freq7 float64 21 | ) 22 | 23 | func main() { 24 | master := snd.NewMixer() 25 | const buffers = 1 26 | if err := al.OpenDevice(buffers); err != nil { 27 | log.Fatal(err) 28 | } 29 | al.Start(master) 30 | 31 | sine := signal.Sawtooth() 32 | 33 | freq0 = notes[15] * math.Pow(2, 30.0/1200) 34 | freq2 = notes[15] * math.Pow(2, -30.0/1200) 35 | 36 | freq1 = notes[7] * math.Pow(2, 30.0/1200) 37 | freq3 = notes[7] * math.Pow(2, -30.0/1200) 38 | 39 | osc0 := snd.NewOscil(sine, freq0, nil) 40 | osc0.SetAmp(snd.Decibel(-12).Amp(), nil) 41 | osc1 := snd.NewOscil(sine, freq2, nil) 42 | osc1.SetAmp(snd.Decibel(-12).Amp(), nil) 43 | 44 | freq4 = notes[27] * math.Pow(2, 10.0/1200) 45 | freq6 = notes[27] * math.Pow(2, -10.0/1200) 46 | 47 | freq5 = notes[19] * math.Pow(2, 10.0/1200) 48 | freq7 = notes[19] * math.Pow(2, -10.0/1200) 49 | 50 | osc2 := snd.NewOscil(sine, freq4, nil) 51 | osc2.SetAmp(snd.Decibel(-15).Amp(), nil) 52 | osc3 := snd.NewOscil(sine, freq6, nil) 53 | osc3.SetAmp(snd.Decibel(-15).Amp(), nil) 54 | 55 | go func() { 56 | for range time.Tick(750 * time.Millisecond) { 57 | freq0, freq1 = freq1, freq0 58 | freq2, freq3 = freq3, freq2 59 | osc0.SetFreq(freq0, nil) 60 | osc1.SetFreq(freq2, nil) 61 | 62 | freq4, freq5 = freq5, freq4 63 | freq6, freq7 = freq7, freq6 64 | osc2.SetFreq(freq4, nil) 65 | osc3.SetFreq(freq6, nil) 66 | } 67 | }() 68 | 69 | mix := snd.NewMixer(osc0, osc1, osc2, osc3) 70 | 71 | adsr := snd.NewADSR(50*time.Millisecond, 450*time.Millisecond, 200*time.Millisecond, 50*time.Millisecond, 0.5, 1, mix) 72 | master.Append(adsr) 73 | 74 | al.Notify() 75 | 76 | for range time.Tick(time.Second) { 77 | log.Printf("underruns=%-4v buflen=%-4v tickavg=%-12s drift=%s\n", 78 | al.Underruns(), al.BufLen(), al.TickAverge(), al.DriftApprox()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/exp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "dasa.cc/signal" 8 | "dasa.cc/snd" 9 | "dasa.cc/snd/al" 10 | ) 11 | 12 | func main() { 13 | master := snd.NewMixer() 14 | const buffers = 1 15 | if err := al.OpenDevice(buffers); err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | // // downsample 192kHz to 44.1kHz 20 | // t := 2.0 21 | 22 | // srcdef := 192000.0 23 | // src := make(snd.Discrete, int(srcdef*t)) 24 | // src.Sample(snd.SineFunc, 440/srcdef, 0) 25 | 26 | // avgdef := 44100.0 27 | // avg := make(snd.Discrete, int(avgdef*t)) 28 | // avg.Sample(src.Interpolate, 1/(avgdef*t), 0) 29 | 30 | // // upsample 22.05kHz to 44.1kHz 31 | // lowdef := 22050.0 32 | // low := make(snd.Discrete, int(lowdef*t)) 33 | // low.Sample(snd.SineFunc, 1/(lowdef*t), 0) 34 | 35 | // out := make(snd.Discrete, int(avgdef*t)) 36 | // out.Sample(low.Interpolate, 1/(avgdef*t), 0) 37 | 38 | // osc := snd.NewOscil(src, 1/t, nil) 39 | 40 | tri := make(signal.Discrete, 1024) 41 | tri.Sample(signal.TriangleFunc, 1./1024, 1./8) 42 | mod := snd.NewOscil(tri, 0.25, nil) 43 | 44 | sine := signal.Sine() 45 | osc := snd.NewOscil(sine, 440, nil) 46 | allpass := snd.NewOscil(sine, 440, nil) 47 | allpass.SetPhase(mod) 48 | phs := snd.NewGain(0.5, snd.NewMixer(allpass, osc)) 49 | 50 | master.Append(phs) 51 | // master.Append(snd.NewGain(snd.DefaultAmpFac, phs)) 52 | 53 | al.Start(master) 54 | for range time.Tick(time.Second) { 55 | log.Printf("underruns=%-4v buflen=%-4v tickavg=%-12s drift=%s\n", 56 | al.Underruns(), al.BufLen(), al.TickAverge(), al.DriftApprox()) 57 | } 58 | } 59 | 60 | // sn returns t seconds of sine wave with n periods at sample rate r. 61 | // For example, 100 periods played back at 44100Hz produces a 441Hz sine wave. 62 | // r must be evenly divisibable by n for result to be periodic. 63 | // The only purpose for producing seconds of audio is to dispel any illusory 64 | // thought that this changes how resampling works later. 65 | // func sn(n, r, t int) snd.Discrete { 66 | // xs := make(snd.Discrete, r*t) 67 | // xs.Sample(snd.SineFunc, float64(n)/float64(r), 0) 68 | // return xs 69 | // } 70 | 71 | /* 72 | const n = 1024 73 | ms := make(snd.Discrete, n) 74 | ms.Sample(snd.SineFunc, 1.0/n, 0) 75 | mod := snd.NewOscil(ms, 0.25, nil) 76 | _ = mod 77 | 78 | sine := make(snd.Discrete, n) 79 | sine.Sample(snd.SineFunc, 1.0/n, 0) 80 | 81 | // some examples 82 | // var tmp snd.Discrete 83 | // _ = tmp 84 | 85 | // downsamples sine into tmp, halving resolution 86 | // tmp = make(snd.Discrete, n/2) 87 | // tmp.Sample(sine.Index, n/2, 0) 88 | // sine = tmp 89 | 90 | // upsamples sine (from prev) into tmp, doubling resolution 91 | // tmp = make(snd.Discrete, n) 92 | // tmp.Sample(sine.Interpolate, n, 0) 93 | // sine = tmp 94 | 95 | // samples half of sine into tmp (makes for interesting effect) 96 | // tmp = make(snd.Discrete, n/2) 97 | // tmp.Sample(sine.Index, n, 0) 98 | // sine = tmp 99 | 100 | // produce t seconds of 441Hz sine wave at different sampling rates 101 | // and use oscillator as clock for playback. All of these are 102 | // resampled on the fly during playback. 103 | const t = 4 104 | // osc := snd.NewOscil(sn(441, 44100, int(t)), 1./t, nil) 105 | 106 | // 107 | // dat := make(snd.Discrete, 256) 108 | // var phase float64 109 | // for i := 0; i < len(dat); i++ { 110 | // phase, dat[i] = snd.Err(i, phase) 111 | // fmt.Println(dat[i]) 112 | // } 113 | // dat.Normalize() 114 | // return 115 | 116 | // 117 | // for i := 1; i < 44100; i++ { 118 | // osc.Prepare(uint64(i)) 119 | // } 120 | // var dat snd.Discrete 121 | // for _, x := range snd.Phasedata { 122 | // dat = append(dat, x-256) 123 | // } 124 | // dat.Normalize() 125 | 126 | // 127 | // dat2 := make(snd.Discrete, 1024) 128 | // dat2.Sample(dat.Index, 1024, 0) 129 | 130 | // mod = snd.NewOscil(dat2, 0.1, nil) 131 | // osc = snd.NewOscil(ms, 440, nil) 132 | // osc.SetAmp(1, mod) 133 | // return 134 | 135 | // osc := snd.NewOscil(sn(50, 22050, int(t)), 1./t, nil) 136 | // osc := snd.NewOscil(sn(25, 11025, int(t)), 1./t, nil) 137 | 138 | // var a snd.Discrete = sn(25, 11025, t) // 4 seconds 441Hz sine @ 11025Hz 139 | // b := make(snd.Discrete, 44100*t) // space for 4 seconds @ 44100Hz 140 | // b.Sample(a.Interpolate, 44100*t, 0) // upsample 441Hz sine @ 11025Hz to 44100Hz 141 | // osc := snd.NewOscil(b, 1./t, nil) // use osc as clock for looping playback 142 | 143 | osc := snd.NewOscil(sine, 440, mod) 144 | osc.SetAmp(0.5, nil) 145 | master.Append(osc) 146 | // osc2 := snd.NewOsc(sine, 0.5, 440) 147 | // master.Append(osc2) 148 | */ 149 | -------------------------------------------------------------------------------- /example/oscil/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "dasa.cc/signal" 8 | "dasa.cc/snd" 9 | ) 10 | 11 | var ( 12 | Decibel = func(dB float64) float64 { return snd.Decibel(-15).Amp() } // TODO maybe make New* constructors take decibel instead of amplitude 13 | Gain = snd.NewGain 14 | Mixer = snd.NewMixer 15 | Oscil = snd.NewOscil 16 | Sine = signal.Sine() 17 | ) 18 | 19 | func main() { 20 | mix := Mixer( 21 | Gain(Decibel(-15), 22 | Oscil(Sine, 220, 23 | Oscil(Sine, 2, nil)))) 24 | 25 | pl := snd.NewPlayer(mix) 26 | if err := pl.Start(); err != nil { 27 | fmt.Printf("Failed to start: %v", err) 28 | os.Exit(1) 29 | } 30 | fmt.Println("Press Enter to quit...") 31 | fmt.Scanln() 32 | pl.Stop() 33 | } 34 | -------------------------------------------------------------------------------- /example/piano/.gitignore: -------------------------------------------------------------------------------- 1 | *.apk 2 | piano 3 | -------------------------------------------------------------------------------- /example/piano/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /example/piano/README.md: -------------------------------------------------------------------------------- 1 | # Run 2 | 3 | ``` 4 | # desktop 5 | go run *.go 6 | 7 | # android 8 | gomobile install 9 | ``` 10 | -------------------------------------------------------------------------------- /example/piano/assets/basic-frag.glsl: -------------------------------------------------------------------------------- 1 | #version 100 2 | precision mediump float; 3 | 4 | uniform vec4 color; 5 | 6 | void main() { 7 | gl_FragColor = color; 8 | } 9 | -------------------------------------------------------------------------------- /example/piano/assets/basic-vert.glsl: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | attribute vec4 position; 4 | uniform mat4 world; 5 | uniform mat4 view; 6 | uniform mat4 proj; 7 | 8 | void main() { 9 | gl_Position = proj * view * world * position; 10 | } 11 | -------------------------------------------------------------------------------- /example/piano/assets/material/glyphs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dskinner/snd/d3216ab5d2c864868cbd2498a33af44315248bc9/example/piano/assets/material/glyphs.png -------------------------------------------------------------------------------- /example/piano/assets/material/material-icons-black-mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dskinner/snd/d3216ab5d2c864868cbd2498a33af44315248bc9/example/piano/assets/material/material-icons-black-mdpi.png -------------------------------------------------------------------------------- /example/piano/assets/material/source-code-pro-glyphs-sdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dskinner/snd/d3216ab5d2c864868cbd2498a33af44315248bc9/example/piano/assets/material/source-code-pro-glyphs-sdf.png -------------------------------------------------------------------------------- /example/piano/key.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | "time" 6 | 7 | "dasa.cc/signal" 8 | "dasa.cc/snd" 9 | ) 10 | 11 | var ( 12 | sawtooth = signal.Sawtooth() 13 | sawsine = signal.SawtoothSynthesis(8) 14 | square = signal.Square() 15 | sqsine = signal.SquareSynthesis(49) 16 | sine = signal.Sine() 17 | triangle = signal.Triangle() 18 | notes = snd.EqualTempermant(12, 440, 48) 19 | 20 | keys [12]Key 21 | reverb *snd.LowPass 22 | metronome *snd.Mixer 23 | loop *snd.Loop 24 | lowpass *snd.LowPass 25 | keymix *snd.Mixer 26 | keygain *snd.Gain 27 | master *snd.Mixer 28 | mastergain *snd.Gain 29 | 30 | bpm = snd.BPM(80) 31 | loopdur = snd.Dtof(bpm.Dur(), snd.DefaultSampleRate) * 8 // nframes 32 | 33 | sndbank = []KeyFunc{NewPianoKey, NewWobbleKey, NewBeatsKey, NewReeseKey} 34 | sndbankpos = 0 35 | 36 | ms = time.Millisecond 37 | ) 38 | 39 | func makekeys() { 40 | keymix.Empty() 41 | for i := range keys { 42 | keys[i] = sndbank[sndbankpos](51 + i) // notes[51] is Major C 43 | keys[i].Freeze() 44 | keymix.Append(keys[i]) 45 | } 46 | // TODO al.Notify() 47 | player.Notify() 48 | } 49 | 50 | type Key interface { 51 | snd.Sound 52 | Press() 53 | Release() 54 | Freeze() 55 | } 56 | 57 | type KeyFunc func(int) Key 58 | 59 | type BeatsKey struct { 60 | *snd.Instrument 61 | adsr *snd.ADSR 62 | } 63 | 64 | func NewBeatsKey(idx int) Key { 65 | osc := snd.NewOscil(sawsine, notes[idx], snd.NewOscil(triangle, 4, nil)) 66 | dmp := snd.NewDamp(bpm.Dur(), osc) 67 | d := snd.BPM(float64(bpm) * 1.25).Dur() 68 | dmp1 := snd.NewDamp(d, osc) 69 | drv := snd.NewDrive(d, osc) 70 | mix := snd.NewMixer(dmp, dmp1, drv) 71 | 72 | frz := snd.NewFreeze(bpm.Dur()*4, mix) 73 | 74 | adsr := snd.NewADSR(250*ms, 500*ms, 300*ms, 400*ms, 0.85, 1.0, frz) 75 | key := &BeatsKey{snd.NewInstrument(adsr), adsr} 76 | key.Off() 77 | return key 78 | } 79 | 80 | func (key *BeatsKey) Press() { 81 | key.adsr.Restart() 82 | key.adsr.Sustain() 83 | key.On() 84 | } 85 | 86 | func (key *BeatsKey) Release() { 87 | key.adsr.Release() 88 | key.OffIn(400 * ms) 89 | } 90 | func (key *BeatsKey) Freeze() {} 91 | 92 | type WobbleKey struct { 93 | *snd.Instrument 94 | adsr *snd.ADSR 95 | } 96 | 97 | func NewWobbleKey(idx int) Key { 98 | osc := snd.NewOscil(sine, notes[idx], snd.NewOscil(triangle, 2, nil)) 99 | adsr := snd.NewADSR(50*ms, 100*ms, 200*ms, 400*ms, 0.6, 0.9, osc) 100 | key := &WobbleKey{snd.NewInstrument(adsr), adsr} 101 | key.Off() 102 | return key 103 | } 104 | 105 | func (key *WobbleKey) Press() { 106 | key.adsr.Restart() 107 | key.adsr.Sustain() 108 | key.On() 109 | } 110 | 111 | func (key *WobbleKey) Release() { 112 | key.adsr.Release() 113 | key.OffIn(400 * ms) 114 | } 115 | func (key *WobbleKey) Freeze() {} 116 | 117 | type ReeseKey struct { 118 | *snd.Instrument 119 | adsr *snd.ADSR 120 | } 121 | 122 | func NewReeseKey(idx int) Key { 123 | sine := signal.Sawtooth() 124 | freq := notes[idx-36] 125 | osc0 := snd.NewOscil(sine, freq*math.Pow(2, 30.0/1200), nil) 126 | osc0.SetAmp(snd.Decibel(-3).Amp(), nil) 127 | osc1 := snd.NewOscil(sine, freq*math.Pow(2, -30.0/1200), nil) 128 | osc1.SetAmp(snd.Decibel(-3).Amp(), nil) 129 | 130 | freq = notes[idx-24] 131 | osc2 := snd.NewOscil(sine, freq, nil) 132 | osc2.SetAmp(snd.Decibel(-6).Amp(), nil) 133 | osc3 := snd.NewOscil(sine, freq, nil) 134 | osc3.SetAmp(snd.Decibel(-6).Amp(), nil) 135 | 136 | mix := snd.NewMixer(osc0, osc1, osc2, osc3) 137 | adsr := snd.NewADSR(1*time.Millisecond, 750*time.Millisecond, 1*time.Millisecond, 2*time.Millisecond, 0.75, 0.8, mix) 138 | 139 | key := &ReeseKey{snd.NewInstrument(adsr), adsr} 140 | key.Off() 141 | return key 142 | } 143 | 144 | func (key *ReeseKey) Press() { 145 | key.adsr.Restart() 146 | key.adsr.Sustain() 147 | key.On() 148 | } 149 | 150 | func (key *ReeseKey) Release() { 151 | key.adsr.Release() 152 | key.OffIn(2 * ms) 153 | } 154 | func (key *ReeseKey) Freeze() {} 155 | 156 | type PianoKey struct { 157 | *snd.Instrument 158 | 159 | freq float64 160 | 161 | osc, mod, phs *snd.Oscil 162 | oscl, modl, phsl *snd.Oscil 163 | oscr, modr, phsr *snd.Oscil 164 | 165 | adsr0, adsr1 *snd.ADSR 166 | 167 | gain *snd.Gain 168 | 169 | dur time.Duration 170 | reldur time.Duration 171 | 172 | frz *snd.Freeze 173 | } 174 | 175 | func NewPianoKey(idx int) Key { 176 | const phasefac float64 = 0.5063999999999971 177 | 178 | k := &PianoKey{} 179 | 180 | k.freq = notes[idx] 181 | k.mod = snd.NewOscil(sqsine, k.freq/2, nil) 182 | k.osc = snd.NewOscil(sawtooth, k.freq, k.mod) 183 | k.phs = snd.NewOscil(square, k.freq*phasefac, nil) 184 | k.osc.SetPhase(k.phs) 185 | 186 | freql := k.freq * math.Pow(2, -10.0/1200) 187 | k.modl = snd.NewOscil(sqsine, freql/2, nil) 188 | k.oscl = snd.NewOscil(sawtooth, freql, k.modl) 189 | k.phsl = snd.NewOscil(square, freql*phasefac, nil) 190 | k.oscl.SetPhase(k.phsl) 191 | 192 | freqr := k.freq * math.Pow(2, 10.0/1200) 193 | k.modr = snd.NewOscil(sqsine, freqr/2, nil) 194 | k.oscr = snd.NewOscil(sawtooth, freqr, k.modr) 195 | k.phsr = snd.NewOscil(square, freqr*phasefac, nil) 196 | k.oscr.SetPhase(k.phsr) 197 | 198 | oscmix := snd.NewMixer(k.osc, k.oscl, k.oscr) 199 | 200 | k.reldur = 1050 * ms 201 | k.dur = 280*ms + k.reldur 202 | k.adsr0 = snd.NewADSR(30*ms, 50*ms, 200*ms, k.reldur, 0.4, 1, oscmix) 203 | k.adsr1 = snd.NewADSR(1*ms, 278*ms, 1*ms, k.reldur, 0.4, 1, oscmix) 204 | adsrmix := snd.NewMixer(k.adsr0, k.adsr1) 205 | 206 | k.gain = snd.NewGain(snd.Decibel(-10).Amp(), adsrmix) 207 | 208 | k.Instrument = snd.NewInstrument(k.gain) 209 | k.Off() 210 | 211 | return k 212 | } 213 | 214 | func (key *PianoKey) Freeze() { 215 | key.On() 216 | key.frz = snd.NewFreeze(key.dur, key.gain) 217 | key.Instrument = snd.NewInstrument(key.frz) 218 | key.Off() 219 | } 220 | 221 | func (key *PianoKey) Press() { 222 | key.On() 223 | key.OffIn(key.dur) 224 | if key.frz == nil { 225 | key.adsr0.Restart() 226 | key.adsr1.Restart() 227 | } else { 228 | key.frz.Restart() 229 | } 230 | } 231 | 232 | func (key *PianoKey) Release() { 233 | if key.frz == nil { 234 | if key.adsr0.Release() && key.adsr1.Release() { 235 | key.OffIn(key.reldur) 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /example/piano/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "runtime/pprof" 10 | "time" 11 | 12 | "dasa.cc/material" 13 | "dasa.cc/material/icon" 14 | "dasa.cc/snd" 15 | "github.com/gen2brain/malgo" 16 | 17 | "golang.org/x/mobile/app" 18 | "golang.org/x/mobile/event/lifecycle" 19 | "golang.org/x/mobile/event/paint" 20 | "golang.org/x/mobile/event/size" 21 | "golang.org/x/mobile/event/touch" 22 | "golang.org/x/mobile/gl" 23 | ) 24 | 25 | const buffers = 1 26 | 27 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 28 | 29 | var ( 30 | env = new(material.Environment) 31 | toolbar *material.Toolbar 32 | btnNext *material.FloatingActionButton 33 | btnkeys [12]*material.Button 34 | decor *material.Material 35 | mixwf *Waveform 36 | 37 | fps int 38 | lastpaint = time.Now() 39 | ) 40 | 41 | var ( 42 | maclose func() 43 | player *snd.Player 44 | ) 45 | 46 | func mastart(pl *snd.Player) func() { 47 | ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) { 48 | fmt.Printf("LOG %v", message) 49 | }) 50 | if err != nil { 51 | fmt.Println(err) 52 | os.Exit(1) 53 | } 54 | 55 | deviceConfig := malgo.DefaultDeviceConfig(malgo.Playback) 56 | deviceConfig.Playback.Format = malgo.FormatF32 57 | deviceConfig.Playback.Channels = pl.Channels() 58 | deviceConfig.SampleRate = pl.SampleRate() 59 | deviceConfig.Alsa.NoMMap = 1 60 | 61 | // This is the function that's used for sending more data to the device for playback. 62 | onSamples := func(pOutputSample, pInputSamples []byte, framecount uint32) { 63 | io.ReadFull(pl, pOutputSample) 64 | } 65 | 66 | deviceCallbacks := malgo.DeviceCallbacks{ 67 | Data: onSamples, 68 | } 69 | device, err := malgo.InitDevice(ctx.Context, deviceConfig, deviceCallbacks) 70 | if err != nil { 71 | fmt.Println(err) 72 | os.Exit(1) 73 | } 74 | 75 | err = device.Start() 76 | if err != nil { 77 | fmt.Println(err) 78 | os.Exit(1) 79 | } 80 | 81 | return func() { 82 | device.Uninit() 83 | _ = ctx.Uninit() 84 | ctx.Free() 85 | } 86 | } 87 | 88 | func init() { 89 | env.SetPalette(material.Palette{ 90 | Primary: material.BlueGrey500, 91 | Dark: material.BlueGrey100, 92 | Light: material.BlueGrey900, 93 | Accent: material.DeepOrangeA200, 94 | }) 95 | } 96 | 97 | func onStart(ctx gl.Context) { 98 | ctx.Enable(gl.BLEND) 99 | ctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 100 | 101 | ctx.Enable(gl.CULL_FACE) 102 | ctx.CullFace(gl.BACK) 103 | 104 | // if err := al.OpenDevice(buffers); err != nil { 105 | // log.Fatal(err) 106 | // } 107 | 108 | env.Load(ctx) 109 | env.LoadIcons(ctx) 110 | env.LoadGlyphs(ctx) 111 | 112 | toolbar = env.NewToolbar(ctx) 113 | toolbar.Title.SetText("snd") 114 | toolbar.Title.SetTextColor(material.White) 115 | toolbar.Nav.SetIconColor(material.White) 116 | 117 | btnLoop := env.NewButton(ctx) 118 | toolbar.AddAction(btnLoop) 119 | btnLoop.SetIcon(icon.AvFiberSmartRecord) 120 | btnLoop.SetIconColor(material.White) 121 | btnLoop.OnPress = func() { 122 | if loop.Recording() { 123 | btnLoop.SetIconColor(material.White) 124 | loop.Stop() 125 | } else { 126 | btnLoop.SetIconColor(env.Palette().Accent) 127 | loop.Record() 128 | } 129 | } 130 | 131 | btnMetronome := env.NewButton(ctx) 132 | toolbar.AddAction(btnMetronome) 133 | btnMetronome.SetIcon(icon.AvSlowMotionVideo) 134 | btnMetronome.SetIconColor(material.White) 135 | btnMetronome.OnPress = func() { 136 | if metronome.IsOff() { 137 | metronome.On() 138 | btnMetronome.SetIconColor(env.Palette().Accent) 139 | } else { 140 | metronome.Off() 141 | btnMetronome.SetIconColor(material.White) 142 | } 143 | } 144 | 145 | btnLowpass := env.NewButton(ctx) 146 | toolbar.AddAction(btnLowpass) 147 | btnLowpass.SetIcon(icon.AvSubtitles) 148 | btnLowpass.SetIconColor(env.Palette().Accent) 149 | btnLowpass.OnPress = func() { 150 | lowpass.SetPassthrough(!lowpass.Passthrough()) 151 | if lowpass.Passthrough() { 152 | btnLowpass.SetIconColor(material.White) 153 | } else { 154 | btnLowpass.SetIconColor(env.Palette().Accent) 155 | } 156 | } 157 | 158 | btnReverb := env.NewButton(ctx) 159 | toolbar.AddAction(btnReverb) 160 | btnReverb.SetIcon(icon.AvSurroundSound) 161 | btnReverb.SetIconColor(env.Palette().Accent) 162 | btnReverb.OnPress = func() { 163 | if reverb.IsOff() { 164 | reverb.On() 165 | keygain.SetAmp(snd.Decibel(-3).Amp()) 166 | btnReverb.SetIconColor(env.Palette().Accent) 167 | } else { 168 | reverb.Off() 169 | keygain.SetAmp(snd.Decibel(3).Amp()) 170 | btnReverb.SetIconColor(material.White) 171 | } 172 | } 173 | 174 | btnNext = env.NewFloatingActionButton(ctx) 175 | btnNext.Mini = true 176 | btnNext.SetColor(env.Palette().Accent) 177 | btnNext.SetIcon(icon.NavigationChevronRight) 178 | btnNext.OnPress = func() { 179 | sndbankpos = (sndbankpos + 1) % len(sndbank) 180 | go makekeys() 181 | } 182 | 183 | decor = env.NewMaterial(ctx) 184 | decor.SetColor(material.BlueGrey900) 185 | 186 | tseq := make(map[touch.Sequence]int) 187 | for i := range btnkeys { 188 | btnkeys[i] = env.NewButton(ctx) 189 | j := i 190 | btnkeys[i].OnTouch = func(ev touch.Event) { 191 | switch ev.Type { 192 | case touch.TypeBegin: 193 | keys[j].Press() 194 | tseq[ev.Sequence] = j 195 | case touch.TypeMove: 196 | // TODO drag finger off piano and it still plays, should stop 197 | if last, ok := tseq[ev.Sequence]; ok { 198 | if j != last { 199 | keys[last].Release() 200 | keys[j].Press() 201 | tseq[ev.Sequence] = j 202 | } 203 | } 204 | case touch.TypeEnd: 205 | keys[j].Release() 206 | delete(tseq, ev.Sequence) 207 | } 208 | } 209 | } 210 | 211 | var err error 212 | 213 | keymix = snd.NewMixer() 214 | go makekeys() 215 | lowpass = snd.NewLowPass(773, keymix) 216 | keygain = snd.NewGain(snd.Decibel(-9).Amp(), lowpass) 217 | 218 | dly := snd.NewDelay(29*time.Millisecond, keygain) 219 | tap0 := snd.NewTap(19*time.Millisecond, dly) 220 | tap1 := snd.NewTap(13*time.Millisecond, dly) 221 | tap2 := snd.NewTap(7*time.Millisecond, dly) 222 | cmb := snd.NewComb(snd.Decibel(-3).Amp(), 11*time.Millisecond, snd.NewMixer(dly, tap0, tap1, tap2)) 223 | reverb = snd.NewLowPass(2000, cmb) 224 | dlymix := snd.NewMixer(reverb, keygain) 225 | 226 | loop = snd.NewLoopFrames(loopdur, dlymix) 227 | loop.SetBPM(bpm) 228 | loopmix := snd.NewMixer(dlymix, loop) 229 | 230 | master = snd.NewMixer(loopmix) 231 | mastergain = snd.NewGain(snd.Decibel(-15).Amp(), master) 232 | mixwf, err = NewWaveform(ctx, 2, mastergain) 233 | mixwf.SetColor(material.BlueGrey700) 234 | if err != nil { 235 | log.Fatal(err) 236 | } 237 | // pan := snd.NewPan(0, mixwf) 238 | 239 | mtrosc := snd.NewOscil(sine, 440, nil) 240 | mtrdmp := snd.NewDamp(bpm.Dur(), mtrosc) 241 | metronome = snd.NewMixer(mtrdmp) 242 | metronome.Off() 243 | master.Append(metronome) 244 | 245 | // al.Start(pan) 246 | // al.Notify() 247 | if maclose != nil { 248 | maclose() 249 | } 250 | player = snd.NewPlayer(mixwf) 251 | maclose = mastart(player) 252 | } 253 | 254 | func onPaint(ctx gl.Context) { 255 | ctx.ClearColor(material.BlueGrey800.RGBA()) 256 | ctx.Clear(gl.COLOR_BUFFER_BIT) 257 | env.Draw(ctx) 258 | mixwf.Draw(ctx, env.View, env.Proj()) 259 | 260 | now := time.Now() 261 | fps = int(time.Second / now.Sub(lastpaint)) 262 | lastpaint = now 263 | } 264 | 265 | func onLayout(sz size.Event) { 266 | toolbar.Span(4, 4, 4) 267 | env.SetOrtho(sz) 268 | env.StartLayout() 269 | env.AddConstraints(btnNext.Constraints(env)...) 270 | env.AddConstraints( 271 | btnNext.EndIn(mixwf.Box, env.Grid.Gutter), btnNext.BottomIn(mixwf.Box, env.Grid.Gutter), 272 | mixwf.StartIn(env.Box, env.Grid.Gutter), mixwf.EndIn(decor.Box, 0), 273 | mixwf.Below(toolbar.Box, env.Grid.Gutter), mixwf.Above(decor.Box, env.Grid.Gutter), mixwf.Z(1), 274 | ) 275 | 276 | wmjr := env.Grid.StepSize()*4/7 - 2 277 | wmnr := wmjr * 13.7 / 23.5 278 | hmjr := wmjr * 4 279 | hmnr := wmnr * 4 280 | 281 | prevmjr := func(n int) int { 282 | switch n { 283 | case 2: 284 | return 0 285 | case 4: 286 | return 2 287 | case 5: 288 | return 4 289 | case 7: 290 | return 5 291 | case 9: 292 | return 7 293 | case 11: 294 | return 9 295 | default: 296 | panic(fmt.Errorf("invalid prevmjr(%v)", n)) 297 | } 298 | } 299 | for i, btn := range btnkeys { 300 | switch i { 301 | case 0: 302 | btn.SetColor(material.BlueGrey100) 303 | env.AddConstraints( 304 | btn.Width(wmjr), btn.Height(hmjr), btn.Z(4), 305 | btn.BottomIn(env.Box, env.Grid.Gutter), btn.StartIn(env.Box, env.Grid.Gutter)) 306 | case 1, 3, 6, 8, 10: 307 | btn.SetColor(material.BlueGrey900) 308 | env.AddConstraints( 309 | btn.Width(wmnr), btn.Height(hmnr), btn.Z(6), 310 | btn.AlignTops(btnkeys[i-1].Box, 0), btn.StartIn(btnkeys[i-1].Box, wmjr-wmnr/2)) 311 | default: 312 | btn.SetColor(material.BlueGrey100) 313 | env.AddConstraints( 314 | btn.Width(wmjr), btn.Height(hmjr), btn.Z(4), 315 | btn.BottomIn(env.Box, env.Grid.Gutter), btn.After(btnkeys[prevmjr(i)].Box, 2)) 316 | } 317 | } 318 | env.AddConstraints( 319 | decor.Height(45), decor.Z(8), decor.Above(btnkeys[0].Box, 2), 320 | decor.StartIn(btnkeys[0].Box, 0), decor.EndIn(btnkeys[len(btnkeys)-1].Box, 0), 321 | ) 322 | env.FinishLayout() 323 | } 324 | 325 | func main() { 326 | flag.Parse() 327 | if *cpuprofile != "" { 328 | f, err := os.Create(*cpuprofile) 329 | if err != nil { 330 | log.Fatal(err) 331 | } 332 | pprof.StartCPUProfile(f) 333 | go func() { 334 | time.Sleep(10 * time.Second) 335 | pprof.StopCPUProfile() 336 | }() 337 | } 338 | 339 | app.Main(func(a app.App) { 340 | // var logdbg *time.Ticker 341 | var glctx gl.Context 342 | for ev := range a.Events() { 343 | switch ev := a.Filter(ev).(type) { 344 | case lifecycle.Event: 345 | switch ev.Crosses(lifecycle.StageVisible) { 346 | case lifecycle.CrossOn: 347 | glctx = ev.DrawContext.(gl.Context) 348 | onStart(glctx) 349 | case lifecycle.CrossOff: 350 | glctx = nil 351 | if maclose != nil { 352 | maclose() 353 | maclose = nil 354 | } 355 | } 356 | case touch.Event: 357 | env.Touch(ev) 358 | case size.Event: 359 | if glctx == nil { 360 | a.Send(ev) 361 | } else { 362 | onLayout(ev) 363 | } 364 | case paint.Event: 365 | if glctx != nil { 366 | onPaint(glctx) 367 | a.Publish() 368 | a.Send(paint.Event{}) 369 | } 370 | } 371 | } 372 | }) 373 | } 374 | -------------------------------------------------------------------------------- /example/piano/waveform.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "dasa.cc/material" 5 | "dasa.cc/material/glutil" 6 | "dasa.cc/snd" 7 | "golang.org/x/mobile/exp/f32" 8 | "golang.org/x/mobile/gl" 9 | ) 10 | 11 | type Waveform struct { 12 | *material.Material 13 | snd.Sound 14 | 15 | prg glutil.Program 16 | vbuf glutil.FloatBuffer 17 | ibuf glutil.UintBuffer 18 | uc gl.Uniform 19 | ap gl.Attrib 20 | 21 | uw, uv, up gl.Uniform 22 | 23 | verts []float32 24 | indices []uint32 25 | outs [][]float64 26 | samples []float64 27 | } 28 | 29 | // TODO just how many samples do we want/need to display something useful? 30 | func NewWaveform(ctx gl.Context, n int, in snd.Sound) (*Waveform, error) { 31 | wf := &Waveform{Material: env.NewMaterial(ctx), Sound: in} 32 | wf.Drawer = wf.Draw 33 | 34 | wf.outs = make([][]float64, n) 35 | for i := range wf.outs { 36 | wf.outs[i] = make([]float64, len(in.Samples())*in.Channels()) 37 | } 38 | wf.samples = make([]float64, len(in.Samples())*in.Channels()*n) 39 | wf.verts = make([]float32, len(wf.samples)*2) 40 | 41 | wf.indices = make([]uint32, len(wf.verts)) 42 | for i := range wf.indices { 43 | wf.indices[i] = uint32((i + 1) / 2) 44 | } 45 | wf.indices[len(wf.indices)-1] = wf.indices[len(wf.indices)-2] 46 | 47 | wf.prg.CreateAndLink(ctx, 48 | glutil.ShaderAsset(gl.VERTEX_SHADER, "basic-vert.glsl"), 49 | glutil.ShaderAsset(gl.FRAGMENT_SHADER, "basic-frag.glsl")) 50 | 51 | wf.vbuf = glutil.NewFloatBuffer(ctx, wf.verts, gl.STREAM_DRAW) 52 | wf.ibuf = glutil.NewUintBuffer(ctx, wf.indices, gl.STATIC_DRAW) 53 | wf.uw = wf.prg.Uniform(ctx, "world") 54 | wf.uv = wf.prg.Uniform(ctx, "view") 55 | wf.up = wf.prg.Uniform(ctx, "proj") 56 | wf.uc = wf.prg.Uniform(ctx, "color") 57 | wf.ap = wf.prg.Attrib(ctx, "position") 58 | return wf, nil 59 | } 60 | 61 | func (wf *Waveform) Prepare(tc uint64) { 62 | wf.Sound.Prepare(tc) 63 | 64 | // cycle outputs 65 | out := wf.outs[0] 66 | for i := 0; i+1 < len(wf.outs); i++ { 67 | wf.outs[i] = wf.outs[i+1] 68 | } 69 | for i, x := range wf.Sound.Samples() { 70 | out[i] = x 71 | } 72 | wf.outs[len(wf.outs)-1] = out 73 | 74 | // sample 75 | for i, out := range wf.outs { 76 | idx := i * len(out) 77 | sl := wf.samples[idx : idx+len(out)] 78 | for j, x := range out { 79 | sl[j] = x 80 | } 81 | } 82 | } 83 | 84 | func (wf *Waveform) Draw(ctx gl.Context, view, proj f32.Mat4) { 85 | world := wf.World() 86 | xstep := float32(1) / float32(len(wf.samples)) 87 | xpos := float32(0) 88 | 89 | // TODO assumes mono 90 | for i, x := range wf.samples { 91 | // clip 92 | if x > 1 { 93 | x = 1 94 | } else if x < -1 { 95 | x = -1 96 | } 97 | 98 | wf.verts[i*2] = float32(xpos) 99 | wf.verts[i*2+1] = float32(x+1) / 2 100 | xpos += xstep 101 | } 102 | 103 | ctx.LineWidth(2) 104 | wf.prg.Use(ctx) 105 | wf.prg.Mat4(ctx, wf.uw, *world) 106 | wf.prg.Mat4(ctx, wf.uv, view) 107 | wf.prg.Mat4(ctx, wf.up, proj) 108 | r, g, b, a := env.Palette().Accent.RGBA() 109 | wf.prg.U4f(ctx, wf.uc, r, g, b, a) 110 | wf.vbuf.Bind(ctx) 111 | wf.vbuf.Update(ctx, wf.verts) 112 | wf.ibuf.Bind(ctx) 113 | wf.prg.Pointer(ctx, wf.ap, 2) 114 | wf.ibuf.Draw(ctx, wf.prg, gl.LINES) 115 | } 116 | -------------------------------------------------------------------------------- /example/plugin/README.md: -------------------------------------------------------------------------------- 1 | Requires go1.8 and linux. 2 | 3 | ``` 4 | go generate // creates reverb.so 5 | go run main.go 6 | ``` 7 | 8 | See here for more info about package plugin: https://tip.golang.org/pkg/plugin/ 9 | -------------------------------------------------------------------------------- /example/plugin/main.go: -------------------------------------------------------------------------------- 1 | //go:generate go build -buildmode=plugin ./reverb 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | "plugin" 8 | "time" 9 | 10 | "dasa.cc/signal" 11 | "dasa.cc/snd" 12 | "dasa.cc/snd/al" 13 | ) 14 | 15 | // TODO consider a similar type for package snd to compliment type Discrete. 16 | // type ProcFunc func(in snd.Sound) snd.Sound 17 | 18 | var reverb func(in snd.Sound) snd.Sound 19 | 20 | func init() { 21 | p, err := plugin.Open("reverb.so") 22 | if err != nil { 23 | log.Fatalf("requires go1.8 and linux; try running `go generate` first: %v", err) 24 | } 25 | v, err := p.Lookup("Reverb") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | reverb = v.(func(in snd.Sound) snd.Sound) 30 | } 31 | 32 | func main() { 33 | master := snd.NewMixer() 34 | gain := snd.NewGain(snd.Decibel(-3).Amp(), master) 35 | const buffers = 1 36 | if err := al.OpenDevice(buffers); err != nil { 37 | log.Fatal(err) 38 | } 39 | al.Start(gain) 40 | 41 | dur := snd.BPM(80).Dur() 42 | mod := snd.NewOscil(signal.Square(), 40, nil) 43 | osc := snd.NewOscil(signal.Sine(), 440, mod) 44 | dmp := snd.NewDamp(dur, osc) 45 | 46 | loop := snd.NewLoop(dur, dmp) 47 | loop.Record() 48 | 49 | master.Append(reverb(loop)) 50 | al.Notify() 51 | 52 | for range time.Tick(time.Second) { 53 | log.Printf("underruns=%-4v buflen=%-4v tickavg=%-12s drift=%s\n", 54 | al.Underruns(), al.BufLen(), al.TickAverge(), al.DriftApprox()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/plugin/reverb/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | 5 | import ( 6 | "time" 7 | 8 | "dasa.cc/snd" 9 | ) 10 | 11 | func Reverb(in snd.Sound) snd.Sound { 12 | dly := snd.NewDelay(29*time.Millisecond, in) 13 | tap0 := snd.NewTap(19*time.Millisecond, dly) 14 | tap1 := snd.NewTap(13*time.Millisecond, dly) 15 | tap2 := snd.NewTap(7*time.Millisecond, dly) 16 | cmb := snd.NewComb(snd.Decibel(-3).Amp(), 11*time.Millisecond, snd.NewMixer(dly, tap0, tap1, tap2)) 17 | return snd.NewLowPass(2000, cmb) 18 | } 19 | -------------------------------------------------------------------------------- /example/rhythm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "dasa.cc/signal" 7 | "dasa.cc/snd" 8 | "dasa.cc/snd/miniaudio" 9 | ) 10 | 11 | var ( 12 | notes = snd.EqualTempermant(12, 440, 48) 13 | sawsine = signal.SawtoothSynthesis(8) 14 | triangle = signal.Triangle() 15 | bpm = snd.BPM(80) 16 | ) 17 | 18 | func rhythm(freq float64) snd.Sound { 19 | osc := snd.NewOscil(sawsine, freq, snd.NewOscil(triangle, 4, nil)) 20 | dmp0 := snd.NewDamp(bpm.Dur(), osc) 21 | quin := snd.BPM(float64(bpm) * 1.25).Dur() 22 | dmp1 := snd.NewDamp(quin, osc) 23 | drv := snd.NewDrive(quin, osc) 24 | mix := snd.NewMixer(dmp0, dmp1, drv) 25 | adsr := snd.NewADSR(250*time.Millisecond, 500*time.Millisecond, 300*time.Millisecond, 400*time.Millisecond, 0.85, 1.0, mix) 26 | adsr.Sustain() 27 | return adsr 28 | } 29 | 30 | func main() { 31 | master := snd.NewMixer() 32 | gain := snd.NewGain(snd.Decibel(-15).Amp(), master) 33 | 34 | mix := snd.NewMixer(rhythm(notes[51]), rhythm(notes[58]), rhythm(notes[60])) // C5 G5 A5 35 | lowpass := snd.NewLowPass(773, mix) 36 | mixgain := snd.NewGain(snd.Decibel(-6).Amp(), lowpass) 37 | 38 | dly := snd.NewDelay(29*time.Millisecond, mixgain) 39 | tap0 := snd.NewTap(19*time.Millisecond, dly) 40 | tap1 := snd.NewTap(13*time.Millisecond, dly) 41 | tap2 := snd.NewTap(7*time.Millisecond, dly) 42 | cmb := snd.NewComb(snd.Decibel(-6).Amp(), 11*time.Millisecond, snd.NewMixer(dly, tap0, tap1, tap2)) 43 | reverb := snd.NewLowPass(2000, cmb) 44 | 45 | master.Append(mixgain, reverb) 46 | 47 | miniaudio.Start(gain) 48 | 49 | // for range time.Tick(time.Second) { 50 | // log.Printf("underruns=%-4v buflen=%-4v tickavg=%-12s drift=%s\n", 51 | // al.Underruns(), al.BufLen(), al.TickAverge(), al.DriftApprox()) 52 | // } 53 | } 54 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "math" 4 | 5 | // LowPass is a 3rd order IIR filter. 6 | // 7 | // Recursive implementation of the Gaussian filter. 8 | type LowPass struct { 9 | *mono 10 | 11 | // normalization factor 12 | b float64 13 | // coefficients 14 | b0, b1, b2, b3 float64 15 | // delays 16 | d1, d2, d3 float64 17 | 18 | // TODO eek, temporary 19 | passthrough bool 20 | } 21 | 22 | func (lp *LowPass) SetPassthrough(b bool) { lp.passthrough = b } 23 | func (lp *LowPass) Passthrough() bool { return lp.passthrough } 24 | 25 | func NewLowPass(freq float64, in Sound) *LowPass { 26 | q := 5.0 27 | s := in.SampleRate() / freq / q 28 | 29 | if s > 2.5 { 30 | q = 0.98711*s - 0.96330 31 | } else { 32 | q = 3.97156 - 4.14554*math.Sqrt(1-0.26891*s) 33 | } 34 | 35 | q2 := q * q 36 | q3 := q * q * q 37 | 38 | // redefined from paper to (1 / b0) to save an op div during prepare. 39 | b0 := 1 / (1.57825 + 2.44413*q + 1.4281*q2 + 0.422205*q3) 40 | b1 := 2.44413*q + 2.85619*q2 + 1.26661*q3 41 | b2 := -(1.4281*q2 + 1.26661*q3) 42 | b3 := 0.422205 * q3 43 | b := 1 - ((b1 + b2 + b3) * b0) 44 | 45 | b1 *= b0 46 | b2 *= b0 47 | b3 *= b0 48 | 49 | return &LowPass{mono: newmono(in), b: b, b0: b0, b1: b1, b2: b2, b3: b3} 50 | } 51 | 52 | func (lp *LowPass) Prepare(uint64) { 53 | for i, x := range lp.in.Samples() { 54 | if lp.off { 55 | lp.out[i] = 0 56 | } else if lp.passthrough { 57 | lp.out[i] = x 58 | } else { 59 | lp.out[i] = lp.b*x + lp.b1*lp.d1 + lp.b2*lp.d2 + lp.b3*lp.d3 60 | } 61 | lp.d3, lp.d2, lp.d1 = lp.d2, lp.d1, lp.out[i] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /filter_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "testing" 4 | 5 | func BenchmarkLowPass(b *testing.B) { 6 | lp := NewLowPass(500, newunit()) 7 | b.ReportAllocs() 8 | b.ResetTimer() 9 | for n := 0; n < b.N; n++ { 10 | lp.Prepare(uint64(n)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /freeze.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "math" 5 | "time" 6 | 7 | "dasa.cc/signal" 8 | ) 9 | 10 | type Freeze struct { 11 | *mono 12 | sig, prv signal.Discrete 13 | r int 14 | } 15 | 16 | func NewFreeze(d time.Duration, in Sound) *Freeze { 17 | f := Dtof(d, in.SampleRate()) 18 | 19 | n := f 20 | if n == 0 || n&(n-1) != 0 { 21 | _, e := math.Frexp(float64(n)) 22 | n = int(math.Ldexp(1, e)) 23 | } 24 | 25 | frz := &Freeze{mono: newmono(nil), prv: make(signal.Discrete, n)} 26 | frz.sig = frz.prv[:f] 27 | 28 | inps := GetInputs(in) 29 | dp := new(Dispatcher) 30 | 31 | // t := time.Now() 32 | buflen := len(in.Samples()) 33 | for i := 0; i < n; i += buflen { 34 | dp.Dispatch(1, inps...) 35 | ringcopy(frz.sig[i:i+buflen], in.Samples(), 0) 36 | } 37 | // log.Println("freeze took", time.Now().Sub(t)) 38 | return frz 39 | } 40 | 41 | func (frz *Freeze) Restart() { frz.r = 0 } 42 | 43 | var empty = make([]float64, 256) 44 | 45 | func ringcopy(dst, src []float64, r int) int { 46 | dn, sn := len(dst), len(src) 47 | for w := 0; w < dn; { 48 | x := copy(dst[w:], src[r:]) 49 | w += x 50 | r += x 51 | if r == sn { 52 | r = 0 53 | } 54 | } 55 | return r 56 | } 57 | 58 | func (frz *Freeze) Off() { 59 | frz.mono.Off() 60 | copy(frz.out, empty) 61 | } 62 | 63 | func (frz *Freeze) Prepare(uint64) { 64 | if frz.off { 65 | n := len(frz.out) 66 | frz.r = (frz.r + n) & (n - 1) 67 | } else { 68 | frz.r = ringcopy(frz.out, frz.sig, frz.r) 69 | } 70 | 71 | // for i := range frz.out { 72 | // if frz.off { 73 | // frz.out[i] = 0 74 | // } else { 75 | // frz.out[i] = frz.sig[frz.pos] 76 | // frz.pos++ 77 | // if frz.pos == frz.nfr { 78 | // frz.pos = 0 79 | // } 80 | // } 81 | // } 82 | } 83 | -------------------------------------------------------------------------------- /freeze_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "dasa.cc/signal" 8 | ) 9 | 10 | func TestRingcopy(t *testing.T) { 11 | xs := []float64{1, 2, 3, 4} 12 | tds := []struct { 13 | dn int 14 | off int 15 | ret int 16 | want []float64 17 | }{ 18 | {2, 2, 0, []float64{3, 4}}, 19 | {8, 2, 2, []float64{3, 4, 1, 2, 3, 4, 1, 2}}, 20 | } 21 | 22 | for i, td := range tds { 23 | have := make([]float64, td.dn) 24 | r := ringcopy(have, xs, td.off) 25 | if r != td.ret { 26 | t.Errorf("tds[%v] returned %v, want %v", i, r, td.ret) 27 | } 28 | for j, x := range td.want { 29 | if x != have[j] { 30 | t.Errorf("tds[%v] have %+v, want %+v", i, have, td.want) 31 | break 32 | } 33 | } 34 | } 35 | } 36 | 37 | func BenchmarkRingcopy(b *testing.B) { 38 | // src := []float64(ExpDecay()) 39 | src := make(signal.Discrete, 32) 40 | src.Sample(signal.ExpDecayFunc, 1./32, 0) 41 | dst := make([]float64, 256) 42 | r := 0 43 | b.ReportAllocs() 44 | b.ResetTimer() 45 | for n := 0; n < b.N; n++ { 46 | r = ringcopy(dst, src, r) 47 | } 48 | } 49 | 50 | func BenchmarkFreeze(b *testing.B) { 51 | frz := NewFreeze(1*time.Second, newunit()) 52 | b.ReportAllocs() 53 | b.ResetTimer() 54 | for n := 0; n < b.N; n++ { 55 | frz.Prepare(uint64(n + 1)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /gain.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | type Gain struct { 4 | *mono 5 | a float64 6 | } 7 | 8 | func NewGain(a float64, in Sound) *Gain { 9 | return &Gain{newmono(in), a} 10 | } 11 | 12 | func (gn *Gain) SetAmp(a float64) { 13 | gn.a = a 14 | } 15 | 16 | func (gn *Gain) Prepare(uint64) { 17 | for i, x := range gn.in.Samples() { 18 | if gn.off { 19 | gn.out[i] = 0 20 | } else { 21 | gn.out[i] = gn.a * x 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dasa.cc/snd 2 | 3 | require ( 4 | dasa.cc/material v0.0.0-20221023172005-d040ab4e414a 5 | dasa.cc/signal v0.0.0-20221023170706-f88a600dd4cc 6 | golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e 7 | gonum.org/v1/plot v0.0.0-20180905080458-5f3c436ce602 8 | ) 9 | 10 | require ( 11 | dasa.cc/simplex v0.0.0-20180617055632-ae0aeef7c530 // indirect 12 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af // indirect 13 | github.com/gen2brain/malgo v0.11.10 // indirect 14 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 15 | github.com/jung-kurt/gofpdf v1.0.0 // indirect 16 | github.com/llgcode/draw2d v0.0.0-20180817132918-587a55234ca2 // indirect 17 | golang.org/x/exp/shiny v0.0.0-20221019170559-20944726eadf // indirect 18 | golang.org/x/image v0.1.0 // indirect 19 | golang.org/x/sys v0.1.0 // indirect 20 | gopkg.in/fsnotify.v1 v1.4.7 // indirect 21 | ) 22 | 23 | go 1.19 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= 19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= 20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 21 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 22 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 23 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 24 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 25 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 26 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 27 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 28 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 29 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 30 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 31 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 32 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 33 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 34 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 35 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 36 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 37 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 38 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 39 | dasa.cc/material v0.0.0-20181002233133-bf1ace7fb445/go.mod h1:TqTvNIPGrw9M9l9iQ1qYad/bAHFlHaNfk9lmUMulKGU= 40 | dasa.cc/material v0.0.0-20221023172005-d040ab4e414a h1:DE6OJd2du9D2syRq34ccTaaoN7JQF3u8LpOinV0zq7U= 41 | dasa.cc/material v0.0.0-20221023172005-d040ab4e414a/go.mod h1:PO0kzsDYUrqXOtZojaAFtBFlN91jAzaKSL2YnDu6c4Q= 42 | dasa.cc/signal v0.0.0-20221023170706-f88a600dd4cc h1:y5WST57M9UvXDpuOGfPGYwUvHW7FfXk39Q60qMQOB3A= 43 | dasa.cc/signal v0.0.0-20221023170706-f88a600dd4cc/go.mod h1:rErB4VIHjdnCd2EPZlEZVs5+tuNrI0uy1XQFwYKOJqs= 44 | dasa.cc/simplex v0.0.0-20180617055632-ae0aeef7c530 h1:QMtj0KysAnW1yNTRWNxLHX0fESpGj9LXiaIAqbsvgoo= 45 | dasa.cc/simplex v0.0.0-20180617055632-ae0aeef7c530/go.mod h1:lh+Ocrt7y3C9PSHXCyuwLBNKHttPozxH8PFV1+3dOcE= 46 | dasa.cc/snd v0.0.0-20180617055848-131b2504d0d2/go.mod h1:8E38iVVJSb2hSPqBarbcVKa+t4ON9CD2i1C7VoBmArA= 47 | dasa.cc/snd v0.0.0-20200301082333-54fd236f2e18/go.mod h1:peulE1ZHMt68RgsMVfgPPgw47j5Dw+XLkU/u3qc802E= 48 | dasa.cc/x v0.0.0-20221023141954-69d42d7509f2/go.mod h1:DiC3F9i4lf5R31iIFYpSQ6FSHgoNU5J/nDd9GqJ7mdA= 49 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 50 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 51 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 52 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 53 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ= 54 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 55 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 56 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 57 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 58 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 59 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 60 | github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= 61 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 62 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 63 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= 64 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 65 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= 66 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 67 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= 68 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 69 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 70 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 71 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 72 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 73 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 74 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 75 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 76 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 77 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 78 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 79 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 80 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 81 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 82 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 83 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 84 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 85 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 86 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 87 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 88 | github.com/gen2brain/malgo v0.11.10 h1:u41QchDBS7Z2rwEVPu7uycK6HA8IyzKoUOhLU7IvYW4= 89 | github.com/gen2brain/malgo v0.11.10/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww= 90 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 91 | github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= 92 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= 93 | github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 94 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 95 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 96 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 97 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 98 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 99 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 100 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 101 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 102 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 103 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 104 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 105 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 106 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 107 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 108 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 109 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 110 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 111 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 112 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 113 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 114 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 115 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 116 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 117 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 118 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 119 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 120 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 121 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 122 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 123 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 124 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 125 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 126 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 127 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 128 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 129 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 130 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 131 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 132 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 133 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 134 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 135 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 136 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 137 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 138 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 139 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 140 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 141 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 142 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 143 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 144 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 145 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 146 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 147 | github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4/go.mod h1:Pw1H1OjSNHiqeuxAduB1BKYXIwFtsyrY47nEqSgEiCM= 148 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 149 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 150 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 151 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 152 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 153 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 154 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 155 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 156 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 157 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 158 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 159 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 160 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 161 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 162 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 163 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 164 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 165 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 166 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 167 | github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= 168 | github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY= 169 | github.com/goxjs/glfw v0.0.0-20220119044647-4bcee99381f2/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY= 170 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 171 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 172 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 173 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 174 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 175 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 176 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 177 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 178 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 179 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 180 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 181 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 182 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 183 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 184 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 185 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 186 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 187 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 188 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 189 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 190 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 191 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 192 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 193 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 194 | github.com/jezek/xgb v1.0.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= 195 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 196 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 197 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 198 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 199 | github.com/jung-kurt/gofpdf v1.0.0 h1:EroSdlP9BOoL5ssLYf3uLJXhCQMMM2fFxCJDKA3RhnA= 200 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 201 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 202 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 203 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 204 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 205 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 206 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 207 | github.com/llgcode/draw2d v0.0.0-20180817132918-587a55234ca2 h1:3xDkT1Tbsw2yDtKWUrROAlr15+dzp76kwucDvAPPnQo= 208 | github.com/llgcode/draw2d v0.0.0-20180817132918-587a55234ca2/go.mod h1:mVa0dA29Db2S4LVqDYLlsePDzRJLDfdhVZiI15uY0FA= 209 | github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb h1:61ndUreYSlWFeCY44JxDDkngVoI7/1MVhEl98Nm0KOk= 210 | github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY= 211 | github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 212 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 213 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 214 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 215 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 216 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 217 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 218 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 219 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 220 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 221 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 222 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 223 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 224 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 225 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 226 | github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= 227 | github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= 228 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 229 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 230 | github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 231 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 232 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 233 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 234 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 235 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 236 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 237 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 238 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 239 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 240 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 241 | github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 242 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= 243 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 244 | github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= 245 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 246 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 247 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 248 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 249 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 250 | github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= 251 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 252 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 253 | github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= 254 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 255 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 256 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 257 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 258 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 259 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 260 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 261 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 262 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 263 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 264 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 265 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 266 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 267 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 268 | go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= 269 | go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= 270 | go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= 271 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 272 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 273 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 274 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 275 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 276 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 277 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 278 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 279 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 280 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 281 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 282 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 283 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 284 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 285 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 286 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 287 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 288 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 289 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 290 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 291 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 292 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 293 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 294 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 295 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= 296 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 297 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 298 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 299 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 300 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 301 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 302 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 303 | golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= 304 | golang.org/x/exp v0.0.0-20221019170559-20944726eadf h1:nFVjjKDgNY37+ZSYCJmtYf7tOlfQswHqplG2eosjOMg= 305 | golang.org/x/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 306 | golang.org/x/exp/shiny v0.0.0-20221019170559-20944726eadf h1:xPP9J4wZqH/24aioQEHL44ZakUGdW7/6dNb3vL83bNo= 307 | golang.org/x/exp/shiny v0.0.0-20221019170559-20944726eadf/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= 308 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 309 | golang.org/x/image v0.0.0-20180926015637-991ec62608f3/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 310 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 311 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 312 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 313 | golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= 314 | golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= 315 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 316 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 317 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 318 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 319 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 320 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 321 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 322 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 323 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 324 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 325 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 326 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 327 | golang.org/x/mobile v0.0.0-20180922163855-920b52be609a/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 328 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 329 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 330 | golang.org/x/mobile v0.0.0-20200222142934-3c8601c510d0/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= 331 | golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= 332 | golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e h1:zSgtO19fpg781xknwqiQPmOHaASr6E7ZVlTseLd9Fx4= 333 | golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= 334 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 335 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 336 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 337 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 338 | golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 339 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 340 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 341 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 342 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 343 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 344 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 345 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 346 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 347 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 348 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 349 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 350 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 351 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 352 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 353 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 354 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 355 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 356 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 357 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 358 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 359 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 360 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 361 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 362 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 363 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 364 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 365 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 366 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 367 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 368 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 369 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 370 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 371 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 372 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 373 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 374 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 375 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 376 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 377 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 378 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 379 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 380 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 381 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 382 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 383 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 384 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 385 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 386 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 387 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 388 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 389 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 390 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 391 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 392 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 393 | golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 394 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 395 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 396 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 397 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 398 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 399 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 400 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 401 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 402 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 403 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 404 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 405 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 406 | golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 407 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 408 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 409 | golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 410 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 411 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 412 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 413 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 414 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 415 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 416 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 417 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 418 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 419 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 420 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 421 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 422 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 423 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 424 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 425 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 426 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 427 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 428 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 429 | golang.org/x/sys v0.0.0-20200301040627-c5d0d7b4ec88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 430 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 431 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 432 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 433 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 434 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 435 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 436 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 437 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 438 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 439 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 440 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 441 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 442 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 443 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 444 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 445 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 446 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 447 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 448 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 449 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 450 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 451 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 452 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 453 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 454 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 455 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 456 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 457 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 458 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 459 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 460 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 461 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 462 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 463 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 464 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 465 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 466 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 467 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 468 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 469 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 470 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 471 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 472 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 473 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 474 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 475 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 476 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 477 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 478 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 479 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 480 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 481 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 482 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 483 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 484 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 485 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 486 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 487 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 488 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 489 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 490 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 491 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 492 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 493 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 494 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 495 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 496 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 497 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 498 | golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 499 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 500 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 501 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 502 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 503 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 504 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 505 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 506 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 507 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 508 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 509 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 510 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 511 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 512 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 513 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 514 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 515 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 516 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 517 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 518 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 519 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 520 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 521 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 522 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 523 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 524 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 525 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 526 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 527 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 528 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 529 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 530 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 531 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 532 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4 h1:nYxTaCPaVoJbxx+vMVnsFb6kw5+6aJCx52m/lmM/Vog= 533 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 534 | gonum.org/v1/netlib v0.0.0-20180930160340-e150bd5bba73/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 535 | gonum.org/v1/plot v0.0.0-20180905080458-5f3c436ce602 h1:vweNa1DjFigVk8LA0oMwfyi6WdNX+ox4vjGHvRqyHYY= 536 | gonum.org/v1/plot v0.0.0-20180905080458-5f3c436ce602/go.mod h1:VIQWjXleEHakKVLjfhAAXUy3mq0NuXvobpOBf0ZBZro= 537 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 538 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 539 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 540 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 541 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 542 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 543 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 544 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 545 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 546 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 547 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 548 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 549 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 550 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 551 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 552 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 553 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 554 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 555 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 556 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= 557 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 558 | google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 559 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 560 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 561 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 562 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 563 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 564 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 565 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 566 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 567 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 568 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 569 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 570 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 571 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 572 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 573 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 574 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 575 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 576 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 577 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 578 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 579 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 580 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 581 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 582 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 583 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 584 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 585 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 586 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 587 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 588 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 589 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 590 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 591 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 592 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 593 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 594 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 595 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 596 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 597 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 598 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 599 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 600 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 601 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 602 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 603 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 604 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 605 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= 606 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 607 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 608 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 609 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 610 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 611 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 612 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 613 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 614 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 615 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 616 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 617 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 618 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 619 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 620 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 621 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 622 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 623 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 624 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 625 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 626 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 627 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 628 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 629 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 630 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 631 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 632 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 633 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 634 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 635 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 636 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 637 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 638 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 639 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 640 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 641 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 642 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 643 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 644 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 645 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 646 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 647 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 648 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 649 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 650 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 651 | honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4= 652 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 653 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 654 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 655 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 656 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 657 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 658 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 659 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 660 | rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= 661 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 662 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 663 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 664 | -------------------------------------------------------------------------------- /instrument.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "time" 4 | 5 | type Instrument struct { 6 | *mono 7 | tm int 8 | } 9 | 10 | func NewInstrument(in Sound) *Instrument { 11 | return &Instrument{newmono(in), 0} 12 | } 13 | 14 | func (nst *Instrument) OffIn(d time.Duration) { 15 | nst.tm = Dtof(d, nst.SampleRate()) 16 | } 17 | 18 | func (nst *Instrument) On() { 19 | nst.tm = 0 // cancels any previous OffIn if not reached 20 | nst.mono.On() 21 | } 22 | 23 | func (nst *Instrument) Prepare(uint64) { 24 | for i := range nst.out { 25 | if nst.off { 26 | nst.out[i] = 0 27 | } else { 28 | nst.out[i] = nst.in.Index(i) 29 | } 30 | 31 | if nst.tm > 0 { 32 | nst.tm-- 33 | if nst.tm == 0 { 34 | nst.Off() 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mixer.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | // TODO should mixer be stereo out? 4 | // TODO perhaps this class is unnecessary, any sound could be a mixer 5 | // if you can set multiple inputs, but might get confusing. 6 | // TODO consider embedding Gain type 7 | type Mixer struct { 8 | *mono 9 | ins []Sound 10 | } 11 | 12 | func NewMixer(ins ...Sound) *Mixer { return &Mixer{newmono(nil), ins} } 13 | func (mix *Mixer) Append(s ...Sound) { mix.ins = append(mix.ins, s...) } 14 | func (mix *Mixer) Empty() { mix.ins = nil } 15 | func (mix *Mixer) Inputs() []Sound { return mix.ins } 16 | 17 | func (mix *Mixer) Prepare(uint64) { 18 | for i := range mix.out { 19 | mix.out[i] = 0 20 | if !mix.off { 21 | for _, in := range mix.ins { 22 | mix.out[i] += in.Index(i) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mixer_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "testing" 4 | 5 | func BenchmarkMixer(b *testing.B) { 6 | mix := NewMixer() 7 | for i := 0; i < 2; i++ { 8 | mix.Append(newunit()) 9 | } 10 | b.ReportAllocs() 11 | b.ResetTimer() 12 | for n := 0; n < b.N; n++ { 13 | mix.Prepare(uint64(n)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /notes.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "math" 4 | 5 | const DefaultNotesLen = 128 6 | 7 | // Notes is a collection of note function evaluations. 8 | type Notes []float64 9 | 10 | // Eval evaluates fn over the length of ns. If ns is nil, ns will be allocated 11 | // with length DefaultNotesLen. 12 | func (ns *Notes) Eval(tones int, freq float64, pos int, fn NotesFunc) { 13 | if *ns == nil { 14 | *ns = make([]float64, DefaultNotesLen) 15 | } 16 | fn(*ns, tones, freq, pos) 17 | } 18 | 19 | // NotesFunc defines a note function to be evaluated over the length of ns. 20 | type NotesFunc func(ns Notes, tones int, freq float64, pos int) 21 | 22 | // EqualTempermantFunc evaluates notes as an octave containing n tones at an 23 | // equal distance on a logarithmic scale. The reference freq and pos is used 24 | // to find all other values. 25 | func EqualTempermantFunc(ns Notes, tones int, freq float64, pos int) { 26 | for i := range ns { 27 | ns[i] = freq * math.Pow(math.Pow(2, 1/float64(tones)), float64(i-pos)) 28 | } 29 | } 30 | 31 | // EqualTempermant is a helper function for returning an evaluated Notes. 32 | func EqualTempermant(tones int, freq float64, pos int) Notes { 33 | var ns Notes 34 | ns.Eval(tones, freq, pos, EqualTempermantFunc) 35 | return ns 36 | } 37 | -------------------------------------------------------------------------------- /notes_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "testing" 4 | 5 | func TestNotes(t *testing.T) { 6 | // values sourced from wikipedia and may be rounded: 7 | // https://en.wikipedia.org/wiki/Piano_key_frequencies 8 | want := []float64{ 9 | 27.5000, 29.1352, 30.8677, 32.7032, 34.6478, 36.7081, 38.8909, 41.2034, 10 | 43.6535, 46.2493, 48.9994, 51.9131, 55.0000, 58.2705, 61.7354, 65.4064, 11 | 69.2957, 73.4162, 77.7817, 82.4069, 87.3071, 92.4986, 97.9989, 103.826, 12 | 110.000, 116.541, 123.471, 130.813, 138.591, 146.832, 155.563, 164.814, 13 | 174.614, 184.997, 195.998, 207.652, 220.000, 233.082, 246.942, 261.626, 14 | 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 15 | 440.000, 466.164, 493.883, 523.251, 554.365, 587.330, 622.254, 659.255, 16 | 698.456, 739.989, 783.991, 830.609, 880.000, 932.328, 987.767, 1046.50, 17 | 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98, 1567.98, 1661.22, 18 | 1760.00, 1864.66, 1975.53, 2093.00, 2217.46, 2349.32, 2489.02, 2637.02, 19 | 2793.83, 2959.96, 3135.96, 3322.44, 3520.00, 3729.31, 3951.07, 4186.01, 20 | } 21 | 22 | notes := Notes(make([]float64, 88)) 23 | notes.Eval(12, 440, 48, EqualTempermantFunc) 24 | 25 | for i, x := range notes { 26 | if !equaleps(x, want[i], 0.01) { 27 | t.Errorf("note(%v) have %v, want %v", i, x, want[i]) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /oscil.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "dasa.cc/signal" 5 | ) 6 | 7 | // TODO consider functional options 8 | 9 | type Oscil struct { 10 | *mono 11 | in signal.Discrete 12 | 13 | amp float64 14 | freq float64 15 | phase float64 16 | 17 | ampmod Sound 18 | freqmod Sound 19 | phasemod Sound 20 | } 21 | 22 | func NewOscil(in signal.Discrete, freq float64, freqmod Sound) *Oscil { 23 | return &Oscil{ 24 | mono: newmono(nil), 25 | in: in, 26 | amp: 1, 27 | freq: freq, 28 | freqmod: freqmod, 29 | } 30 | } 31 | 32 | func (osc *Oscil) SetFreq(hz float64, mod Sound) { 33 | osc.freq = hz 34 | osc.freqmod = mod 35 | } 36 | 37 | func (osc *Oscil) SetAmp(fac float64, mod Sound) { 38 | osc.amp = fac 39 | osc.ampmod = mod 40 | } 41 | 42 | func (osc *Oscil) SetPhase(mod Sound) { 43 | osc.phasemod = mod 44 | } 45 | 46 | func (osc *Oscil) Inputs() []Sound { 47 | return []Sound{osc.freqmod, osc.ampmod, osc.phasemod} 48 | } 49 | 50 | func (osc *Oscil) Prepare(tc uint64) { 51 | frame := int(tc-1) * len(osc.out) 52 | nfreq := osc.freq / osc.sr 53 | 54 | // phase := float64(frame) * nfreq 55 | 56 | for i := range osc.out { 57 | interval := nfreq 58 | if osc.freqmod != nil { 59 | interval *= osc.freqmod.Index(frame + i) 60 | } 61 | 62 | offset := 0.0 63 | if osc.phasemod != nil { 64 | offset = osc.phasemod.Index(frame + i) 65 | } 66 | 67 | amp := osc.amp 68 | if osc.ampmod != nil { 69 | amp *= osc.ampmod.Index(frame + i) 70 | } 71 | 72 | osc.out[i] = amp * osc.in.At(osc.phase+offset) 73 | osc.phase += interval 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /oscil_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "testing" 5 | 6 | "dasa.cc/signal" 7 | ) 8 | 9 | func BenchmarkOscil(b *testing.B) { 10 | osc := NewOscil(signal.Sine(), 440, nil) 11 | b.ReportAllocs() 12 | b.ResetTimer() 13 | for n := 0; n < b.N; n++ { 14 | osc.Prepare(uint64(n)) 15 | } 16 | } 17 | 18 | func BenchmarkOscilMod(b *testing.B) { 19 | osc := NewOscil(signal.Sine(), 440, NewOscil(signal.Sine(), 2, nil)) 20 | inps := GetInputs(osc) 21 | b.ReportAllocs() 22 | b.ResetTimer() 23 | for n := 0; n < b.N; n++ { 24 | for _, inp := range inps { 25 | inp.sd.Prepare(uint64(n)) 26 | } 27 | } 28 | } 29 | 30 | func BenchmarkOscilAmp(b *testing.B) { 31 | osc := NewOscil(signal.Sine(), 440, nil) 32 | osc.SetAmp(1, NewOscil(signal.Sine(), 2, nil)) 33 | inps := GetInputs(osc) 34 | b.ReportAllocs() 35 | b.ResetTimer() 36 | for n := 0; n < b.N; n++ { 37 | for _, inp := range inps { 38 | inp.sd.Prepare(uint64(n)) 39 | } 40 | } 41 | } 42 | 43 | func BenchmarkOscilPhase(b *testing.B) { 44 | osc := NewOscil(signal.Sine(), 440, nil) 45 | osc.SetPhase(NewOscil(signal.Sine(), 2, nil)) 46 | inps := GetInputs(osc) 47 | b.ReportAllocs() 48 | b.ResetTimer() 49 | for n := 0; n < b.N; n++ { 50 | for _, inp := range inps { 51 | inp.sd.Prepare(uint64(n)) 52 | } 53 | } 54 | } 55 | 56 | func BenchmarkOscilAll(b *testing.B) { 57 | osc := NewOscil(signal.Sine(), 440, NewOscil(signal.Sine(), 2, nil)) 58 | osc.SetAmp(1, NewOscil(signal.Sine(), 2, nil)) 59 | osc.SetPhase(NewOscil(signal.Sine(), 2, nil)) 60 | inps := GetInputs(osc) 61 | b.ReportAllocs() 62 | b.ResetTimer() 63 | for n := 0; n < b.N; n++ { 64 | for _, inp := range inps { 65 | inp.sd.Prepare(uint64(n)) 66 | } 67 | } 68 | } 69 | 70 | func BenchmarkOscilReuse(b *testing.B) { 71 | mod := NewOscil(signal.Sine(), 2, nil) 72 | osc := NewOscil(signal.Sine(), 440, mod) 73 | osc.SetAmp(1, mod) 74 | osc.SetPhase(mod) 75 | inps := GetInputs(osc) 76 | b.ReportAllocs() 77 | b.ResetTimer() 78 | for n := 0; n < b.N; n++ { 79 | for _, inp := range inps { 80 | inp.sd.Prepare(uint64(n)) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /pan.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "math" 4 | 5 | var ( 6 | onesqrt2 = 1 / math.Sqrt(2) 7 | 8 | panres float64 = 512 9 | panfac [1024]float64 10 | ) 11 | 12 | func init() { 13 | for i := range panfac { 14 | n := float64(i)/panres - 1 15 | panfac[i] = onesqrt2 * (1 - n) / math.Sqrt(1+(n*n)) 16 | } 17 | } 18 | 19 | func getpanfac(xf float64) float64 { 20 | if xf > 1 { 21 | xf = 1 22 | } else if xf < -1 { 23 | xf = -1 24 | } 25 | return panfac[int(panres*(1+xf))] 26 | } 27 | 28 | type Pan struct { 29 | *stereo 30 | xf float64 31 | } 32 | 33 | func NewPan(xf float64, in Sound) *Pan { 34 | return &Pan{newstereo(in), xf} 35 | } 36 | 37 | // SetAmount sets amount an input is panned across two outputs where amt belongs to [-1..1]. 38 | func (pan *Pan) SetAmount(xf float64) { pan.xf = xf } 39 | 40 | // Prepare interleaves the left and right channels. 41 | func (pan *Pan) Prepare(uint64) { 42 | for i, x := range pan.in.Samples() { 43 | if pan.l.off { 44 | pan.l.out[i] = 0 45 | } else { 46 | pan.l.out[i] = x * getpanfac(pan.xf) 47 | } 48 | if pan.r.off { 49 | pan.r.out[i] = 0 50 | } else { 51 | pan.r.out[i] = x * getpanfac(-pan.xf) 52 | } 53 | pan.out[i*2] = pan.l.out[i] 54 | pan.out[i*2+1] = pan.r.out[i] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pan_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import "testing" 4 | 5 | func BenchmarkPan(b *testing.B) { 6 | pan := NewPan(0, newunit()) 7 | b.ReportAllocs() 8 | b.ResetTimer() 9 | for n := 0; n < b.N; n++ { 10 | pan.Prepare(uint64(n)) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /player.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "math" 7 | 8 | "github.com/gen2brain/malgo" 9 | ) 10 | 11 | type pbufc struct { 12 | xs []float64 13 | w int 14 | } 15 | 16 | func newpbufc(n int) *pbufc { 17 | return &pbufc{xs: make([]float64, n)} 18 | } 19 | 20 | func (b *pbufc) readf32(bin []byte) { 21 | n := len(bin) / 4 22 | for i, x := range b.xs[:n] { 23 | n := math.Float32bits((float32)(x)) 24 | bin[4*i] = byte(n) 25 | bin[4*i+1] = byte(n >> 8) 26 | bin[4*i+2] = byte(n >> 16) 27 | bin[4*i+3] = byte(n >> 24) 28 | } 29 | copy(b.xs, b.xs[n:]) 30 | b.w -= n 31 | } 32 | 33 | func (b *pbufc) readi16(bin []byte) { 34 | n := len(bin) / 2 35 | for i, x := range b.xs[:n] { 36 | n := int16(math.MaxInt16 * x) 37 | bin[2*i] = byte(n) 38 | bin[2*i+1] = byte(n >> 8) 39 | } 40 | copy(b.xs, b.xs[n:]) 41 | b.w -= n 42 | } 43 | 44 | func (b *pbufc) write(xs []float64) { 45 | copy(b.xs[b.w:], xs) 46 | b.w += len(xs) 47 | } 48 | 49 | func (b *pbufc) nwrites(atsize int) int { 50 | return (len(b.xs) - b.w) / atsize 51 | } 52 | 53 | type Player struct { 54 | dp *Dispatcher 55 | in Sound 56 | tc uint64 57 | 58 | inputs []*Input 59 | 60 | line *pbufc 61 | 62 | uninit func() 63 | } 64 | 65 | func NewPlayer(in Sound) *Player { 66 | return &Player{ 67 | dp: new(Dispatcher), 68 | in: in, 69 | inputs: GetInputs(in), 70 | line: newpbufc(4096), 71 | } 72 | } 73 | 74 | func (p *Player) Notify() { 75 | if p.in != nil { 76 | p.inputs = GetInputs(p.in) 77 | } 78 | } 79 | 80 | func (p *Player) Channels() uint32 { return uint32(p.in.Channels()) } 81 | func (p *Player) SampleRate() uint32 { return uint32(p.in.SampleRate()) } 82 | 83 | // func binf32(xs []float32) []byte { 84 | // return unsafe.Slice((*byte)(unsafe.Pointer(&xs[0])), 4*len(xs)) 85 | // } 86 | 87 | func (p *Player) Read(bin []byte) (int, error) { 88 | nwrites := p.line.nwrites(DefaultBufferLen) 89 | // fmt.Printf("performing nwrites %v\n", nwrites) 90 | for i := 0; i < nwrites; i++ { 91 | p.tc++ 92 | p.dp.Dispatch(p.tc, p.inputs...) 93 | p.line.write(p.in.Samples()) 94 | } 95 | 96 | p.line.readf32(bin) 97 | 98 | return len(bin), nil 99 | } 100 | 101 | func (pl *Player) Start() error { 102 | ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) { 103 | fmt.Printf("LOG %v", message) 104 | }) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | deviceConfig := malgo.DefaultDeviceConfig(malgo.Playback) 110 | deviceConfig.Playback.Format = malgo.FormatF32 111 | deviceConfig.Playback.Channels = pl.Channels() 112 | deviceConfig.SampleRate = pl.SampleRate() 113 | deviceConfig.Alsa.NoMMap = 1 114 | 115 | // This is the function that's used for sending more data to the device for playback. 116 | onSamples := func(pOutputSample, pInputSamples []byte, framecount uint32) { 117 | io.ReadFull(pl, pOutputSample) 118 | } 119 | 120 | deviceCallbacks := malgo.DeviceCallbacks{ 121 | Data: onSamples, 122 | } 123 | device, err := malgo.InitDevice(ctx.Context, deviceConfig, deviceCallbacks) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | err = device.Start() 129 | if err != nil { 130 | return err 131 | } 132 | 133 | pl.uninit = func() { 134 | device.Uninit() 135 | _ = ctx.Uninit() 136 | ctx.Free() 137 | } 138 | return nil 139 | } 140 | 141 | func (pl *Player) Stop() { 142 | var uninit func() 143 | uninit, pl.uninit = pl.uninit, uninit 144 | if uninit != nil { 145 | uninit() 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /plot_test.go: -------------------------------------------------------------------------------- 1 | //go:build plot 2 | // +build plot 3 | 4 | package snd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | "time" 12 | 13 | "gonum.org/v1/plot" 14 | "gonum.org/v1/plot/plotter" 15 | "gonum.org/v1/plot/plotutil" 16 | ) 17 | 18 | type plttr struct { 19 | *plot.Plot 20 | nproc int 21 | nlines int 22 | 23 | tc uint64 24 | } 25 | 26 | func newplttr(nproc int) *plttr { 27 | p, err := plot.New() 28 | if err != nil { 29 | panic(err) 30 | } 31 | plt := &plttr{Plot: p, nproc: nproc} 32 | plt.X.Min, plt.X.Max = 0, 1 33 | plt.Y.Min, plt.Y.Max = -1.5, 1.5 34 | return plt 35 | } 36 | 37 | func (plt *plttr) add(name string, sd Sound) { 38 | inps := GetInputs(sd) 39 | dp := new(Dispatcher) 40 | var out []float64 41 | for i := 1; i <= plt.nproc; i++ { 42 | dp.Dispatch(plt.tc+uint64(i), inps...) 43 | out = append(out, sd.Samples()...) 44 | } 45 | 46 | plt.addDiscrete(name, Discrete(out)) 47 | } 48 | 49 | func (plt *plttr) addDiscrete(name string, sig Discrete) { 50 | // TODO there appears to be a bug in gonum plot where certain 51 | // dashed lines for a particular result will not render correctly. 52 | // Raw calls of plotutil are just tossed in here for now and avoids 53 | // dashed lines. 54 | l, err := plotter.NewLine(xyer([]float64(sig))) 55 | if err != nil { 56 | panic(err) 57 | } 58 | l.Color = plotutil.Color(plt.nlines) 59 | // l.Dashes = plotutil.Dashes(plt.nlines) 60 | 61 | plt.Add(l) 62 | plt.Legend.Add(name, l) 63 | 64 | plt.nlines++ 65 | } 66 | 67 | func (plt *plttr) save(name string) error { 68 | plt.Add(plotter.NewGrid()) 69 | if err := os.MkdirAll("plots", 0755); err != nil { 70 | return err 71 | } 72 | return plt.Save(1000, 500, filepath.Join("plots", name)) 73 | } 74 | 75 | func xyer(out []float64) plotter.XYs { 76 | n := len(out) 77 | xys := make(plotter.XYs, n) 78 | for i, v := range out { 79 | xys[i].X = float64(i) / float64(n) 80 | xys[i].Y = v 81 | } 82 | return xys 83 | } 84 | 85 | func TestPlotOscil(t *testing.T) { 86 | plt := newplttr(4) 87 | freq := 440.0 88 | pth := 3 89 | td := []struct { 90 | name string 91 | fn func() Discrete 92 | }{ 93 | // {"Sine", Sine}, 94 | // {"Square", Square}, 95 | {"Triangle", Triangle}, 96 | {"Sawtooth", Sawtooth}, 97 | {"Square Sinusoidal", func() Discrete { return SquareSynthesis(pth) }}, 98 | // {"Sawtooth Sinusoidal", func() Discrete { return SawtoothSynthesis(pth) }}, 99 | } 100 | 101 | for _, d := range td { 102 | osc := NewOscil(d.fn(), freq, nil) 103 | plt.add(d.name, osc) 104 | } 105 | 106 | if err := plt.save("oscil.png"); err != nil { 107 | t.Fatal(err) 108 | } 109 | } 110 | 111 | func TestPlotPhaser(t *testing.T) { 112 | plt := newplttr(4) 113 | sawtooth := Sawtooth() 114 | square := Square() 115 | 116 | osc0 := NewOscil(sawtooth, 440, nil) 117 | plt.add("oscil", osc0) 118 | 119 | osc1 := NewOscil(sawtooth, 440, nil) 120 | osc1.SetPhase(NewOscil(square, 440*0.4275, nil)) 121 | plt.add("phased", osc1) 122 | 123 | if err := plt.save("phaser.png"); err != nil { 124 | t.Fatal(err) 125 | } 126 | } 127 | 128 | func TestPlotPhaser2(t *testing.T) { 129 | plt := newplttr(16) 130 | plt.tc = 0 131 | 132 | sine := Sine() 133 | 134 | tri := make(Discrete, 1024) 135 | tri.Sample(TriangleFunc, 1./1024, 1./8) 136 | mod := NewOscil(tri, 2, nil) 137 | plt.add("mod", mod) 138 | 139 | osc := NewOscil(sine, 440, nil) 140 | // plt.add("osc", osc) 141 | 142 | allpass := NewOscil(sine, 440, nil) 143 | allpass.SetPhase(mod) 144 | // plt.add("allpass", allpass) 145 | 146 | plt.add("phs", NewGain(0.5, NewMixer(allpass, osc))) 147 | 148 | if err := plt.save("phaser2.png"); err != nil { 149 | t.Fatal(err) 150 | } 151 | } 152 | 153 | func TestPlotADSR(t *testing.T) { 154 | plt := newplttr(256) 155 | ms := time.Millisecond 156 | plt.add("adsr", NewADSR(10*ms, 5*ms, 400*ms, 350*ms, 0.4, 1, nil)) 157 | if err := plt.save("adsr.png"); err != nil { 158 | t.Fatal(err) 159 | } 160 | } 161 | 162 | func TestPlotLowPass(t *testing.T) { 163 | plt := newplttr(4) 164 | 165 | mix0 := NewMixer(NewOscil(Sine(), 520, nil), NewOscil(Sine(), 440, nil)) 166 | plt.add("Mix Sine [520Hz, 440Hz]", mix0) 167 | 168 | mix1 := NewMixer(NewOscil(Sine(), 520, nil), NewOscil(Sine(), 440, nil)) 169 | lp := NewLowPass(500, mix1) 170 | plt.add("Low Pass [500Hz]", lp) 171 | 172 | if err := plt.save("lowpass.png"); err != nil { 173 | t.Fatal(err) 174 | } 175 | } 176 | 177 | func TestPlotDelay(t *testing.T) { 178 | plt := newplttr(4) 179 | 180 | osc0 := NewOscil(Sine(), 440, nil) 181 | plt.add("Sine 440Hz", osc0) 182 | 183 | dly := NewDelay(Ftod(DefaultBufferLen, DefaultSampleRate), NewOscil(Sine(), 440, nil)) 184 | plt.add("Delay", dly) 185 | 186 | if err := plt.save("delay.png"); err != nil { 187 | t.Fatal(err) 188 | } 189 | } 190 | 191 | func TestPlotDamp(t *testing.T) { 192 | plt := newplttr(4) 193 | 194 | osc0 := NewOscil(Sine(), 440, nil) 195 | plt.add("440Hz", osc0) 196 | 197 | d := Ftod(DefaultBufferLen*4, DefaultSampleRate) 198 | 199 | osc1 := NewOscil(Sine(), 440, nil) 200 | dmp := NewDamp(d, osc1) 201 | plt.add("Damped", dmp) 202 | 203 | plt.add("Force", NewDamp(d, newunit())) 204 | 205 | if err := plt.save("damp.png"); err != nil { 206 | t.Fatal(err) 207 | } 208 | } 209 | 210 | func TestPlotDrive(t *testing.T) { 211 | plt := newplttr(8) 212 | 213 | osc0 := NewOscil(Sine(), 440, nil) 214 | plt.add("440Hz", osc0) 215 | 216 | d := Ftod(DefaultBufferLen*4, DefaultSampleRate) 217 | 218 | osc1 := NewOscil(Sine(), 440, nil) 219 | drv := NewDrive(d, osc1) 220 | plt.add("Driven", drv) 221 | 222 | plt.add("Force", NewDrive(d, newunit())) 223 | 224 | if err := plt.save("drive.png"); err != nil { 225 | t.Fatal(err) 226 | } 227 | } 228 | 229 | func TestPlotNorm(t *testing.T) { 230 | plt := newplttr(8) 231 | 232 | sq0 := Sine() 233 | for i := 3; i <= 99; i += 2 { 234 | sq0.AdditiveSynthesis(fundamental, i) 235 | } 236 | sq1 := make(Discrete, len(sq0)) 237 | copy(sq1, sq0) 238 | sq0.Normalize() 239 | sq1.NormalizeRange(-1, 1) 240 | plt.addDiscrete("Normalize", sq0) 241 | plt.addDiscrete("NormalizeRange", sq1) 242 | 243 | drv := LinearDrive() 244 | plt.addDiscrete("Drive", drv) 245 | 246 | if err := plt.save("norm.png"); err != nil { 247 | t.Fatal(err) 248 | } 249 | } 250 | 251 | func TestPlotFreeze(t *testing.T) { 252 | plt := newplttr(8) 253 | 254 | osc0 := NewOscil(Sine(), 440, NewOscil(Sawtooth(), 23, nil)) 255 | osc0.SetPhase(NewOscil(Square(), 231, nil)) 256 | plt.add("Oscil", osc0) 257 | 258 | osc1 := NewOscil(Sine(), 440, NewOscil(Sawtooth(), 23, nil)) 259 | osc1.SetPhase(NewOscil(Square(), 231, nil)) 260 | frz := NewFreeze(50*time.Millisecond, osc1) 261 | plt.add("Freeze", frz) 262 | 263 | if err := plt.save("freeze.png"); err != nil { 264 | t.Fatal(err) 265 | } 266 | } 267 | 268 | func TestPlotSampleDown(t *testing.T) { 269 | plt := newplttr(1) 270 | 271 | a := make(Discrete, 256) 272 | a.Sample(SineFunc, 1./256, 0) 273 | plt.addDiscrete("Sine SR 256", a) 274 | 275 | b := make(Discrete, 256) 276 | b.Sample(SineFunc, 1./128, 0) 277 | plt.addDiscrete("Sine SR 128", b) 278 | 279 | c := make(Discrete, 128) 280 | c.Sample(a.Interp, 1./64, 0) // * (len(a)/256) == 1 281 | 282 | d := make(Discrete, 256) 283 | d.Sample(c.Interp, (1./256)*(128/64), 0) 284 | 285 | c = append(c, make(Discrete, 128)...) // pad so plotter doesn't stretch compared to others 286 | plt.addDiscrete("Downsample SR 256 -> 64", c) 287 | plt.addDiscrete("Upsample SR 64 -> 256", d) 288 | 289 | if err := plt.save("sampledown.png"); err != nil { 290 | t.Fatal(err) 291 | } 292 | } 293 | 294 | func TestPlotVerse(t *testing.T) { 295 | plt := newplttr(1) 296 | 297 | a := make(Discrete, 256) 298 | a.Sample(ExpDecayFunc, 1./256, 0) 299 | plt.addDiscrete("ExpDecay", a) 300 | 301 | b := make(Discrete, 256) 302 | b.Sample(a.Interp, 1./256, 0) 303 | b.Reverse() 304 | plt.addDiscrete("Reverse", b) 305 | 306 | c := make(Discrete, 256) 307 | c.Sample(a.Interp, 1./256, 0) 308 | c.UnitInverse() 309 | plt.addDiscrete("UnitInverse", c) 310 | 311 | d := make(Discrete, 256) 312 | d.Sample(a.Interp, 1./256, 0) 313 | d.AdditiveInverse() 314 | plt.addDiscrete("AdditiveInverse", d) 315 | 316 | e := make(Discrete, 256) 317 | copy(e, c) 318 | f := make(Discrete, 256) 319 | copy(f, b) 320 | for i, x := range f { 321 | f[i] = 1 + x 322 | } 323 | e = append(e, f...) 324 | e.Normalize() 325 | 326 | g := make(Discrete, 256) 327 | g.Sample(e.Interp, 1./256, 0) 328 | fmt.Printf("%#v\n", g) 329 | plt.addDiscrete("Ease", g) 330 | 331 | if err := plt.save("verse.png"); err != nil { 332 | t.Fatal(err) 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /ring.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | // Ring modulator 4 | type Ring struct { 5 | *mono 6 | in0, in1 Sound 7 | } 8 | 9 | func NewRing(in0, in1 Sound) *Ring { 10 | return &Ring{newmono(nil), in0, in1} 11 | } 12 | 13 | func (ng *Ring) Inputs() []Sound { 14 | return []Sound{ng.in0, ng.in1} 15 | } 16 | 17 | func (ng *Ring) Prepare(uint64) { 18 | for i := range ng.out { 19 | if ng.off { 20 | ng.out[i] = 0 21 | } else { 22 | ng.out[i] = ng.in0.Index(i) * ng.in1.Index(i) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /snd.go: -------------------------------------------------------------------------------- 1 | // Package snd provides methods and types for sound processing and synthesis. 2 | // 3 | // Audio hardware is accessed via package snd/al which in turn manages the 4 | // dispatching of sound synthesis via golang.org/x/mobile/audio/al. Start 5 | // the dispatcher as follows: 6 | // 7 | // const buffers = 1 8 | // if err := al.OpenDevice(buffers); err != nil { 9 | // log.Fatal(err) 10 | // } 11 | // al.Start() 12 | // 13 | // Once running, add a source for sound synthesis. For example: 14 | // 15 | // osc := snd.NewOscil(snd.Sine(), 440, nil) 16 | // al.AddSource(osc) 17 | // 18 | // This results in a 440Hz tone being played back through the audio hardware. 19 | // 20 | // Synthesis types in package snd implement the Sound interface and many type 21 | // methods accept a Sound argument that can affect sampling. For example, one 22 | // may modulate an oscillator by passing in a third argument to NewOscil. 23 | // 24 | // sine := snd.Sine() 25 | // mod := snd.NewOscil(sine, 2, nil) 26 | // osc := snd.NewOscil(sine, 440, mod) 27 | // 28 | // The above results in a lower frequency sound that may require decent speakers 29 | // to hear properly. 30 | // 31 | // # Signals 32 | // 33 | // Note the sine argument in the previous example. There are two conceptual types 34 | // of sounds, ContinuousFunc and Discrete. ContinuousFunc represents an indefinite 35 | // series over time. Discrete is the sampling of a ContinuousFunc over an interval. 36 | // Functions such as Sine, Triangle, and Square (non-exhaustive) return Discretes 37 | // created by sampling a ContinuousFunc such as SineFunc, TriangleFunc, and SquareFunc. 38 | // 39 | // Discrete signals serve as a lookup table to efficiently synthesize sound. 40 | // A Discrete is a []float64 and can sample any ContinuousFunc, within the 41 | // package or user defined which is a func(t float64) float64. 42 | // 43 | // Discrete signals may be further modified with intent or arbitrarily. For example, 44 | // Discrete.Add(Discrete, int) performs additive synthesis and is used by functions 45 | // such as SquareSynthesis(int) to return an approximation of a square signal 46 | // based on a sinusoidal. 47 | // 48 | // # Time Approximation 49 | // 50 | // Functions that take a time.Duration argument approximate the value to the 51 | // closest number of frames. For example, if sample rate is 44.1kHz and duration 52 | // is 75ms, this results in the argument representing 3307 frames which is 53 | // approximately 74.99ms. 54 | package snd // import "dasa.cc/snd" 55 | 56 | import ( 57 | "fmt" 58 | "math" 59 | "time" 60 | 61 | "dasa.cc/signal" 62 | ) 63 | 64 | // TODO double check benchmarks, results may be incorrect due to new dispatcher scheme 65 | // TODO pick a consistent api style 66 | // TODO many sounds don't respect off, double check everything 67 | // TODO many sounds only support mono 68 | // TODO support upsampling and downsampling 69 | // TODO migrate most things to Discrete (e.g. mono.out) 70 | // TODO many Prepare funcs need to check if their inputs have altered state (turned on/off, etc) 71 | // during sampling, not just before or after, otherwise this introduces a delay. For example, 72 | // the current defaults of 256 frame length buffer at 44.1kHz would result in a 5.8ms delay. 73 | // Solution needs to account for the updated method for dispatching prepares. 74 | // TODO look into a type Sampler interface { Sample(int) float64 } 75 | // TODO more documentation 76 | // TODO implement sheperd tone for fun: 77 | // https://en.wikipedia.org/wiki/Shepard_tone 78 | // http://music.columbia.edu/cmc/MusicAndComputers/chapter4/04_02.php 79 | 80 | const ( 81 | DefaultSampleRate float64 = 48000 // 44100 82 | DefaultSampleBitDepth = 16 // TODO not currently used for anything 83 | DefaultBufferLen = 256 84 | DefaultAmpFac float64 = 0.31622776601683794 // -10dB 85 | 86 | twopi = 2 * math.Pi 87 | ) 88 | 89 | // Decibel is relative to full scale; anything over 0dB will clip. 90 | type Decibel float64 91 | 92 | // Amp converts dB to amplitude multiplier. 93 | func (db Decibel) Amp() float64 { 94 | return math.Pow(10, float64(db)/20) 95 | } 96 | 97 | func (db Decibel) String() string { 98 | return fmt.Sprintf("%vdB", float64(db)) 99 | } 100 | 101 | // Hertz is defined as cycles per second and is synonymous with frequency. 102 | type Hertz float64 103 | 104 | func (hz Hertz) Period() float64 { 105 | return 1 / float64(hz) 106 | } 107 | 108 | // Angular returns the angular frequency as 2 * pi * hz and is synonymous with radians. 109 | func (hz Hertz) Angular() float64 { 110 | return twopi * float64(hz) 111 | } 112 | 113 | // Normalized returns the angular frequency of hz divided by the sample rate sr. 114 | func (hz Hertz) Normalized(sr float64) float64 { 115 | return hz.Angular() / sr 116 | } 117 | 118 | func (hz Hertz) String() string { 119 | return fmt.Sprintf("%vHz", float64(hz)) 120 | } 121 | 122 | // BPM respresents beats per minute and is a measure of tempo. 123 | type BPM float64 124 | 125 | // Dur returns the time duration of bpm. 126 | func (bpm BPM) Dur() time.Duration { 127 | return time.Duration(float64(time.Minute) / float64(bpm)) 128 | } 129 | 130 | // Hertz returns the frequency of bpm as bpm / 2. 131 | func (bpm BPM) Hertz() float64 { 132 | return float64(bpm) / 2 133 | } 134 | 135 | // TODO rename as Buffer? 136 | // TODO what about handling []byte instead of []float? 137 | // Sound represents a type capable of producing sound data. 138 | type Sound interface { 139 | // TODO embed Sampler for Buffer? 140 | // Sampler 141 | 142 | // Channels returns the frame size in samples of the internal buffer. 143 | Channels() int 144 | 145 | // SampleRate returns the number of digital samples of sound pressure per second. 146 | SampleRate() float64 147 | 148 | // Prepare is when a sound should prepare sample frames. 149 | Prepare(uint64) 150 | 151 | // Inputs should return all inputs a Sound wants discovered by a dispatcher. 152 | // TODO consider other methods for handling this, check book multidimensional data structures 153 | Inputs() []Sound 154 | 155 | // Samples returns prepared samples slice. 156 | // 157 | // TODO maybe ditch this, point of architecture is you can't mess 158 | // with an input's output but a slice exposes that. Or, discourage 159 | // use by making a copy of data. 160 | // TODO rename to Data()? So, Buffer.Data() 161 | Samples() signal.Discrete 162 | 163 | Interp(t float64) float64 164 | 165 | At(t float64) float64 166 | 167 | Index(i int) float64 168 | } 169 | 170 | // TODO everything is a buffer if it has an `out Discrete` and tracks phase of input; size of input doesn't matter 171 | 172 | // TODO rename as buffer? 173 | // TODO see work on System type 174 | type mono struct { 175 | sr float64 176 | in Sound 177 | out signal.Discrete 178 | off bool 179 | } 180 | 181 | func newmono(in Sound) *mono { 182 | return &mono{ 183 | sr: DefaultSampleRate, 184 | in: in, 185 | out: make(signal.Discrete, DefaultBufferLen), 186 | } 187 | } 188 | 189 | func (sd *mono) SampleRate() float64 { return sd.sr } 190 | func (sd *mono) Samples() signal.Discrete { return sd.out } 191 | func (sd *mono) Index(i int) float64 { return sd.out.Index(i) } 192 | func (sd *mono) At(t float64) float64 { return sd.out.At(t) } 193 | func (sd *mono) Interp(t float64) float64 { return sd.out.Interp(t) } 194 | func (sd *mono) Channels() int { return 1 } 195 | func (sd *mono) IsOff() bool { return sd.off } 196 | func (sd *mono) Off() { sd.off = true } 197 | func (sd *mono) On() { sd.off = false } 198 | func (sd *mono) Inputs() []Sound { return []Sound{sd.in} } 199 | 200 | type stereo struct { 201 | l, r *mono 202 | in Sound 203 | out signal.Discrete 204 | tc uint64 205 | } 206 | 207 | func newstereo(in Sound) *stereo { 208 | return &stereo{ 209 | l: newmono(nil), 210 | r: newmono(nil), 211 | in: in, 212 | out: make(signal.Discrete, DefaultBufferLen*2), 213 | } 214 | } 215 | 216 | func (sd *stereo) SampleRate() float64 { return sd.l.sr } 217 | func (sd *stereo) Samples() signal.Discrete { return sd.out } 218 | func (sd *stereo) Index(i int) float64 { return sd.out.Index(i) } 219 | func (sd *stereo) At(t float64) float64 { return sd.out.At(t) } 220 | func (sd *stereo) Interp(t float64) float64 { return sd.out.Interp(t) } 221 | func (sd *stereo) Channels() int { return 2 } 222 | func (sd *stereo) IsOff() bool { return sd.l.off || sd.r.off } 223 | func (sd *stereo) Off() { sd.l.off, sd.r.off = false, false } 224 | func (sd *stereo) On() { sd.l.off, sd.r.off = true, true } 225 | func (sd *stereo) Inputs() []Sound { return []Sound{sd.in} } 226 | -------------------------------------------------------------------------------- /snd_test.go: -------------------------------------------------------------------------------- 1 | package snd 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "dasa.cc/signal" 8 | ) 9 | 10 | const epsilon = 0.0001 11 | 12 | func equals(a, b float64) bool { 13 | return equaleps(a, b, epsilon) 14 | } 15 | 16 | func equaleps(a, b float64, eps float64) bool { 17 | return (a-b) < eps && (b-a) < eps 18 | } 19 | 20 | type unit struct{ *mono } 21 | 22 | func newunit() *unit { 23 | u := &unit{newmono(nil)} 24 | for i := range u.out { 25 | u.out[i] = DefaultAmpFac 26 | } 27 | return u 28 | } 29 | 30 | func (u *unit) Prepare(uint64) {} 31 | func (u *unit) Inputs() []Sound { return nil } 32 | 33 | type zeros struct{ *mono } 34 | 35 | func newzeros() *zeros { return &zeros{newmono(nil)} } 36 | 37 | func (z *zeros) Prepare(uint64) { 38 | for i := range z.out { 39 | if z.off { 40 | z.out[i] = 0 41 | } else { 42 | z.out[i] = 1 43 | } 44 | } 45 | } 46 | 47 | func (z *zeros) Inputs() []Sound { return nil } 48 | 49 | func BenchmarkZeros(b *testing.B) { 50 | z := newzeros() 51 | b.ReportAllocs() 52 | b.ResetTimer() 53 | for n := 1; n <= b.N; n++ { 54 | z.Prepare(uint64(n)) 55 | } 56 | } 57 | 58 | // mksound returns a 12-key piano synth as might be found in an app. 59 | func mksound() Sound { 60 | mix := NewMixer() 61 | for i := 0; i < 12; i++ { 62 | oscil := NewOscil(signal.Sawtooth(), 440, NewOscil(signal.Sine(), 2, nil)) 63 | oscil.SetPhase(NewOscil(signal.Square(), 200, nil)) 64 | 65 | comb := NewComb(0.8, 10*time.Millisecond, oscil) 66 | adsr := NewADSR(50*time.Millisecond, 500*time.Millisecond, 100*time.Millisecond, 350*time.Millisecond, 0.4, 1, comb) 67 | instr := NewInstrument(adsr) 68 | mix.Append(instr) 69 | } 70 | loop := NewLoop(5*time.Second, mix) 71 | mixloop := NewMixer(mix, loop) 72 | lp := NewLowPass(1500, mixloop) 73 | return NewPan(0, lp) 74 | } 75 | 76 | // func mkthoughtsaboutcreatingstuff() Sound { 77 | // What if these were Options passed in ? 78 | // How many things actually have options? 79 | 80 | // Would need a way that an "option" and an "instance" could 81 | // interchangably be used here, such as implementing the same interface? 82 | // or having some type of lazy initialization that would allow easy access 83 | // to the instance pointers. 84 | 85 | // sine := Sine() 86 | // sawtooth := Sawtooth(4) 87 | // square := Square(4) 88 | 89 | // phaser := snd.Oscil{ 90 | // Harm: square, 91 | // Freq: 200, 92 | // }.New() 93 | 94 | // osc := snd.Oscil{ 95 | // Harm: sawtooth, 96 | // Freq: 440, 97 | // Mod: snd.Oscil{Harm: sine, Freq: 2}, // every new osc has independent mod 98 | // Phase: phaser, // every new osc reuses same phaser 99 | // }.New() 100 | 101 | // cmb := snd.Comb{ 102 | // Gain: 0.8, 103 | // Dur: 10 * time.Millisecond, 104 | // }.New() 105 | 106 | // env := snd.ADSR{ 107 | // Attack: 50 * time.Millisecond, 108 | // Decay: 500 * time.Millisecond, 109 | // Sustain: 100 * time.Millisecond, 110 | // Release: 350 * time.Millisecond, 111 | // SusAmp: 0.4, 112 | // MaxAmp: 1, 113 | // }.New() 114 | 115 | // cmb.SetInput(osc) 116 | // env.SetInput(cmb) 117 | 118 | // this is just bad for so many reasons. 119 | // &Pan{ 120 | // Left: 1, 121 | // Right: 1, 122 | // In: &Waveform{ 123 | // Buf: 4, 124 | // In: &LowPass{ 125 | // Cutoff: 1500, 126 | // In: &Mixer{ 127 | // Ins: []Sound{ 128 | // &Loop{ 129 | // Duration: 5, 130 | // }, 131 | // &Mixer{ 132 | // Ins: []Sound{} 133 | // }, 134 | // }, 135 | // }, 136 | // }, 137 | // }, 138 | // } 139 | // } 140 | 141 | func TestDecibel(t *testing.T) { 142 | tests := []struct { 143 | db Decibel 144 | amp float64 145 | }{ 146 | {0, 1}, 147 | {1, 1.1220}, 148 | {3, 1.4125}, 149 | {6, 1.9952}, 150 | {10, 3.1622}, 151 | } 152 | 153 | for _, test := range tests { 154 | if !equals(test.db.Amp(), test.amp) { 155 | t.Errorf("%s have %v, want %v", test.db, test.db.Amp(), test.amp) 156 | } 157 | } 158 | } 159 | --------------------------------------------------------------------------------