├── LICENSE ├── README.md ├── chromaprint └── chromaprint.go ├── go.mod ├── go.sum └── gochroma.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ivan Utkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## gochroma [![GoDoc](https://godoc.org/github.com/go-fingerprint/gochroma?status.svg)](https://godoc.org/github.com/go-fingerprint/gochroma) 2 | 3 | Go bindings and high-level API to acoustic fingerprinting library chromaprint 4 | 5 | #### Licenses 6 | 7 | * chromaprint: [LGPL2.1](http://www.gnu.org/licenses/lgpl-2.1.html) 8 | * gochroma: MIT 9 | -------------------------------------------------------------------------------- /chromaprint/chromaprint.go: -------------------------------------------------------------------------------- 1 | // Package chromaprint is a Go binding to 2 | // acoustic fingerprinting library chromaprint. 3 | package chromaprint 4 | 5 | // #cgo pkg-config: fftw3 6 | // #cgo LDFLAGS: -lchromaprint -lm -lstdc++ 7 | // #include 8 | // #include 9 | // 10 | // void **allocate_mem() { 11 | // return malloc(sizeof(void*)); 12 | // } 13 | import "C" 14 | 15 | import ( 16 | "errors" 17 | "reflect" 18 | "time" 19 | "unsafe" 20 | ) 21 | 22 | // Possible errors that may occur during function calls. 23 | var ( 24 | ErrSetOption = errors.New("chromaprint: failed to set option") 25 | ErrStart = errors.New("chromaprint: failed to restart chromaprint") 26 | ErrFeed = errors.New("chromaprint: failed to send data to the fingerprint calculator") 27 | ErrFinish = errors.New("chromaprint: failed to process buffered audiodata") 28 | ErrFprint = errors.New("chromaprint: failed to calculate compressed fingerprint") 29 | ErrRawFprint = errors.New("chromaprint: failed to calculate raw fingerprint") 30 | ) 31 | 32 | // Available algorithms for fingerprinting. 33 | const ( 34 | CHROMAPRINT_ALGORITHM_TEST1 = C.CHROMAPRINT_ALGORITHM_TEST1 35 | CHROMAPRINT_ALGORITHM_TEST2 = C.CHROMAPRINT_ALGORITHM_TEST2 36 | CHROMAPRINT_ALGORITHM_TEST3 = C.CHROMAPRINT_ALGORITHM_TEST3 37 | CHROMAPRINT_ALGORITHM_TEST4 = C.CHROMAPRINT_ALGORITHM_TEST4 38 | 39 | CHROMAPRINT_ALGORITHM_DEFAULT = C.CHROMAPRINT_ALGORITHM_DEFAULT 40 | ) 41 | 42 | // ChromaprintVersion returns version numper of chromaprint library. 43 | func ChromaprintVersion() string { 44 | return C.GoString(C.chromaprint_get_version()) 45 | } 46 | 47 | // A ChromaprintContext holds all data structures required for fingerprint 48 | // calculation. 49 | type ChromaprintContext struct { 50 | context *C.ChromaprintContext 51 | algorithm int 52 | } 53 | 54 | // NewChromaprint creates new CromaprintContext with given algorithm. 55 | // Returned ChromaprintContext must be freed with Free(). 56 | func NewChromaprint(algorithm int) *ChromaprintContext { 57 | return &ChromaprintContext{C.chromaprint_new(C.int(algorithm)), algorithm} 58 | } 59 | 60 | // Free ChromaprintContext 61 | func (ctx *ChromaprintContext) Free() { 62 | C.chromaprint_free(ctx.context) 63 | } 64 | 65 | // Algorithm returns selected algorithm, one of the 66 | // CHROMAPRINT_ALGORITHM_TEST1 etc. 67 | func (ctx *ChromaprintContext) Algorithm() int { 68 | return ctx.algorithm 69 | } 70 | 71 | // SetOption sets a configuration option for the selected algorithm. 72 | // Currently only one option is available: 'silence_threshold' 73 | // with possible values 0-32767. 74 | func (ctx *ChromaprintContext) SetOption(name string, value int) error { 75 | cname := C.CString(name) 76 | defer C.free(unsafe.Pointer(cname)) 77 | 78 | if int(C.chromaprint_set_option(ctx.context, cname, C.int(value))) < 1 { 79 | return ErrSetOption 80 | } 81 | return nil 82 | } 83 | 84 | // Start restarts the computation of a fingerprint with a new audio stream. 85 | func (ctx *ChromaprintContext) Start(sampleRate, numChannels int) error { 86 | if int(C.chromaprint_start(ctx.context, C.int(sampleRate), C.int(numChannels))) < 1 { 87 | return ErrStart 88 | } 89 | return nil 90 | } 91 | 92 | // Feed sends audio data, encoded in 16-bit signed integers with 93 | // native byte-order, which in most architectures such as Intel x86 94 | // and x86-64 is little-endian. 95 | func (ctx *ChromaprintContext) Feed(data []byte) error { 96 | if int(C.chromaprint_feed(ctx.context, (*C.int16_t)(unsafe.Pointer(&data[0])), C.int(len(data)/2))) < 1 { 97 | return ErrFeed 98 | } 99 | return nil 100 | } 101 | 102 | // Finish processes any remaining buffered data and calculates the fingerprint. 103 | func (ctx *ChromaprintContext) Finish() error { 104 | if r := C.chromaprint_finish(ctx.context); int(r) < 1 { 105 | return ErrFinish 106 | } 107 | return nil 108 | } 109 | 110 | // GetFingerprint returns the calculated fingerprint as a compressed string. 111 | func (ctx *ChromaprintContext) GetFingerprint() (fprint string, err error) { 112 | ptr := (**C.char)(unsafe.Pointer(C.allocate_mem())) 113 | defer C.free(unsafe.Pointer(ptr)) 114 | 115 | if int(C.chromaprint_get_fingerprint(ctx.context, ptr)) < 1 { 116 | return "", ErrFprint 117 | } 118 | 119 | fprint = C.GoString(*ptr) 120 | C.chromaprint_dealloc(unsafe.Pointer(*ptr)) 121 | return 122 | } 123 | 124 | // GetRawFingerprint returns the calculated fingerprint 125 | // as a slice of 32-bit integers. 126 | func (ctx *ChromaprintContext) GetRawFingerprint() (fprint []int32, err error) { 127 | ptr := C.allocate_mem() 128 | defer C.free(unsafe.Pointer(ptr)) 129 | 130 | var size C.int 131 | if int(C.chromaprint_get_raw_fingerprint(ctx.context, (**C.uint32_t)(unsafe.Pointer(ptr)), &size)) < 1 { 132 | return nil, ErrRawFprint 133 | } 134 | 135 | fprint = goInt32s(*ptr, int(size)) 136 | return 137 | } 138 | 139 | // GetItemDuration returns the duration of one item in the raw fingerprint in milliseconds. 140 | func (ctx *ChromaprintContext) GetItemDuration() time.Duration { 141 | ms := int(C.chromaprint_get_item_duration_ms(ctx.context)) 142 | return time.Duration(ms) * time.Millisecond 143 | } 144 | 145 | // GetItemDurationSamples returns the duration of one item in the raw fingerprint in samples. 146 | func (ctx *ChromaprintContext) GetItemDurationSamples() int { 147 | return int(C.chromaprint_get_item_duration(ctx.context)) 148 | } 149 | 150 | // GetSampleRate gets the sampling rate that is internally used for fingerprinting. 151 | func (ctx *ChromaprintContext) GetSampleRate() int { 152 | return int(C.chromaprint_get_sample_rate(ctx.context)) 153 | } 154 | 155 | // GetNumChannels gets the number of channels that is internally used for fingerprinting. 156 | func (ctx *ChromaprintContext) GetNumChannels() int { 157 | return int(C.chromaprint_get_num_channels(ctx.context)) 158 | } 159 | 160 | func goInt32s(ptr unsafe.Pointer, size int) (ints []int32) { 161 | hdr := reflect.SliceHeader{ 162 | Data: uintptr(ptr), 163 | Len: size, 164 | Cap: size, 165 | } 166 | cints := *(*[]int32)(unsafe.Pointer(&hdr)) 167 | ints = make([]int32, size) 168 | copy(ints, cints) 169 | return 170 | } 171 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-fingerprint/gochroma 2 | 3 | go 1.16 4 | 5 | require github.com/go-fingerprint/fingerprint v0.0.0-20140803133125-29397256b7ff 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-fingerprint/fingerprint v0.0.0-20140803133125-29397256b7ff h1:MVMRAz9+9uI8+nzBPHILUVLNzH+jaKKmGmEf5GrgNsM= 2 | github.com/go-fingerprint/fingerprint v0.0.0-20140803133125-29397256b7ff/go.mod h1:p+iFTUBRUOKBOZtWQCQAZHhLI7fC5bMdiDc5B4PPBU0= 3 | -------------------------------------------------------------------------------- /gochroma.go: -------------------------------------------------------------------------------- 1 | // Package gochroma provides a high-level API to the acoustic 2 | // fingerprinting library chromaprint. 3 | package gochroma 4 | 5 | import ( 6 | "github.com/go-fingerprint/fingerprint" 7 | "github.com/go-fingerprint/gochroma/chromaprint" 8 | "io" 9 | ) 10 | 11 | const ( 12 | seconds = 10 13 | minmaxseconds = 120 14 | ) 15 | 16 | // Available algorithms for fingerprinting. 17 | const ( 18 | Algorithm1 = chromaprint.CHROMAPRINT_ALGORITHM_TEST1 19 | Algorithm2 = chromaprint.CHROMAPRINT_ALGORITHM_TEST2 20 | Algorithm3 = chromaprint.CHROMAPRINT_ALGORITHM_TEST3 21 | Algorithm4 = chromaprint.CHROMAPRINT_ALGORITHM_TEST4 22 | 23 | AlgorithmDefault = chromaprint.CHROMAPRINT_ALGORITHM_DEFAULT 24 | ) 25 | 26 | // A Printer is a fingerprint.Calculator backed by libchromaprint. 27 | type Printer struct { 28 | context *chromaprint.ChromaprintContext 29 | } 30 | 31 | // New creates new Printer. Returned Printer must be closed 32 | // with Close(). Note that if libchromaprint is compiled with FFTW, 33 | // New should be called only from one goroutine at a time. 34 | func New(algorithm int) (p *Printer) { 35 | return &Printer{chromaprint.NewChromaprint(algorithm)} 36 | } 37 | 38 | // Close existing Printer. 39 | func (p *Printer) Close() { 40 | p.context.Free() 41 | } 42 | 43 | // Fingerprint implements fingerprint.Calculator interface. 44 | func (p *Printer) Fingerprint(i fingerprint.RawInfo) (fprint string, err error) { 45 | if err = p.prepare(i); err != nil { 46 | return 47 | } 48 | fprint, err = p.context.GetFingerprint() 49 | return 50 | } 51 | 52 | // RawFingerprint implements fingerprint.Calculator interface. 53 | func (p *Printer) RawFingerprint(i fingerprint.RawInfo) (fprint []int32, err error) { 54 | if err = p.prepare(i); err != nil { 55 | return 56 | } 57 | fprint, err = p.context.GetRawFingerprint() 58 | return 59 | } 60 | 61 | func (p *Printer) prepare(i fingerprint.RawInfo) error { 62 | if i.MaxSeconds < minmaxseconds { 63 | i.MaxSeconds = minmaxseconds 64 | } 65 | ctx := p.context 66 | rate, channels := i.Rate, i.Channels 67 | if err := ctx.Start(int(rate), int(channels)); err != nil { 68 | return err 69 | } 70 | numbytes := 2 * seconds * rate * channels 71 | buf := make([]byte, numbytes) 72 | for total := uint(0); total <= i.MaxSeconds; total += seconds { 73 | read, err := i.Src.Read(buf) 74 | if err != nil && err != io.EOF { 75 | return err 76 | } 77 | if read == 0 { 78 | break 79 | } 80 | if err := ctx.Feed(buf[:read]); err != nil { 81 | return err 82 | } 83 | } 84 | if err := ctx.Finish(); err != nil { 85 | return err 86 | } 87 | return nil 88 | } 89 | --------------------------------------------------------------------------------