added changes on checking similarity
@ -41,6 +41,8 @@ INSTALLED_APPS = [
|
|||||||
"corsheaders",
|
"corsheaders",
|
||||||
|
|
||||||
"django_extensions",
|
"django_extensions",
|
||||||
|
|
||||||
|
'rest_framework',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
@ -19,4 +19,5 @@ from django.urls import path , include
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('', include('base.urls')),
|
path('', include('base.urls')),
|
||||||
|
path('api/', include('base.api.urls')),
|
||||||
]
|
]
|
||||||
|
|||||||
0
base/api/__init__.py
Normal file
BIN
base/api/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/calltomapper.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/calltoreducer.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/check_similarity.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/haystack.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/haystackmapper.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/haystackreducer.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/interfaceCLI.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/librosa_run.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/needlestorage.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/run.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/serializers.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/urls.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/views.cpython-39.pyc
Normal file
BIN
base/api/__pycache__/wavsound.cpython-39.pyc
Normal file
10
base/api/calltomapper.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from .haystackmapper import mapper
|
||||||
|
|
||||||
|
def calltomapper(haystacks, needle, processnumber, totalprocesses, return_emissions):
|
||||||
|
|
||||||
|
""" calltomapper is a target function of a process from interface
|
||||||
|
which in turn calls the method haystackmap.mapper with argument needle """
|
||||||
|
|
||||||
|
print("Finished with process ", processnumber, " word(sample) size of ",len(needle))
|
||||||
|
|
||||||
|
return_emissions += mapper(haystacks, needle)
|
||||||
5
base/api/calltoreducer.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from .haystack import haystack
|
||||||
|
from .haystackreducer import haystackreducer
|
||||||
|
|
||||||
|
def calltoreducer(emissions, key, join):
|
||||||
|
join[key] = haystackreducer(emissions)
|
||||||
30
base/api/check_similarity.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import librosa
|
||||||
|
import librosa.display as display
|
||||||
|
import math
|
||||||
|
|
||||||
|
def cosine_similarity1(v1,v2):
|
||||||
|
"compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)"
|
||||||
|
sumxx, sumxy, sumyy = 0, 0, 0
|
||||||
|
for i in range(len(v1)):
|
||||||
|
x = v1[i]; y = v2[i]
|
||||||
|
sumxx += x*x
|
||||||
|
sumyy += y*y
|
||||||
|
sumxy += x*y
|
||||||
|
return sumxy/math.sqrt(sumxx*sumyy)
|
||||||
|
|
||||||
|
def check_similarity(audio_path, audio_path2):
|
||||||
|
# load audio files
|
||||||
|
y1, sr1 = librosa.load(audio_path)
|
||||||
|
y2, sr2 = librosa.load(audio_path2)
|
||||||
|
# compute spectrogram for the audio files
|
||||||
|
S1 = librosa.stft(y1)
|
||||||
|
S2 = librosa.stft(y2)
|
||||||
|
# convert to power spectrogram
|
||||||
|
S_db = librosa.amplitude_to_db(abs(S1))
|
||||||
|
S2_db = librosa.amplitude_to_db(abs(S2))
|
||||||
|
# display spectrogram
|
||||||
|
# display.specshow(S_db, y_axis='linear', x_axis='time', sr=sr, hop_length=512)
|
||||||
|
# display.specshow(S2_db, y_axis='linear', x_axis='time', sr=sr2, hop_length=512)
|
||||||
|
# plt.show()
|
||||||
|
# compute cosine similarity
|
||||||
|
return cosine_similarity1(S_db.flatten(), S2_db.flatten())
|
||||||
118
base/api/dbtest.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
from .haystackmapper import mapper
|
||||||
|
from .haystackreducer import haystackreducer
|
||||||
|
from .haystack import haystack
|
||||||
|
from .needlestorage import needlestorage
|
||||||
|
from .wavsound import wavsound
|
||||||
|
from multiprocessing import Pool, Process, Manager
|
||||||
|
from .calltomapper import calltomapper
|
||||||
|
import time
|
||||||
|
import profile
|
||||||
|
import re
|
||||||
|
|
||||||
|
""" dbtest is a simulation module to measure time complexity of
|
||||||
|
database search applied to a virtual database"""
|
||||||
|
|
||||||
|
def test():
|
||||||
|
button_wavsound = wavsound('button.wav')
|
||||||
|
|
||||||
|
haystackss = [] # split database into list of smaller database
|
||||||
|
keynames = []
|
||||||
|
db_size = 300 # Set Database Size
|
||||||
|
num_split_db = 2 # Set number of split databases
|
||||||
|
size_split_db = int(db_size/num_split_db)
|
||||||
|
|
||||||
|
for i in range(num_split_db):
|
||||||
|
haystackss.append([])
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
for i in range(db_size):
|
||||||
|
split_db_key = int(counter / size_split_db)
|
||||||
|
keynames.append(i)
|
||||||
|
haystackss[split_db_key].append(haystack(i,button_wavsound.get_data()))
|
||||||
|
counter+=1
|
||||||
|
|
||||||
|
|
||||||
|
#haystacks.append(haystack("7",[1, 2, 3, 4, 5]))
|
||||||
|
|
||||||
|
button_needle_factory = needlestorage(button_wavsound,1000,50)
|
||||||
|
emissions = []
|
||||||
|
|
||||||
|
|
||||||
|
print("USING MAP PROCESS and Manager")
|
||||||
|
|
||||||
|
needles = button_needle_factory.get_needles()
|
||||||
|
print(needles[0])
|
||||||
|
|
||||||
|
manager = Manager()
|
||||||
|
return_emissions = manager.dict()
|
||||||
|
jobs = []
|
||||||
|
pnum = 0
|
||||||
|
|
||||||
|
# number of needles not size of each needle
|
||||||
|
len_needles = len(needles)
|
||||||
|
print ("Number of Needles: ",len_needles)
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for needle in needles:
|
||||||
|
for haystacks in haystackss:
|
||||||
|
p = Process(target=calltomapper, args=(haystacks,needle,pnum,len_needles*num_split_db,return_emissions))
|
||||||
|
jobs.append(p)
|
||||||
|
p.start()
|
||||||
|
pnum += 1
|
||||||
|
print(time.time() - start_time)
|
||||||
|
|
||||||
|
for proc in jobs:
|
||||||
|
proc.join() # wait for each process to end completely
|
||||||
|
|
||||||
|
print(time.time() - start_time)
|
||||||
|
emissions_list = sum(return_emissions.values(),[])
|
||||||
|
print("Reduce Result:")
|
||||||
|
print(haystackreducer(emissions_list,keynames))
|
||||||
|
print("Done")
|
||||||
|
print(time.time() - start_time)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is a pool implementation of parallel processing, it has been
|
||||||
|
commented out as it was slower than the Process method
|
||||||
|
|
||||||
|
print(button_wavsound)
|
||||||
|
print("Utilizing MapReduce Pattern")
|
||||||
|
|
||||||
|
pool = Pool(2) # if it is a quad-core machine it can be set to 4
|
||||||
|
print(button_needle_factory.get_needles())
|
||||||
|
emissions = pool.map(haystackmap.mapper, button_needle_factory.get_needles())
|
||||||
|
print(emissions)
|
||||||
|
print(haystackreducer(sum(emissions,[])))
|
||||||
|
|
||||||
|
emissions = []
|
||||||
|
"""
|
||||||
|
|
||||||
|
""" The algorithm below is a serial method, no optimization """
|
||||||
|
"""
|
||||||
|
print("Long Way")
|
||||||
|
start_long_time = time.time()
|
||||||
|
#haystackmap.clear_emission()
|
||||||
|
|
||||||
|
i = 10000 # cautionary protection from accidental infinite loop
|
||||||
|
|
||||||
|
while i > 0:
|
||||||
|
needle = button_needle_factory.pop_unused_needle()
|
||||||
|
if (needle == []):
|
||||||
|
break
|
||||||
|
emissions += mapper(haystacks,needle)
|
||||||
|
i -= 1
|
||||||
|
print("Total So Far: ",len(emissions))
|
||||||
|
|
||||||
|
print("Final:",haystackreducer(emissions, keynames))
|
||||||
|
timelapse_serial = time.time() - start_long_time
|
||||||
|
print (db_size + 1, timelapse_parallel, timelapse_serial)
|
||||||
|
|
||||||
|
with open('output.txt', 'a') as outputfile:
|
||||||
|
outputfile.write(str(db_size + 1) +' '+str(timelapse_parallel) +' '+str(timelapse_serial) + '\n')
|
||||||
|
"""
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
||||||
|
profile.run('re.compile("mapper")')
|
||||||
19
base/api/haystack.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
class haystack:
|
||||||
|
|
||||||
|
""" haystack represents a single entry in the database
|
||||||
|
it contains a sequence of integers """
|
||||||
|
|
||||||
|
def __init__(self,name,data):
|
||||||
|
self.name=name
|
||||||
|
self.data=data
|
||||||
|
self.length = len(data)
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def get_length(self):
|
||||||
|
return self.length
|
||||||
65
base/api/haystackmapper.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from .haystack import haystack
|
||||||
|
|
||||||
|
|
||||||
|
# class haystackmapper:
|
||||||
|
|
||||||
|
# """
|
||||||
|
# haystackmapper is a class responsible for map process,
|
||||||
|
# it emits key-value pair if a needle can be found in the haystack
|
||||||
|
# (key: haystack name, value: 1 represents a match)
|
||||||
|
# """
|
||||||
|
|
||||||
|
# emission = []
|
||||||
|
# def __init__ (self, haystacks):
|
||||||
|
# self.haystacks = haystacks
|
||||||
|
def mapper (stack, needle):
|
||||||
|
len_needle = len(needle)
|
||||||
|
emission = []
|
||||||
|
for haystack in stack:
|
||||||
|
len_haystack = haystack.get_length()
|
||||||
|
data = haystack.get_data()
|
||||||
|
for i in range(len_haystack):
|
||||||
|
|
||||||
|
#get subsequence length
|
||||||
|
len_sub = min(len_needle, (len_haystack - i))
|
||||||
|
|
||||||
|
subsequence = data[i:i + len_needle]
|
||||||
|
nomatch = 0
|
||||||
|
"""
|
||||||
|
Uncomment this for debugging:
|
||||||
|
#difference = []
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len_sub <= 1:
|
||||||
|
nomatch = 1
|
||||||
|
break
|
||||||
|
for i in range(len_sub): # len_sub <= len_needle
|
||||||
|
"""
|
||||||
|
Uncomment this for debugging:
|
||||||
|
if abs(needle[i]) > 100 :
|
||||||
|
difference.append(abs(subsequence[i] - needle[i])/needle[i] )
|
||||||
|
else:
|
||||||
|
difference.append(0)
|
||||||
|
"""
|
||||||
|
if abs(needle[i]) > 100 and abs(subsequence[i] - needle[i]) > abs(0.05*needle[i]):
|
||||||
|
# allow slight variation
|
||||||
|
nomatch = 1
|
||||||
|
break
|
||||||
|
|
||||||
|
if nomatch == 0:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Uncomment this section for deubgging to show comparison
|
||||||
|
print(needle)
|
||||||
|
print(subsequence)
|
||||||
|
print(difference)
|
||||||
|
print(haystack.get_name(),nomatch)
|
||||||
|
"""
|
||||||
|
|
||||||
|
emission.append([haystack.get_name(), 1])
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return emission
|
||||||
|
|
||||||
8
base/api/haystackreducer.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
def haystackreducer(emissions):
|
||||||
|
"""
|
||||||
|
haystackreducer is a class responsible for reducing emissions
|
||||||
|
from multiple map processes. It tallies the number of matches
|
||||||
|
from key-value pairs emitted from each mapper
|
||||||
|
"""
|
||||||
|
return len(emissions)
|
||||||
|
|
||||||
95
base/api/interface.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
from haystackmapper import haystackmapper
|
||||||
|
from haystackreducer import haystackreducer
|
||||||
|
from haystack import haystack
|
||||||
|
from needlestorage import needlestorage
|
||||||
|
from wavsound import wavsound
|
||||||
|
from multiprocessing import Pool, Process, Manager
|
||||||
|
from calltomapper import calltomapper
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
def run():
|
||||||
|
|
||||||
|
""" run runs the database search taking three user inputs, the query wav file,
|
||||||
|
number of partitions, and number of partition samples"""
|
||||||
|
|
||||||
|
good_file = 0
|
||||||
|
|
||||||
|
while (good_file == 0):
|
||||||
|
query = raw_input("Submit .wav file to search against database (Example: button.wav): ")
|
||||||
|
if (os.path.isfile(query)):
|
||||||
|
good_file = 1
|
||||||
|
|
||||||
|
#Instantiate Wavsound objects from the wav files
|
||||||
|
t_wavsounds = {}
|
||||||
|
query_wavsound = wavsound(query)
|
||||||
|
print("\n**Higher number of partitions increases false positive rates, \nwhile lower number of partitions increases false negative rates\n")
|
||||||
|
partition = raw_input("Set number of partitions of the query from 1 to " + str(int(len(query_wavsound.get_data())/3))+": ")
|
||||||
|
samples = raw_input("Set number of samples of partitions from 1 to " + partition + " (Recommend < 50): ")
|
||||||
|
|
||||||
|
# Database Structure
|
||||||
|
haystacks = []
|
||||||
|
|
||||||
|
# Database look up directory
|
||||||
|
rootdir = 'db'
|
||||||
|
|
||||||
|
for subdir, __, files in os.walk(rootdir):
|
||||||
|
for file in files:
|
||||||
|
# for debug print (subdir+"/"+file)
|
||||||
|
t_wavsounds[subdir+"/"+file] = wavsound(subdir+"/"+file)
|
||||||
|
# for debug print(t_wavsounds[subdir+"/"+file])
|
||||||
|
haystacks.append(haystack(subdir+"/"+file,t_wavsounds[subdir+"/"+file].get_data()))
|
||||||
|
|
||||||
|
query_needle_factory = needlestorage(query_wavsound,int(partition),int(samples))
|
||||||
|
|
||||||
|
|
||||||
|
haystackmap = haystackmapper(haystacks)
|
||||||
|
|
||||||
|
needles = query_needle_factory.get_needles()
|
||||||
|
len_needles = len(needles)
|
||||||
|
len_needle = len(needles[0]) # size is the same for all needles
|
||||||
|
|
||||||
|
manager = Manager()
|
||||||
|
|
||||||
|
# Map processes emit key-value pairs to emissions
|
||||||
|
return_emissions = manager.dict()
|
||||||
|
|
||||||
|
# Job is a list of processes
|
||||||
|
jobs = []
|
||||||
|
|
||||||
|
# Process number
|
||||||
|
pnum = 0
|
||||||
|
|
||||||
|
print "Number of Needles: ", len(needles)
|
||||||
|
|
||||||
|
# Database query time
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
#Distribute processes using multiprocessor
|
||||||
|
for needle in needles:
|
||||||
|
p = Process(target=calltomapper, args=(haystackmap,needle,pnum,len_needles,return_emissions))
|
||||||
|
jobs.append(p)
|
||||||
|
p.start()
|
||||||
|
pnum += 1
|
||||||
|
|
||||||
|
for proc in jobs:
|
||||||
|
proc.join()
|
||||||
|
|
||||||
|
# flatten return_emissions into a list
|
||||||
|
emissions_list = sum(return_emissions.values(),[])
|
||||||
|
|
||||||
|
print "Search Result:"
|
||||||
|
|
||||||
|
result_dict = haystackreducer(emissions_list)
|
||||||
|
|
||||||
|
# Tabulate % match (wav files with 0% match are excluded from the result)
|
||||||
|
for key in result_dict:
|
||||||
|
print str(key),": ",(25-len(str(key)))*" ",str("{0:.2f}".format(int(result_dict[key])/len(needles)*100)),"% match"
|
||||||
|
|
||||||
|
# Show search time
|
||||||
|
timelapse_parallel = time.time() - start_time
|
||||||
|
print timelapse_parallel, "seconds"
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print ".WAV Search Engine Version 1 (Only Python Ver 2.X.X.)"
|
||||||
|
run()
|
||||||
50
base/api/interfaceCLI.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from .run import *
|
||||||
|
from .calltoreducer import calltoreducer
|
||||||
|
def cek_simlilarity2(recorded_sound,url_bacaan):
|
||||||
|
# print (".WAV Search Engine Version 1 (For Python Ver. 3+) ")
|
||||||
|
|
||||||
|
good_file = 0
|
||||||
|
|
||||||
|
# while (good_file == 0):
|
||||||
|
# query = input("Submit .wav file to search against repository (Example: button.wav): ")
|
||||||
|
# if (os.path.isfile(query)):
|
||||||
|
# good_file = 1
|
||||||
|
# query_wavsound = wavsound(query)
|
||||||
|
|
||||||
|
query_wavsound = wavsound(recorded_sound)
|
||||||
|
|
||||||
|
# print("\n**Higher number of partitions increases false positive rates, \nwhile lower number of partitions increases false negative rates\n")
|
||||||
|
# samplelength = input("Set word size (sample length) (5 ~ 100) : ");
|
||||||
|
samplelength = 5
|
||||||
|
# samples = input("Set number of samples (n) of partitions from 1 to " + str(int(len(query_wavsound.get_data())/float(samplelength))) + ": ")
|
||||||
|
samples = 3
|
||||||
|
|
||||||
|
# repository look up directory
|
||||||
|
# print(url_bacaan)
|
||||||
|
# dbdir = input("Enter repository directory to search (example: 'db') : ")
|
||||||
|
dbdir = url_bacaan
|
||||||
|
# max_split = int(input("Set maximum allowable number of split repositories : "))
|
||||||
|
max_split = 2
|
||||||
|
|
||||||
|
# repository query time
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
result_lst = run(recorded_sound, int(samplelength), samples, dbdir, max_split)
|
||||||
|
|
||||||
|
# output
|
||||||
|
output = "Search Result: \n"
|
||||||
|
|
||||||
|
keluaran = ""
|
||||||
|
# Tabulate % match (wav files with 0% match are excluded from the result)
|
||||||
|
for pair in result_lst:
|
||||||
|
output += pair[0] + " : " + (40-len(pair[0]))*" " + pair[1] + "% match" + "\n"
|
||||||
|
keluaran = (40-len(pair[0]))*" " + pair[1] + "% "
|
||||||
|
|
||||||
|
# print(keluaran)
|
||||||
|
# Show search time
|
||||||
|
timelapse_parallel = time.time() - start_time
|
||||||
|
output = output + str(timelapse_parallel) + "seconds"
|
||||||
|
# print(output)
|
||||||
|
# remove all the spaces
|
||||||
|
keluaran = keluaran.replace(" ","")
|
||||||
|
return keluaran
|
||||||
196
base/api/interfaceGUI.py
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from run import *
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
# application is a GUI interface of run.py
|
||||||
|
|
||||||
|
class application:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.samples = 12
|
||||||
|
self.samplelength = 80
|
||||||
|
self.max_split = 2
|
||||||
|
self.root = tk.Tk()
|
||||||
|
self.root.wm_title("Audio Search Engine")
|
||||||
|
self.show_enclosure()
|
||||||
|
self.show_file_entry()
|
||||||
|
self.show_buttons()
|
||||||
|
self.show_result()
|
||||||
|
self.show_canvas()
|
||||||
|
self.show_menu()
|
||||||
|
self.root.iconbitmap("img/logo.ico")
|
||||||
|
self.root.mainloop()
|
||||||
|
self.filename=""
|
||||||
|
|
||||||
|
# show right enclosure frame
|
||||||
|
def show_enclosure(self):
|
||||||
|
self.group_enclosure =tk.LabelFrame(self.root, text="", padx=5, pady=5, background="white")
|
||||||
|
self.group_enclosure.pack(side="right")
|
||||||
|
|
||||||
|
# show audio wavform canvas
|
||||||
|
def show_canvas (self):
|
||||||
|
self.group_canvas = tk.LabelFrame(self.group_enclosure, text="Top(Query) Bottom(Best Match)", padx=5, pady=5)
|
||||||
|
self.group_canvas.pack(side='top')
|
||||||
|
|
||||||
|
self.canvas_query = tk.Canvas(self.group_canvas, width = 200, height = 100, bg = 'black')
|
||||||
|
self.canvas_result = tk.Canvas(self.group_canvas, width = 200, height = 100, bg = 'black')
|
||||||
|
# pack the canvas into a frame/form
|
||||||
|
|
||||||
|
self.canvas_query.pack(expand = 'yes', fill = 'both')
|
||||||
|
self.canvas_query.create_line(0, 50, 199.9, 50, fill="red", dash=(4, 4))
|
||||||
|
|
||||||
|
self.canvas_result.pack(expand = 'yes', fill = 'both', side='bottom')
|
||||||
|
self.canvas_result.create_line(0, 50, 199.9, 50, fill="red", dash=(4, 4))
|
||||||
|
|
||||||
|
#self.photo = tk.PhotoImage(file = 'img/search.png', width = 70, height = 70 )
|
||||||
|
#self.canvas.create_image(5, 5, image=self.photo, anchor="nw")
|
||||||
|
|
||||||
|
def clear_canvas(self):
|
||||||
|
self.canvas_query.create_rectangle(0, 0, 200, 100, fill="black")
|
||||||
|
self.canvas_result.create_rectangle(0, 0, 200, 100, fill="black")
|
||||||
|
self.canvas_query.create_line(0, 50, 199.9, 50, fill="red", dash=(4, 4))
|
||||||
|
self.canvas_result.create_line(0, 50, 199.9, 50, fill="red", dash=(4, 4))
|
||||||
|
|
||||||
|
# draw_wavform draws waveform of wav data to a target canvas
|
||||||
|
def draw_wavform (self, wavdata, color, target):
|
||||||
|
unit_x=200/len(wavdata)
|
||||||
|
unit_y=20/max(wavdata)
|
||||||
|
for i in range(len(wavdata)-1):
|
||||||
|
y_1 = wavdata[i]*unit_y + 50
|
||||||
|
x_1 = i*unit_x
|
||||||
|
y_2 = wavdata[i+1]*unit_y + 50
|
||||||
|
x_2 = (i+1)*unit_x
|
||||||
|
if target == "query":
|
||||||
|
self.canvas_query.create_line(x_1, y_1, x_2, y_2, fill=color, width=0.5)
|
||||||
|
elif target == "result":
|
||||||
|
self.canvas_result.create_line(x_1, y_1, x_2, y_2, fill=color, width=0.5)
|
||||||
|
|
||||||
|
# refresh parameter labels
|
||||||
|
def refresh_parameters (self):
|
||||||
|
newtext="Number of Audio Samples to Compare: " + str(self.samples) + " Repository Maximum Split Paramter: " + str(self.max_split) + " Word Length: " + str(self.samplelength )
|
||||||
|
self.label_result.config(text=newtext)
|
||||||
|
|
||||||
|
# decrease number of samples (words or needles)
|
||||||
|
def sample_up (self):
|
||||||
|
self.samples += 1
|
||||||
|
self.refresh_parameters()
|
||||||
|
|
||||||
|
# decrease number of samples (words or needles)
|
||||||
|
def sample_down (self):
|
||||||
|
if self.samples > 0:
|
||||||
|
self.samples -= 1
|
||||||
|
self.refresh_parameters()
|
||||||
|
|
||||||
|
# increase samplelength (word size)
|
||||||
|
def word_up (self):
|
||||||
|
self.samplelength += 5
|
||||||
|
self.refresh_parameters()
|
||||||
|
|
||||||
|
# decrease samplelength (word size)
|
||||||
|
def word_down (self):
|
||||||
|
if self.samplelength > 5:
|
||||||
|
self.samplelength -= 5
|
||||||
|
self.refresh_parameters()
|
||||||
|
|
||||||
|
# show_menu shows the menu at the top
|
||||||
|
def show_menu (self):
|
||||||
|
self.menu = tk.Menu(self.root)
|
||||||
|
self.menu.add_command(label="Search", command=self.on_button_click)
|
||||||
|
self.menu.add_command(label="Precision +", command=self.sample_up)
|
||||||
|
self.menu.add_command(label="Precision -", command=self.sample_down)
|
||||||
|
self.menu.add_command(label="Word Size +", command=self.word_up)
|
||||||
|
self.menu.add_command(label="Word Size -", command=self.word_down)
|
||||||
|
self.menu.add_command(label="Quit", command=self.quit)
|
||||||
|
self.root.config (menu=self.menu)
|
||||||
|
|
||||||
|
# add style (optional)
|
||||||
|
def add_config (self):
|
||||||
|
self.style = ttk.Style()
|
||||||
|
#self.style.configure(... enter here ...)
|
||||||
|
|
||||||
|
# show textbox that holds the result of the search
|
||||||
|
def show_result (self):
|
||||||
|
self.group_result = tk.LabelFrame(self.root, text="", padx=5, pady=5)
|
||||||
|
self.group_result.pack(padx=10, pady=15, side = 'left')
|
||||||
|
self.label_result = tk.Label(self.group_result, text="Number of Audio Samples to Compare: " + str(self.samples) + " Repository Maximum Split Paramter: " + str(self.max_split) + " Word Length: " + str(self.samplelength ))
|
||||||
|
self.label_result.pack()
|
||||||
|
self.text_result = tk.Text(self.group_result, height="20")
|
||||||
|
self.text_result.configure(background='black', foreground='cyan')
|
||||||
|
self.text_result.pack()
|
||||||
|
|
||||||
|
# show file entry input box
|
||||||
|
def show_file_entry(self):
|
||||||
|
# GROUP ENTRY
|
||||||
|
|
||||||
|
self.group_entry = tk.LabelFrame(self.group_enclosure, text="", padx=5, pady=5)
|
||||||
|
self.group_entry.pack(padx=10, pady=10, side = 'bottom')
|
||||||
|
|
||||||
|
|
||||||
|
self.label_file = tk.Label(self.group_entry, text="Query Filename")
|
||||||
|
self.label_file.pack(padx=58)
|
||||||
|
self.entry_file = tk.Entry(self.group_entry, bd =5, relief="flat")
|
||||||
|
self.entry_file.pack()
|
||||||
|
self.entry_file.insert(0, "voicequery.wav")
|
||||||
|
self.label_db = tk.Label(self.group_entry, text="Path to repository")
|
||||||
|
self.label_db.pack()
|
||||||
|
self.entry_db = tk.Entry(self.group_entry, bd =5, relief="flat")
|
||||||
|
self.entry_db.insert(0, "db_voice")
|
||||||
|
self.entry_db.pack()
|
||||||
|
|
||||||
|
# show search button below the file entry
|
||||||
|
def show_buttons (self):
|
||||||
|
root = self.root
|
||||||
|
tk.Button(self.group_entry, padx=15, text="Search", command=self.on_button_click).pack()
|
||||||
|
|
||||||
|
# on click event for the button
|
||||||
|
def on_button_click(self):
|
||||||
|
|
||||||
|
self.filename = self.entry_file.get()
|
||||||
|
self.clear_canvas()
|
||||||
|
if (os.path.isfile(self.filename)):
|
||||||
|
query_wavsound = wavsound(self.filename)
|
||||||
|
self.dbroot = self.entry_db.get()
|
||||||
|
samples = self.samples
|
||||||
|
partition = int(len(query_wavsound.get_data())/self.samplelength)
|
||||||
|
max_split = self.max_split
|
||||||
|
|
||||||
|
# repository query time
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
result_lst = run(self.filename, self.samplelength, samples, self.dbroot, max_split)
|
||||||
|
|
||||||
|
# output
|
||||||
|
output = "Search Result: \n"
|
||||||
|
|
||||||
|
# Tabulate % match (wav files with 0% match are excluded from the result)
|
||||||
|
for pair in result_lst:
|
||||||
|
output += pair[0] + " : " + (40-len(pair[0]))*" " + pair[1] + "% match" + "\n"
|
||||||
|
|
||||||
|
# Show search time
|
||||||
|
timelapse_parallel = time.time() - start_time
|
||||||
|
output = output + str(timelapse_parallel) + "seconds"
|
||||||
|
|
||||||
|
self.text_result.insert('1.0', output + "\n" )
|
||||||
|
|
||||||
|
self.draw_wavform(query_wavsound.get_data(),"cyan","query")
|
||||||
|
|
||||||
|
top_match_wavsoundfile = output.split()[2]
|
||||||
|
print( output.split())
|
||||||
|
print(top_match_wavsoundfile)
|
||||||
|
top_match_wavsound = wavsound(top_match_wavsoundfile)
|
||||||
|
|
||||||
|
self.draw_wavform(top_match_wavsound.get_data(),"white","result")
|
||||||
|
|
||||||
|
# quit application
|
||||||
|
def quit(self):
|
||||||
|
self.root.destroy()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
my_app = application()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
72
base/api/librosa_run.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import librosa
|
||||||
|
import librosa.display as display
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import os
|
||||||
|
# from numpy import array
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from numpy.linalg import norm
|
||||||
|
from dtw import dtw
|
||||||
|
|
||||||
|
# import math
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
warnings.filterwarnings("ignore")
|
||||||
|
|
||||||
|
def dot(A,B):
|
||||||
|
return (sum(a*b for a,b in zip(A,B)))
|
||||||
|
|
||||||
|
def cosine_similarity(a,b):
|
||||||
|
return dot(a,b) / ( (dot(a,a) **.5) * (dot(b,b) ** .5) )
|
||||||
|
|
||||||
|
def fungsi_librosa(sound_url1,sound_url2):
|
||||||
|
|
||||||
|
print(sound_url1)
|
||||||
|
print(sound_url2)
|
||||||
|
|
||||||
|
y1, sr1 = librosa.load(sound_url1)
|
||||||
|
y2, sr2 = librosa.load(sound_url2)
|
||||||
|
|
||||||
|
mfcc1 = librosa.feature.mfcc(y1,sr1)
|
||||||
|
mfcc2 = librosa.feature.mfcc(y2,sr2)
|
||||||
|
|
||||||
|
|
||||||
|
path = 'static/created_image/'
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.makedirs(path)
|
||||||
|
|
||||||
|
today_date = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
|
||||||
|
path = path + today_date+ '/'
|
||||||
|
path_nya = path
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.makedirs(path)
|
||||||
|
|
||||||
|
hour_min_sec = datetime.now().strftime("%H-%M-%S")
|
||||||
|
|
||||||
|
plt.figure(figsize=(14, 5))
|
||||||
|
display.waveshow(y1, sr=sr1)
|
||||||
|
plt.savefig(path+'spec'+hour_min_sec+'.png')
|
||||||
|
|
||||||
|
plt.figure(figsize=(14, 5))
|
||||||
|
display.waveshow(y2, sr=sr2)
|
||||||
|
plt.savefig(path+'spec1'+hour_min_sec+'.png')
|
||||||
|
|
||||||
|
dist, cost, acc_cost, path = dtw(mfcc1.T, mfcc2.T, dist=lambda x, y: norm(x - y, ord=1))
|
||||||
|
|
||||||
|
array1 = []
|
||||||
|
for nums in mfcc1:
|
||||||
|
for val in nums:
|
||||||
|
array1.append(val)
|
||||||
|
|
||||||
|
array2 = []
|
||||||
|
for nums in mfcc2:
|
||||||
|
for val in nums:
|
||||||
|
array2.append(val)
|
||||||
|
|
||||||
|
cosine_similaritynya = cosine_similarity(array1, array2)
|
||||||
|
|
||||||
|
return {'dist': dist, 'cosine_similaritynya': cosine_similaritynya, 'path': path_nya, 'hour_min_sec': hour_min_sec}
|
||||||
|
|
||||||
64
base/api/mapreducetest.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
from haystackmapper import haystackmapper
|
||||||
|
from haystackreducer import haystackreducer
|
||||||
|
from haystack import haystack
|
||||||
|
from multiprocessing import Pool, Process, Manager
|
||||||
|
from simplfunction import simplefunction
|
||||||
|
from calltomapper import calltomapper
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
"""Map reduce test is a simple testing module to check functionality
|
||||||
|
of map-reduce implementation"""
|
||||||
|
|
||||||
|
haystacks = []
|
||||||
|
haystacks.append(haystack("0",[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
|
||||||
|
haystacks.append(haystack("1",[3, 2, 2, 3, 4, 3, 6, 7, 8, 9]))
|
||||||
|
haystacks.append(haystack("2",[1, 6, -1, 0, 4, 0, 6, 7, 8, 9]))
|
||||||
|
haystacks.append(haystack("3",[3, 3, 3]))
|
||||||
|
|
||||||
|
haystackmap = haystackmapper(haystacks)
|
||||||
|
|
||||||
|
emissions= []
|
||||||
|
|
||||||
|
print("USING MAP POOL")
|
||||||
|
pool = Pool(2) # if it is a quad-core machine it can be set to 4
|
||||||
|
emissions = pool.map(haystackmap.mapper, [[2],[3]])
|
||||||
|
print(emissions)
|
||||||
|
print(haystackreducer(sum(emissions,[])))
|
||||||
|
emissions= []
|
||||||
|
|
||||||
|
print("USING MAP PROCESS")
|
||||||
|
p = Process(target=simplefunction, args=(1,2))
|
||||||
|
p.start()
|
||||||
|
p = Process(target=simplefunction, args=(1,3))
|
||||||
|
p.start()
|
||||||
|
p = Process(target=simplefunction, args=(1,4))
|
||||||
|
p.start()
|
||||||
|
p.join()
|
||||||
|
|
||||||
|
print("USING MAP PROCESS WITH MANAGER")
|
||||||
|
needles = [2, 3]
|
||||||
|
manager = Manager()
|
||||||
|
return_emissions = manager.dict()
|
||||||
|
jobs = []
|
||||||
|
pnum = 0
|
||||||
|
for needle in needles:#distributive not iteration
|
||||||
|
p = Process(target=calltomapper, args=(haystackmap,[needle],pnum,return_emissions))
|
||||||
|
jobs.append(p)
|
||||||
|
p.start()
|
||||||
|
pnum += 1
|
||||||
|
|
||||||
|
for proc in jobs:
|
||||||
|
proc.join()
|
||||||
|
|
||||||
|
emissions_list = sum(return_emissions.values(),[])
|
||||||
|
print(haystackreducer(emissions_list ))
|
||||||
|
|
||||||
|
print("Long Way (Serial Method)")
|
||||||
|
|
||||||
|
needles = [2, 3]
|
||||||
|
haystackmap = haystackmapper(haystacks)
|
||||||
|
for needle in needles:#distributive not iteration
|
||||||
|
#print (needle)
|
||||||
|
emissions = emissions + haystackmap.mapper([ needle ])
|
||||||
|
print(haystackreducer(emissions))
|
||||||
35
base/api/needlestorage.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from .wavsound import wavsound
|
||||||
|
|
||||||
|
class needlestorage:
|
||||||
|
|
||||||
|
"""needlestorage object generates needles (subsequence sample) of wavsound
|
||||||
|
object data based on number of partitions (num_chunk) and
|
||||||
|
number of samples to be analyzed (limit). The needles/samples are picked
|
||||||
|
deterministically so that the data sampled are evenly
|
||||||
|
distributed across the wav file) """
|
||||||
|
|
||||||
|
needles = []
|
||||||
|
def __init__(self, wavsound, sample_length, limit):
|
||||||
|
u=0
|
||||||
|
x=0
|
||||||
|
self.needles = []
|
||||||
|
data = wavsound.get_data()[::16] # skip every 16
|
||||||
|
print(int(len(data)/limit)-sample_length, "is skip value")
|
||||||
|
# determine the gap between the starting positions of two consecutive
|
||||||
|
# needles (i.e. opposite of degree of overlap)
|
||||||
|
skips_per_neeedle = max(1,int(len(data)/limit)-sample_length)
|
||||||
|
for i in range (len(data)):
|
||||||
|
if u >= limit:
|
||||||
|
break
|
||||||
|
if (x % skips_per_neeedle == 0):
|
||||||
|
self.needles.append(data[i:i+sample_length])
|
||||||
|
u = u + 1
|
||||||
|
x = x + 1
|
||||||
|
def pop_unused_needle(self):
|
||||||
|
if len(self.needles) == 0:
|
||||||
|
return []
|
||||||
|
return self.needles.pop(0)
|
||||||
|
def get_needles(self):
|
||||||
|
return self.needles
|
||||||
|
def clear_needles(self):
|
||||||
|
self.needles = []
|
||||||
109
base/api/run.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# from haystackmapper import haystackmapper
|
||||||
|
from .haystackreducer import haystackreducer
|
||||||
|
from .haystack import haystack
|
||||||
|
from .needlestorage import needlestorage
|
||||||
|
from .wavsound import wavsound
|
||||||
|
from multiprocessing import Pool, Process, Manager
|
||||||
|
from .calltomapper import calltomapper
|
||||||
|
from .calltoreducer import calltoreducer
|
||||||
|
from operator import itemgetter
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
def run(query, sample_length, samples, rootdir, max_split):
|
||||||
|
|
||||||
|
""" run runs the repository search taking three user inputs, the query wav file,
|
||||||
|
sample_length, and number of partition samples"""
|
||||||
|
|
||||||
|
|
||||||
|
#Instantiate Wavsound objects from the wav files
|
||||||
|
|
||||||
|
t_wavsounds = {}
|
||||||
|
query_wavsound = wavsound(query)
|
||||||
|
|
||||||
|
# repository Structure
|
||||||
|
|
||||||
|
haystackss = [] # split repository into list of smaller repository
|
||||||
|
key_names = []
|
||||||
|
|
||||||
|
# repository Spliting Parameters (1 to number of repository entries)
|
||||||
|
|
||||||
|
db_size_per_split = 100
|
||||||
|
|
||||||
|
for i in range(max_split):
|
||||||
|
haystackss.append([])
|
||||||
|
|
||||||
|
# Read Files in the DB
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
for subdir, __, files in os.walk(rootdir):
|
||||||
|
for file in files:
|
||||||
|
key_names.append(subdir+"/"+file)
|
||||||
|
split_db_key = min(max_split, int(counter / db_size_per_split))
|
||||||
|
t_wavsounds[subdir+"/"+file] = wavsound(subdir+"/"+file)
|
||||||
|
haystackss[split_db_key].append(haystack(subdir+"/"+file,t_wavsounds[subdir+"/"+file].get_data()[::16]))
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
query_needle_factory = needlestorage(query_wavsound,sample_length,int(samples))
|
||||||
|
|
||||||
|
# Get segments of the query data as needles
|
||||||
|
needles = query_needle_factory.get_needles()
|
||||||
|
#print("...", len(needles), "needles")
|
||||||
|
query_needle_factory.clear_needles()
|
||||||
|
|
||||||
|
# MAP --------------------------------------------------
|
||||||
|
|
||||||
|
# Manager to keep track of all map results
|
||||||
|
manager = Manager()
|
||||||
|
|
||||||
|
# Map processes emit key-value pairs to emissions
|
||||||
|
return_emissions = manager.list()
|
||||||
|
|
||||||
|
# Job is a list of processes
|
||||||
|
jobs = []
|
||||||
|
|
||||||
|
# Process number
|
||||||
|
pnum = 0
|
||||||
|
|
||||||
|
#Distribute processes using multiprocessor
|
||||||
|
len_needles = len(needles)
|
||||||
|
for needle in needles:
|
||||||
|
for haystacks in haystackss:
|
||||||
|
if haystacks != []:
|
||||||
|
#print(len_needles)
|
||||||
|
p = Process(target=calltomapper, args=(haystacks,needle,pnum,len_needles*len(haystackss),return_emissions))
|
||||||
|
jobs.append(p)
|
||||||
|
p.start()
|
||||||
|
pnum += 1
|
||||||
|
|
||||||
|
for proc in jobs:
|
||||||
|
proc.join()
|
||||||
|
|
||||||
|
# SHUFFLE/REDUCE ------------------------------------------
|
||||||
|
|
||||||
|
# Job is a list of processes
|
||||||
|
jobs = []
|
||||||
|
|
||||||
|
# Manager to keep track of all map results
|
||||||
|
manager_2 = Manager()
|
||||||
|
result_dict = manager_2.dict()
|
||||||
|
|
||||||
|
for key in key_names:
|
||||||
|
key_list = [1 for x in return_emissions if x[0] == key]
|
||||||
|
print (key, key_list)
|
||||||
|
q = Process(target=calltoreducer, args=(key_list, key, result_dict))
|
||||||
|
jobs.append(q)
|
||||||
|
q.start()
|
||||||
|
|
||||||
|
for proc in jobs:
|
||||||
|
proc.join()
|
||||||
|
|
||||||
|
result_lst = []
|
||||||
|
print(len(needles), "is length of needles")
|
||||||
|
if len(result_dict.items()) != 0:
|
||||||
|
for key, value in sorted(result_dict.items(), key=lambda pair: pair[1], reverse=True):
|
||||||
|
if value > 0:
|
||||||
|
result_lst.append([str(key), str((int(value)/len(needles)*100))])
|
||||||
|
|
||||||
|
needles = []
|
||||||
|
return result_lst
|
||||||
BIN
base/api/run.pyc
Normal file
8
base/api/serializers.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
from base.models import tb_bacaan
|
||||||
|
|
||||||
|
|
||||||
|
class tb_bacaanSerializer(ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = tb_bacaan
|
||||||
|
fields = '__all__'
|
||||||
8
base/api/urls.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.getRoutes),
|
||||||
|
path('bacaans/', views.getBacaans),
|
||||||
|
path('bacaans/<str:pk>/', views.getBacaan),
|
||||||
|
]
|
||||||
64
base/api/views.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import os
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from base.models import tb_bacaan
|
||||||
|
from .serializers import tb_bacaanSerializer
|
||||||
|
from .check_similarity import check_similarity
|
||||||
|
from .interfaceCLI import cek_simlilarity2
|
||||||
|
|
||||||
|
from .librosa_run import fungsi_librosa
|
||||||
|
|
||||||
|
@api_view(['GET', 'POST'])
|
||||||
|
def getRoutes(request):
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
|
||||||
|
routes = [
|
||||||
|
'GET /api',
|
||||||
|
'POST /api/bacaan',
|
||||||
|
|
||||||
|
|
||||||
|
]
|
||||||
|
return Response(routes)
|
||||||
|
if request.method == 'POST':
|
||||||
|
url_bacaan = request.POST['url_bacaan']
|
||||||
|
nama = request.FILES['sound'].name
|
||||||
|
nama = nama.replace('Z', '').replace('.wav', '').replace('.', '').replace('-', ' ').replace(':', ' ').replace('T', ' ')
|
||||||
|
path = 'static/uploaded/'
|
||||||
|
# print(bool(request.FILES.get('hehe', False))) # check if file is empty , true is not empty . false is empty
|
||||||
|
handle_uploaded_file(request.FILES['sound'],url_bacaan,nama)
|
||||||
|
# check_similarity(path+nama+'.wav', 'static/audio/'+url_bacaan)
|
||||||
|
# similarity = check_similarity(path+nama+'.wav', 'static/audio/'+url_bacaan)
|
||||||
|
librosa_run = fungsi_librosa(path+nama+'.wav', 'static/audio/'+url_bacaan)
|
||||||
|
# remove athe last 5 char on url_bacaan
|
||||||
|
url_bacaan = url_bacaan[:-5]
|
||||||
|
# print(url_bacaan)
|
||||||
|
# similarity2 = cek_simlilarity2(path+nama+'.wav', 'static/audio/'+url_bacaan)
|
||||||
|
dataall = {'data' : 'sini data'}
|
||||||
|
#add librosa_run dictinary to dataall
|
||||||
|
dataall.update(librosa_run)
|
||||||
|
return Response(dataall)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_uploaded_file(f,url_bacaan,nama):
|
||||||
|
path = 'static/uploaded/' # this is the path to the folder where you want to save the file
|
||||||
|
isExist = os.path.exists(path) # check if the folder exist
|
||||||
|
if not isExist: # if not exist then create the folder
|
||||||
|
os.mkdir(path) # create the folder
|
||||||
|
with open(path+nama+'.wav', 'wb+') as destination:
|
||||||
|
for chunk in f.chunks():
|
||||||
|
destination.write(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
def getBacaans(request):
|
||||||
|
bacaans = tb_bacaan.objects.all()
|
||||||
|
serializer = tb_bacaanSerializer(bacaans, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
def getBacaan(request,pk):
|
||||||
|
bacaan = tb_bacaan.objects.get(id=pk)
|
||||||
|
serializer = tb_bacaanSerializer(bacaan)
|
||||||
|
return Response(serializer.data)
|
||||||
12
base/api/wavread.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from .wavsound import wavsound
|
||||||
|
|
||||||
|
"""wavread is a testing module to test the functionality of wavsound"""
|
||||||
|
|
||||||
|
button_wavsound = wavsound('db/button.wav')
|
||||||
|
print(button_wavsound)
|
||||||
|
|
||||||
|
beep_wavsound = wavsound('db/buttonresampled.wav')
|
||||||
|
print(beep_wavsound)
|
||||||
|
print (len(button_wavsound.get_data()))
|
||||||
|
print (len(beep_wavsound.get_data()),len(button_wavsound.get_data()))
|
||||||
|
print(button_wavsound.get_chunk(0,100))
|
||||||
53
base/api/wavsound.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import wave, struct
|
||||||
|
|
||||||
|
class wavsound:
|
||||||
|
|
||||||
|
"""wavsound object collects data from a wav file and stores it
|
||||||
|
as a list of integers """
|
||||||
|
|
||||||
|
def __init__(self, wav_file):
|
||||||
|
self.data = []# set it as a local variable
|
||||||
|
if wav_file == '':
|
||||||
|
return
|
||||||
|
#print(wav_file)
|
||||||
|
waveFile = wave.open(wav_file, 'r')
|
||||||
|
|
||||||
|
length = waveFile.getnframes()
|
||||||
|
for i in range(0,length):
|
||||||
|
waveData = waveFile.readframes(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = struct.unpack("<h", waveData)
|
||||||
|
except:
|
||||||
|
data = struct.unpack("<L", waveData)
|
||||||
|
# data = struct.unpack("<h", waveData)
|
||||||
|
# data = struct.unpack("<L", waveData)
|
||||||
|
self.data.append(int(data[0]))
|
||||||
|
|
||||||
|
# return data
|
||||||
|
def get_data(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
# set data from a list of int
|
||||||
|
def set_data(self, data):
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
# copy data from other wavsound
|
||||||
|
def copy_from(self, other_wavsound):
|
||||||
|
self.data = other_wavsound.get_data()
|
||||||
|
|
||||||
|
# Get a small chunk of wavsound
|
||||||
|
def get_chunk (self, section_no, num_chunk):
|
||||||
|
new_wavsound = wavsound('')
|
||||||
|
data = self.get_data()
|
||||||
|
#print(len(self.get_data()))
|
||||||
|
length = int(len(self.get_data())/num_chunk)
|
||||||
|
#print(length)
|
||||||
|
new_wavsound.set_data(data[(length*section_no):(length*(section_no+1))])
|
||||||
|
return new_wavsound
|
||||||
|
def __repr__(self):
|
||||||
|
strr = ''
|
||||||
|
for i in self.data:
|
||||||
|
strr = strr + " " + str(i)
|
||||||
|
return strr
|
||||||
|
|
||||||
@ -2,11 +2,15 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<main class="update-account layout">
|
<main class="update-account layout">
|
||||||
|
{% csrf_token %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="layout__box">
|
<div class="layout__box">
|
||||||
<div class="layout__boxHeader">
|
<div class="layout__boxHeader">
|
||||||
<div class="layout__boxTitle">
|
<div class="layout__boxTitle">
|
||||||
<a href="profile.html" style="text-align: left;">
|
<audio id="myAudio_{{bacaan.judul_bacaan}}">
|
||||||
|
<source src="{% static 'audio/' %}{{bacaan.audio_url}}" type="audio/wav">
|
||||||
|
</audio>
|
||||||
|
<a href="{% url 'home' %}" style="text-align: left;">
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"
|
||||||
viewBox="0 0 32 32">
|
viewBox="0 0 32 32">
|
||||||
<title>arrow-left</title>
|
<title>arrow-left</title>
|
||||||
@ -22,7 +26,7 @@
|
|||||||
|
|
||||||
<center><img src="{% static 'images/' %}{{bacaan.image_url}}" style="{{style}}" alt=""></center>
|
<center><img src="{% static 'images/' %}{{bacaan.image_url}}" style="{{style}}" alt=""></center>
|
||||||
|
|
||||||
<center><p class="btn btn--main">Dengar <i id="i_{{bacaan.judul_bacaan}}" class="fa fa-play-circle"></i></p></center>
|
<center><p class="btn btn--main" onclick="playAudio()">Dengar <i id="i_{{bacaan.judul_bacaan}}" class="fa fa-play-circle"></i></p></center>
|
||||||
<br>
|
<br>
|
||||||
<center><button class="btn" id="recordButton" style="background-color: green;">Rekam <i id="i_{{bacaan.judul_bacaan}}" class="fa fa-play-circle"></i></button>   <button id="pauseButton" style="cursor: not-allowed; background-color: grey;" class="btn btn--default" disabled>Pause <i id="i_{{bacaan.judul_bacaan}}" class="fa fa-pause-circle"></i></button>   <button id="stopButton" style="cursor: not-allowed; background-color: grey;" class="btn btn--default" disabled>Stop <i id="i_{{bacaan.judul_bacaan}}" class="fa fa-stop-circle"></i></button></center>
|
<center><button class="btn" id="recordButton" style="background-color: green;">Rekam <i id="i_{{bacaan.judul_bacaan}}" class="fa fa-play-circle"></i></button>   <button id="pauseButton" style="cursor: not-allowed; background-color: grey;" class="btn btn--default" disabled>Pause <i id="i_{{bacaan.judul_bacaan}}" class="fa fa-pause-circle"></i></button>   <button id="stopButton" style="cursor: not-allowed; background-color: grey;" class="btn btn--default" disabled>Stop <i id="i_{{bacaan.judul_bacaan}}" class="fa fa-stop-circle"></i></button></center>
|
||||||
<!-- <br> -->
|
<!-- <br> -->
|
||||||
@ -30,13 +34,52 @@
|
|||||||
<br>
|
<br>
|
||||||
<div id="formats" style="display: none;">Format: start recording to see sample rate</div>
|
<div id="formats" style="display: none;">Format: start recording to see sample rate</div>
|
||||||
<center><div id="recordingsList"></div></center>
|
<center><div id="recordingsList"></div></center>
|
||||||
|
<center><p id="penilaian"></p></center>
|
||||||
|
<div id="image_sound" style="display: none;">
|
||||||
|
<h5>Bacaan Ustazah</h5>
|
||||||
|
<img id="img_bacaan_ustazah" >
|
||||||
|
<br><br>
|
||||||
|
<h5>Bacaan Anda</h5>
|
||||||
|
<img id="img_bacaan_anda">
|
||||||
|
</div>
|
||||||
|
<div id="error_teks" style="display: none;"><h2>Tidak Dapat Menilai Bacaan Anda. Sila Coba Refresh Halaman</h2></div>
|
||||||
<!-- <div id="disini"></div> -->
|
<!-- <div id="disini"></div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script src="{% static 'js/recorder.js' %}"></script>
|
<script src="{% static 'js/recorder.js' %}"></script>
|
||||||
|
<script src="{% static 'js/block/js/jquery-3.3.1.min.js' %}"></script>
|
||||||
|
<script src="{% static 'js/block/jquery.blockUI.js' %}"></script>
|
||||||
|
<script src="{% static 'js/main.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
function playAudio() {
|
||||||
|
let audio = document.getElementById("myAudio_{{bacaan.judul_bacaan}}");
|
||||||
|
document.getElementById("i_{{bacaan.judul_bacaan}}").setAttribute("class", "fa fa-pause-circle");
|
||||||
|
audio.play();
|
||||||
|
|
||||||
|
|
||||||
|
//sleep 3 second
|
||||||
|
setTimeout(function(){
|
||||||
|
document.getElementById("i_{{bacaan.judul_bacaan}}").setAttribute("class", "fa fa-play-circle");
|
||||||
|
}, 5000);
|
||||||
|
// if audio is playing in other audio tag, stop it else play it
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
let get_url = window.location.host
|
||||||
|
let get_http = window.location.protocol
|
||||||
|
// console.log(get_http)
|
||||||
|
// console.log(get_url)
|
||||||
|
|
||||||
|
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||||
|
console.log(csrftoken);
|
||||||
URL = window.URL || window.webkitURL;
|
URL = window.URL || window.webkitURL;
|
||||||
|
|
||||||
var gumStream; //stream from getUserMedia()
|
var gumStream; //stream from getUserMedia()
|
||||||
@ -148,7 +191,18 @@
|
|||||||
function stopRecording() {
|
function stopRecording() {
|
||||||
console.log("stopButton clicked");
|
console.log("stopButton clicked");
|
||||||
|
|
||||||
|
setAttributes(recordButton, {
|
||||||
|
"style": "background-color: green; cursor: pointer;",
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
setAttributes(stopButton, {
|
||||||
|
"style": "background-color: grey;cursor: not-allowed;",
|
||||||
|
});
|
||||||
|
|
||||||
|
setAttributes(pauseButton, {
|
||||||
|
"style": "background-color: grey;cursor: not-allowed;",
|
||||||
|
});
|
||||||
//disable the stop button, enable the record too allow for new recordings
|
//disable the stop button, enable the record too allow for new recordings
|
||||||
stopButton.disabled = true;
|
stopButton.disabled = true;
|
||||||
recordButton.disabled = false;
|
recordButton.disabled = false;
|
||||||
@ -167,7 +221,7 @@
|
|||||||
rec.exportWAV(createDownloadLink);
|
rec.exportWAV(createDownloadLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDownloadLink(blob) {
|
async function createDownloadLink(blob) {
|
||||||
|
|
||||||
var url = URL.createObjectURL(blob);
|
var url = URL.createObjectURL(blob);
|
||||||
var au = document.createElement('audio');
|
var au = document.createElement('audio');
|
||||||
@ -191,6 +245,69 @@
|
|||||||
link.download = filename+".wav"; //download forces the browser to donwload the file using the filename
|
link.download = filename+".wav"; //download forces the browser to donwload the file using the filename
|
||||||
link.innerHTML = "Save to disk";
|
link.innerHTML = "Save to disk";
|
||||||
|
|
||||||
|
|
||||||
|
sedang_proses()
|
||||||
|
try{
|
||||||
|
let data = new FormData()
|
||||||
|
data.append('sound', blob, filename+".wav")
|
||||||
|
data.append('url_bacaan', "{{bacaan.audio_url}}")
|
||||||
|
//create fetch with headers
|
||||||
|
let response = await fetch('/api/', {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': csrftoken,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let json = await response.json()
|
||||||
|
console.log(json)
|
||||||
|
// if response code is 200, then the request is successful
|
||||||
|
if(response.status == 200){
|
||||||
|
|
||||||
|
|
||||||
|
// div id="image_sound" remove attr style
|
||||||
|
let image_sound = document.getElementById('image_sound')
|
||||||
|
image_sound.removeAttribute('style')
|
||||||
|
// img id="img_bacaan_ustazah" src= json.path +'spec1'+json.hour_min_sec+'.png'
|
||||||
|
let img_bacaan_ustazah = document.getElementById('img_bacaan_ustazah')
|
||||||
|
img_bacaan_ustazah.src = get_http+"//"+get_url+"/"+ json.path +'spec1'+json.hour_min_sec+'.png'
|
||||||
|
// img id="img_bacaan_anda" src= json.path +'spec'+json.hour_min_sec+'.png'
|
||||||
|
let img_bacaan_anda = document.getElementById('img_bacaan_anda')
|
||||||
|
img_bacaan_anda.src = get_http+"//"+get_url+"/"+ json.path +'spec'+json.hour_min_sec+'.png'
|
||||||
|
// div id="error_teks" attr style="display: none;"
|
||||||
|
let error_teks = document.getElementById('error_teks')
|
||||||
|
error_teks.setAttribute('style', 'display: none;')
|
||||||
|
// nilainya = json.cosine_similarity to %
|
||||||
|
let nilainya = json.cosine_similaritynya * 100
|
||||||
|
// only 2 decimal places from nilainya
|
||||||
|
nilainya = nilainya.toFixed(2)
|
||||||
|
// p id="penilaian" innerHTML= Penilaian Kesamaan Bacaan Anda Dengan Ustazah adalah sebanyak '+nilainya+'%'
|
||||||
|
let penilaian = document.getElementById('penilaian')
|
||||||
|
penilaian.innerHTML = 'Penilaian Kesamaan Bacaan Anda Dengan Ustazah adalah sebanyak '+nilainya+'%'
|
||||||
|
}else{
|
||||||
|
// div id="image_sound" attr style="display: none;"
|
||||||
|
let image_sound = document.getElementById('image_sound')
|
||||||
|
image_sound.setAttribute('style', 'display: none;')
|
||||||
|
// div id="error_teks" remove attr style
|
||||||
|
let error_teks = document.getElementById('error_teks')
|
||||||
|
let penilaian = document.getElementById('penilaian')
|
||||||
|
penilaian.innerHTML = null
|
||||||
|
}
|
||||||
|
$.unblockUI();
|
||||||
|
// console.log(json)
|
||||||
|
}catch(err){
|
||||||
|
// div id="image_sound" attr style="display: none;"
|
||||||
|
let image_sound = document.getElementById('image_sound')
|
||||||
|
image_sound.setAttribute('style', 'display: none;')
|
||||||
|
// div id="error_teks" remove attr style
|
||||||
|
let error_teks = document.getElementById('error_teks')
|
||||||
|
let penilaian = document.getElementById('penilaian')
|
||||||
|
penilaian.innerHTML = null
|
||||||
|
console.log(err)
|
||||||
|
$.unblockUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(link.href)
|
||||||
//add the new audio element to li
|
//add the new audio element to li
|
||||||
// li.appendChild(au);
|
// li.appendChild(au);
|
||||||
|
|
||||||
|
|||||||
30
cert.crt
@ -1,20 +1,20 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
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
|
||||||
fzV5l6RMZbShDhBm2yLQuLk968AWjeYyAjuq8g==
|
9AQKcadiJyQW8qFX7ldj+zgeqaaQxTKrYI4b3Q==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|||||||
50
cert.key
@ -1,27 +1,27 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
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
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|||||||
BIN
db.sqlite3
BIN
static/audio/1/ustazah/1/1.wav
Normal file
BIN
static/audio/1/ustazah/2/2.wav
Normal file
BIN
static/audio/1/ustazah/3/3.wav
Normal file
BIN
static/audio/1/ustazah/4/4.wav
Normal file
BIN
static/audio/1/ustazah/5/5.wav
Normal file
BIN
static/audio/1/ustazah/6/6.wav
Normal file
BIN
static/audio/2/ustazah/1/1.wav
Normal file
BIN
static/audio/2/ustazah/2/2.wav
Normal file
BIN
static/audio/3/ustazah/1/1.wav
Normal file
BIN
static/audio/3/ustazah/2/2.wav
Normal file
BIN
static/audio/3/ustazah/3/3.wav
Normal file
BIN
static/audio/4/ustazah/1/1.wav
Normal file
BIN
static/audio/4/ustazah/2/2.wav
Normal file
BIN
static/audio/4/ustazah/3/3.wav
Normal file
BIN
static/audio/5/ustazah/1/1.wav
Normal file
BIN
static/audio/5/ustazah/2/2.wav
Normal file
BIN
static/audio/5/ustazah/3/3.wav
Normal file
BIN
static/audio/6/ustazah/1/1.wav
Normal file
BIN
static/audio/6/ustazah/2/2.wav
Normal file
BIN
static/audio/6/ustazah/3/3.wav
Normal file
BIN
static/created_image/2022-03-20/spec114-39-06.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec114-48-13.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec114-53-15.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec115-02-02.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec115-04-47.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec115-05-10.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec115-05-45.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec115-06-38.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec115-06-53.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec123-15-39.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
static/created_image/2022-03-20/spec14-39-06.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
static/created_image/2022-03-20/spec14-48-13.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
static/created_image/2022-03-20/spec14-53-15.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
static/created_image/2022-03-20/spec15-02-02.png
Normal file
|
After Width: | Height: | Size: 51 KiB |