Idea de proyecto | Cree una plataforma de aprendizaje musical basada en AR utilizando MERN Stack

En este artículo, construiremos una aplicación web basada en AR llamada GuitAR. Enseña a los usuarios a aprender a tocar la guitarra proyectando las cuerdas que se tocarán en la guitarra en la transmisión de la cámara del usuario. Ahora, todo lo que el usuario tiene que hacer es tocar las cuerdas resaltadas para reproducir una canción en particular. Aquí hay una imagen de muestra para una mejor comprensión.

 Resultado final. PD: estamos usando el teléfono aquí, pero un marcador impreso dará un mejor resultado.

Construiremos este proyecto con React JS para el front-end, ExpressJS ejecutándose en NodeJS para el back-end y MongoDB para la base de datos. También usaremos Selenium, BeautifulSoup4, Pymango para construir nuestra base de datos de canciones y ArucoJS, que es una biblioteca de CV popular para JavaScript. También usaremos Firebase Authentication para integrar el inicio de sesión de Google en la aplicación. ¡Finalmente, usaremos Github Actions para implementar el proyecto en heroku!

Este artículo asume una comprensión de MERN Stack, web scrapping y algo de trigonometría y geometría de coordenadas. No entraremos en detalles del código básico de frontend y backend porque ya hay muchos artículos excelentes que lo tratan y, por lo tanto, nos centraremos principalmente en la parte AR del proyecto. Puede consultar el código completo en Github Repos. 

Enlaces:

Nota: antes de comenzar con los aspectos técnicos, familiaricémonos con la jerga de la guitarra. 

  • La tablatura de guitarra, generalmente conocida como «pestaña», es un método para anotar música que permite a los guitarristas principiantes aprender canciones rápida y fácilmente. Las tablas de guitarra comparten similitudes con la notación de pentagrama al mostrarle qué notas tocar, cuánto tiempo tocarlas y qué técnicas usar. Estas pestañas seguirán cambiando a lo largo de una canción. Se nos pedirá que mantengamos dónde cambian estas pestañas en relación con las letras en la base de datos. 
  • El diapasón (también conocido como diapasón en instrumentos con trastes) es un componente importante de la mayoría de los instrumentos de cuerda. Es una tira larga y delgada de material, generalmente madera, que se lamina en la parte delantera del mástil de un instrumento. Las cuerdas corren sobre el diapasón, entre la cejilla y el puente. Para tocar el instrumento, un músico presiona las cuerdas contra el diapasón para cambiar la longitud de vibración, cambiando el tono. Esto se llama detener las strings.

 

Paso 1: Creación de la base de datos: primero , crearemos una base de datos en el atlas de MongoDB. Esta base de datos contendrá las letras y las pestañas mencionadas anteriormente junto con la configuración del usuario. Inicie sesión en MongoDB, cree un clúster y cree una base de datos. Eliminaremos https://www.ultimate-guitar.com/, pero no dude en usar cualquier otro método de su elección. El script para la araña se puede encontrar en la sección de enlaces. Una vez que haya terminado de desechar, guarde los datos en la base de datos usando el siguiente código:

Python3

import os
import pymongo
from dotenv import load_dotenv
load_dotenv()
  
  
class Mongo:
    MONGODB_PASSWORD = os.environ.get("MONGODB_PASSWORD")
  
    # Replace this with your connection url from mongodb
    connection_url = "mongodb+srv://admin:" 
        + MONGODB_PASSWORD + 
        "@cluster0.jx3fl.mongodb.net/guitarappdb?retryWrites=true&w=majority"
    print(connection_url)
  
    client = pymongo.MongoClient(connection_url)
  
    songs = db["songsv2"]
  
    def __getSchema(self, title, artist, info1, info2, info3, data):
        return {
            "title": title,
            "artist": artist,
            "info1": info1,
            "info2": info2,
            "info3": info3,
            "data": data
        }
  
    def addSong(self, title, artist, info1, info2, info3, data):
        id = self.songs.insert_one(self.__getSchema(
            title, artist, info1, info2, info3, data
        ))

Al final, la base de datos debería verse así, donde <tag></tag> encierra las pestañas:

_id:ObjectId("604c6b18ab0d440efde7ae4f")
title:"Drivers License"
artist:"Olivia Rodrigo"
info1:""
info2:"Difficulty: novice"
info3:""
data:"[Verse 1]
      <tag>G</tag>  I got my driver’s license last week
       Just lik..."

Paso 2: construir el back-end

1. Configuración: cree una carpeta y ejecute npm init en ella. Después de completar la configuración, edite package.json para que se vea así:

// package.json

{
  "name": "guitar-app-backend",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "<YOUR_NAME>",
  "license": "ISC",
}

Esto permitirá usar la sintaxis de ES6 en nuestra aplicación. Ahora instale express, mongoose, dotenv, cors y nodemon usando npm o yarn.

2. Crear esquema: primero crearemos el esquema para acceder a las canciones que hemos agregado a la base de datos anteriormente.

Javascript

// dbSongs.js
import mongoose from 'mongoose'
  
const songsSchema = mongoose.Schema({
    title : String,
    artist : String,
    info1 : String,
    info2 : String,
    info3 : String,
    data : String
})
  
export default mongoose.model('songsv2', songsSchema)

Ahora, podemos crear el esquema de usuario para guardar la configuración del usuario. Dado que no almacenamos información confidencial como contraseñas o datos financieros, podemos prescindir de cualquier tipo de seguridad como el cifrado. Hablaremos de campos como «textColors» y «guitarra» mientras construimos la interfaz.

Javascript

// dbUser.js
import mongoose from 'mongoose'
  
const userSchema = mongoose.Schema({
    email : String,
    displayName : {type : String, default : "User"},
    photoUrl : 
        {type : String, default : "<DEFAULT_PROFILE_PICTURE_URL>"},
    speed : {type : Number, default : 1},
    strokeColor : {type : String, default : "red"},
    textColor : {type : String, default : "black"},
    guitar : {
        m_ratio1 : {type : Number, default : 0},
        n_ratio1 : {type : Number, default : 0},
        m_ratio2 : {type : Number, default : 0},
        n_ratio2 : {type : Number, default : 0},
        m_ratio3 : {type : Number, default : 0},
        n_ratio3 : {type : Number, default : 0},
        m_ratio4 : {type : Number, default : 0},
        n_ratio4 : {type : Number, default : 0},
    }
})
  
export default mongoose.model('userInfo', userSchema)

3. Crear puntos finales: ahora escribiremos los puntos finales para las API. El código completo para los puntos finales se puede encontrar en el repositorio de backend en la sección de enlaces. Una vez que el backend está completo, está listo para probar. Ejecute nodemon server.js y use cartero para probar la API. Asegúrese de configurar el archivo .env o cambie la URL de conexión de mongodb a su propia URL (no recomendado si planea hacer público su código) antes de ejecutar.

4. Configurar CI/CD: envíe el código a GitHub en su repositorio usando git. Cree su aplicación Heroku y agregue la clave API de Heroku y la contraseña de mongodb en los secretos de GitHub. Además, agregue la contraseña de MongoDB en el entorno de Heroku. Ahora, vaya a su repositorio, vaya a acciones y configure un nuevo flujo de trabajo de la siguiente manera:

name: Build And Deploy

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:
    
    name : Build
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x, 15.x]
    env:
      MONGODB_PASSWORD: ${{secrets.MONGODB_PASSWORD}}

    steps:
    - uses: actions/checkout@v2
    - name: Install Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}
        
    - name: Install NPM Package
      run: npm ci
      
    - name: Build project
      run: npm run build --if-present
      env:
        CI: false
  
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v2
      - name: Push to Heroku
        uses: akhileshns/heroku-deploy@v3.4.6
        with:
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: "<YOUR_HEROKU_APP_NAME_HERE>"
          heroku_email: "<YOUR_HEROKU_EMAILID_HERE>"

      - name: Run a one-line script
        run: echo successfully run

Nota: Si planea agregar CI/CD, asegúrese de usar el archivo .env para la URL de conexión de mongodb.

Paso 3: configurar la interfaz

1. Crear una aplicación de reacción: use npx create-react-app para crear una aplicación de reacción. Limpie la aplicación predeterminada e instale las siguientes dependencias usando npm o yarn:

Dependencia Función
Aruco JS Visión por computador
Reaccionar GSAP Animación
Axios Redes
base de fuego Autenticación de Google
Núcleo de interfaz de usuario de material interfaz de usuario
Iconos de interfaz de usuario de materiales interfaz de usuario
reaccionar redux Accesorios globales
Reaccionar enrutador Enrutamiento
Reaccionar jugador Insertar vídeo de Vimeo
Reaccionar cámara web Acceder a la cámara web
Tres JS Gráficos 3D
Vanta JS Fondos animados

2. Cree la página de inicio de sesión: esta página utilizará Firebase para mostrar la ventana emergente de inicio de sesión de Google al usuario. Después de obtener los datos de la cuenta de Google, consuma /api/v1/users/findbyemail y /api/v1/users/create para crear un nuevo usuario u obtener la configuración del usuario, si el usuario ya existe. Una vez que tenga los datos de usuario de la base de datos, guárdelos en redux para poder acceder a ellos desde cualquier lugar. Obtiene el código completo del repositorio de frontend en la sección de enlaces. Así es como debería verse el resultado:

Página de inicio de sesión

 

3. Cree la página de canciones: esta página contendrá una lista de varias canciones que el usuario puede reproducir. Esto se puede construir consumiendo /api/v1/songs/get . Este punto final toma la página, el límite y la consulta como consulta de solicitud, lo que ayudará en la paginación y la funcionalidad de búsqueda. También debe contener una barra de navegación para navegar a diferentes páginas. Obtiene el código completo del repositorio de frontend en la sección de enlaces. Así es como debería verse la página:

Página de todas las canciones

 

4. Agregar página de configuración: esta página permitirá al usuario establecer el color de las proyecciones AR y la velocidad de las canciones almacenadas en el esquema de usuario de la base de datos. Se puede acceder a los valores actuales desde los datos de usuario que almacenamos en redux. Podemos actualizar esta configuración mediante /api/v1/users/updateStrokeColor , /api/v1/users/updateSpeed ​​y /api/v1/users/updateTextColor. Debería verse así después de la finalización:

Página de configuración

 

Paso 4: Creación de funciones auxiliares de realidad aumentada

Ahora, estamos en la parte más difícil pero más interesante del proyecto. Antes de comenzar, comprendamos nuestro enfoque del problema AR. Antes de buscar la solución, consideremos algunas suposiciones que hemos hecho sobre el problema para una solución más fácil.

Suposiciones:

  • La posición del marcador en la guitarra no cambia después del procedimiento de configuración. Esto nos permite almacenar los datos posicionales durante la configuración y usarlos para trazar más adelante. Si se cambia la posición, el usuario debe volver a realizar la configuración.
  • La guitarra está paralela a la cámara. Esto reduce el problema a uno 2D que es mucho más fácil de resolver y también reduce el cálculo. Calcular la tercera dimensión no agrega mucha funcionalidad en comparación con la configuración adicional y el cálculo necesarios para resolverlo. Podemos verificar esta condición asegurándonos de que todos los bordes del marcador tengan la misma longitud que el cuadrado del marcador.
  • Confiamos en que los usuarios seleccionen las cuatro esquinas del diapasón con precisión durante el proceso de configuración.

Ahora, para ubicar el diapasón, consideramos tres esquinas del marcador como un sistema de coordenadas, de modo que la esquina central se encuentra en el origen y las otras dos en los ejes x e y. Ahora que tenemos nuestros ejes, debemos preocuparnos por mapear puntos en estos ejes. En lugar de almacenar las longitudes reales de las coordenadas x e y, almacenamos su relación con el tamaño del marcador, lo que nos permite escalar las distancias según la distancia entre el marcador y la cámara.

Mapeo de puntos con marcador

Ahora que hemos mapeado los puntos con el marcador, necesitamos convertirlos con las coordenadas del lienzo. Podemos hacer esto fácilmente con algo de trigonometría básica.

x = x_{origin} + x_{len}\times m \times cos(\Theta ) + y_{len}\times n \times sin(\Theta )

y = y_{origin} + x_{len}\times m \times sin(\Theta ) + y_{len}\times n \times cos(\Theta )

Ahora podemos codificar lo mismo en JavaScript:

Javascript

export default function Map  (origin_x, origin_y , x_axis_x, 
                               x_axis_y, m_ratio, n_ratio) {
      
    // Offset prevent division by 0
    const theta =  Math.atan((x_axis_y - origin_y) / 
        (x_axis_x - origin_x + 0.000000001))
      
    const m = Math.sqrt(Math.pow((origin_x - x_axis_x), 2)
        + Math.pow((origin_y - x_axis_y), 2)) * m_ratio
          
    const n = Math.sqrt(Math.pow((origin_x - x_axis_x), 2) 
        + Math.pow((origin_y - x_axis_y), 2)) * n_ratio
      
    var x_val = origin_x + m * Math.cos(theta) + n * Math.sin(theta)
      
    if(origin_x - x_axis_x > 0){
        x_val = origin_x - m * Math.cos(theta) - n * Math.sin(theta)
    }
      
    var y_val = origin_y + m * Math.sin(theta) - n * Math.cos(theta)
      
    if(origin_x - x_axis_x > 0){
        y_val = origin_y - m * Math.sin(theta) + n * Math.cos(theta)
    }
  
    return {
        x : x_val,
        y : y_val
    }
}

Ahora que tenemos las cuatro esquinas que necesitamos para obtener la posición de las cuerdas en el tablero. Podemos hacerlo fácilmente dividiendo el rectángulo en partes iguales. Esto se puede hacer mediante las siguientes funciones:

Javascript

const SplitLine =  (pt1, pt2, n) => {
    var ans = []
    n--;
    for(var i = 0; i <= n; i ++){
        var x = (pt1.x * (n - i)  + pt2.x * i) / n
        var y = (pt1.y * (n - i)  + pt2.y * i) / n
        ans.push({
            x : x,
            y : y
        })
    }
    return ans
}
  
export default SplitLine

Ahora que tenemos estos puntos, todo lo que tenemos que hacer es unir puntos opuestos para dibujar una cuerda. Pero antes de comenzar a dibujar, debemos encontrar los puntos de intersección de dos strings para resaltar las strings que se presionarán. Podemos hacer eso usando la siguiente función usando geometría básica: 

Javascript

const FindIntersection = (A, B, C, D) => {
    var a1 = B.y - A.y;
    var b1 = A.x - B.x;
    var c1 = a1*(A.x) + b1*(A.y);
         
    var a2 = D.y - C.y;
    var b2 = C.x - D.x;
    var c2 = a2*(C.x)+ b2*(C.y);
         
    var determinant = a1*b2 - a2*b1;
         
    if (determinant === 0)
    {
        return {x: -1, y: -1}
    }
    else
    {
        var x = (b2 * c1 - b1 * c2) / determinant;
        var y = (a1 * c2 - a2 * c1) / determinant;
        return {x: x, y: y};
    }
}
  
export default FindIntersection

Ahora tenemos todo lo que necesitamos para dibujar en nuestro lienzo. Esto se puede hacer usando el siguiente código que toma el lienzo para dibujar, la transmisión de video, la posición actual del marcador y la lista de puntos para resaltar junto con algunas configuraciones como su parámetro:

Javascript

import FindIntersection from "../Utils/FindIntersection";
import SplitLine from "../Utils/SplitLine";
import Map from "../Utils/Map";
const AR = require("js-aruco").AR;
  
const draw = (canvas, video, ptr, list, textColor, strokeColor) => {
      
  // list is the list of points to be highlighted
  var ctx = canvas.getContext("2d", { alpha: false });
  
  canvas.width = video.video.videoWidth;
  
  canvas.height = video.video.videoHeight;
  
  ctx.translate(canvas.width, 0);
  ctx.scale(-1, 1);
  ctx.drawImage(video.video, 0, 0, canvas.width, canvas.height);
  ctx.scale(-1, 1);
  ctx.translate(-canvas.width, 0);
  ctx.lineWidth = 5;
  const detector = new AR.Detector();
  var markers = detector.detect(ctx.getImageData(0, 0, 1280, 720));
  
  if (markers.length > 0) {
    const corners = markers[0].corners;
    let pt1, pt2, pt3, pt4;
    pt1 = Map(
      corners[1].x,
      corners[1].y,
      corners[0].x,
      corners[0].y,
      ptr.m_ratio1,
      ptr.n_ratio1
    );
    pt2 = Map(
      corners[1].x,
      corners[1].y,
      corners[0].x,
      corners[0].y,
      ptr.m_ratio2,
      ptr.n_ratio2
    );
    pt3 = Map(
      corners[1].x,
      corners[1].y,
      corners[0].x,
      corners[0].y,
      ptr.m_ratio3,
      ptr.n_ratio3
    );
    pt4 = Map(
      corners[1].x,
      corners[1].y,
      corners[0].x,
      corners[0].y,
      ptr.m_ratio4,
      ptr.n_ratio4
    );
  
    ctx.strokeStyle = `#${strokeColor}`;
  
    ctx.beginPath();
    ctx.moveTo(pt1.x, pt1.y);
    ctx.lineTo(pt2.x, pt2.y);
    ctx.lineTo(pt3.x, pt3.y);
    ctx.lineTo(pt4.x, pt4.y);
    ctx.lineTo(pt1.x, pt1.y);
    ctx.stroke();
  
    var top = SplitLine(pt1, pt2, 6);
    var bottom = SplitLine(pt4, pt3, 6);
  
    var i;
  
    for (i = 0; i < top.length; i++) {
      ctx.beginPath();
      ctx.moveTo(top[i].x, top[i].y);
      ctx.lineTo(bottom[i].x, bottom[i].y);
      ctx.stroke();
    }
  
    var right = SplitLine(pt3, pt2, 6);
    var left = SplitLine(pt4, pt1, 6);
    right.reverse();
    left.reverse();
  
    for (i = 0; i < right.length; i++) {
      ctx.beginPath();
      ctx.moveTo(right[i].x, right[i].y);
      ctx.lineTo(left[i].x, left[i].y);
      ctx.stroke();
    }
  
    if (list) {
      for (var pos = 0; pos < list.length; pos++) {
        ctx.font = "30px Arial";
        ctx.fillStyle = `#${textColor}`;
        var res = FindIntersection(
          top[list[pos].x - 1],
          bottom[list[pos].x - 1],
          {
            x: (left[list[pos].y - 1].x + left[list[pos].y].x) / 2,
            y: (left[list[pos].y - 1].y + left[list[pos].y].y) / 2,
          },
          {
            x: (right[list[pos].y - 1].x + right[list[pos].y].x) / 2,
            y: (right[list[pos].y - 1].y + right[list[pos].y].y) / 2,
          }
        );
        ctx.fillText(`${list[pos].fing}`, res.x, res.y + 5);
      }
    } else {
      console.error("Tab Not Found");
    }
  }
  
  ctx.stroke();
};
  
export default draw;

Ahora que ya tenemos el código requerido para la parte AR del proyecto, todo lo que queda es obtener m_ration y n_ration del clic del usuario durante el procedimiento de configuración. Esto se puede hacer invirtiendo la función de mapeo.

Javascript

const Plot = (origin_x, origin_y , x_axis_x, 
                 x_axis_y, x_val, y_val) => {
  
    const theta =  Math.atan((x_axis_y - origin_y) 
            / (x_axis_x - origin_x + 0.000000001))
  
    let a , b;
    if(origin_x - x_axis_x > 0){
        a = origin_x - x_val
        b = origin_y - y_val        
    } else {
        a = x_val - origin_x
        b = y_val - origin_y
    }
  
    const sin = Math.sin(theta)
    const cos = Math.cos(theta)
  
    const m = a * cos - b * sin
    const n = a * sin - b * cos
  
    const len =Math.sqrt(
        (Math.pow((origin_x - x_axis_x), 2) + 
        Math.pow((origin_y - x_axis_y), 2)))
  
    return {
        m_ratio : m / len,
        n_ratio : n / len
    }
}
  
export default Plot

Paso 5: Cree los componentes AR: finalmente, estamos listos con todas las funciones auxiliares necesarias para construir los componentes AR. Ahora podemos integrarlos con las vistas llamando a la función dibujar.

1. Página de configuración del edificio: esta página permite al usuario seleccionar las cuatro esquinas del diapasón de su guitarra. Convertiremos estas coordenadas de cada esquina en m_ration y n_ration usando la función de trazado que construimos anteriormente. Una vez que tengamos las raciones para las cuatro esquinas, podemos consumir /api/v1/users/updateGuitar para guardar estas actualizaciones en nuestra base de datos. Una vez que haya terminado, la página debería verse así:

 

2. Almacenamiento de canciones: antes de crear la página de práctica, necesitamos una forma de procesar las canciones que obtenemos del backend y almacenarlas. Esto se puede hacer mediante las siguientes funciones:

Javascript

const Song = (s) => {
    const LYRICS = 'LYRICS'
    const TAB = 'TAB'
      
    const list = []
  
    const getNext = (pos) => {
        pos += 5;
        var tab = ""
        var n = s.length;
        while(pos < n){
            if(s.charAt(pos) === '<')
                break;
            tab += s.charAt(pos)
            pos++;
        }
        return {
            pos : pos + 5,
            tab : tab
        }
    }
  
    var text = ""
    for (var i = 0; i < s.length; i++) {
        var c = s.charAt(i);
        if(c === '<'){
            if(text !=="")
                list.push({
                    data: text,
                    type: LYRICS
                })
            var tab;
            var res = getNext(i);
            tab = res.tab
            i = res.pos
            text = ""
            list.push({
                type: TAB,
                data: tab
            })
        } else
            text += s.charAt(i)
    }
  
    if(text !== '')
        list.push({
            type:LYRICS,
            data:text
        })
    return list
  
}
  
export default Song

3. Página de construcción de canciones: ahora finalmente estamos listos para construir la página donde el usuario realmente practicará con la guitarra. En primer lugar, al cargar, cargaremos la canción de la consulta. También tendremos estado llamado pos, tab y text que será nuestra posición en la lista de canciones, tab actual y la letra a mostrar respectivamente. Después de cada intervalo de velocidad (por ejemplo, después de 2 segundos si la velocidad es 2) actualizaremos estas variables a las siguientes de la canción. Finalmente, llamaremos a la función dibujar dentro de requestAnimationFrame para actualizar el lienzo de manera eficiente. El código completo se puede encontrar a continuación:

Javascript

import React, { useState, useRef, useEffect } from "react";
import Webcam from "react-webcam";
import axios from "../../Utils/axios";
import "./PlaySong.css";
import { useSelector } from "react-redux";
import Song from "./../../Utils/Song";
import tabs from "./../../Utils/Tabs";
import Promt from "./../../components/Promt/Promt";
import Image from "./../../images/finger_coding.jpg";
import Draw from "../../hooks/Draw";
import RotateDevice from 
    "./../../animations/RotateDevice/RotateDevice";
  
export default function PlaySong(props) {
  const User = useSelector((state) => state.isLoggedIn).user;
  
  const webcamRef = useRef(null);
  const canvasRef = useRef(null);
  const points = useRef(null);
  const song = useRef(null);
  const pos = useRef(0);
  const tab = useRef(null);
  const getNextTimerId = useRef(null);
  const playButton = useRef(null);
  const strokeColor = useRef("Red");
  const textColor = useRef("Red");
  const speed = useRef(2);
  
  const [Text, setText] = useState("");
  const [IsPaused, setIsPaused] = useState(true);
  const [IsLanscape, setIsLanscape] = useState(
    window.matchMedia("(orientation: landscape)").matches
  );
  
  var supportsOrientationChange = "onorientationchange" in window,
    orientationEvent = supportsOrientationChange
      ? "orientationchange"
      : "resize";
  window.addEventListener(
    orientationEvent,
    function () {
      setIsLanscape(window.matchMedia(
          "(orientation: landscape)").matches);
    },
    false
  );
  
  function getNext() {
    console.log(pos.current);
    const LYRICS = "LYRICS";
    if (song.current) {
      if (pos.current < song.current.length) {
        if (song.current[pos.current].type === LYRICS) {
          setText(song.current[pos.current].data);
          pos.current = pos.current + 1;
        }
      }
      if (pos.current < song.current.length) {
        tab.current = song.current[pos.current].data;
        pos.current = pos.current + 1;
      }
      if (pos.current >= song.current.length) {
        if (playButton.current) 
            playButton.current.classList.toggle("pause");
        setIsPaused(true);
        pos.current = 0;
        setText("");
        tab.current = null;
        return;
      }
    }
  
    getNextTimerId.current = 
        setTimeout(getNext, speed.current * 1000);
  }
  
  const getAnimation = () => {
    const video = webcamRef.current;
    const canvas = canvasRef.current;
    const ptr = points.current;
    if (video && canvas) {
      var list = tabs.get(tab.current);
      Draw(canvas, video, ptr, list, 
          textColor.current, strokeColor.current);
      window.requestAnimationFrame(() => {
        return getAnimation();
      });
    }
  };
  
  useEffect(() => {
    var Query = new URLSearchParams(props.location.search);
    const getSongUrl = "/api/v1/songs/getFromTitle?title=" 
                + Query.get("title");
    axios.get(getSongUrl).then((res, err) => {
      if (err) alert(err);
      else {
        song.current = Song(res.data.data);
        console.log(song.current);
      }
    });
  
    const getAccountUrl = 
        "/api/v1/users/findbyemail?email=" + User.email;
    axios.get(getAccountUrl).then((res, err) => {
      if (err) alert(err);
      else {
        points.current = res.data.guitar;
        speed.current = res.data.speed;
        textColor.current = res.data.textColor
        strokeColor.current = res.data.strokeColor
      }
    });
  
    window.requestAnimationFrame(getAnimation);
  }, []);
  
  const videoConstraints = {
    width: 1280,
    height: 720,
    facingMode: "user",
  };
  
  const playPause = (e) => {
    e.target.classList.toggle("pause");
    if (IsPaused) getNext();
    else if (getNextTimerId.current) 
        clearTimeout(getNextTimerId.current);
  
    setIsPaused(!IsPaused);
  };
  
  return (
    <>
      {!IsLanscape ? (
        <RotateDevice />
      ) : (
        <div className="playsong__container">
          <canvas ref={canvasRef} className="playsong__canvas" />
          <div className="play__pause__button__container">
            <div
              class="play play__pause__button"
              onClick={playPause}
              ref={playButton}
            />
          </div>
          {Text === "" ? null : <p className="lyrics">{Text}</p>
}
          <div className="playsong__promt_area">
            <Promt
              text="Rock On!"
              description="Press fret with same number as below "
              img={Image}
            />
          </div>
          <Webcam
            className="playsong__cam"
            audio={false}
            ref={webcamRef}
            style={{ width: "0%", height: "0%" }}
            videoConstraints={videoConstraints}
          />
        </div>
      )}
    </>
  );
}

Al final, esta página debería verse así:

 

4. Configurar CI/CD: envíe el código a Github en su repositorio usando git. Cree su aplicación Heroku y agregue la clave API de Heroku en los secretos de GitHub. Ahora, vaya a su repositorio, vaya a acciones y configure un nuevo flujo de trabajo de la siguiente manera:

name: Build And Deploy

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    
    name : Build
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x, 15.x]
        # See supported Node.js release schedule 
        # at https://nodejs.org/en/about/releases/

    steps:
    - uses: actions/checkout@v2
    - name: Install Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}
        
    - name: Install NPM Package
      run: npm ci
      
    - name: Build project
      run: npm run build
      env:
        CI: false
  
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v2
      - name: Push to Heroku
        uses: akhileshns/heroku-deploy@v3.4.6
        with:
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: "<HEROKU_APP_NAME_HERE>"
          heroku_email: "<YOUR_HEROKU_EMAIL_HERE>"

      - name: Run a one-line script
        run: echo successfully run

Salida: ahora podemos probar la aplicación implementada y debería verse así:

Aplicaciones:

  • Guit.ar mejora la velocidad de aprendizaje en torno a un 40%. Ayuda a desarrollar la memoria muscular al brindar retroalimentación visual en vivo al usuario. Se ha demostrado que la retroalimentación visual (VFb) impulsa las etapas de adquisición y retención del aprendizaje motor asociado con el entrenamiento en una tarea de alcance como aprender a tocar la guitarra.
  • Guit.ar es un gran complemento para instituciones musicales y universidades. Esto es útil para el aprendizaje a distancia, especialmente durante el encierro.
  • Aprender guitarra implica aprender muchas cosas redundantes antes de tocar cualquier canción. Guit.ar ayuda a eliminar el desorden y comenzar a tocar música interesante desde el primer día.
  • Los usuarios pueden convertir su guitarra en un juego musical usando Guit.ar. Convierte una guitarra en un juego de arcade donde el objetivo es tocar la cuerda resaltada en un tiempo determinado.

Compañero de equipo: https://auth.geeksforgeeks.org/user/ayushpandya517/

Publicación traducida automáticamente

Artículo escrito por prathameshbhalekar13 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *