diff --git a/go.mod b/go.mod index bc49759..37ebc41 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module xyosc go 1.23.2 require ( - github.com/MicahParks/peakdetect v0.1.2 github.com/chewxy/math32 v1.11.1 github.com/fsnotify/fsnotify v1.8.0 github.com/gen2brain/malgo v0.11.23 + github.com/goccmack/godsp v0.1.1 github.com/godbus/dbus v4.1.0+incompatible github.com/hajimehoshi/ebiten/v2 v2.8.6 github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f @@ -21,8 +21,10 @@ require ( github.com/ebitengine/hideconsole v1.0.0 // indirect github.com/ebitengine/purego v0.8.2 // indirect github.com/go-text/typesetting v0.2.1 // indirect + github.com/goccmack/goutil v0.4.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/jezek/xgb v1.1.1 // indirect + github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 // indirect golang.org/x/image v0.23.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect diff --git a/go.sum b/go.sum index 207cfc9..3297466 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,4 @@ -github.com/MicahParks/peakdetect v0.1.2 h1:DYQXgBzfl/kkuTKErM/4/2iSkk63okzTka6haTQFK5Y= -github.com/MicahParks/peakdetect v0.1.2/go.mod h1:78d4YnCFxrVbu1Calxc3LIOqN/xtcr7a8lmSwPRylts= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/chewxy/math32 v1.11.1 h1:b7PGHlp8KjylDoU8RrcEsRuGZhJuz8haxnKfuMMRqy8= github.com/chewxy/math32 v1.11.1/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -10,30 +9,41 @@ github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gen2brain/malgo v0.11.23 h1:3/VAI8DP9/Wyx1CUDNlUQJVdWUvGErhjHDqYcHVk9ME= github.com/gen2brain/malgo v0.11.23/go.mod h1:f9TtuN7DVrXMiV/yIceMeWpvanyVzJQMlBecJFVMxww= +github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/goccmack/godsp v0.1.1 h1:NLPDr47wwVdDtQjSca8FSSpcAQKUKbmQq5ligBTZPwc= +github.com/goccmack/godsp v0.1.1/go.mod h1:SxJmlwp2eWh5NYP0Oo/ptCd/oqkj1lehn6ApcXtPb4U= +github.com/goccmack/goutil v0.4.0 h1:or+SequGBcQp7Rf5q719HlOxtEGueaXEenDbc3pANgk= +github.com/goccmack/goutil v0.4.0/go.mod h1:dPBoKv07AeI2DGYE3ECrSLOLpGaBIBGCUCGKHclOPyU= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/hajimehoshi/bitmapfont/v3 v3.2.0 h1:0DISQM/rseKIJhdF29AkhvdzIULqNIIlXAGWit4ez1Q= github.com/hajimehoshi/bitmapfont/v3 v3.2.0/go.mod h1:8gLqGatKVu0pwcNCJguW3Igg9WQqVXF0zg/RvrGQWyg= github.com/hajimehoshi/ebiten/v2 v2.8.6 h1:Dkd/sYI0TYyZRCE7GVxV59XC+WCi2BbGAbIBjXeVC1U= github.com/hajimehoshi/ebiten/v2 v2.8.6/go.mod h1:cCQ3np7rdmaJa1ZnvslraVlpxNb3wCjEnAP1LHNyXNA= github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU= github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leberKleber/go-mpris v1.1.0 h1:bHAnmUjVoxAs4uMHH9lfQ8bOm284UWtI7JhLvkiF7O8= github.com/leberKleber/go-mpris v1.1.0/go.mod h1:OwKywFZwFGC0p/8xBUTUXMIFZy0Rq/7C6EayfeASTA0= +github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk= +github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -44,8 +54,14 @@ github.com/smallnest/ringbuffer v0.0.0-20241129171057-356c688ba81d h1:Kpy9DIOvTw github.com/smallnest/ringbuffer v0.0.0-20241129171057-356c688ba81d/go.mod h1:tAG61zBM1DYRaGIPloumExGvScf08oHuo0kFoOqdbT0= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/youpy/go-riff v0.0.0-20131220112943-557d78c11efb/go.mod h1:83nxdDV4Z9RzrTut9losK7ve4hUnxUR8ASSz4BsKXwQ= +github.com/youpy/go-wav v0.0.0-20160223082350-b63a9887d320/go.mod h1:Zf+Ju+8Ofy5zx/YWWArfcGnl5FAsWumLq/uHeRGgL60= github.com/ztrue/tracerr v0.4.0 h1:vT5PFxwIGs7rCg9ZgJ/y0NmOpJkPCPFK8x0vVIYzd04= github.com/ztrue/tracerr v0.4.0/go.mod h1:PaFfYlas0DfmXNpo7Eay4MFhZUONqvXM+T2HyGPpngk= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= @@ -54,6 +70,12 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -61,3 +83,4 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/vendor/github.com/MicahParks/peakdetect/README.md b/vendor/github.com/MicahParks/peakdetect/README.md deleted file mode 100644 index 11a225a..0000000 --- a/vendor/github.com/MicahParks/peakdetect/README.md +++ /dev/null @@ -1,142 +0,0 @@ -[![Go Reference](https://pkg.go.dev/badge/github.com/MicahParks/peakdetect.svg)](https://pkg.go.dev/github.com/MicahParks/peakdetect) [![Go Report Card](https://goreportcard.com/badge/github.com/MicahParks/peakdetect)](https://goreportcard.com/report/github.com/MicahParks/peakdetect) -# peakdetect -Detect peaks in realtime timeseries data using z-scores. This is a Golang implementation for the algorithm described -by [this StackOverflow answer](https://stackoverflow.com/a/22640362/14797322). - -Unlike some implementations, a goal is to minimize the memory footprint and allow for the processing of new data points -without reprocessing old ones. - -```go -import "github.com/MicahParks/peakdetect" -``` - -# Configuration -`Lag` determines how much your data will be smoothed and how adaptive the algorithm is to change in the long-term -average of the data. The more stationary your data is, the more lags you should include (this should improve the -robustness of the algorithm). If your data contains time-varying trends, you should consider how quickly you want the -algorithm to adapt to these trends. I.e., if you put lag at 10, it takes 10 'periods' before the algorithm's threshold -is adjusted to any systematic changes in the long-term average. So choose the lag parameter based on the trending -behavior of your data and how adaptive you want the algorithm to be. - -`Influence` determines the influence of signals on the algorithm's detection threshold. If put at 0, signals have no -influence on the threshold, such that future signals are detected based on a threshold that is calculated with a mean -and standard deviation that is not influenced by past signals. If put at 0.5, signals have half the influence of normal -data points. Another way to think about this is that if you put the influence at 0, you implicitly assume stationary ( -i.e. no matter how many signals there are, you always expect the time series to return to the same average over the long -term). If this is not the case, you should put the influence parameter somewhere between 0 and 1, depending on the -extent to which signals can systematically influence the time-varying trend of the data. E.g., if signals lead to a -structural break of the long-term average of the time series, the influence parameter should be put high (close to 1) so -the threshold can react to structural breaks quickly - -`Threshold` is the number of standard deviations from the moving mean above which the algorithm will classify a new -datapoint as being a signal. For example, if a new datapoint is 4.0 standard deviations above the moving mean and the -threshold parameter is set as 3.5, the algorithm will identify the datapoint as a signal. This parameter should be set -based on how many signals you expect. For example, if your data is normally distributed, a threshold (or: z-score) of -3.5 corresponds to a signaling probability of 0.00047 (from this table), which implies that you expect a signal once -every 2128 datapoints (1/0.00047). The threshold therefore directly influences how sensitive the algorithm is and -thereby also determines how often the algorithm signals. Examine your own data and choose a sensible threshold that -makes the algorithm signal when you want it to (some trial-and-error might be needed here to get to a good threshold for -your purpose) - -# Usage -```go -package main - -import ( - "fmt" - "log" - - "github.com/MicahParks/peakdetect" -) - -// This example is the equivalent of the R example from the algorithm's author. -// https://stackoverflow.com/a/54507329/14797322 -func main() { - data := []float64{1, 1, 1.1, 1, 0.9, 1, 1, 1.1, 1, 0.9, 1, 1.1, 1, 1, 0.9, 1, 1, 1.1, 1, 1, 1, 1, 1.1, 0.9, 1, 1.1, 1, 1, 0.9, 1, 1.1, 1, 1, 1.1, 1, 0.8, 0.9, 1, 1.2, 0.9, 1, 1, 1.1, 1.2, 1, 1.5, 1, 3, 2, 5, 3, 2, 1, 1, 1, 0.9, 1, 1, 3, 2.6, 4, 3, 3.2, 2, 1, 1, 0.8, 4, 4, 2, 2.5, 1, 1, 1} - - // Algorithm configuration from example. - const ( - lag = 30 - threshold = 5 - influence = 0 - ) - - // Create then initialize the peak detector. - detector := peakdetect.NewPeakDetector() - err := detector.Initialize(influence, threshold, data[:lag]) // The length of the initial values is the lag. - if err != nil { - log.Fatalf("Failed to initialize peak detector.\nError: %s", err) - } - - // Start processing new data points and determine what signal, if any they produce. - // - // This method, .Next(), is best for when data are being processed in a stream, but this simply iterates over a - // slice. - nextDataPoints := data[lag:] - for i, newPoint := range nextDataPoints { - signal := detector.Next(newPoint) - var signalType string - switch signal { - case peakdetect.SignalNegative: - signalType = "negative" - case peakdetect.SignalNeutral: - signalType = "neutral" - case peakdetect.SignalPositive: - signalType = "positive" - } - - println(fmt.Sprintf("Data point at index %d has the signal: %s", i+lag, signalType)) - } - - // This method, .NextBatch(), is a helper function for processing many data points at once. It's returned slice - // should produce the same signal outputs as the loop above. - signals := detector.NextBatch(nextDataPoints) - println(fmt.Sprintf("1:1 ratio of batch inputs to signal outputs: %t", len(signals) == len(nextDataPoints))) -} -``` - -# Testing -``` -$ go test -cover -race -PASS -coverage: 100.0% of statements -ok github.com/MicahParks/peakdetect 0.019s -``` - -# Performance -To further improve performance, this algorithm uses Welford's algorithm on initialization -and an adaptation of [this StackOverflow answer](https://stackoverflow.com/a/14638138/14797322) to calculate the mean -and population standard deviation for the lag period (sliding window). This appears to improve performance by more than -a factor of 10! - -`v0.0.4` -``` -goos: linux -goarch: amd64 -pkg: github.com/MicahParks/peakdetect -cpu: AMD Ryzen 9 7950X 16-Core Processor -BenchmarkPeakDetector_NextBatch-32 1000000000 0.0000221 ns/op -PASS -ok github.com/MicahParks/peakdetect 0.003s -``` - -`v0.1.0` -``` -goos: linux -goarch: amd64 -pkg: github.com/MicahParks/peakdetect -cpu: AMD Ryzen 9 7950X 16-Core Processor -BenchmarkPeakDetector_NextBatch-32 1000000000 0.0000011 ns/op -PASS -ok github.com/MicahParks/peakdetect 0.003s -``` - -# References -Brakel, J.P.G. van (2014). "Robust peak detection algorithm using z-scores". Stack Overflow. Available -at: https://stackoverflow.com/questions/22583391/peak-signal-detection-in-realtime-timeseries-data/22640362#22640362 -(version: 2020-11-08). - -* [StackOverflow: Peak detection in realtime timeseries data](https://stackoverflow.com/a/22640362/14797322). -* [StackOverflow: sliding window for online algorithm to calculate mean and standard devation](https://stackoverflow.com/a/14638138/14797322). -* [Welford's algorithm related blog post](https://www.johndcook.com/blog/standard_deviation/). -* Yeah, I used [Wikipedia](https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance) too. diff --git a/vendor/github.com/MicahParks/peakdetect/peakdetect.go b/vendor/github.com/MicahParks/peakdetect/peakdetect.go deleted file mode 100644 index 55c1b7d..0000000 --- a/vendor/github.com/MicahParks/peakdetect/peakdetect.go +++ /dev/null @@ -1,186 +0,0 @@ -package peakdetect - -import ( - "errors" - "fmt" - "math" -) - -const ( - // SignalNegative indicates that a particular value is a negative peak. - SignalNegative Signal = -1 - // SignalNeutral indicates that a particular value is not a peak. - SignalNeutral Signal = 0 - // SignalPositive indicates that a particular value is a positive peak. - SignalPositive Signal = 1 -) - -// Signal is a set of enums that indicates what type of peak, if any a particular value is. -type Signal int8 - -// ErrInvalidInitialValues indicates that the initial values provided are not valid to initialize a PeakDetector. -var ErrInvalidInitialValues = errors.New("the initial values provided are invalid") - -type peakDetector struct { - index uint - influence float64 - lag uint - movingMeanStdDev *movingMeanStdDev - prevMean float64 - prevStdDev float64 - prevValue float64 - threshold float64 -} - -// PeakDetector detects peaks in realtime timeseries data using z-scores. -// -// This is a Golang interface for the algorithm described by this StackOverflow answer: -// https://stackoverflow.com/a/22640362/14797322 -// -// Brakel, J.P.G. van (2014). "Robust peak detection algorithm using z-scores". Stack Overflow. Available -// at: https://stackoverflow.com/questions/22583391/peak-signal-detection-in-realtime-timeseries-data/22640362#22640362 -// (version: 2020-11-08). -type PeakDetector interface { - // Initialize initializes the PeakDetector with its configuration and initialValues. The initialValues are the first - // values to be processed by the PeakDetector. The length of these values are used to configure the PeakDetector's - // lag (see description below). The PeakDetector will never return any signals for the initialValues. - // - // influence determines the influence of signals on the algorithm's detection threshold. If put at 0, signals have - // no influence on the threshold, such that future signals are detected based on a threshold that is calculated with - // a mean and standard deviation that is not influenced by past signals. If put at 0.5, signals have half the - // influence of normal data points. Another way to think about this is that if you put the influence at 0, you - // implicitly assume stationary (i.e. no matter how many signals there are, you always expect the time series to - // return to the same average over the long term). If this is not the case, you should put the influence parameter - // somewhere between 0 and 1, depending on the extent to which signals can systematically influence the time-varying - // trend of the data. E.g., if signals lead to a structural break of the long-term average of the time series, the - // influence parameter should be put high (close to 1) so the threshold can react to structural breaks quickly. - // - // threshold is the number of standard deviations from the moving mean above which the algorithm will classify a new - // datapoint as being a signal. For example, if a new datapoint is 4.0 standard deviations above the moving mean and - // the threshold parameter is set as 3.5, the algorithm will identify the datapoint as a signal. This parameter - // should be set based on how many signals you expect. For example, if your data is normally distributed, a - // threshold (or: z-score) of 3.5 corresponds to a signaling probability of 0.00047 (from this table), which implies - // that you expect a signal once every 2128 datapoints (1/0.00047). The threshold therefore directly influences how - // sensitive the algorithm is and thereby also determines how often the algorithm signals. Examine your own data and - // choose a sensible threshold that makes the algorithm signal when you want it to (some trial-and-error might be - // needed here to get to a good threshold for your purpose). - // - // lag determines how much your data will be smoothed and how adaptive the algorithm is to change in the long-term - // average of the data. The more stationary your data is, the more lags you should include (this should improve the - // robustness of the algorithm). If your data contains time-varying trends, you should consider how quickly you want - // the algorithm to adapt to these trends. I.e., if you put lag at 10, it takes 10 'periods' before the algorithm's - // threshold is adjusted to any systematic changes in the long-term average. So choose the lag parameter based on - // the trending behavior of your data and how adaptive you want the algorithm to be. - Initialize(influence, threshold float64, initialValues []float64) error - // Next processes the next value and determines its signal. - Next(value float64) Signal - // NextBatch processes the next values and determines their signals. Their signals will be returned in a slice equal - // to the length of the input. - NextBatch(values []float64) []Signal -} - -// NewPeakDetector creates a new PeakDetector. It must be initialized before use. -func NewPeakDetector() PeakDetector { - return &peakDetector{ - movingMeanStdDev: &movingMeanStdDev{}, - } -} - -func (p *peakDetector) Initialize(influence, threshold float64, initialValues []float64) error { - p.lag = uint(len(initialValues)) - if p.lag == 0 { - return fmt.Errorf("the length of the initial values is zero, the length is used as the lag for the algorithm: %w", ErrInvalidInitialValues) - } - p.influence = influence - p.threshold = threshold - - p.prevMean, p.prevStdDev = p.movingMeanStdDev.initialize(initialValues) - p.prevValue = initialValues[p.lag-1] - - return nil -} - -func (p *peakDetector) Next(value float64) (signal Signal) { - p.index++ - if p.index == p.lag { - p.index = 0 - } - - if math.Abs(value-p.prevMean) > p.threshold*p.prevStdDev { - if value > p.prevMean { - signal = SignalPositive - } else { - signal = SignalNegative - } - value = p.influence*value + (1-p.influence)*p.prevValue - } else { - signal = SignalNeutral - } - - p.prevMean, p.prevStdDev = p.movingMeanStdDev.next(value) - p.prevValue = value - - return signal -} - -func (p *peakDetector) NextBatch(values []float64) []Signal { - signals := make([]Signal, len(values)) - for i, v := range values { - signals[i] = p.Next(v) - } - return signals -} - -// meanStdDev determines the mean and population standard deviation for the given population. -type movingMeanStdDev struct { - cache []float64 - cacheLen float64 - cacheLenU uint - index uint - prevMean float64 - prevVariance float64 -} - -// initialize creates the needed assets for the movingMeanStdDev. It also computes the resulting mean and population -// standard deviation using Welford's method. -// -// https://www.johndcook.com/blog/standard_deviation/ -func (m *movingMeanStdDev) initialize(initialValues []float64) (mean, stdDev float64) { - m.cacheLenU = uint(len(initialValues)) - m.cacheLen = float64(m.cacheLenU) - m.cache = make([]float64, m.cacheLenU) - copy(m.cache, initialValues) - - mean = initialValues[0] - prevMean := mean - var sumOfSquares float64 - for i := uint(2); i <= m.cacheLenU; i++ { - value := initialValues[i-1] - mean = prevMean + (value-prevMean)/float64(i) - sumOfSquares = sumOfSquares + (value-prevMean)*(value-mean) - prevMean = mean - } - - m.prevMean = mean - m.prevVariance = sumOfSquares / m.cacheLen - return mean, math.Sqrt(m.prevVariance) -} - -// Next computes the next mean and population standard deviation. It uses a sliding window and is based on Welford's -// method. -// -// https://stackoverflow.com/a/14638138/14797322 -func (m *movingMeanStdDev) next(value float64) (mean, stdDev float64) { - outOfWindow := m.cache[m.index] - m.cache[m.index] = value - m.index++ - if m.index == m.cacheLenU { - m.index = 0 - } - - newMean := m.prevMean + (value-outOfWindow)/m.cacheLen - m.prevVariance = m.prevVariance + (value-newMean+outOfWindow-m.prevMean)*(value-outOfWindow)/(m.cacheLen) - m.prevMean = newMean - - return m.prevMean, math.Sqrt(m.prevVariance) -} diff --git a/vendor/github.com/MicahParks/peakdetect/LICENSE b/vendor/github.com/goccmack/godsp/LICENSE similarity index 100% rename from vendor/github.com/MicahParks/peakdetect/LICENSE rename to vendor/github.com/goccmack/godsp/LICENSE diff --git a/vendor/github.com/goccmack/godsp/Readme.md b/vendor/github.com/goccmack/godsp/Readme.md new file mode 100644 index 0000000..d4ba5ef --- /dev/null +++ b/vendor/github.com/goccmack/godsp/Readme.md @@ -0,0 +1,18 @@ +# Package godsp + +Package godsp is a Go package developed to support some basic signal processing functions using the discrete wavelet transform (DWT). + +## Packages + +- **go-dsp**: General functions on vectors or sets of vectors. +- **go-dsp/dbscan**: Implementation of DBSCAN (https://en.wikipedia.org/wiki/DBSCAN) to cluster histogram bins. +- **go-dsp/dwt**: Lifting implementation of the discrete wavelet transform using the Daubechies 4 wavelet. See: + + Ripples in Mathematics. The Discrete Wavelet Transform. + A. Jensen and A. la Cour-Harbo + Springer 2001 + Section 3.4 + +## Installation + + $ go get github.com/mjibson/go-dsp/fft diff --git a/vendor/github.com/goccmack/godsp/dsp.go b/vendor/github.com/goccmack/godsp/dsp.go new file mode 100644 index 0000000..1e0f59a --- /dev/null +++ b/vendor/github.com/goccmack/godsp/dsp.go @@ -0,0 +1,526 @@ +/* +Copyright 2019 Marius Ackerman +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package dsp has a set of digital signal processing functions that are primarily +designed to support the discrete wavelet transform +("https://github.com/goccmack/dsp/dwt") +*/ +package godsp + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "math" + "strconv" + "strings" + + myioutil "github.com/goccmack/goutil/ioutil" +) + +// Abs returns |x| +func Abs(x []float64) []float64 { + x1 := make([]float64, len(x)) + for i, f := range x { + x1[i] = math.Abs(f) + } + return x1 +} + +// AbsInt returns |x| +func AbsInt(x []int) []int { + x1 := make([]int, len(x)) + for i, e := range x { + if e < 0 { + x1[i] = -e + } else { + x1[i] = e + } + } + return x1 +} + +// AbsAll returns Abs(x) for every x in X +func AbsAll(X [][]float64) [][]float64 { + x1 := make([][]float64, len(X)) + for i, x := range X { + x1[i] = Abs(x) + } + return x1 +} + +/* +Average returns Sum(x)/len(x). +*/ +func Average(x []float64) float64 { + return Sum(x) / float64(len(x)) +} + +/* +DivS returns x/s where x is a vector and s a scalar. +*/ +func DivS(x []float64, s float64) []float64 { + y := make([]float64, len(x)) + for i := range x { + y[i] = x[i] / s + } + return y +} + +/* +DownSampleAll returns DownSample(x, len(x)/min(len(xs))) for all x in xs +*/ +func DownSampleAll(xs [][]float64) [][]float64 { + N := len(xs[0]) + for _, x := range xs { + if len(x) < N { + N = len(x) + } + } + ys := make([][]float64, len(xs)) + for i, x := range xs { + ys[i] = DownSample(x, len(x)/N) + } + return ys +} + +/* +DownSample returns x downsampled by n +Function panics if len(x) is not an integer multiple of n. +*/ +func DownSample(x []float64, n int) []float64 { + if len(x)%n != 0 { + panic(fmt.Sprintf("len(x) (%d) is not an integer multiple of n (%d)", len(x), n)) + } + + x1 := make([]float64, len(x)/n) + for i, j := 0, 0; j < len(x1); i, j = i+n, j+1 { + x1[j] = x[i] + } + return x1 +} + +// FindMax returns the value and index of the first element of x equal to the maximum value in x. +func FindMax(x []float64) (value float64, index int) { + value, index = x[0], 0 + for i := 1; i < len(x)-1; i++ { + if x[i] > value { + value, index = x[i], i + } + } + return +} + +// FindMax* returns the value and index of the first element of x equal to the maximum value in x. +func FindMaxI(x []int) (value int, index int) { + value, index = x[0], 0 + for i := 1; i < len(x)-1; i++ { + if x[i] > value { + value, index = x[i], i + } + } + return +} + +// FindMin returns the value and index of the first element of x equal to the minimum value in x. +func FindMin(x []float64) (value float64, index int) { + value, index = x[0], 0 + for i := 1; i < len(x)-1; i++ { + if x[i] < value { + value, index = x[i], i + } + } + return +} + +/* +Float32ToFloat64 returns a copy of x with type []float64 +*/ +func Float32ToFloat64(x []float32) []float64 { + y := make([]float64, len(x)) + for i, f := range x { + y[i] = float64(f) + } + return y +} + +func IsPowerOf2(x int) bool { + return (x != 0) && ((x & (x - 1)) == 0) +} + +/* +LoadFloats reads a text file containing one float per line. +*/ +func LoadFloats(fname string) []float64 { + data, err := ioutil.ReadFile(fname) + if err != nil { + panic(err) + } + rdr := bufio.NewReader(bytes.NewBuffer(data)) + x := make([]float64, 0, 1024) + for s, err := rdr.ReadString('\n'); err == nil; s, err = rdr.ReadString('\n') { + f, err := strconv.ParseFloat(strings.TrimSuffix(s, "\n"), 64) + if err != nil { + panic(err) + } + x = append(x, f) + } + return x +} + +// Log2 returns the integer log base 2 of n. +// E.g.: log2(12) ~ 3.6. Log2 returns 3 +func Log2(n int) int { + return int(math.Log2(float64(n))) +} + +/* +LowpassFilterAll returns LowpassFilter(x) for all x in xs. +*/ +func LowpassFilterAll(xs [][]float64, alpha float64) [][]float64 { + ys := make([][]float64, len(xs)) + for i, x := range xs { + ys[i] = LowpassFilter(x, alpha) + } + return ys +} + +/* +LowpassFilter returns x filtered by alpha +*/ +func LowpassFilter(x []float64, alpha float64) []float64 { + y := make([]float64, len(x)) + y[0] = alpha * x[0] + for i := 1; i < len(x); i++ { + y[i] = y[i-1] + alpha*(x[i]-y[i-1]) + } + return y +} + +// Max returns the maximum value of the elements of x +func Max(x []float64) float64 { + max := x[0] + for _, f := range x { + if f > max { + max = f + } + } + return max +} + +// MaxInt returns the maximum value of the elements of x +func MaxInt(x []int) int { + max := x[0] + for _, f := range x { + if f > max { + max = f + } + } + return max +} + +/* +MovAvg returns the moving average for each x[i], given by sum(x[i-w:i+w])/(2w) +*/ +func MovAvg(x []float64, w int) []float64 { + y := make([]float64, len(x)) + for i := w; i < len(x)-w; i++ { + y[i] = Sum(x[i-w:i+w]) / float64(2*w) + } + return y +} + +/* +Multiplex returns on vector with the element of vs interleaved +*/ +func Multiplex(channels [][]float64) []float64 { + numChans := len(channels) + chanLen := len(channels[0]) + buf := make([]float64, numChans*chanLen) + for i := 0; i < chanLen; i++ { + k := i * numChans + for j := 0; j < numChans; j++ { + buf[k+j] = channels[j][i] + } + } + return buf +} + +// Normalise returns x/max(x) +func Normalise(x []float64) []float64 { + x1 := make([]float64, len(x)) + sum := Max(x) + for i, f := range x { + x1[i] = f / sum + } + return x1 +} + +// Normalise returns x/max(x) for all x in xs +func NormaliseAll(xs [][]float64) [][]float64 { + x1 := make([][]float64, len(xs)) + for i, x := range xs { + x1[i] = Normalise(x) + } + return x1 +} + +// Pow2 returns 2^x. +// The function panics if x < 0 +func Pow2(x int) int { + if x < 0 { + panic(fmt.Sprintf("X = %d", x)) + } + pw := 1 + for i := 1; i <= x; i++ { + pw *= 2 + } + return pw +} + +// Range returns an interger range 0:1:n-1 +func Range(n int) []int { + rng := make([]int, n) + for i := range rng { + rng[i] = i + } + return rng +} + +/* +RemoveAvgAllZ removes the average of all vectors x in xs. The minimum value +of any x[i] is 0. +*/ +func RemoveAvgAllZ(xs [][]float64) [][]float64 { + xs1 := make([][]float64, len(xs)) + for i, x := range xs { + xs1[i] = RemoveAvg(x) + } + return xs1 +} + +// RemoveAvgZ returns x[i] = x[i]-sum(x)/len(x) or 0 if x[i]-sum(x)/len(x) < 0 +func RemoveAvg(x []float64) []float64 { + x1 := make([]float64, len(x)) + avg := Sum(x) / float64(len(x)) + for i, f := range x { + x1[i] = f - avg + if x1[i] < 0 { + x1[i] = 0 + } + } + return x1 +} + +// Smooth smoothts x: x[i] = sum(x[i-wdw:i+wdw])/(2*wdw) +func Smooth(x []float64, wdw int) { + for i := 0; i < wdw; i++ { + x[i] = 0 + } + for i := wdw; i < len(x)-wdw; i++ { + x[i] = Sum(x[i-wdw:i+wdw]) / float64((2 * wdw)) + } +} + +/* +Sub returns x - y. The function panics if len(x) != len(y). +*/ +func Sub(x, y []float64) []float64 { + if len(x) != len(y) { + panic("len(x) != len(y)") + } + x1 := make([]float64, len(x)) + for i := range x { + x1[i] = x[i] - y[i] + } + return x1 +} + +// Sum returns the sum of the elements of the vector x +func Sum(x []float64) float64 { + sum := 0.0 + for _, f := range x { + sum += f + } + return sum +} + +// SumVectors returns the sum of the vectors in X. +// The function panics if all vectors don't have the same length +func SumVectors(X [][]float64) []float64 { + N := len(X[0]) + for i, x := range X { + if len(x) != N { + panic(fmt.Sprintf("N=%d but len(X[%d]=%d", N, i, len(x))) + } + } + sum := make([]float64, N) + for i := 0; i < N; i++ { + for j := range X { + sum[i] += X[j][i] + } + } + return sum +} + +func ToFloat(x []int) []float64 { + y := make([]float64, len(x)) + for i, e := range x { + y[i] = float64(e) / float64(math.MaxInt64) + } + return y +} + +/* +ToInt returns y * math.MaxInt64. +The range of x is [-1.0,1.0]. +The function panics if bitsPerSample is not one of 8,16,32. +*/ +func ToInt(x []float64, bitsPerSample int) []int { + y := make([]int, len(x)) + if bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 32 { + panic(fmt.Sprintf("Invalid bitsPerSample %d", bitsPerSample)) + } + max := float64(int(1)<= 0 && i < len(x)-wdw { + slp = slope(x[i : i+wdw]) + i += step + } + _, maxI = FindMax(x[from:i]) + maxI += from + slopeEnd = i + return +} + +func findLocalMin(x []float64, from, wdw, step int) (minI, slopeEnd int) { + i, slp := from+wdw, 0 + for slp <= 0 && i < len(x)-wdw { + slp = slope(x[i : i+wdw]) + i += step + } + _, minI = FindMin(x[from:i]) + minI += from + slopeEnd = i + return +} + +func findNon0Slope(x []float64, from, wdw int) (slp, end int) { + for i := from; i < len(x)-wdw; i++ { + slp := slope(x[i : i+wdw]) + if slp != 0 { + return slp, i + } + } + return 0, len(x) +} + +// slope returns +1, 0, -1 +func slope(x []float64) int { + end := len(x) - 1 + if x[0] < x[end] { + return -1 + } + if x[0] == x[end] { + return 0 + } + return 1 +} + +func ivecContain(x []int, v int) bool { + for _, v1 := range x { + if v1 == v { + return true + } + } + return false +} + +// WriteAllDataFile writes each xs[i] in xs to a test file `fname_i.txt` +func WriteAllDataFile(xs [][]float64, fname string) { + for i, xs := range xs { + WriteDataFile(xs, fmt.Sprintf("%s_%d", fname, i)) + } +} + +// WriteDataFile writes x to a text file `fname.txt` +func WriteDataFile(x []float64, fname string) { + buf := new(bytes.Buffer) + for _, f := range x { + fmt.Fprintf(buf, "%f\n", f) + } + if err := myioutil.WriteFile(fname+".txt", buf.Bytes()); err != nil { + panic(err) + } +} + +// WriteIntDataFile writes x to a text file `fname.txt` +func WriteIntDataFile(x []int, fname string) { + buf := new(bytes.Buffer) + for _, f := range x { + fmt.Fprintf(buf, "%d\n", f) + } + if err := myioutil.WriteFile(fname+".txt", buf.Bytes()); err != nil { + panic(err) + } +} + +/* +WriteIntMatrixDataFile writes an integer matrix to a text file `fname.csv` +*/ +func WriteIntMatrixDataFile(x [][]int, fname string) { + buf := new(bytes.Buffer) + for _, row := range x { + for i, col := range row { + if i > 0 { + fmt.Fprint(buf, ",") + } + fmt.Fprintf(buf, "%d", col) + } + fmt.Fprintln(buf) + } + if err := myioutil.WriteFile(fname+".csv", buf.Bytes()); err != nil { + panic(err) + } +} + +/* +Xcorr returns the cross correlation of x with y for maxDelay. +*/ +func Xcorr(x, y []float64, maxDelay int) (corr []float64) { + N := len(x) + corr = make([]float64, maxDelay) + for k := 0; k < maxDelay; k++ { + for n := 0; n < N-k; n++ { + corr[k] += x[n] * y[n+k] + } + corr[k] /= float64(N) + } + return +} diff --git a/vendor/github.com/goccmack/godsp/peaks/peaks.go b/vendor/github.com/goccmack/godsp/peaks/peaks.go new file mode 100644 index 0000000..7f404c8 --- /dev/null +++ b/vendor/github.com/goccmack/godsp/peaks/peaks.go @@ -0,0 +1,135 @@ +// Copyright 2019 Marius Ackerman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* +Package peaks finds the maxima in a vector. It works by lowering a horizontal line +across the signal, revealing peaks as it proceeds. Peaks that are closer to +each other than a minimum separation distance are merged to the left (lower index). +*/ +package peaks + +import ( + "math" + "sort" + + "github.com/goccmack/godsp" +) + +const ( + empty = -1 +) + +/* +Get returns a slice containing the indices of the peaks in x. +sep is the minimum distance between 2 peaks. Peaks closer to each other than +sep are merged to the lower index. +*/ +func Get(x []float64, sep int) []int { + pks := []int{} + for i := range x { + if isMax(i, i-sep, i+sep, x) { + pks = append(pks, i) + } + } + return pks +} + +func getMaxIndex(x []float64) int { + i, max := 0, math.Inf(-1) + for j, y := range x { + if y > max { + i, max = j, y + } + } + if max > 0 { + return i + } + return -1 +} + +func isMax(i, min, max int, x []float64) bool { + if min < 0 { + min = 0 + } + if max > len(x) { + max = len(x) + } + for j := min; j < i; j++ { + if x[j] >= x[i] { + return false + } + } + for j := i + 1; j < max; j++ { + if x[j] > x[i] { + return false + } + } + return true +} + +func getWindow(i, sep int, x []float64) (min, max int) { + min, max = i-sep, i+sep + if min < 0 { + min = 0 + } + if max > len(x) { + max = len(x) + } + return +} + +// func Get(x []float64, sep int) []int { +// si := getSortedIndices(x) +// pks := getEmptyPeaks(len(x)) +// for _, xi := range si { +// if pks[xi] == empty { +// markNeighbours(xi, sep, pks) +// } +// } +// uniquePeaks := make([]int, 0, len(x)/(2*sep)) +// for i, xi := range pks { +// if i == xi { +// uniquePeaks = append(uniquePeaks, xi) +// } +// } +// return uniquePeaks +// } + +func getEmptyPeaks(n int) []int { + epks := make([]int, n) + for i := range epks { + epks[i] = empty + } + return epks +} + +func getSortedIndices(x []float64) []int { + idx := godsp.Range(len(x)) + sort.SliceStable(idx, func(i, j int) bool { return x[i] > x[j] }) + return idx +} + +func markNeighbours(xi, sep int, pks []int) { + min := xi - sep + if min < 0 { + min = 0 + } + max := xi + sep + if max > len(pks) { + max = len(pks) + } + for i := min; i < max; i++ { + pks[i] = xi + } +} diff --git a/vendor/github.com/goccmack/godsp/wavread.go b/vendor/github.com/goccmack/godsp/wavread.go new file mode 100644 index 0000000..64b31d2 --- /dev/null +++ b/vendor/github.com/goccmack/godsp/wavread.go @@ -0,0 +1,56 @@ +// Copyright 2019 Marius Ackerman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package godsp + +import ( + "bytes" + "io/ioutil" + + "github.com/mjibson/go-dsp/wav" +) + +/* +ReadWavFile returns the demultiplexed channels of a wav file, and the sample rate in Hz. +*/ +func ReadWavFile(wavName string) (channels [][]float64, sampleRate, bitsPerSample int) { + buf, err := ioutil.ReadFile(wavName) + if err != nil { + panic(err) + } + rdr, err := wav.New(bytes.NewBuffer(buf)) + if err != nil { + panic(err) + } + numSamples, numChannels := rdr.Samples, int(rdr.NumChannels) + sampleRate = int(rdr.SampleRate) + bitsPerSample = int(rdr.Header.BitsPerSample) + channels = make([][]float64, numChannels) + chanLen := numSamples / numChannels + for i := range channels { + channels[i] = make([]float64, chanLen) + } + samples, err := rdr.ReadFloats(rdr.Samples) + if err != nil { + panic(err) + } + for i, j := 0, 0; i < len(samples); { + for _, ch := range channels { + ch[j] = float64(samples[i]) + i++ + } + j++ + } + return +} diff --git a/vendor/github.com/goccmack/goutil/LICENSE b/vendor/github.com/goccmack/goutil/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/github.com/goccmack/goutil/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/goccmack/goutil/ioutil/ioutil.go b/vendor/github.com/goccmack/goutil/ioutil/ioutil.go new file mode 100644 index 0000000..50994de --- /dev/null +++ b/vendor/github.com/goccmack/goutil/ioutil/ioutil.go @@ -0,0 +1,55 @@ +// Copyright 2020 Marius Ackerman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* +Package ioutil contains functions for writing directories and files. +*/ +package ioutil + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +// FilePermission given to all created files and directories +const FilePermission = 0731 + +// Exist returns true if path exists, otherwise false. +func Exist(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +// MkdirAll makes all the directories in path. +func MkdirAll(path string) error { + if path == "" { + return nil + } + return os.MkdirAll(path, FilePermission) +} + +// WriteFile creates all the non-existend directories in path before writing +// data to path. +func WriteFile(path string, data []byte) error { + dir, _ := filepath.Split(path) + if err := MkdirAll(dir); err != nil { + return fmt.Errorf("Error creating directory %s: %s", dir, err) + } + if err := ioutil.WriteFile(path, data, FilePermission); err != nil { + return fmt.Errorf("Error writing file %s: %s\n", path, err) + } + return nil +} diff --git a/vendor/github.com/mjibson/go-dsp/LICENSE b/vendor/github.com/mjibson/go-dsp/LICENSE new file mode 100644 index 0000000..d412027 --- /dev/null +++ b/vendor/github.com/mjibson/go-dsp/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2011 Matt Jibson + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/vendor/github.com/mjibson/go-dsp/wav/float.wav b/vendor/github.com/mjibson/go-dsp/wav/float.wav new file mode 100644 index 0000000..4d61315 Binary files /dev/null and b/vendor/github.com/mjibson/go-dsp/wav/float.wav differ diff --git a/vendor/github.com/mjibson/go-dsp/wav/small.wav b/vendor/github.com/mjibson/go-dsp/wav/small.wav new file mode 100644 index 0000000..e01fb14 Binary files /dev/null and b/vendor/github.com/mjibson/go-dsp/wav/small.wav differ diff --git a/vendor/github.com/mjibson/go-dsp/wav/wav.go b/vendor/github.com/mjibson/go-dsp/wav/wav.go new file mode 100644 index 0000000..cd388c2 --- /dev/null +++ b/vendor/github.com/mjibson/go-dsp/wav/wav.go @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2012 Matt Jibson + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Package wav provides support for the WAV file format. +// +// Supported formats are PCM 8- and 16-bit, and IEEE float. Extended chunks +// (JUNK, bext, and others added by tools like ProTools) are ignored. +package wav + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "io/ioutil" + "math" + "time" +) + +const ( + wavFormatPCM = 1 + wavFormatIEEEFloat = 3 +) + +// Header contains Wav fmt chunk data. +type Header struct { + AudioFormat uint16 + NumChannels uint16 + SampleRate uint32 + ByteRate uint32 + BlockAlign uint16 + BitsPerSample uint16 +} + +// Wav reads wav files. +type Wav struct { + Header + // Samples is the total number of available samples. + Samples int + // Duration is the estimated duration based on reported samples. + Duration time.Duration + + r io.Reader +} + +// New reads the WAV header from r. +func New(r io.Reader) (*Wav, error) { + var w Wav + header := make([]byte, 16) + if _, err := io.ReadFull(r, header[:12]); err != nil { + return nil, err + } + if string(header[0:4]) != "RIFF" { + return nil, fmt.Errorf("wav: missing RIFF") + } + if string(header[8:12]) != "WAVE" { + return nil, fmt.Errorf("wav: missing WAVE") + } + hasFmt := false + for { + if _, err := io.ReadFull(r, header[:8]); err != nil { + return nil, err + } + sz := binary.LittleEndian.Uint32(header[4:]) + switch typ := string(header[:4]); typ { + case "fmt ": + if sz < 16 { + return nil, fmt.Errorf("wav: bad fmt size") + } + f := make([]byte, sz) + if _, err := io.ReadFull(r, f); err != nil { + return nil, err + } + if err := binary.Read(bytes.NewBuffer(f), binary.LittleEndian, &w.Header); err != nil { + return nil, err + } + switch w.AudioFormat { + case wavFormatPCM: + case wavFormatIEEEFloat: + default: + return nil, fmt.Errorf("wav: unknown audio format: %02x", w.AudioFormat) + } + hasFmt = true + case "data": + if !hasFmt { + return nil, fmt.Errorf("wav: unexpected fmt chunk") + } + w.Samples = int(sz) / int(w.BitsPerSample) * 8 + w.Duration = time.Duration(w.Samples) * time.Second / time.Duration(w.SampleRate) / time.Duration(w.NumChannels) + w.r = io.LimitReader(r, int64(sz)) + return &w, nil + default: + io.CopyN(ioutil.Discard, r, int64(sz)) + } + } +} + +// ReadSamples returns a [n]T, where T is uint8, int16, or float32, based on the +// wav data. n is the number of samples to return. +func (w *Wav) ReadSamples(n int) (interface{}, error) { + var data interface{} + switch w.AudioFormat { + case wavFormatPCM: + switch w.BitsPerSample { + case 8: + data = make([]uint8, n) + case 16: + data = make([]int16, n) + default: + return nil, fmt.Errorf("wav: unknown bits per sample: %v", w.BitsPerSample) + } + case wavFormatIEEEFloat: + data = make([]float32, n) + default: + return nil, fmt.Errorf("wav: unknown audio format") + } + if err := binary.Read(w.r, binary.LittleEndian, data); err != nil { + return nil, err + } + return data, nil +} + +// ReadFloats is like ReadSamples, but it converts any underlying data to a +// float32. +func (w *Wav) ReadFloats(n int) ([]float32, error) { + d, err := w.ReadSamples(n) + if err != nil { + return nil, err + } + var f []float32 + switch d := d.(type) { + case []uint8: + f = make([]float32, len(d)) + for i, v := range d { + f[i] = float32(v) / math.MaxUint8 + } + case []int16: + f = make([]float32, len(d)) + for i, v := range d { + f[i] = (float32(v) - math.MinInt16) / (math.MaxInt16 - math.MinInt16) + } + case []float32: + f = d + default: + return nil, fmt.Errorf("wav: unknown type: %T", d) + } + return f, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 752c49f..4d12fca 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,3 @@ -# github.com/MicahParks/peakdetect v0.1.2 -## explicit; go 1.13 -github.com/MicahParks/peakdetect # github.com/chewxy/math32 v1.11.1 ## explicit; go 1.13 github.com/chewxy/math32 @@ -46,6 +43,13 @@ github.com/go-text/typesetting/language github.com/go-text/typesetting/segmenter github.com/go-text/typesetting/shaping github.com/go-text/typesetting/unicodedata +# github.com/goccmack/godsp v0.1.1 +## explicit; go 1.13 +github.com/goccmack/godsp +github.com/goccmack/godsp/peaks +# github.com/goccmack/goutil v0.4.0 +## explicit; go 1.13 +github.com/goccmack/goutil/ioutil # github.com/godbus/dbus v4.1.0+incompatible ## explicit github.com/godbus/dbus @@ -107,6 +111,9 @@ github.com/kirsle/configdir # github.com/leberKleber/go-mpris v1.1.0 ## explicit; go 1.19 github.com/leberKleber/go-mpris +# github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 +## explicit +github.com/mjibson/go-dsp/wav # github.com/smallnest/ringbuffer v0.0.0-20241129171057-356c688ba81d ## explicit; go 1.19 github.com/smallnest/ringbuffer