Update GitHub

This commit is contained in:
Chigozirim Igweamaka 2024-04-26 05:39:40 +01:00
parent e661dc5e2f
commit 7dcc7b1b52
8 changed files with 276 additions and 158 deletions

136
server.go
View file

@ -4,7 +4,6 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"song-recognition/shazam" "song-recognition/shazam"
@ -15,7 +14,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/pion/webrtc/v4" "github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media/oggwriter" "go.mongodb.org/mongo-driver/bson/primitive"
socketio "github.com/googollee/go-socket.io" socketio "github.com/googollee/go-socket.io"
) )
@ -42,6 +41,21 @@ func GinMiddleware(allowOrigin string) gin.HandlerFunc {
} }
} }
type DownloadStatus struct {
Type string
Message string
}
func downloadStatus(msgType, message string) string {
data := map[string]interface{}{"type": msgType, "message": message}
jsonData, err := json.Marshal(data)
if err != nil {
fmt.Println("Error marshalling JSON:", err)
return ""
}
return string(jsonData)
}
func main() { func main() {
router := gin.New() router := gin.New()
@ -54,13 +68,6 @@ func main() {
return nil return nil
}) })
server.OnEvent("/", "initOffer", func(s socketio.Conn, initEncodedOffer string) {
log.Println("initOffer: ", initEncodedOffer)
peerConnection := signal.SetupWebRTC(initEncodedOffer)
s.Emit("initAnswer", signal.Encode(*peerConnection.LocalDescription()))
})
server.OnEvent("/", "totalSongs", func(socket socketio.Conn) { server.OnEvent("/", "totalSongs", func(socket socketio.Conn) {
db, err := utils.NewDbClient() db, err := utils.NewDbClient()
if err != nil { if err != nil {
@ -100,41 +107,54 @@ func main() {
tracksInAlbum, err := spotify.AlbumInfo(spotifyURL) tracksInAlbum, err := spotify.AlbumInfo(spotifyURL)
if err != nil { if err != nil {
fmt.Println("log error: ", err) fmt.Println("log error: ", err)
if len(err.Error()) <= 25 {
socket.Emit("downloadStatus", downloadStatus("error", err.Error()))
}
return return
} }
socket.Emit("albumStat", fmt.Sprintf("%v songs found in album.", len(tracksInAlbum))) statusMsg := fmt.Sprintf("%v songs found in album.", len(tracksInAlbum))
socket.Emit("downloadStatus", downloadStatus("info", statusMsg))
totalTracksDownloaded, err := spotify.DlAlbum(spotifyURL, tmpSongDir) totalTracksDownloaded, err := spotify.DlAlbum(spotifyURL, tmpSongDir)
if err != nil { if err != nil {
socket.Emit("downloadStatus", fmt.Sprintf("Failed to download album.")) socket.Emit("downloadStatus", downloadStatus("error", "Couldn't to download album."))
return return
} }
socket.Emit("downloadStatus", fmt.Sprintf("%d songs downloaded from album", totalTracksDownloaded)) statusMsg = fmt.Sprintf("%d songs downloaded from album", totalTracksDownloaded)
socket.Emit("downloadStatus", downloadStatus("success", statusMsg))
} else if strings.Contains(spotifyURL, "playlist") { } else if strings.Contains(spotifyURL, "playlist") {
tracksInPL, err := spotify.PlaylistInfo(spotifyURL) tracksInPL, err := spotify.PlaylistInfo(spotifyURL)
if err != nil { if err != nil {
fmt.Println("log error: ", err) fmt.Println("log error: ", err)
if len(err.Error()) <= 25 {
socket.Emit("downloadStatus", downloadStatus("error", err.Error()))
}
return return
} }
socket.Emit("playlistStat", fmt.Sprintf("%v songs found in playlist.", len(tracksInPL))) statusMsg := fmt.Sprintf("%v songs found in playlist.", len(tracksInPL))
socket.Emit("downloadStatus", downloadStatus("info", statusMsg))
totalTracksDownloaded, err := spotify.DlPlaylist(spotifyURL, tmpSongDir) totalTracksDownloaded, err := spotify.DlPlaylist(spotifyURL, tmpSongDir)
if err != nil { if err != nil {
fmt.Println("log errorr: ", err) fmt.Println("log errorr: ", err)
socket.Emit("downloadStatus", fmt.Sprintf("Failed to download playlist.")) socket.Emit("downloadStatus", downloadStatus("error", "Couldn't download playlist."))
return return
} }
socket.Emit("downloadStatus", fmt.Sprintf("%d songs downloaded from playlist", totalTracksDownloaded)) statusMsg = fmt.Sprintf("%d songs downloaded from playlist.", totalTracksDownloaded)
socket.Emit("downloadStatus", downloadStatus("success", statusMsg))
} else if strings.Contains(spotifyURL, "track") { } else if strings.Contains(spotifyURL, "track") {
trackInfo, err := spotify.TrackInfo(spotifyURL) trackInfo, err := spotify.TrackInfo(spotifyURL)
if err != nil { if err != nil {
fmt.Println("log error: ", err) fmt.Println("log error: ", err)
if len(err.Error()) <= 25 {
socket.Emit("downloadStatus", downloadStatus("error", err.Error()))
}
return return
} }
@ -151,22 +171,33 @@ func main() {
} }
if chunkTag != nil { if chunkTag != nil {
socket.Emit("downloadStatus", fmt.Sprintf( statusMsg := fmt.Sprintf(
"'%s' by '%s' already exists in the database (https://www.youtube.com/watch?v=%s)", "'%s' by '%s' already exists in the database (https://www.youtube.com/watch?v=%s)",
trackInfo.Title, trackInfo.Artist, chunkTag["youtubeid"])) trackInfo.Title, trackInfo.Artist, chunkTag["youtubeid"])
fmt.Println("Emitting1")
socket.Emit("downloadStatus", downloadStatus("error", statusMsg))
return return
} }
totalDownloads, err := spotify.DlSingleTrack(spotifyURL, tmpSongDir) totalDownloads, err := spotify.DlSingleTrack(spotifyURL, tmpSongDir)
if err != nil { if err != nil {
socket.Emit("downloadStatus", fmt.Sprintf("Failed to download '%s' by '%s'", trackInfo.Title, trackInfo.Artist)) statusMsg := fmt.Sprintf("Couldn't download '%s' by '%s'", trackInfo.Title, trackInfo.Artist)
fmt.Println("Emitting2")
socket.Emit("downloadStatus", downloadStatus("error", statusMsg))
return return
} }
statusMsg := ""
if totalDownloads != 1 { if totalDownloads != 1 {
socket.Emit("downloadStatus", fmt.Sprintf("'%s' by '%s' failed to download", trackInfo.Title, trackInfo.Artist)) statusMsg = fmt.Sprintf("'%s' by '%s' failed to download", trackInfo.Title, trackInfo.Artist)
fmt.Println("Emitting2")
socket.Emit("downloadStatus", downloadStatus("error", statusMsg))
} else { } else {
socket.Emit("downloadStatus", fmt.Sprintf("'%s' by '%s' was downloaded", trackInfo.Title, trackInfo.Artist)) statusMsg = fmt.Sprintf("'%s' by '%s' was downloaded", trackInfo.Title, trackInfo.Artist)
fmt.Println("Emitting3")
socket.Emit("downloadStatus", downloadStatus("success", statusMsg))
} }
} else { } else {
@ -184,24 +215,32 @@ func main() {
} }
// Save the decoded data to a file // Save the decoded data to a file
err = ioutil.WriteFile("recorded_audio.ogg", decodedData, 0644) sampleRate := 44100
channels := 1
bitsPerSample := 16
err = utils.WriteWavFile("blob.wav", decodedData, sampleRate, channels, bitsPerSample)
if err != nil { if err != nil {
fmt.Println("Error: Failed to write file to disk:", err) fmt.Println("Error: Failed to write wav file: ", err)
return
} }
fmt.Println("Audio saved successfully.") fmt.Println("Audio saved successfully.")
matches, err := shazam.Match(decodedData) matches, err := shazam.FindMatches(decodedData)
if err != nil { if err != nil {
fmt.Println("Error: Failed to match:", err) fmt.Println("Error: Failed to match:", err)
return return
} }
jsonData, err := json.Marshal(matches) var matchesChunkTags []primitive.M
for _, match := range matches {
matchesChunkTags = append(matchesChunkTags, match.ChunkTag)
}
if len(matches) > 5 { jsonData, err := json.Marshal(matchesChunkTags)
jsonData, err = json.Marshal(matches[:5])
if len(matchesChunkTags) > 5 {
jsonData, err = json.Marshal(matchesChunkTags[:5])
} }
if err != nil { if err != nil {
@ -210,39 +249,35 @@ func main() {
} }
socket.Emit("matches", string(jsonData)) socket.Emit("matches", string(jsonData))
fmt.Println("BLOB: ", matches)
}) })
server.OnEvent("/", "engage", func(s socketio.Conn, encodedOffer string) { server.OnEvent("/", "engage", func(socket socketio.Conn, encodedOffer string) {
log.Println("engage: ", encodedOffer) log.Println("Offer received from client ", socket.ID())
peerConnection := signal.SetupWebRTC(encodedOffer) peerConnection := signal.SetupWebRTC(encodedOffer)
// Allow us to receive 1 audio track // Allow us to receive 1 audio track
if _, err := peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil { if _, err := peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil {
panic(err) fmt.Println("AAAAA")
}
// Set a handler for when a new remote track starts, this handler saves buffers to disk as
// an Ogg file.
oggFile, err := oggwriter.New("output.ogg", 48000, 1)
if err != nil {
panic(err) panic(err)
} }
peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
codec := track.Codec() codec := track.Codec()
if strings.EqualFold(codec.MimeType, webrtc.MimeTypeOpus) { if strings.EqualFold(codec.MimeType, webrtc.MimeTypeOpus) {
// fmt.Println("Got Opus track, saving to disk as output.opus (44.1 kHz, 1 channel)") fmt.Println("Getting tracks")
// signal.SaveToDisk(oggFile, track)
matches, err := signal.MatchSampleAudio(track) matches, err := signal.MatchSampleAudio(track)
if err != nil { if err != nil {
panic(err) fmt.Println("CCCCC")
fmt.Println("Error getting matches: ", err)
return
} }
jsonData, err := json.Marshal(matches) jsonData, err := json.Marshal(matches)
if err != nil {
fmt.Println("Log error: ", err)
return
}
if len(matches) > 5 { if len(matches) > 5 {
jsonData, err = json.Marshal(matches[:5]) jsonData, err = json.Marshal(matches[:5])
@ -255,8 +290,8 @@ func main() {
fmt.Println(string(jsonData)) fmt.Println(string(jsonData))
s.Emit("matches", string(jsonData)) socket.Emit("matches", string(jsonData))
peerConnection.Close() // peerConnection.Close()
} }
}) })
@ -266,16 +301,17 @@ func main() {
fmt.Printf("Connection State has changed %s \n", connectionState.String()) fmt.Printf("Connection State has changed %s \n", connectionState.String())
if connectionState == webrtc.ICEConnectionStateConnected { if connectionState == webrtc.ICEConnectionStateConnected {
fmt.Println("Ctrl+C the remote client to stop the demo") fmt.Println("WebRTC Connected. Client: ", socket.ID())
} else if connectionState == webrtc.ICEConnectionStateFailed || connectionState == webrtc.ICEConnectionStateClosed { } else if connectionState == webrtc.ICEConnectionStateFailed || connectionState == webrtc.ICEConnectionStateClosed {
if closeErr := oggFile.Close(); closeErr != nil {
panic(closeErr)
}
fmt.Println("Done writing media files") if connectionState == webrtc.ICEConnectionStateFailed {
fmt.Println("WebRTC connection failed. Client: ", socket.ID())
socket.Emit("failedToEngage", "")
}
// Gracefully shutdown the peer connection // Gracefully shutdown the peer connection
if closeErr := peerConnection.Close(); closeErr != nil { if closeErr := peerConnection.Close(); closeErr != nil {
fmt.Println("Gracefully shutdown the peer connection")
panic(closeErr) panic(closeErr)
} }
@ -284,7 +320,7 @@ func main() {
}) })
// Emit answer in base64 // Emit answer in base64
s.Emit("serverEngaged", signal.Encode(*peerConnection.LocalDescription())) socket.Emit("serverEngaged", signal.Encode(*peerConnection.LocalDescription()))
}) })
server.OnError("/", func(s socketio.Conn, e error) { server.OnError("/", func(s socketio.Conn, e error) {

View file

@ -2,11 +2,16 @@ package shazam
import ( import (
"math" "math"
"math/cmplx"
) )
// fft performs the Fast Fourier Transform on the input signal. // Fft performs the Fast Fourier Transform on the input signal.
func Fft(complexArray []complex128) []complex128 { func FFT(input []float64) []complex128 {
// Convert input to complex128
complexArray := make([]complex128, len(input))
for i, v := range input {
complexArray[i] = complex(v, 0)
}
fftResult := make([]complex128, len(complexArray)) fftResult := make([]complex128, len(complexArray))
copy(fftResult, complexArray) // Copy input to result buffer copy(fftResult, complexArray) // Copy input to result buffer
return recursiveFFT(fftResult) return recursiveFFT(fftResult)
@ -31,7 +36,7 @@ func recursiveFFT(complexArray []complex128) []complex128 {
fftResult := make([]complex128, N) fftResult := make([]complex128, N)
for k := 0; k < N/2; k++ { for k := 0; k < N/2; k++ {
t := cmplx.Exp(-2i * math.Pi * complex(float64(k), 0) / complex(float64(N), 0)) t := complex(math.Cos(-2*math.Pi*float64(k)/float64(N)), math.Sin(-2*math.Pi*float64(k)/float64(N)))
fftResult[k] = even[k] + t*odd[k] fftResult[k] = even[k] + t*odd[k]
fftResult[k+N/2] = even[k] - t*odd[k] fftResult[k+N/2] = even[k] - t*odd[k]
} }

View file

@ -32,7 +32,13 @@ type ChunkTag struct {
TimeStamp string TimeStamp string
} }
func Match(sampleAudio []byte) ([]primitive.M, error) { type Match struct {
songKey string
ChunkTag primitive.M
WeightedScore float64
}
func FindMatches(sampleAudio []byte) ([]Match, error) {
sampleChunks := Chunkify(sampleAudio) sampleChunks := Chunkify(sampleAudio)
chunkFingerprints, _ := FingerprintChunks(sampleChunks, nil) chunkFingerprints, _ := FingerprintChunks(sampleChunks, nil)
@ -63,51 +69,42 @@ func Match(sampleAudio []byte) ([]primitive.M, error) {
} }
} }
maxMatchCount := 0 var matches []Match
var maxMatch string
matches := make(map[string][]int)
for songKey, timestamps := range songsTimestamps { for songKey, timestamps := range songsTimestamps {
timestampsInSeconds, err := timestampsInSeconds(timestamps) timestampsInSeconds, err := timestampsInSeconds(timestamps)
if err != nil && err.Error() == "insufficient timestamps" { if err != nil {
continue
} else if err != nil {
return nil, err return nil, err
} }
maxPeak, differenceSum, err := getMaxPeak(timestampsInSeconds) maxPeak, differenceSum, err := getMaxPeak(timestampsInSeconds)
if err != nil { if err != nil {
return nil, err if err.Error() == "insufficient timestamps" || err.Error() == "no peak was identified" {
} continue
fmt.Printf("%s MaxPeak: %v, DifferenceSum: %d\n", songKey, maxPeak, differenceSum) } else {
fmt.Println("=====================================================\n") return nil, err
differences, err := timeDifference(timestamps)
if err != nil && err.Error() == "insufficient timestamps" {
continue
} else if err != nil {
return nil, err
}
// fmt.Printf("%s DIFFERENCES: %d\n", songKey, differences)
if len(differences) >= 2 {
matches[songKey] = differences
if len(differences) > maxMatchCount {
maxMatchCount = len(differences)
maxMatch = songKey
} }
} }
weightedScore := float64(differenceSum) / float64(len(maxPeak))
matches = append(matches, Match{songKey, chunkTags[songKey], weightedScore})
fmt.Printf("%s MaxPeak: %v, DifferenceSum: %d\n", songKey, maxPeak, differenceSum)
fmt.Println("=====================================================\n")
} }
sortedChunkTags := sortMatchesByTimeDifference(matches, chunkTags) sort.Slice(matches, func(i, j int) bool {
return matches[i].WeightedScore < matches[j].WeightedScore
})
// fmt.Println("SORTED CHUNK TAGS: ", sortedChunkTags) display := make(map[string]float64)
// fmt.Println("MATCHES: ", matches) for _, match := range matches {
fmt.Println("MATCH: ", maxMatch) key := match.songKey
// fmt.Println() display[key] = match.WeightedScore
return sortedChunkTags, nil }
fmt.Println("New Matches: ", display)
fmt.Println("Matches: ", matches)
return matches, nil
} }
func sortMatchesByTimeDifference(matches map[string][]int, chunkTags map[string]primitive.M) []primitive.M { func sortMatchesByTimeDifference(matches map[string][]int, chunkTags map[string]primitive.M) []primitive.M {
@ -174,8 +171,10 @@ func getMaxPeak(timestamps []int) ([]int, int, error) {
// Ensure timestamps are in ascending order // Ensure timestamps are in ascending order
if minuend > subtrahend { if minuend > subtrahend {
peaks = append(peaks, cluster) if len(cluster) > 0 {
cluster = nil peaks = append(peaks, cluster)
cluster = nil
}
continue continue
} }
@ -187,11 +186,17 @@ func getMaxPeak(timestamps []int) ([]int, int, error) {
} else if difference <= maxDifference { } else if difference <= maxDifference {
cluster = append(cluster, subtrahend) cluster = append(cluster, subtrahend)
} else if difference > maxDifference { } else if difference > maxDifference {
peaks = append(peaks, cluster) if len(cluster) > 0 {
cluster = nil peaks = append(peaks, cluster)
cluster = nil
}
} }
} }
if len(peaks) < 1 {
return nil, 0, fmt.Errorf("no peak was identified")
}
// Identify the largest peak(s) // Identify the largest peak(s)
largestPeak := [][]int{peaks[0]} largestPeak := [][]int{peaks[0]}
for _, peak := range peaks[1:] { for _, peak := range peaks[1:] {
@ -208,16 +213,19 @@ func getMaxPeak(timestamps []int) ([]int, int, error) {
if len(largestPeak) > 1 { if len(largestPeak) > 1 {
fmt.Println("Largest Peak > 1: ", largestPeak) fmt.Println("Largest Peak > 1: ", largestPeak)
// Deduplicate largest peaks in order to get accurate sum of difference // Deduplicate largest peaks to get accurate result.
var largestPeakDeDuplicated [][]int // How? Consider two peaks: A: [53, 53, 53] and B: [14, 15].
// Peak A has only one unique value (53) repeated three times, while peak B has two unique values (14 and 15).
// In this case, peak B would be prioritized over peak A
var largestPeakDeduplicated [][]int
for _, peak := range largestPeak { for _, peak := range largestPeak {
largestPeakDeDuplicated = append(largestPeakDeDuplicated, deduplicate(peak)) largestPeakDeduplicated = append(largestPeakDeduplicated, deduplicate(peak))
} }
fmt.Println("Largest Peak deduplicated: ", largestPeakDeDuplicated) fmt.Println("Largest Peak deduplicated: ", largestPeakDeduplicated)
minDifferenceSum := math.Inf(1) minDifferenceSum := math.Inf(1)
var peakWithMinDifferenceSum []int var peakWithMinDifferenceSum []int
for idx, peak := range largestPeakDeDuplicated { for idx, peak := range largestPeakDeduplicated {
if len(peak) <= 1 { if len(peak) <= 1 {
continue continue
} }
@ -228,15 +236,15 @@ func getMaxPeak(timestamps []int) ([]int, int, error) {
} }
if differenceSum < minDifferenceSum { if differenceSum < minDifferenceSum {
minDifferenceSum = differenceSum minDifferenceSum = differenceSum
fmt.Printf("%v vs %v\n", largestPeak[idx], peak)
peakWithMinDifferenceSum = largestPeak[idx] peakWithMinDifferenceSum = largestPeak[idx]
} }
} }
// In the case where no peak with the min difference sum was identified, // In the case where no peak with the min difference sum was identified,
// probably because they were all duplicates, return the first from the largestspeaks // probably because they are all duplicates, return the first from the largestspeaks
if len(peakWithMinDifferenceSum) == 0 { if len(peakWithMinDifferenceSum) == 0 {
peakWithMinDifferenceSum = largestPeak[0] peakWithMinDifferenceSum = largestPeak[0]
minDifferenceSum = 0
} }
return peakWithMinDifferenceSum, int(minDifferenceSum), nil return peakWithMinDifferenceSum, int(minDifferenceSum), nil
@ -252,49 +260,6 @@ func getMaxPeak(timestamps []int) ([]int, int, error) {
return maxPeak, differenceSum, nil return maxPeak, differenceSum, nil
} }
func timeDifference(timestamps []string) ([]int, error) {
if len(timestamps) < 2 {
return nil, fmt.Errorf("insufficient timestamps")
}
layout := "15:04:05"
timestampsInSeconds := make([]int, len(timestamps))
for i, ts := range timestamps {
parsedTime, err := time.Parse(layout, ts)
if err != nil {
return nil, fmt.Errorf("error parsing timestamp %q: %w", ts, err)
}
hours := parsedTime.Hour()
minutes := parsedTime.Minute()
seconds := parsedTime.Second()
timestampsInSeconds[i] = (hours * 3600) + (minutes * 60) + seconds
}
// sort.Ints(timestampsInSeconds)
differencesSet := map[int]struct{}{}
var differences []int
for i := len(timestampsInSeconds) - 1; i >= 1; i-- {
difference := timestampsInSeconds[i] - timestampsInSeconds[i-1]
// maxSeconds = 15
if difference > 0 && difference <= 15 {
differencesSet[difference] = struct{}{}
differences = append(differences, difference)
}
}
differencesList := []int{}
if len(differencesSet) > 0 {
for k := range differencesSet {
differencesList = append(differencesList, k)
}
}
return timestampsInSeconds, nil
}
// Chunkify divides the input audio signal into chunks and calculates the Short-Time Fourier Transform (STFT) for each chunk. // Chunkify divides the input audio signal into chunks and calculates the Short-Time Fourier Transform (STFT) for each chunk.
// The function returns a 2D slice containing the STFT coefficients for each chunk. // The function returns a 2D slice containing the STFT coefficients for each chunk.
func Chunkify(audio []byte) [][]complex128 { func Chunkify(audio []byte) [][]complex128 {
@ -360,7 +325,7 @@ func FingerprintChunks(chunks [][]complex128, chunkTag *ChunkTag) ([]int64, map[
if chunkCount == chunksPerSecond { if chunkCount == chunksPerSecond {
chunkCount = 0 chunkCount = 0
chunkTime = chunkTime.Add(1 * time.Second) chunkTime = chunkTime.Add(1 * time.Second)
fmt.Println(chunkTime.Format("15:04:05")) // fmt.Println(chunkTime.Format("15:04:05"))
} }
} }

91
shazam/spectrogram.go Normal file
View file

@ -0,0 +1,91 @@
package shazam
import (
"errors"
"fmt"
"math"
)
const (
dspRatio = 4
lowPassFilter = 5000.0 // 5kHz
samplesPerWindow = 1024
)
func Spectrogram(samples []float64, channels, sampleRate int) [][]complex128 {
lpf := NewLowPassFilter(lowPassFilter, float64(sampleRate))
filteredSamples := lpf.Filter(samples)
downsampledSamples, err := downsample(filteredSamples, dspRatio)
if err != nil {
fmt.Println("Couldn't downsample audio samples: ", err)
}
hopSize := samplesPerWindow / 32
numOfWindows := len(downsampledSamples) / (samplesPerWindow - hopSize)
spectrogram := make([][]complex128, numOfWindows)
// Apply Hamming window function
windowSize := len(samples)
for i := 0; i < len(downsampledSamples); i++ {
downsampledSamples[i] = 0.54 - 0.46*math.Cos(2*math.Pi*float64(i)/(float64(windowSize)-1))
}
// Perform STFT
for i := 0; i < numOfWindows; i++ {
start := i * hopSize
end := start + samplesPerWindow
if end > len(downsampledSamples) {
end = len(downsampledSamples)
}
spec := make([]float64, samplesPerWindow)
for j := start; j < end; j++ {
spec[j-start] = downsampledSamples[j]
}
applyHammingWindow(spec)
spectrogram[i] = FFT(spec)
}
return spectrogram
}
func applyHammingWindow(samples []float64) {
windowSize := len(samples)
for i := 0; i < windowSize; i++ {
samples[i] *= 0.54 - 0.46*math.Cos(2*math.Pi*float64(i)/(float64(windowSize)-1))
}
}
// Downsample downsamples a list of float64 values from 44100 Hz to a specified ratio by averaging groups of samples
func downsample(input []float64, ratio int) ([]float64, error) {
// Ensure the ratio is valid and compatible with the input length
if ratio <= 0 || len(input)%ratio != 0 {
return nil, errors.New("invalid or incompatible ratio")
}
// Calculate the size of the output slice
outputSize := len(input) / ratio
// Create the output slice
output := make([]float64, outputSize)
// Iterate over the input and calculate averages for each group of samples
for i := 0; i < outputSize; i++ {
startIndex := i * ratio
endIndex := startIndex + ratio
sum := 0.0
// Sum up the values in the current group of samples
for j := startIndex; j < endIndex; j++ {
sum += input[j]
}
// Calculate the average for the current group
output[i] = sum / float64(ratio)
}
return output, nil
}

View file

@ -73,17 +73,17 @@ func MatchSampleAudio(track *webrtc.TrackRemote) ([]primitive.M, error) {
defer ticker.Stop() defer ticker.Stop()
var sampleAudio []byte var sampleAudio []byte
var matches []primitive.M var matches []shazam.Match
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
// Process sampleAudio every 2 seconds // Process sampleAudio every 2 seconds
if len(sampleAudio) > 0 { if len(sampleAudio) > 0 {
matchess, err := shazam.Match(sampleAudio) matchess, err := shazam.FindMatches(sampleAudio)
matches = matchess matches = matchess
if err != nil { if err != nil {
fmt.Println(err) fmt.Println("An Error: ", err)
return nil, nil return nil, nil
} }
@ -103,7 +103,12 @@ func MatchSampleAudio(track *webrtc.TrackRemote) ([]primitive.M, error) {
case <-stop: case <-stop:
// Stop after 15 seconds // Stop after 15 seconds
fmt.Println("Stopped after 15 seconds") fmt.Println("Stopped after 15 seconds")
return matches, nil var matchesChunkTags []primitive.M
for _, match := range matches {
matchesChunkTags = append(matchesChunkTags, match.ChunkTag)
}
return matchesChunkTags, nil
default: default:
// Read RTP packets and accumulate sampleAudio // Read RTP packets and accumulate sampleAudio
rtpPacket, _, err := track.ReadRTP() rtpPacket, _, err := track.ReadRTP()

View file

@ -104,7 +104,11 @@ func dlTrack(tracks []Track, path string) (int, error) {
} }
// check if song exists // check if song exists
songExists, _ := db.SongExists(trackCopy.Title, trackCopy.Artist, "") songExists, err := db.SongExists(trackCopy.Title, trackCopy.Artist, "")
if err != nil {
logMessage := fmt.Sprintln("error checking song existence: ", err)
slog.Error(logMessage)
}
if songExists { if songExists {
logMessage := fmt.Sprintf("'%s' by '%s' already downloaded\n", trackCopy.Title, trackCopy.Artist) logMessage := fmt.Sprintf("'%s' by '%s' already downloaded\n", trackCopy.Title, trackCopy.Artist)
slog.Info(logMessage) slog.Info(logMessage)
@ -120,12 +124,19 @@ func dlTrack(tracks []Track, path string) (int, error) {
} }
// Check if YouTube ID exists // Check if YouTube ID exists
ytIdExists, _ := db.SongExists("", "", ytID) ytIdExists, err := db.SongExists("", "", ytID)
fmt.Printf("%s exists? = %v\n", ytID, ytIdExists)
if err != nil {
logMessage := fmt.Sprintln("error checking song existence: ", err)
slog.Error(logMessage)
}
if ytIdExists { // try to get the YouTube ID again if ytIdExists { // try to get the YouTube ID again
logMessage := fmt.Sprintf("YouTube ID exists. Trying again: %s\n", ytID) logMessage := fmt.Sprintf("YouTube ID exists. Trying again: %s\n", ytID)
fmt.Println("WARN: ", logMessage)
slog.Warn(logMessage) slog.Warn(logMessage)
ytID, err := GetYoutubeId(*trackCopy) ytID, err = GetYoutubeId(*trackCopy)
if ytID == "" || err != nil { if ytID == "" || err != nil {
logMessage := fmt.Sprintf("Error (1): '%s' by '%s' could not be downloaded: %s\n", trackCopy.Title, trackCopy.Artist, err) logMessage := fmt.Sprintf("Error (1): '%s' by '%s' could not be downloaded: %s\n", trackCopy.Title, trackCopy.Artist, err)
slog.Info(logMessage) slog.Info(logMessage)
@ -133,7 +144,11 @@ func dlTrack(tracks []Track, path string) (int, error) {
return return
} }
ytIdExists, _ := db.SongExists("", "", ytID) ytIdExists, err := db.SongExists("", "", ytID)
if err != nil {
logMessage := fmt.Sprintln("error checking song existence: ", err)
slog.Error(logMessage)
}
if ytIdExists { if ytIdExists {
logMessage := fmt.Sprintf("'%s' by '%s' could not be downloaded: YouTube ID (%s) exists\n", trackCopy.Title, trackCopy.Artist, ytID) logMessage := fmt.Sprintf("'%s' by '%s' could not be downloaded: YouTube ID (%s) exists\n", trackCopy.Title, trackCopy.Artist, ytID)
slog.Error(logMessage) slog.Error(logMessage)
@ -285,6 +300,11 @@ func processAndSaveSong(songFilePath, songTitle, songArtist, ytID string) error
} }
defer db.Close() defer db.Close()
err = db.RegisterSong(songTitle, songArtist, ytID)
if err != nil {
return err
}
audioBytes, err := convertStereoToMono(songFilePath) audioBytes, err := convertStereoToMono(songFilePath)
if err != nil { if err != nil {
return fmt.Errorf("error converting song to mono: %v", err) return fmt.Errorf("error converting song to mono: %v", err)
@ -308,11 +328,6 @@ func processAndSaveSong(songFilePath, songTitle, songArtist, ytID string) error
} }
} }
err = db.RegisterSong(songTitle, songArtist, ytID)
if err != nil {
return err
}
fmt.Println("Fingerprints saved in MongoDB successfully") fmt.Println("Fingerprints saved in MongoDB successfully")
return nil return nil
} }

View file

@ -79,7 +79,8 @@ func convertStringDurationToSeconds(durationStr string) int {
// GetYoutubeId takes the query as string and returns the search results video ID's // GetYoutubeId takes the query as string and returns the search results video ID's
func GetYoutubeId(track Track) (string, error) { func GetYoutubeId(track Track) (string, error) {
songDurationInSeconds := track.Duration songDurationInSeconds := track.Duration
searchQuery := fmt.Sprintf("'%s' %s %s", track.Title, track.Artist, track.Album) // searchQuery := fmt.Sprintf("'%s' %s %s", track.Title, track.Artist, track.Album)
searchQuery := fmt.Sprintf("'%s' %s", track.Title, track.Artist)
searchResults, err := ytSearch(searchQuery, 10) searchResults, err := ytSearch(searchQuery, 10)
if err != nil { if err != nil {

View file

@ -113,7 +113,7 @@ func ReadWavInfo(filename string) (*WavInfo, error) {
} }
// WavBytesToFloat64 converts a slice of bytes from a .wav file to a slice of float64 samples // WavBytesToFloat64 converts a slice of bytes from a .wav file to a slice of float64 samples
func WavBytesToFloat64(input []byte) ([]float64, error) { func WavBytesToSamples(input []byte) ([]float64, error) {
if len(input)%2 != 0 { if len(input)%2 != 0 {
return nil, errors.New("invalid input length") return nil, errors.New("invalid input length")
} }