Personalized HTML Input Field for Digital Keyboard Scores

As a new JavaScript beginner, I have experimented with various approaches in an attempt to create a website that features a digital piano experience without the actual instrument. My goal is to develop a platform where pressing multiple keys together will result in them being grouped and displayed accordingly.

For instance, if I were to press "a" followed by "b," and simultaneously press "c" along with "d," the format would appear as: "a b [cd]". Despite numerous failed attempts and seeking guidance from ChatGPT, I found myself unable to achieve my goal. Not entirely unexpected

Answer №1

Hello, keyboard interactions

Have a look at this simple demonstration - press the Run button below to see it in action:

function App() {
  const [state, setState] = React.useState(new Set())
  React.useEffect(() => {
    function onkeydown(event) {
      setState(s => ISet.add(s, event.key))
    }
    function onkeyup(event) {
      setState(s => ISet.delete(s, event.key))
    }
    addEventListener("keyup", onkeyup)
    addEventListener("keydown", onkeydown)
    return () => {
      removeEventListener("keyup", onkeyup)
      removeEventListener("keydown", onkeydown)
    }
  }, [])
  return <div>
    {JSON.stringify(Array.from(state))}
  </div>
}

const ISet = {
  add(t, v) {
    return new Set(t).add(v)
  },
  delete(t, v) {
    const r = new Set(t)
    r.delete(v)
    return r
  }
}

ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<ol>
  <li>Click here to focus the demo's iframe</li>
  <li>Press any combination of keys on the keyboard</li>
</ol>
<div id="app"></div>

To enhance this further, you can integrate the state changes with various WebAudio functionalities such as -

  1. Playing piano samples using AudioBufferSourceNode
  2. Synthesizing audio through OscillatorNode

Hello, music composition

Building upon the previous code snippet, we create a virtual piano based on a QWERTY keyboard layout -

// W E T Y U O P ]
// A S D F G H J K L ; '

const piano = new Map(Array.from(
  "awsedftgyhujkolp;'",
  (key, semitone) => [key, semitone]
))

Next, we define a createOsc function that calculates the frequency for each semitone and plays it -

function createOsc(audioCtx, semitone) {
  const hz = 440 * Math.pow(2, semitone / 12)
  const osc = audioCtx.createOscillator()
  osc.type = "square"
  osc.frequency.setValueAtTime(hz, audioCtx.currentTime)
  osc.connect(audioCtx.destination)
  osc.start();
  return osc
}

Press Run below to execute the code and start composing your musical masterpiece:

function App() {
  const [state, setState] = React.useState(new Map())
  const audioCtx = React.useRef(new AudioContext())
  const tuning = React.useRef(-9)
  React.useEffect(() => {
    function onkeydown(e) {
      setState(prev => {
        const osc = prev.get(e.key)
        if (osc != null) return prev
        const semitone = piano.get(e.key)
        if (semitone != null) return IMap.add(
          prev,
          e.key,
          createOsc(audioCtx.current, semitone + tuning.current)
        )
        return prev
      })
    }
    function onkeyup(e) {
      setState(prev => {
        const osc = prev.get(e.key)
        if (osc != null) {
          osc.stop()
          return IMap.delete(prev, e.key)
        }
        return prev
      })
    }
    addEventListener("keyup", onkeyup)
    addEventListener("keydown", onkeydown)
    return () => {
      removeEventListener("keyup", onkeyup)
      removeEventListener("keydown", onkeydown)
    }
  }, [])
  return <div>
    {JSON.stringify(Array.from(state.keys()))}
  </div>
}
 
const piano = new Map(Array.from(
  "awsedftgyhujkolp;'",
  (key, semitone) => [key, semitone]
))

function createOsc(audioCtx, semitone) {
  const hz = 440 * Math.pow(2, semitone / 12)
  const osc = audioCtx.createOscillator()
  osc.type = "square"
  osc.frequency.setValueAtTime(hz, audioCtx.currentTime)
  osc.connect(audioCtx.destination)
  osc.start();
  return osc
}

const IMap = {
  add(t, k, v) {
    return new Map(t).set(k, v)
  },
  delete(t, k) {
    const r = new Map(t)
    r.delete(k)
    return r
  }
}

ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<ol>
  <li>Click here to focus the demo's iframe</li>
  <li>Play piano on your QWERTY keyboard</li>
</ol>
<pre>
  W E   T Y U   O P   ]
 A S D F G H J K L ; '
</pre>

<div id="app"></div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Safari Glitch in Bootstrap 4

For a simplified version, you can check it out here: https://jsfiddle.net/dkmsuhL3/ <html xmlns="http://www.w3.org/1999/xhtml"> <title>Testing Bootstrap Bug</title> <!-- Bootstrap V4 --> <link rel="stylesheet" href="https://m ...

Is there a way to set formArray values back to their default values without using form.reset(), which sets them to null?

I am a beginner when it comes to using Angular and I am currently dealing with a form that consists of the following fields. this.catform = new FormGroup({ name: new FormControl('', Validators.required,), source: new FormControl('' ...

What is the best way to create a responsive menu in code?

I am working on creating a responsive menu. Check out cuppcomputing For the desktop version, the whole panel will be displayed when it meets the max-width: 600px (tablet and mobile size). Initially, only the title is shown, and clicking on it will reveal ...

Sorting through mongoose information on the front end using EJS depending on a dropdown pick

Beginner embarking on my inaugural project here. I have developed 2 Mongoose schematics and models for managing categories and products. The products are nested within the corresponding categories model. Using Node and Express, I successfully sent all ca ...

Bootstrap sticky footer with adjustable height

Striving to achieve a sticky footer with a custom height on my website has turned out to be more challenging than I initially expected. Presented below is a snapshot of the current state of my footer: https://i.sstatic.net/SXaTA.png The issue arises whe ...

The xhr.upload event listener is triggered when the xhr.responseText is empty after loading

const request = new XMLHttpRequest(); request.open('put', url, false); request.upload.addEventListener('load', function(e) { alert(request.responseText); }, false); Why is the responseText property of the XMLHttpRequest object empt ...

Tips on saving an array as a variable

Can anyone help me figure out how to store a PHP variable into a JavaScript variable? var myArray; function retrieveData(id) { $.post( "main/updater.php", { id:id } ).done(function( data ) { myArray = data; }); } I&ap ...

Retrieve all div elements by their data attribute until reaching a certain condition

I am working on an innovative jquery accordion menu using divs instead of the typical li tags. Each div contains a unique data attribute (data-level) with values ranging from 1 to 4, and so on... To achieve the desired functionality, my "toggle" function ...

What are the steps to globalize the ng-bootstrap datepicker?

For my current project, I am utilizing the ng-bootstrap datePicker component. The demo for my simple datePicker widget can be found here. However, I am now seeking to internationalize it by setting it to use the Russian language. Any assistance with this ...

adding numerous items to an array using JavaScript

How can I add multiple objects to an array all at once? router.post(`/products`, upload.array("photos" , 10), async (req, res) => { console.log(res); try { let product = new Product(); req.files.forEach(file => { product.p ...

The time-out counter fails to detect the input field

After writing a method to reset the timeout on mouse click, keyup, and keypress events, I realized that it does not account for input fields. This means that when I am actively typing in a field, the timeout will still occur. Below is the code snippet: ...

Optimizing jQuery UI autocomplete choices through filtering

Currently utilizing the jqueryUI autocomplete feature on my website. Let's say I have the following entries in my database... apple ape abraham aardvark Upon typing "a" in the autocomplete widget, a list appears below the input field displaying the ...

Setting new query parameters while maintaining existing ones without deleting them in Next.js v13

"use client"; import { useCallback, useEffect, useState } from "react"; import Accordion from "../accordion/accordion"; import { useRouter, usePathname, useSearchParams, useParams, } from "next/navigation"; i ...

Retrieving text data from the JSON response received from the Aeris API

I am attempting to showcase the word "General" text on the HTML page using data from the API found under details.risk.type. The JSON Response Is As Follows... { "success": true, "error": null, "response": [ ...

Tips for customizing WTForm fields with unique CSS classes

I've recently put together a basic form snippet: class PostForm(FlaskForm): # for publishing a new blog post title = StringField("Title", validators=[DataRequired()]) submit = SubmitField('Post') The structure of my HT ...

Meteor Js Boostrap Menu Formatting Challenges

Greetings and good afternoon. I am currently working on developing a social networking application using the Meteor Js Stack, but I am encountering some issues with my navigation bar wrapping on mobile devices. Despite setting my dropdown to pull-right, it ...

"Encountered a 400 Bad Request error while attempting to retrieve data from Spring Boot using

I am attempting to remove an entity using a specified ID, but I encountered a 400 bad request error when trying to fetch from my API. async deleteFood(foodId) { const params = { 'id' : foodId } const rawResponse = await fe ...

Snap to the top of the table with merged columns using scroll feature

I am currently attempting to create a table with horizontal scrolling and snapping behavior for the yellow header columns that span multiple columns. These headers should snap to the end of the red column, while always maintaining alignment at the beginnin ...

Can a repetitive 'setTimeout' function invocation ultimately cause the JS Engine to crash?

Imagine a scenario where I require data from the server every 10 seconds. A function would be created to fetch the data using AJAX, and then setTimeout would be used to schedule the function to run again: function RetrieveData(){ $.ajax({ url: " ...

What are some ways to prevent having to constantly check for the existence of an object within another object when working

<template> <img :src="user.avatar_thumbnail"> <a :href="user.profile.link"/> </template> <script> export default { data: function () { user: {} }, ...