BashGU
Electronic Library

     

Details

Мищенко, К. А. Интерактивное клиент-серверное приложение для идентификации аккордов с использованием спектрального анализа звука: выпускная квалификационная работа по направлению подготовки 01.04.02 Прикладная математика и информатика. Направленность: Технологии проектирования системного и прикладного программного обеспечения [Электронный ресурс] / К. А. Мищенко; Уфимский университет науки и технологий, Стерлитамакский филиал, факультет математики и информационных технологий, кафедра математического моделирования ; научный руководитель А. А. Акимов. — Стерлитамак, 2025. — 81 с. — <URL:https://elib.bashedu.ru/dl/diplom/SF/2025/FMIT/MishchenkoKA_01.04.02_OZMPMI_mag_2025_VKR.pdf>.

Record create date: 2/5/2025

Subject: Техника; магистратура; ВКР проектного типа; мобильное приложение

UDC: 004.9

LBC: 32

Collections: Магистерские диссертации; Общая коллекция

Allowed Actions:

*^% Action 'Read' will be available if you login and work on the computer in the reading rooms of the Library

Group: Anonymous

Network: Internet

Document access rights

Network User group Action
Library BashGU Local Network Authenticated users Read
Library BashGU Local Network All
Internet Authenticated users Read
-> Internet All

Table of Contents

  • ДИПЛОМ.docx
    • СОДЕРЖАНИЕ
    • ВВЕДЕНИЕ
    • ГЛАВА 1. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ
      • 1.1. Анализ предметной области
        • 1.1.1. Обработка звуковых сигналов: подходы и их эволюция
        • 1.1.2. Подход спектрального анализа и его применение в нейросетях
        • 1.1.4. Анализ существующих методов и решений задачи классификации аккордов
      • 1.2. Описание процессов создания нейросети для проектного решения
        • 1.2.1. Выбор обучающего набора данных и модели нейросети
        • 1.2.2. Реализация обучающего модуля нейросети
        • 1.2.3. Логика работы основного модуля нейросети
      • 1.3. Описание технологий проектного решения
        • 1.3.1 Сервер на базе операционной системы Ubuntu Server
        • 1.3.2. Веб-сервер NGINX
        • 1.3.3. Docker как средство контейнеризации
        • 1.3.4. Dockerized SMTP сервер Mailcow
        • 1.3.5. Язык программирования Python
        • 1.3.6. Python библиотека TensorFlow
        • 1.3.7. Django
        • 1.3.8. Librosa
        • 1.3.9. Язык HMTL
        • 1.3.10. Каскадная таблица стилей
        • 1.3.11. JavaScript
        • 1.3.12. Графический игровой движок Godot
    • ГЛАВА 2. ПРАКТИЧЕСКАЯ ЧАСТЬ
      • 2.1. Настройка серверной инфраструктуры
      • 2.2. Организация базы данных учетных записей пользователей
      • 2.3. Реализация точек доступа к приложению
      • 2.4. Функционал регистрации и авторизации пользователей
      • 2.5. Реализация клиентской части проектного решения
  • 14b4e9e8f35342056d7d03840cca5edd7d8a70cc9e48f5b4ccf1645d2e8403cd.pdf
  • ДИПЛОМ.docx
    • СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
  • 14b4e9e8f35342056d7d03840cca5edd7d8a70cc9e48f5b4ccf1645d2e8403cd.pdf
  • ДИПЛОМ.docx
    • ПРИЛОЖЕНИЯ
      • ПРИЛОЖЕНИЕ № 1. Keras модель нейросети
    • from keras import layers, Model, Input
    • from keras.regularizers import l2
    • from keras.layers import LayerNormalization
    • def model_Conv2D(n_classes, n_mels, frames):
    • input_shape = (n_mels, frames, 1)
    • inputs = Input(shape=input_shape)
    • x = LayerNormalization(axis=1, name='batch_norm')(inputs)
    • x = layers.Conv2D(8, kernel_size=(7, 7), activation='tanh', padding='same', name='conv2d_tanh')(x)
    • x = layers.MaxPooling2D(pool_size=(2, 2), padding='same', name='max_pool_2d_1')(x)
    • x = layers.Conv2D(16, kernel_size=(5, 5), activation='relu', padding='same', name='conv2d_relu_1')(x)
    • x = layers.MaxPooling2D(pool_size=(2, 2), padding='same', name='max_pool_2d_2')(x)
    • x = layers.Conv2D(16, kernel_size=(3, 3), activation='relu', padding='same', name='conv2d_relu_2')(x)
    • x = layers.MaxPooling2D(pool_size=(2, 2), padding='same', name='max_pool_2d_3')(x)
    • x = layers.Conv2D(32, kernel_size=(3, 3), activation='relu', padding='same', name='conv2d_relu_3')(x)
    • x = layers.MaxPooling2D(pool_size=(2, 2), padding='same', name='max_pool_2d_4')(x)
    • x = layers.Conv2D(32, kernel_size=(3, 3), activation='relu', padding='same', name='conv2d_relu_4')(x)
    • x = layers.Flatten(name='flatten')(x)
    • x = layers.Dropout(rate=0.2, name='dropout')(x)
    • x = layers.Dense(64, activation='relu', activity_regularizer=l2(0.001), name='dense')(x)
    • outputs = layers.Dense(n_classes, activation='softmax', name='softmax')(x)
    • model = Model(inputs=inputs, outputs=outputs, name='2d_convolution')
    • model.compile(optimizer='adam',
    • loss='categorical_crossentropy',
    • metrics=['accuracy'])
    • return model
      • ПРИЛОЖЕНИЕ № 2. Обучающий модуль нейросети
    • import utils, os
    • import model as keras_model
    • import matplotlib.pyplot as plt
    • import numpy as np
    • base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    • resources_dir = os.path.join(base_dir, "app", "resources")
    • dataset_dir = os.path.join(base_dir, "dataset")
    • train_path = os.path.join(dataset_dir, "training")
    • test_path = os.path.join(dataset_dir, "test")
    • model_path = os.path.join(resources_dir, "trained_model.keras")
    • meta_path = os.path.join(resources_dir, "meta.json")
    • os.makedirs(resources_dir, exist_ok=True)
    • sr = 16000
    • dt = 1.0
    • N_CLASSES = 8
    • n_mels = 64
    • hop_length = 256
    • n_fft = 1024
    • threshold = 0.01
    • frames = (sr // hop_length) + 1
    • classes = sorted(os.listdir(test_path))
    • meta = {
    • "sr": sr,
    • "dt": dt,
    • "n_classes": N_CLASSES,
    • "n_mels": n_mels,
    • "hop_length": hop_length,
    • "n_fft": n_fft,
    • "threshold": threshold,
    • "frames": frames,
    • "classes": classes
    • }
    • X_train, Y_train = utils.get_data(train_path, sr, dt, N_CLASSES, n_mels, hop_length, n_fft, threshold, frames)
    • X_train.shape, Y_train.shape
    • model = keras_model.model_Conv2D(N_CLASSES, n_mels, frames)
    • batch_size = 32
    • epochs = 30
    • history = model.fit(X_train, Y_train, batch_size=batch_size, epochs=epochs, verbose=1, shuffle=True)
    • model.save(model_path)
    • utils.serialize_dict(meta, meta_path)
      • ПРИЛОЖЕНИЕ № 3. Модуль прогнозирования
    • from keras.saving import load_model
    • from .scripts import utils
    • import os, sys
    • import numpy as np
    • if getattr(sys, 'frozen', False):
    • application_path = sys._MEIPASS
    • else:
    • application_path = os.path.dirname(os.path.abspath(__file__))
    • model = None
    • meta = None
    • def predict_chord_from_audio(audio):
    • mask = utils.envelope(audio, meta["sr"], meta["threshold"])
    • y = audio[mask]
    • if len(y) >= meta["sr"]:
    • y = y[:meta["sr"]]
    • else:
    • y = np.pad(y, (0, meta["sr"] - len(y)), mode='constant')
    • mel_spec = utils.get_melspectogram(y, meta["sr"], meta["n_fft"], meta["hop_length"], meta["n_mels"])
    • mel_spec = mel_spec[np.newaxis, ...]
    • return predict_chord_from_mel(mel_spec)
    • def predict_chord_from_mel(mel_data):
    • y_pred = model.predict(mel_data, verbose=0)
    • predicted_label_idx = np.argmax(y_pred)
    • chance = y_pred[0][predicted_label_idx]
    • predicted_label = meta["classes"][predicted_label_idx]
    • return predicted_label, chance
    • def start():
    • global model
    • global meta
    • model = load_model(os.path.join(application_path, "resources/trained_model.keras"))
    • meta = utils.deserialize_dict(os.path.join(application_path, "resources/meta.json"))
      • ПРИЛОЖЕНИЕ № 4. Конфигурационный файл NGINX
    • events {}
    • http {
    • include mime.types;
    • client_max_body_size 100M;
    • # Сервер для chordfinder.ru
    • server {
    • server_name chordfinder.ru;
    • location / {
    • proxy_pass http://127.0.0.1:5000/;
    • }
    • location /static/ {
    • root /var/www/chordfinder;
    • }
    • listen 443 ssl;
    • ssl_certificate /etc/letsencrypt/live/chordfinder.ru/fullchain.pem;
    • ssl_certificate_key /etc/letsencrypt/live/chordfinder.ru/privkey.pem;
    • ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    • }
    • # Сервер для mail.chordfinder.ru
    • server {
    • server_name mail.chordfinder.ru;
    • location / {
    • proxy_pass https://127.0.0.1:8443; # Проксируем к Mailcow
    • proxy_set_header Host $host;
    • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    • proxy_set_header X-Forwarded-Proto $scheme;
    • }
    • listen 443 ssl;
    • ssl_certificate /etc/letsencrypt/live/mail.chordfinder.ru/fullchain.pem;
    • ssl_certificate_key /etc/letsencrypt/live/mail.chordfinder.ru/privkey.pem;
    • ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    • }
    • # HTTP редиректы на HTTPS
    • server {
    • listen 80;
    • server_name chordfinder.ru mail.chordfinder.ru;
    • return 301 https://$host$request_uri;
    • }
    • }
      • ПРИЛОЖЕНИЕ № 5. Настройки подключения к SMTP
    • # Emailing settings
    • EMAIL_BACKEND = '' #ENV_OVERRIDE
    • EMAIL_HOST = '' #ENV_OVERRIDE
    • EMAIL_FROM = '' #ENV_OVERRIDE
    • EMAIL_HOST_USER = '' #ENV_OVERRIDE
    • EMAIL_HOST_PASSWORD = '' #ENV_OVERRIDE
    • EMAIL_PORT = '' #ENV_OVERRIDE
    • EMAIL_USE_TLS = '' #ENV_OVERRIDE
      • ПРИЛОЖЕНИЕ № 6. Модель профиля пользователя Django
    • from django.db import models
    • from django.contrib.auth.models import User
    • from django.db.models.signals import post_save
    • from django.dispatch import receiver
    • import secrets
    • class Profile(models.Model):
    • user = models.OneToOneField(User, on_delete=models.CASCADE)
    • password_reset_token = models.CharField(max_length=256, default="")
    • api_key = models.CharField(max_length=64, unique=True, default=secrets.token_urlsafe)
    • def __str__(self):
    • return self.user.username
    • @receiver(post_save, sender=User)
    • def create_user_profile(sender, instance, created, **kwargs):
    • if created:
    • Profile.objects.create(user=instance)
    • @receiver(post_save, sender=User)
    • def save_user_profile(sender, instance, **kwargs):
    • instance.profile.save()
      • ПРИЛОЖЕНИЕ № 7. Конфигурация маршрутов Django
    • from django.contrib import admin
    • from django.urls import path, include
    • from django.urls import path
    • from ai.app_views import activate_account, elements, homepage, profile, sign_up, sign_in, password_reset, predict, docs
    • urlpatterns = [
    • path('admin/', admin.site.urls),
    • path('', homepage.homepage, name='homepage_url'),
    • path('docs', docs.docs, name='docs_url'),
    • path('api/predict', predict.predict, name='predict_url'),
    • path('accounts/', include("django.contrib.auth.urls")),
    • path('accounts/profile/', profile.profile, name='profile_url'),
    • path('elements', elements.elements, name='elements_url'),
    • path('sign_in', sign_in.sign_in, name='sign_in_url'),
    • path('sign_up/', sign_up.sign_up, name='sign_up_url'),
    • path('sign_up/success', sign_up.sign_up_success, name="sign_up_success_url"),
    • path('activate_account//', activate_account.activate_account, name='activate_account_url'),
    • path("password_change", profile.password_change, name="password_change_url"),
    • path("password_reset", password_reset.password_reset_request, name="password_reset_request_url"),
    • path("password_reset/sent", password_reset.password_reset_request_sent, name="password_reset_request_success_url"),
    • path('reset//', password_reset.password_reset_confirm, name='password_reset_confirm_url'),
    • ]
      • ПРИЛОЖЕНИЕ № 8. Точка входа прогнозирования данных
    • from librosa import resample
    • from django.http import JsonResponse
    • from ..neural_network.model import predict_chord_from_audio
    • import json
    • import numpy as np
    • def predict(request):
    • label = ""
    • chance = ""
    • if request.method != 'GET':
    • return JsonResponse({'error': 'Only GET requests are allowed'}, status=405)
    • try:
    • data = json.loads(request.body.decode('utf-8'))
    • audio = np.array(data["input"])
    • resampled = resample(y=audio, orig_sr=44100, target_sr=16000)
    • except json.JSONDecodeError:
    • return JsonResponse({'error': 'Missing required parameter: input'}, status=400)
    • label, chance = predict_chord_from_audio(resampled)
    • return JsonResponse({'label': label, 'chance': str(chance)})
      • ПРИЛОЖЕНИЕ № 9. Инициализация объекта нейросети
    • from django.apps import AppConfig
    • class AiConfig(AppConfig):
    • default_auto_field = 'django.db.models.BigAutoField'
    • name = 'ai'
    • def ready(self):
    • from ai.neural_network.model import start
    • start()
      • ПРИЛОЖЕНИЕ № 10. Реализация регистрации пользователей
    • from ..forms import UserRegistrationForm
    • from django.contrib.sites.shortcuts import get_current_site
    • from ..tokens import account_activation_token
    • from django.template.loader import render_to_string
    • from django.utils.encoding import force_bytes
    • from django.contrib.auth import get_user_model
    • from django.utils.http import urlsafe_base64_encode
    • from django.db.models import Q
    • from django.shortcuts import render, redirect
    • from django.core.mail import EmailMessage
    • from django.conf import settings
    • import json
    • def sign_up(request):
    • ERROR_CODES = {
    • "password_too_similar": "Пароль не должен совпадать с именем пользователя",
    • "password_too_short": "Пароль слишком короткий (Минимум 8 символов)",
    • "password_mismatch": "Пароли не совпадают",
    • "password_too_common": "Пароль слишком простой",
    • "unique": "Пользователь с таким именем уже существует",
    • }
    • if request.method == "POST":
    • form = UserRegistrationForm(request.POST)
    • form.errors_to_render = []
    • if form.is_valid():
    • associated_user = get_user_model().objects.filter(Q(email=form.cleaned_data.get('email'))).first()
    • if associated_user:
    • form.errors_to_render = ["Пользователь с таким Email уже зарегистрирован"]
    • else:
    • user = form.save(commit=False)
    • user.is_active=False
    • user.save()
    • if send_activation_link(request, user, form.cleaned_data.get('email')):
    • return redirect('/sign_up/success')
    • else:
    • errors = json.loads(form.errors.as_json())
    • print("СПИСОК ОШИБОК:")
    • for field_error in errors.keys():
    • print(field_error, errors[field_error])
    • for type_error in errors[field_error]:
    • code = type_error["code"]
    • if str(code).__contains__("password"):
    • print(type_error)
    • form.errors_to_render.append(ERROR_CODES[code])
    • elif str(code).__contains__("unique"):
    • form.errors_to_render.append(ERROR_CODES[code])
    • else:
    • form.errors_to_render.append(type_error['message'])
    • else:
    • form = UserRegistrationForm()
    • return render(request, "sign_up.html", context={"form": form})
    • def send_activation_link(request, user, to_email):
    • mail_subject = f"Активация аккаунта {user.username}"
    • token = account_activation_token.make_token(user)
    • uid = urlsafe_base64_encode(force_bytes(user.pk))
    • domain = get_current_site(request).domain if settings.ENV_NAME == 'local' else settings.SITE_URL
    • protocol = 'http' if settings.ENV_NAME == 'local' else 'https'
    • url = "activate_account"
    • message = render_to_string("template_activate_account.html", {
    • 'user': user.username,
    • 'domain': domain,
    • 'url': url,
    • 'uid': uid,
    • 'token': token,
    • "protocol": protocol
    • })
    • if settings.ENV_NAME == "production":
    • email = EmailMessage(mail_subject, message, to=[to_email], from_email=settings.EMAIL_FROM)
    • if email.send():
    • return True
    • else:
    • return False
    • else:
    • print("Токен отправлен", f"{protocol}://{domain}/{url}/{uid}/{token}")
    • return True
    • def sign_up_success(request):
    • data = {}
    • return render(request, 'sign_up_success.html', context=data)
      • ПРИЛОЖЕНИЕ № 11. Ограничение доступа к API
    • from django.http import JsonResponse
    • from ai.models import Profile
    • class APIKeyMiddleware:
    • def __init__(self, get_response):
    • self.get_response = get_response
    • def __call__(self, request):
    • # Проверяем только для API путей
    • if request.path.startswith('/api/'):
    • # Проверяем, есть ли заголовки
    • api_key = request.headers.get('Authorization', '').split('Bearer ')[-1] if hasattr(request, 'headers') else None
    • if not api_key or not Profile.objects.filter(api_key=api_key).exists():
    • return JsonResponse({'error': 'Invalid or missing API key'}, status=403)
    • # Продолжаем обработку запроса
    • return self.get_response(request)
      • ПРИЛОЖЕНИЕ № 12. Клиентское приложение
    • extends Label
    • export var api_key: String = ""
    • export var host: String = "localhost"
    • export var port: int = 8000
    • export var use_https: bool = false
    • var url = ""
    • onready var headers = [
    • ​"Content-Type: application/json",
    • ​"Authorization: Bearer %s" % api_key
    • ]
    • var http_request: HTTPRequest
    • var audio_player: AudioStreamPlayer
    • var capture_effect: AudioEffectCapture
    • const BUFFER_SIZE: int = 64512
    • func _ready():
    • ​configure_http_request()
    • ​configure_audio()
    • func configure_http_request():
    • ​if use_https:
    • ​​url += "https://"
    • ​else:
    • ​​url += "http://"
    • ​url += str(host) + ":" + str(port) + "/api/predict"
    • ​http_request = HTTPRequest.new()
    • ​add_child(http_request, true)
    • ​var _err = http_request.connect("request_completed", self, "_on_request_completed")
    • func configure_audio():
    • ​audio_player = AudioStreamPlayer.new()
    • ​add_child(audio_player)
    • ​audio_player.stream = AudioStreamMicrophone.new()
    • ​var bus_index = AudioServer.get_bus_index("Master")
    • ​AudioServer.set_bus_mute(bus_index, true)
    • ​capture_effect = AudioEffectCapture.new()
    • ​capture_effect.buffer_length = 1.0
    • ​AudioServer.add_bus_effect(bus_index, capture_effect)
    • ​audio_player.play()
    • func _process(_delta):
    • ​if is_instance_valid(capture_effect):
    • ​​if capture_effect.can_get_buffer(BUFFER_SIZE):
    • ​​​var microphone_data = capture_effect.get_buffer(BUFFER_SIZE)
    • ​​​var left_channel = get_channel_from_buffer(microphone_data, 0)
    • ​​​if left_channel[0] != 0:
    • ​​​​send_frames(left_channel)
    • ​if Input.is_action_just_pressed("ui_cancel"):
    • ​​get_tree().quit()
    • func get_channel_from_buffer(data: PoolVector2Array, channel_id: int) -> Array:
    • ​var _channel_data: Array
    • ​for frame in data:
    • ​​_channel_data.append(frame[channel_id])
    • ​return _channel_data
    • func send_frames(frames: Array):
    • ​var json_body = JSON.print({"input": frames})
    • ​if frames[0] != 0 and http_request.get_http_client_status() != HTTPClient.STATUS_REQUESTING:
    • ​​var _err = http_request.request(url, headers, false, HTTPClient.METHOD_GET, json_body)
    • func _on_request_completed(_result, _response_code, _headers, body):
    • ​if body:
    • ​​var json_result = JSON.parse(body.get_string_from_utf8()).result
    • ​​if json_result:
    • ​​​text = json_result.get("label") if json_result.get("label") else ""

Usage statistics

stat Access count: 0
Last 30 days: 0
Detailed usage statistics