html Javascript Programing Musical Instruments

I made a random music notation generator app to memorize guitar frets.

Third in a series of apps to memorize guitar frets

This is the third application for learning guitar frets.

This time, the application is for practicing to hold the correct position of the frets as soon as you see the music sheet.

I know I will be told to stop making tools all the time and start practicing, but I did not learn my lesson and made them again.

Here are the tools I have created so far.

It's still better to be able to look at the music sheet and play it.

I have learned the sound of the fretboard much better than before.

But it is still far from being free and flexible.

I wanted to do a little more musical practice than simply mapping note names to fret positions.

It is a good practice to play a variety of songs.

However, in my case, I tend to remember songs by their front and back positions, not by their note names.

When I practice, my hands learn and the note names disappear from your mind.

When I looked through a music reading practice book once, the description of how to use it said, "Don't memorize the music.

This is quite difficult (laughs).

You have to practice to learn to play it, but when you practice, you remember it. It is a very vexing problem.

Then I thought, why not have the computer create more and more random musical scores?

The idea is that even if you remember one musical score through repetitive practice, you can create an infinite number of new ones.

Automatic generation of random sequence of sounds, with performance function

Although I say "musical notation," this time we will practice only the pitch of the notes. We will leave the length of notes as a separate issue for now.

The URL for the application is here.

App

The code is available on Github.

https://github.com/ryjkmr/reading-music-sheet-training-app

It is easy to use.

When you access the URL of the application, a musical score is immediately drawn.

Notes are black circles, so there is no length. There are no bar divisions. You can play it at any length you like.

The "redraw" button can be used to re-generate the image as many times as necessary.

The range of notes generated is E2 to E3 by default, covering the guitar's 6th string open to the 12th fret of the 1st string.

The slider allows you to change the range, so you can create a musical score for a specific string or a specific position.

The key signature setting is not supported, but # and b can be added as an accidental symbol. The presence and type of the accidentals can be toggled using the radio buttons.

An automatic performance function is also added.

The play and stop buttons can be used to play and stop. The note being played is highlighted. The Tone.js library is used for playback.

Perhaps because of the synthetic sound, the bass notes are difficult to hear, so I added an option to play them an octave higher.

The playback tempo can be changed in real time with the slider.

Code

The code is a web application made with HTML, CSS, and Javascript. It cannot be fully processed unless it is on a server.

The source code is available on Github. I will post the source code in the article, but please assume that Github is more up-to-date, as updating the code on the blog is more time-consuming.

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="style.css">
  <title>Guitar fret memory & music reading practice app</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
</head>

<body>
  <canvas id="musicSheet" width="2000px" height="300px"></canvas>
  <br>
  <!-- <canvas id="myCanvas" width="1000" height="500"></canvas> -->
  <div class="ML20">
    <p>
      <label class="sliderLabel" for="minValue">Low: <span class="slider-value" id="min-value-label"></span></label>
      <input type="range" id="minValue" name="minValue" min="0" max="30" value="0" />
      <br>
      <label class="sliderLabel" for="maxValue"></label>
      <input type="range" id="maxValue" name="maxValue" min="0" max="30" value="30" />
      <label class="sliderLabel" for="maxValue">High:<span class="slider-value" id="max-value-label"></span></label>
    </p>

    <p>
    <form>
      <label> <input type="radio" name="choice" value="plane" checked> なし </label>
      <label> <input type="radio" name="choice" value="sharp"> # </label>
      <label> <input type="radio" name="choice" value="flat"> b </label>

      <!-- <button type="button" onclick="getSharpFlatOption()">選択</button> -->
    </form>
    </p>
    <label for=""></label><button id="apply_range" class="ML20">redraw</button>
    <button id="start" class="ML20">start</button>
    <button id="stop">stop</button>
    <label for="octaveUp">Octave Up</label>
    <input type="checkbox" id="octaveUp" value="example">
    <label for="range-slider" class="sliderLabel ML20">BPM:<span id="slider-value">100</span></label>
    <input type="range" id="range-slider" name="range-slider" min="50" max="200" value="100">
  </div>

  <br>
  <br>
  <hr>
  <div class="ML20">
    <h2>ギターフレット記憶&譜読み練習アプリ <br>
      Guitar fret memory & music reading practice app</h2>
    <p>譜面を見てギターのフレットを押さえられるようになるための練習アプリです。<br>
      ランダムだけどなんとなく音楽的っぽくもある音の並びを生成します。</p>
    <p>This is an application for practicing to become able to hold down the frets of the guitar by looking at the music
      sheet. <br>Generates a sequence of random but somewhat musical sounds.
    </p>
    <ul>
      <li> 上部の二つのスライダーで生成する音程の範囲を設定します。</li>
      <li> ラジオボタンで臨時記号(#,b)の有無を設定します。</li>
      <li> redrawボタンで設定に合った譜面を生成します。</li>
      <li> playボタンで演奏します。stopボタンで演奏を停止します。</li>
      <li> 低音が聞き取りづらい時は「Octave Up」のチェックボタンをオンにして再生します。</li>
      <li> BPMスライダーで再生テンポを調整します。</li>
    </ul>
    <ul>
      <li> The top two sliders set the range of tones to be generated. </li>
      <li> The radio buttons allow you to set the presence or absence of accidentals (#,b). </li>
      <li> Press the redraw button to generate a musical score that matches your settings. </li>
      <li> Press "play" to play, "stop" to stop playing. </li>
      <li> If you have difficulty hearing the bass note, turn on the "Octave Up" check button then play. </li>
      <li> Adjust the playback tempo with the BPM slider. </li>


      Translated with www.DeepL.com/Translator (free version)
    </ul>
  </div>
  <script src="script.js"></script>
</body>

</html>
window.onload = function () {


  const canvas = document.getElementById('musicSheet');
  const ctx = canvas.getContext('2d');

  const applyRangeBtn = document.getElementById("apply_range");
  applyRangeBtn.addEventListener("click", drawMusicSheet, false);

  const LINE_SPACING = 16;
  const NOTE_WIDTH = 15;
  const NOTE_HEIGHT = 22;
  const NOTE_OFFSET_START = 80;
  let NOTE_OFFSET = NOTE_OFFSET_START;
  const STAFF_OFFSET = 50;
  const LINE_WIDTH = 2;

  let previousInvertedArea = { startX: null, width: null };//演奏中の音符を反転表示するためのメモリー

  //全部の音符の名前
  let noteOrder =
    ["E5", "Eb5", "D#5", "D5", "Db5", "C#5", "C5", "B4", "Bb4", "A#4", "A4", "Ab4", "G#4", "G4", "Gb4", "F#4",
      "F4", "E4", "Eb4", "D#4", "D4", "Db4", "C#4", "C4", "B3", "Bb3", "A#3", "A3", "Ab3", "G#3", "G3", "Gb3", "F#3",
      "F3", "E3", "Eb3", "D#3", "D3", "Db3", "C#3", "C3",
      "B2", "Bb2", "A#2", "A2", "Ab2", "G#2", "G2", "Gb2", "F#2", "F2", "E2"];
  //noteOrder内の音符の縦位置
  const noteIndexDic = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 8, 9, 9, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 18, 18, 18,
    19, 19, 19, 20, 20, 21, 21,];

  const notes = {
    plane: ["E5", "D5", "C5", "B4", "A4", "G4", "F4", "E4", "D4", "C4", "B3", "A3", "G3", "F3", "E3", "D3", "C3", "B2", "A2", "G2", "F2", "E2"]
    , sharp: ["E5", "D#5", "D5", "C#5", "C5", "B4", "A#4", "A4", "G#4", "G4", "F#4", "F4", "E4", "D#4", "D4", "C#4", "C4", "B3", "A#3", "A3", "G#3", "G3", "F#3", "F3", "E3", "D#3", "D3", "C#3", "C3", "B2", "A#2", "A2", "G#2", "G2", "F#2", "F2", "E2"]
    , flat: ["E5", "Eb5", "D5", "Db5", "C5", "B4", "Bb4", "A4", "Ab4", "G4", "Gb4", "F4", "E4", "Eb4", "D4", "Db4", "C4", "B3", "Bb3", "A3", "Ab3", "G3", "Gb3", "F3", "E3", "Eb3", "D3", "Db3", "C3", "B2", "Bb2", "A2", "Ab2", "G2", "Gb2", "F2", "E2"]
  }


  let sequence = [];//音程シーケンスを入れる配列
  let planeSharpOrFlat = getSharpFlatOption;
  // 生成する音の範囲設定
  const minValueSldr = document.getElementById("minValue");
  const maxValueSldr = document.getElementById("maxValue");

  minValueSldr.min = 0;
  minValueSldr.max = 21;
  minValueSldr.value = 0;

  maxValueSldr.min = 0;
  maxValueSldr.max = 21;
  maxValueSldr.value = 21;

  minValueSldr.addEventListener("input", updateMinSlider);
  maxValueSldr.addEventListener("input", updateMaxSlider);

  // 最小値ラベル要素を取得する
  let minValueLabel = document.getElementById('min-value-label');

  // 最大値ラベル要素を取得する
  let maxValueLabel = document.getElementById('max-value-label');

  // 初期表示時にラベルを更新する
  updateLabels();
  drawMusicSheet();


  const startBtn = document.getElementById("start");
  const stopBtn = document.getElementById("stop");

  startBtn.addEventListener("click", function () {
    startMetronome(200);
  });
  stopBtn.addEventListener("click", function () {
    stopMetronome();
  });

  let octaveUp = false;
  const octaveUpBtn = document.getElementById("octaveUp");
  octaveUpBtn.addEventListener("change", function () {
    if (octaveUpBtn.checked) {
      console.log("チェックされています");
      octaveUp = true;
    } else {
      console.log("チェックされていません");
      octaveUp = false;
    }
  });



  function drawMusicSheet() {
    stopMetronome();
    clearCanvas();
    draw5lines();
    drawTrebleClef();
    planeSharpOrFlat = getSharpFlatOption();//#、bの有無を取得
    const noteRange = convertRangeToNoteOrderIndex(notes[planeSharpOrFlat]);//#、bの有無に合わせたインデックスの最大値と最小値を取得
    console.log(noteRange.max, noteRange.min);
    sequence = generateSequence(40, noteRange.max, noteRange.min);//取得したレンジの範囲で音のシーケンスを生成
    console.log("sequence:" + sequence);

    NOTE_OFFSET = NOTE_OFFSET_START
    // 生成したシーケンスを順番に描画
    for (const seq of sequence) {
      drawNote(notes[planeSharpOrFlat][seq]);
    }



    NOTE_OFFSET = NOTE_OFFSET_START;//reset offset for redraw
  }

  function draw5lines() {
    ctx.strokeStyle = 'black';
    ctx.lineWidth = LINE_WIDTH;

    for (let i = -3; i <= 8; i++) {
      if (i >= 1 && i <= 5) {
        ctx.strokeStyle = 'black';
      } else {
        ctx.strokeStyle = 'white';
      };
      ctx.beginPath();
      ctx.moveTo(0, STAFF_OFFSET + (i * LINE_SPACING));
      ctx.lineTo(canvas.width, STAFF_OFFSET + (i * LINE_SPACING));
      ctx.stroke();
    }
  }

  function drawNote(note) {

    // const noteOrderAndIndex = [{ 'note': 'E5', 'pos': 0 }, { 'note': 'Eb5', 'pos': 0 }, { 'note': 'D#5', 'pos': 1 }, { 'note': 'D5', 'pos': 1 }, { 'note': 'Db5', 'pos': 1 }, { 'note': 'C#5', 'pos': 2 }, { 'note': 'C5', 'pos': 2 }, { 'note': 'B4', 'pos': 3 }, { 'note': 'Bb4', 'pos': 3 }, { 'note': 'A#4', 'pos': 4 }, { 'note': 'A4', 'pos': 4 }, { 'note': 'Ab4', 'pos': 4 }, { 'note': 'G#4', 'pos': 5 }, { 'note': 'G4', 'pos': 5 }, { 'note': 'Gb4', 'pos': 5 }, { 'note': 'F#4', 'pos': 6 }, { 'note': 'F4', 'pos': 6 }, { 'note': 'E4', 'pos': 7 }, { 'note': 'Eb4', 'pos': 7 }, { 'note': 'D#4', 'pos': 8 }, { 'note': 'D4', 'pos': 8 }, { 'note': 'Db4', 'pos': 8 }, { 'note': 'C#4', 'pos': 9 }, { 'note': 'C4', 'pos': 9 }, { 'note': 'B3', 'pos': 10 }, { 'note': 'Bb3', 'pos': 10 }, { 'note': 'A#3', 'pos': 11 }, { 'note': 'A3', 'pos': 11 }, { 'note': 'Ab3', 'pos': 11 }, { 'note': 'G#3', 'pos': 12 }, { 'note': 'G3', 'pos': 12 }, { 'note': 'Gb3', 'pos': 12 }, { 'note': 'F#3', 'pos': 13 }, { 'note': 'F3', 'pos': 13 }, { 'note': 'E3', 'pos': 14 }, { 'note': 'Eb3', 'pos': 14 }, { 'note': 'D#3', 'pos': 15 }, { 'note': 'D3', 'pos': 15 }, { 'note': 'Db3', 'pos': 15 }, { 'note': 'C#3', 'pos': 16 }, { 'note': 'C3', 'pos': 16 }, { 'note': 'B2', 'pos': 17 }, { 'note': 'Bb2', 'pos': 17 }, { 'note': 'A#2', 'pos': 18 }, { 'note': 'A2', 'pos': 18 }, { 'note': 'Ab2', 'pos': 18 }, { 'note': 'G#2', 'pos': 19 }, { 'note': 'G2', 'pos': 19 }, { 'note': 'Gb2', 'pos': 19 }, { 'note': 'F#2', 'pos': 20 }, { 'note': 'F2', 'pos': 20 }, { 'note': 'E2', 'pos': 21 }];

    const noteIndex = noteIndexDic[noteOrder.indexOf(note)];
    // const noteIndex = noteDic[note];

    let x = NOTE_OFFSET;
    const y = STAFF_OFFSET + (noteIndex - 4) * LINE_SPACING / 2;

    if (note.includes('#')) {
      x += 10;
      NOTE_OFFSET += 10;
      drawSharp(x - NOTE_HEIGHT, y);
    }
    if (note.includes('b')) {
      x += 10;
      NOTE_OFFSET += 10;
      drawFlat(x - NOTE_HEIGHT, y);
    }


    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(70 * Math.PI / 180);
    ctx.fillStyle = 'black';
    ctx.beginPath();
    ctx.ellipse(0, 0, NOTE_WIDTH / 2, NOTE_HEIGHT / 2, 0, 0, 2 * Math.PI);
    ctx.fill();
    ctx.restore();

    //上側の補助線
    if (noteIndex <= 4) {
      ctx.strokeStyle = 'black';
      for (let i = STAFF_OFFSET; i >= y; i -= LINE_SPACING) {
        ctx.beginPath();
        ctx.moveTo(x - NOTE_WIDTH, i);
        ctx.lineTo(x + NOTE_WIDTH, i);
        ctx.stroke();
        // console.log(NOTE_OFFSET);
      }
    }

    //下側の補助線
    if (noteIndex >= 16) {
      ctx.strokeStyle = 'black';
      for (let i = STAFF_OFFSET + LINE_SPACING * 5; i <= y; i += LINE_SPACING) {
        ctx.beginPath();
        ctx.moveTo(x - NOTE_WIDTH, i);
        ctx.lineTo(x + NOTE_WIDTH, i);
        ctx.stroke();
        // console.log(NOTE_OFFSET);
      }
    }
    NOTE_OFFSET += NOTE_WIDTH * 2 + 10;


  }



  function drawTrebleClef(x, y) {

    // PNG画像の読み込み
    const pngImage = new Image();
    pngImage.src = "Treble_Clef.png";

    // 画像の読み込みが完了したら、Canvasに描画
    pngImage.onload = function () {
      // 画像の描画
      ctx.drawImage(pngImage, 5, 55, 40, 100);
    }
  }


  function drawSharp(x, y) {

    // PNG画像の読み込み
    const pngImage = new Image();
    pngImage.src = "sharp.png";

    // 画像の読み込みが完了したら、Canvasに描画
    pngImage.onload = function () {
      // 画像の描画
      ctx.drawImage(pngImage, x - 5, y - 14, 15, 30);
    }
  }
  function drawFlat(x, y) {

    // PNG画像の読み込み
    const pngImage = new Image();
    pngImage.src = "flat.png";

    // 画像の読み込みが完了したら、Canvasに描画
    pngImage.onload = function () {
      // 画像の描画
      ctx.drawImage(pngImage, x - 5, y - 20, 15, 30);
    }

  }

  function generateSequence(length = 40, minValue = 0, maxValue = 21) {
    // 初期化
    let sequence = [];
    let currentNum = Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue;
    let isAscending = Math.random() >= 0.5;
    let consecutiveChanges = 0;

    // 数列を生成
    while (sequence.length < length) {
      sequence.push(currentNum);
      let changeDirection = false;
      if (consecutiveChanges >= 3) {
        // 3回以上連続して方向が変わった場合は、方向を強制的に反転させる
        changeDirection = true;
        consecutiveChanges = 0;
      } else {
        // 2種類のランダム関数を使用して、数列の方向を決定する
        let randomNum1 = Math.random();
        let randomNum2 = Math.random();
        if (isAscending && randomNum1 < randomNum2) {
          changeDirection = true;
        } else if (!isAscending && randomNum1 > randomNum2) {
          changeDirection = true;
        }
        // 最大値や最小値が連続する場合は、方向をランダムに変更する
        if (currentNum === minValue && !isAscending) {
          changeDirection = true;
        } else if (currentNum === maxValue && isAscending) {
          changeDirection = true;
        }
      }
      if (changeDirection) {
        isAscending = !isAscending;
        consecutiveChanges++;
      } else {
        consecutiveChanges = 0;
      }
      let nextNum = currentNum;
      let tryCount = 0;
      while (nextNum === currentNum && tryCount < 10) {
        // 同じ数字が続かないように、ランダムに数値を生成する
        if (isAscending) {
          nextNum = currentNum + Math.floor(Math.random() * 5) + 1;
        } else {
          nextNum = currentNum - Math.floor(Math.random() * 3) - 1;
        }
        // 最小値から最大値の範囲に収める
        nextNum = Math.max(minValue, Math.min(maxValue, nextNum));
        tryCount++;
      }
      currentNum = nextNum;
    }
    return sequence;
  }

  function clearCanvas() {
    ctx.fillStyle = 'white'; // fillStyleプロパティに白色を設定
    ctx.fillRect(0, 0, canvas.width, canvas.height); // 全体を白く塗りつぶす
  }




  function updateMinSlider() {
    let minValue = parseInt(minValueSldr.value);
    let maxValue = parseInt(maxValueSldr.value);

    if (minValue >= maxValue - 1) {
      minValueSldr.value = maxValue - 1;
      minValue = maxValue - 1;
    }
    updateLabels();

  }

  function updateMaxSlider() {
    let minValue = parseInt(minValueSldr.value);
    let maxValue = parseInt(maxValueSldr.value);

    if (maxValue <= minValue + 1) {
      maxValueSldr.value = minValue + 1;
      maxValue = minValue + 1;
    }
    updateLabels();

  }

  function convertRangeToNoteOrderIndex(notes) {

    const minIndex = notes.findIndex(item => item === minValueLabel.textContent);
    const maxIndex = notes.findIndex(item => item === maxValueLabel.textContent);
    // console.log(minIndex, notes[minIndex], maxIndex, notes[maxIndex]);
    return { min: minIndex, max: maxIndex };
  }


  function updateLabels() {
    const noteOrderForRange = ["E5", "D5", "C5", "B4", "A4", "G4", "F4", "E4", "D4", "C4", "B3", "A3", "G3", "F3", "E3", "D3", "C3", "B2", "A2", "G2", "F2", "E2"];

    // 最小値ラベルを更新する
    minValueLabel.textContent = noteOrderForRange[noteOrderForRange.length - 1 - minValueSldr.value];

    // 最大値ラベルを更新する
    maxValueLabel.textContent = noteOrderForRange[noteOrderForRange.length - 1 - maxValueSldr.value];
  }

  function getSharpFlatOption() {
    const radioButtons = document.getElementsByName('choice');
    let selectedValue;

    for (const radioButton of radioButtons) {
      if (radioButton.checked) {
        selectedValue = radioButton.value;
        console.log(`選択された値は ${selectedValue} です。`);
        return selectedValue;
        break;
      }
    }
  }


  const synth = new Tone.Synth({
    oscillator: {
      type: "sine"
    },
    envelope: {
      attack: 0.0001,
      decay: 0.5,
      sustain: 0.5,
      release: 0.1
    }
  }).toDestination();

  let loop = null;

  async function startMetronome(
    tempo = 100,
    // notes = ["C6", "E4"],
    oscillatorType = "triangle"
  ) {
    let notesToPlay = [];
    for (const seq of sequence) {
      notesToPlay.push(notes[planeSharpOrFlat][seq]);
    }

    if (octaveUp) notesToPlay = NotesOctaveUp(notesToPlay);

    tempo = Math.max(50, Math.min(300, tempo));
    let noteIndex = 0;

    if (loop) {
      loop.stop();
      loop.dispose();
    }

    console.log("notesToPlay:" + notesToPlay);
    NOTE_OFFSET = NOTE_OFFSET_START
    loop = new Tone.Loop((time) => {
      const noteToPlay = notesToPlay[noteIndex];
      // synth.triggerAttackRelease("C4", "16n", time);
      if (noteToPlay.includes('#')) {
        // x += 10;
        NOTE_OFFSET += 10;
      }
      if (noteToPlay.includes('b')) {
        // x += 10;
        NOTE_OFFSET += 10;
      }

      invertRectColors(NOTE_OFFSET, 20);
      synth.triggerAttackRelease(noteToPlay, "8n", time);
      NOTE_OFFSET += NOTE_WIDTH * 2 + 10;
      noteIndex = (noteIndex + 1) % sequence.length;
      if (noteIndex === 0) NOTE_OFFSET = NOTE_OFFSET_START;
    }, "4n");

    // console.log(tempo);
    Tone.Transport.bpm.value = parseInt(tempoSlider.value);
    loop.start(0);
    Tone.Transport.start();
  }

  function stopMetronome() {
    Tone.Transport.stop();
    if (previousInvertedArea.startX !== null) {
      invertRectColors(0, 0);// 反転の残りを除去
    }
  }

  function startMetronome1(tempo, oscillatorType = "sine") {
    if (tempo < 50 || tempo > 300) {
      console.error("Tempo must be between 50 and 300");
      return;
    }

    if (loop) {
      loop.stop();
      loop.dispose();
    }

    loop = new Tone.Loop((time) => {
      synth.triggerAttackRelease("C5", "16n", time);
    }, "4n");

    Tone.Transport.bpm.value = tempo;
    loop.start(0);
    Tone.Transport.start();
  }


  function invertRectColors(x, width) {
    // if (!canvas || !canvas.getContext) {
    //   console.log("no canvas");
    //   return;
    // }
    console.log(x, width);
    // const ctx = canvas.getContext("2d");
    const canvasHeight = canvas.height;
    const halfWidth = width / 2;
    const startX = x - halfWidth;

    // 前回反転させた範囲の色を元に戻す
    if (
      previousInvertedArea.startX !== null &&
      previousInvertedArea.width !== null
    ) {
      const prevImageData = ctx.getImageData(
        previousInvertedArea.startX,
        0,
        previousInvertedArea.width,
        canvasHeight
      );
      for (let i = 0; i < prevImageData.data.length; i += 4) {
        prevImageData.data[i] = 255 - prevImageData.data[i]; // R
        prevImageData.data[i + 1] = 255 - prevImageData.data[i + 1]; // G
        prevImageData.data[i + 2] = 255 - prevImageData.data[i + 2]; // B
      }
      ctx.putImageData(prevImageData, previousInvertedArea.startX, 0);
    }

    // 新たな範囲の色を反転させる
    if (width !== 0) {
      const newImageData = ctx.getImageData(startX, 0, width, canvasHeight);
      for (let i = 0; i < newImageData.data.length; i += 4) {
        newImageData.data[i] = 255 - newImageData.data[i]; // R
        newImageData.data[i + 1] = 255 - newImageData.data[i + 1]; // G
        newImageData.data[i + 2] = 255 - newImageData.data[i + 2]; // B
      }
      ctx.putImageData(newImageData, startX, 0);

      // 新たな範囲を記録
      previousInvertedArea.startX = startX;
      previousInvertedArea.width = width;
    } else {
      // widthが0の場合、前回反転させた範囲の記録をリセット
      previousInvertedArea.startX = null;
      previousInvertedArea.width = null;
    }
  }

  function NotesOctaveUp(arr, offset = 1) {
    return arr.map(str => {
      return str.replace(/(d+)/g, (match, num) => {
        return parseInt(num) + offset;
      });
    });
  }


  const tempoSlider = document.getElementById('range-slider');
  const tempoValue = document.getElementById('slider-value');

  tempoValue.innerText = tempoSlider.value;

  tempoSlider.addEventListener('input', function () {
    tempoValue.innerText = tempoSlider.value;
    Tone.Transport.bpm.value = parseInt(tempoSlider.value);

  });

}
canvas {
  border: 1px solid black;
}

.slider-value {
  font-size: larger;
  font-weight: bolder;
}

#minValue,
#maxValue {
  width: 200px;
}

label.sliderLabel {
  display: inline-block;
  width: 75px;
}

.ML20 {
  margin-left: 20px;
}

button {
  font-size: large;

}

ChatGPT was very helpful in coding

ChatGPT played a major role in this coding.

While I can't leave the coding completely to them, they are very useful for writing processes that I know how to do but would be too much trouble to actually write the code. Also, even for simple code, it is faster to ask ChatGPT than to Google how to write it or what options are available.

Here, we first write a specification-like document and communicate it to ChatGPT.If there is a problem with the code indicated by ChatGPT, I submit a request for correction. Once some of the code was completed, I wrote the rest by myself.

The following situations were used.

Have them do the tedious routine of drawing to Canvas.

Drawing staves and notes on canvas.

Drawing the canvas is very tedious if done by hand, but if you ask ChatGPT to do it for you, they will do it in no time.

ChatGPT also understands some musical terms and concepts such as "notes" and "staves". This helped me to communicate what I wanted to do. We are not talking about whether or not ChatGPT "really" understands. It is important that the conversation is understandable.

Perhaps it was easier to request than a human programmer who knew nothing about music.

Powerful for generating random sounds

Random sound generation is where ChatGPT was most effective.

What we are doing is generating an array with a fixed range of random numbers.

However, I try to make sure that it is not totally random, but that there is some continuity and a moderate amount of up and down direction.

I started by having them create a simple random generation program, and then added orders such as, "I want it to be continuous," "I want it to be directional," and so on.

I honestly do not understand what kind of processing is being done in this part of the process, as it is almost entirely left to ChatGPT.

Well, if there is a bug, it is not life-threatening, so I don't need to know.

Automatic performance using Tone.js

I had ChatGPT create a template for automatic performance using Tone.js, and I have been extending it.

I had never understood how to use synthesizer instruments, but by having chatGPT create a simple sample code, I was able to understand it.

-html, Javascript, Programing, Musical Instruments