Commit a678e778 authored by Arttu Ylä-Sahra's avatar Arttu Ylä-Sahra

Initial commit from codebase

parents
workspace.xml
tasks.xml
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GoRedundantParens" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
</profile>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/echoic.iml" filepath="$PROJECT_DIR$/.idea/echoic.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="SCSS" />
</project>
\ No newline at end of file
This diff is collapsed.
# Echoic
## An interactive music recognition tool
We've all experienced that situation. You hear a tune.. and you are sure you've heard it sometime before. Perhaps you even have it in your own library. But what _is it?_
This tool is intended to remedy that, at least for one's personal music libraries. A tidy summer project, if one will; it is not too functional just yet.
# Requirements
So far, you need a **Linux** system with suitable **FFTW** (Fastest Fourier Transform in the West) library and development support to run this program.
# Execution
`go run echoic.go` in the main project directory
# License
Echoic itself is licensed under GNU AGPL v3 or later at your choice. Any dependencies are licensed with their own respective licenses.
\ No newline at end of file
package main
import (
"github.com/arttuys/echoic/pkg/recognizer/fftw/cgo_api"
"fmt"
"time"
"github.com/arttuys/echoic/pkg/recognizer/fftw"
)
/**
func testJoin() {
var x = fftw.NewArrayN([]uint64{3,2,1,2})
x.Elements = []complex128{2,6,9,3,4,1,2,5,8,7,4,4}
var y = fftw.NewArrayN([]uint64{3,2,1,2})
y.Elements = []complex128{7,1,4,7,2,5,2,8,1,3,5,7}
z := fftw.Join([]*fftw.ComplexArrayN{x,y})
z.Elements[3] = 0
z.Elements[14] = 1
fmt.Println(x)
fmt.Println(y)
fmt.Println(z)
}
func testFFTW() {
cgo_api.InitFFTWThreads()
}
func main() {
testFFTW()
for i := 0; i < 10; i++ {
z := url_scrambler.IdentifierToScrambledString([4]uint32{0,3,7,4},uint64(i))
fmt.Println(z)
fmt.Println(url_scrambler.ScrambledStringToIdentifier([4]uint32{0,3,7,4}, z))
}
var x = fftw.NewArrayN([]uint64{4,1,9,2})
fmt.Println(x.DifferentialN())
var y = fftw.NewArrayN([]uint64{8,7,1,5,3})
fmt.Println(y.DifferentialN())
fmt.Println(y.RawIndex([]uint64{7,2,0,4,1}))
z := y.RawIndex([]uint64{7,2,0,4,1})
y.Elements[z] = 0.3+2.1i;
var yb = y.Slice([]uint64{7,2,0})
y.Elements[z] = 0.6+2.1i;
fmt.Println("Y")
fmt.Println(y.N)
fmt.Println((y.Elements))
fmt.Println("YB")
fmt.Println(yb.N)
fmt.Println((yb.Elements))
testJoin()
}
**/
func main() {
cgo_api.InitFFTWThreads()
fmt.Println(cgo_api.LoadFFTWSystemWisdom())
fmt.Println(time.Now())
plan, _ := cgo_api.NewFFTWPlan([]uint{256}, true)
fmt.Println(time.Now())
res, _ := fftw.NewArrayN([]uint{256})
if (res == nil) {
panic ("Array creation failed!")
}
for i := 0; i < 256; i++ {
res.Elements[i] = 1
}
res.Elements[3] = 1
fmt.Println(res)
fmt.Println(plan.DoDFFTForArray(res))
fmt.Println(res)
if (plan != nil) {
fmt.Println("Closing")
plan.Close()
}
// Make a reverse plan
plan2, _ := cgo_api.NewFFTWPlan([]uint{256}, false)
fmt.Println(plan2.DoDFFTForArray(res))
fmt.Println(res)
// Descale
for i := 0; i < 256; i++ {
var divisor = complex128(1.0/256.0)
res.Elements[i] *= divisor
}
fmt.Println(res)
}
package url_scrambler
/*
Echoic - an interactive music identification tool
Copyright (C) 2018 Arttu Ylä-Sahra
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
URL Scrambler - decode and encode given identifiers. For speed, this
is implemented using the XTEA algorithm, which should provide sufficient
security for this use-case; it should prevent against casual
onlookers only, not from motivated attackers. And be fast!
*/
import (
"strconv"
"strings"
)
// NumRounds specifies the amount of XTEA rounds done
const NumRounds = 32
// Delta specifies the delta constant required by XTEA
const Delta uint32 = 0x9E3779B9
// NumRoundsDelta specifies a helper constant for XTEA decryption, calculated as follows: (NumRounds * Delta) % (2 pow 32)
const NumRoundsDelta uint32 = 3337565984
// IdentifierToScrambledString transforms an unsigned 64bit identifier into a base 36 string, encrypted using XTEA
func IdentifierToScrambledString(key [4]uint32, identifier uint64) (string) {
// Disjoint the identifier into two 32-bit units, little-endian order
var i0, i1, sum uint32 = uint32(identifier), uint32(identifier>>32), 0
for i := 0; i < NumRounds; i++ {
// This is safe, Golang language specification indicates that wrap-around is allowed and ordinary behavior
i0 += (((i1 << 4) ^ (i1 >> 5)) + i1) ^ (sum + key[sum&3])
sum += Delta
i1 += (((i0 << 4) ^ (i0 >> 5)) + i0) ^ (sum + key[(sum>>11)&3])
}
return strings.ToUpper(strconv.FormatUint(uint64(i1)<<32+uint64(i0), 36))
}
// ScrambledStringToIdentifier attempts to decode a scrambled string into an identifier using a provided key; it may fail if the string does not decode into a valid 64bit unsigned integer
func ScrambledStringToIdentifier(key [4]uint32, scrambled string) (uint64, error) {
// Try to first decode our string
encrypted, decodeErr := strconv.ParseUint(strings.ToLower(scrambled), 36, 64)
if decodeErr != nil {
return 0, decodeErr
}
var i0, i1, sum = uint32(encrypted), uint32(encrypted>>32), NumRoundsDelta
for i := 0; i < NumRounds; i++ {
i1 -= (((i0 << 4) ^ (i0 >> 5)) + i0) ^ (sum + key[(sum>>11)&3])
sum -= Delta
i0 -= (((i1 << 4) ^ (i1 >> 5)) + i1) ^ (sum + key[sum&3])
}
return uint64(i1)<<32 + uint64(i0), nil
}
package url_scrambler
/*
Echoic - an interactive music identification tool
Copyright (C) 2018 Arttu Ylä-Sahra
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
URL scrambler tests
*/
import (
"testing"
"math/rand"
)
func TestAll(t *testing.T) {
for range [100]int{} {
key := [4]uint32{rand.Uint32(), rand.Uint32(), rand.Uint32(), rand.Uint32() % (1 << 12)}
invalidKey := [4]uint32{rand.Uint32(), rand.Uint32(), rand.Uint32(), (1 << 13) + (rand.Uint32() % (1 << 12))}
if (key == invalidKey) {
t.Fatal("Test for URLScrambler broken, two random keys equivalent")
}
id := rand.Uint64()
// Generate a string
str := IdentifierToScrambledString(key, id)
// And back again, both with a valid and an invalid key
id1, err1 := ScrambledStringToIdentifier(key, str)
id2, err2 := ScrambledStringToIdentifier(invalidKey, str)
if (err1 != nil || err2 != nil) {
t.Fatal("Valid strings errored")
}
if (id != id1 || id == id2) {
t.Fatal("Decryption did not work properly")
}
}
// Finally, attempt a faulty decode
_, err := ScrambledStringToIdentifier([4]uint32{0, 1, 2, 3}, "Totally Invalid!")
if (err == nil) {
t.Fatal("Invalid string did not error!")
}
}
package fftw
/*
Echoic - an interactive music identification tool
Copyright (C) 2018 Arttu Ylä-Sahra
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Simple complex N-dimensional array library, intended to be used with the FFTW libraries but perhaps for other generic storage too
*/
import (
"reflect"
"errors"
)
// An N-dimensional array; from 1 to N dimensions
type ComplexArrayN struct {
N []uint
Elements []complex128
}
// Len() returns the product of lengths of all dimensions; this is a simple length in case of 1-dimensional arrays
func (a *ComplexArrayN) Len() uint {
if (a == nil) {
panic("Attempted to use a method on a nil array")
}
var tsize uint
tsize = 1
for _, dimsize := range a.N {
tsize = SafeMult(tsize, dimsize)
}
return tsize
}
// Dim() returns the amount of dimensions in this array
func (a *ComplexArrayN) Dims() int {
if (a == nil) {
panic("Attempted to use a method on a nil array")
}
return len(a.N)
}
// Set() sets a value at a given set of indexes, or panics if the indexes do not resolve into a valid location. An incomplete index may be given, in which case the rest are assumed to be zero
func (a* ComplexArrayN) Set(indexes []uint, val complex128) error {
if (a == nil) {
panic("Attempted to use a method on a nil array")
}
rawInd, err := a.RawIndex(indexes)
if (err != nil) {
return err
}
a.Elements[rawInd] = val
return nil
}
// Get() gets a value at a given set of indexes, or panics if they do not resolve into a valid location. An incomplete index may be given, in which case rest of the indexes are assumed to be zero
func (a* ComplexArrayN) Get(indexes []uint) (complex128, error) {
if (a == nil) {
panic("Attempted to use a method on a nil array")
}
rawInd, err := a.RawIndex(indexes)
if (err != nil) {
return 0, err
}
return a.Elements[rawInd], nil
}
// DifferentialN() calculates the length of the dimensions; that is, how many elements of the array one of these indexes will contain. By consequence, the last dimension will always have length of 1, as it indicates a single item.
func (a *ComplexArrayN) DifferentialN() []uint {
if (a == nil) {
panic("Attempted to use a method on a nil array")
}
var diffInds = make([]uint, len(a.N))
diffInds[len(diffInds)-1] = 1
// Start from the second to last
for i := len(diffInds) - 2; i >= 0; i-- {
diffInds[i] = SafeMult(diffInds[i+1], a.N[i+1])
}
return diffInds
}
// Join() joins several complex arrays to a N+1 array, where the new first dimension's size matches the length of the provided list. All arrays must be of the same exact shape, otherwise it is not possible to join them
func Join(arrays []*ComplexArrayN) (*ComplexArrayN, error) {
if (arrays == nil || (len(arrays) == 0)) {
return nil, errors.New("arrays to join must be provided")
}
first := arrays[0]
if (first == nil) {
return nil, errors.New("no array may be nil")
}
newDim := append([]uint{uint(len(arrays))}, first.N...)
var resultArr []complex128
resultArr = append(resultArr, first.Elements...)
for i := 1; i < len(arrays); i++ {
if (arrays[i] == nil) {
return nil, errors.New("no array may be nil")
}
if !reflect.DeepEqual(first.N, arrays[i].N) {
return nil, errors.New("arrays to be joined must be of same shape")
}
resultArr = append(resultArr, arrays[i].Elements...)
}
// All our arrays have a valid shape
var narr ComplexArrayN
narr.N = newDim
narr.Elements = resultArr
return &narr, nil
}
// RawIndex() returns the raw index in the array, which contains the desired item
func (a *ComplexArrayN) RawIndex(indexes []uint) (uint, error) {
if (a == nil) {
panic("Attempted to use a method on a nil array")
}
if !a.indexArrValid(indexes) {
return 0, errors.New("indexes nonexistent, too long or out of range")
}
var index uint = 0
// Determine the differential indexes
var diffIndexes = a.DifferentialN()
for i := 0; i < len(indexes); i++ {
index += indexes[i] * diffIndexes[i]
}
return index, nil
}
// indexArrValid checks if the subarray of indexes could be potentially valid; must not be too long, and none of the indexes can be too large
func (a *ComplexArrayN) indexArrValid(indexes []uint) bool {
if (indexes == nil) {
return false // Nil indexes are obviously not valid
}
// Check all indexes are valid
if len(indexes) > len(a.N) {
return false
}
for i := 0; i < len(indexes); i++ {
if indexes[i] >= a.N[i] {
return false // Too large
}
}
return true
}
// Slice() returns a slice of the array. The type of array returned is determined by the list of indexes; each given index slices a single part from the respective dimension, in row-major order. If the amount of given indexes is zero, a copy of the array is returned
// If the amount of indexes match to the number of dimensions, an one-dimensional, N=1 unit will be returned.
//
// If any of the indexes are larger than their respective dimensions or there are more indexes than dimensions, this function will panic
func (a *ComplexArrayN) Slice(indexes []uint) (*ComplexArrayN, error) {
if (a == nil) {
panic("Attempted to use a method on a nil array")
}
if !a.indexArrValid(indexes) {
return nil, errors.New("index array too long, or some of the indexes go outside their respective dimensions")
}
if len(indexes) == 0 {
var narr ComplexArrayN
narr.N = make([]uint, len(a.N))
copy(narr.N, a.N)
narr.Elements = make([]complex128, len(a.Elements))
copy(narr.Elements, a.Elements)
return &narr,nil
}
// If we have provided all indexes, create a single index element
if len(indexes) == len(a.N) {
narr, _ := NewArrayN([]uint{1}) // Shoud not fail, perfectly valid
finalInd, _ := a.Get(indexes) // If the index arr is valid as verified above, this should be OK as well
narr.Elements[0] = finalInd
return narr, nil
}
// OK, we only want a part. Get the differential N's
diffN := a.DifferentialN()
startIndex, _ := a.RawIndex(indexes) // Indexes have been already verified as valid, should not fail
length := diffN[len(indexes)-1]
endIndex := startIndex + length - 1
// Slice a part of the dimensions
dimSlice := append([]uint{}, a.N[(len(indexes)):len(a.N)]...)
// Since we take a slice of valid dimensions, this should always pass
narr, _ := NewArrayN(dimSlice)
copy(narr.Elements, a.Elements[startIndex:endIndex+1])
return narr, nil
}
// NewArrayN() initializes a new ComplexArrayN, essentially creating the correctly sized data structures
func NewArrayN(n []uint) (*ComplexArrayN, error) {
var arr ComplexArrayN
var tsize uint
// Calculate the appropriate size
tsize = 1
if (n == nil || len(n) == 0) {
return nil, errors.New("must specify a nonempty dimension")
}
for _, dimsize := range n {
if (dimsize == 0) {
return nil, errors.New("dimension size 0 given, not valid")
}
tsize = SafeMult(tsize, dimsize)
}
arr.Elements = make([]complex128, tsize)
arr.N = make([]uint, len(n))
copy(arr.N, n)
return &arr, nil
}
// SafeMult() checks if a multiplication is possible without overflow; if not, it will panic
func SafeMult(a, b uint) uint {
if a <= 1 || b <= 1 {
return a * b // Unable to overflow
}
c := a * b
if (c / b != a) {
panic("Multiplication overflowed in safeMult")
}
return c
}
\ No newline at end of file
package fftw
/*
Echoic - an interactive music identification tool
Copyright (C) 2018 Arttu Ylä-Sahra
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Tests for complex N-dimensional arrays
*/
import (
"testing"
"reflect"
"fmt"
)
// Basic operations
func TestArrayBasics(t *testing.T) {
invalidValues := []([]uint){nil, {}, {2, 4, 2, 0}}
for _, v := range invalidValues {
_, err1 := NewArrayN(v)
if (err1 == nil) {
t.Fatal("Errors not thrown properly for invalid creation")
}
}
basicArray, _ := NewArrayN([]uint{4,6,8,5,3,1,1,2})
if (basicArray.Len() != 5760 || basicArray.Dims() != 8) {
t.Fatal("Array instantiated with bad dimensions")
}
}
// Join
func TestArrayJoin(t *testing.T) {
basicArray, _ := NewArrayN([]uint{2,2})
// Attempt basic invalid joins
invalidJoins := []([]*ComplexArrayN){nil, {nil}, {}, {basicArray, nil}}
for _, v := range invalidJoins {
res, err := Join(v)
if (res != nil || err == nil) {
t.Fatal("Invalid join led to a valid result")
}
}
// Let's try a baseline case
res, err := Join([]*ComplexArrayN{basicArray})
if (res == nil || err != nil) {
t.Fatal("Valid single join failed!")
}
if (!reflect.DeepEqual(res.Elements, []complex128{0,0,0,0}) || !reflect.DeepEqual(res.N, []uint{1,2,2})) {
t.Fatal("Invalid, nonstandard join created!")
}
// Let's try wonkily shaped ones
anotherArray, _ := NewArrayN([]uint{1,2})
res, err = Join([]*ComplexArrayN{basicArray, anotherArray})
if (res != nil || err == nil) {
t.Fatal("Allowed to join misshaped arrays!")
}
// Now, let's see how this merges
yetAnotherArray, _ := NewArrayN([]uint{2,2})
yetAnotherArray.Elements = []complex128{1,2,3,4}
res, err = Join([]*ComplexArrayN{yetAnotherArray,basicArray,yetAnotherArray})
if (res == nil || err != nil) {
t.Fatal("Valid nontrivial join failed!")
}
if (!reflect.DeepEqual(res.Elements, []complex128{1,2,3,4,0,0,0,0,1,2,3,4}) || !reflect.DeepEqual(res.N, []uint{3,2,2})) {
t.Fatal("Invalid, nonstandard join created!")
}
}
// Slices
func TestSlicing(t *testing.T) {
basicArray, _ := NewArrayN([]uint{2,1,2})
invalidValues := []([]uint){nil, {2, 0, 1}, {1, 0, 0, 3}}
for _, v := range invalidValues {
_, err1 := basicArray.Slice(v)
if (err1 == nil) {
t.Fatal("Errors not thrown properly for invalid creation")
}
}
// Check that slices are actual copies, even in case of zero slices
copiedArray, err := basicArray.Slice([]uint{})
if (copiedArray == nil || err != nil) {
t.Fatal("Single-unit copy failed")
}
if (&copiedArray == &basicArray || &copiedArray.N == &basicArray.N || &copiedArray.Elements == &basicArray.Elements) {
t.Fatal("Non-genuine copy for Slice")
}
if (!reflect.DeepEqual(copiedArray.N, basicArray.N) || !reflect.DeepEqual(copiedArray.Elements, basicArray.Elements)) {
fmt.Println(copiedArray.N)
fmt.Println(basicArray.N)
fmt.Println(copiedArray.Elements)
fmt.Println(basicArray.Elements)
t.Fatal("Mismatching contents for copies")
}
// Next, make a zero slice
basicArray.Elements = []complex128{9,3,2,5}
newSlice, _ := basicArray.Slice([]uint{1,0,0})
if (!reflect.DeepEqual(newSlice.N, []uint{1}) || !reflect.DeepEqual(newSlice.Elements, []complex128{2})) {
t.Fatal("Improper slicing!")
}
// Compare to how an incomplete slice behaves
newSlice2, _ := basicArray.Slice([]uint{1})
if (!reflect.DeepEqual(newSlice2.N, []uint{1,2}) || !reflect.DeepEqual(newSlice2.Elements, []complex128{2,5})) {
t.Fatal("Improper slicing!")
}
// Compare to how an incomplete slice behaves