Code viewer for World: Using ChatGPT to write a M...
const openaiUrl = "https://api.openai.com/v1/chat/completions";
const theModel = "gpt-3.5-turbo";
let apiKey = "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";
let userPrompt = "Enter your mood:"; // Update initial prompt
let selectedMood = ""; // Declare a variable to store the selected mood


// Set up the body styles
$('body').css({
    "margin": "20px",
    "padding": "20px",
    "display": "flex",
    "flex-direction": "column",
    "align-items": "center",
    "height": "100vh",
    "font-family": "Noto Sans Mono",
});

// Set the font family for the select and option elements
$("select, option").css({
    "font-family": "Noto Sans Mono",
});

// Define the URLs for the fonts
const fontUrls = [
    "https://fonts.googleapis.com",
    "https://fonts.gstatic.com",
    "https://fonts.googleapis.com/css2?family=Gloock&family=Lato:ital,wght@1,900&family=Noto+Sans+Mono&family=Roboto:ital,wght@0,400;0,700;1,100;1,300&display=swap"
];

// For each URL, create a new link element, set its rel and href attributes, and append it to the head of the document
fontUrls.forEach(url => {
    let link = document.createElement("link");
    link.rel = "stylesheet";
    link.href = url;
    document.head.appendChild(link);
});


// UI Elements
document.write(`


<div style="text-align: center;">
    <h1>Using ChatGPT to write a melody based on your mood</h1>

    <script>
        // Function to change the color of the element on mouseover
        function changeColor(element, color) {
            element.style.backgroundColor = color;
        }

        // Function to reset the color of the element on mouseout
        function resetColor(element) {
            if (element.innerText !== selectedMood) {
                element.style.backgroundColor = "";
            }
        }

        // Function to select a mood
        function selectMood(mood) {
            selectedMood = mood;
            const moodCells = document.querySelectorAll("#moodTable td");
            moodCells.forEach(cell => {
                if (cell.innerText === mood) {
                    cell.style.backgroundColor = "lightgray";
                } else {
                    cell.style.backgroundColor = "";
                }
            });
        }
    </script>

    <div style="width:60vw; margin-bottom: 20px;">
        <h3> Select your mood </h3>
        <table id="moodTable" style="margin: 0 auto; border-collapse: separate; border-spacing: 0 10px;">
            <tr>
                <td onclick="selectMood('happy')" onmouseover="changeColor(this, 'yellow')" onmouseout="resetColor(this)" style="cursor: pointer; border-top: 1px solid black; padding: 10px;">Happy</td>
                <td onclick="selectMood('sad')" onmouseover="changeColor(this, 'blue')" onmouseout="resetColor(this)" style="cursor: pointer; font-family: Noto Sans Mono; border-top: 1px solid black; padding: 10px;">Sad</td>
                <td onclick="selectMood('calm')" onmouseover="changeColor(this, 'green')" onmouseout="resetColor(this)" style="cursor: pointer; font-family: Helvetica; border-top: 1px solid black; padding: 10px;">Calm</td>
            </tr>
            <tr>
                <td onclick="selectMood('angry')" onmouseover="changeColor(this, 'red')" onmouseout="resetColor(this)" style="cursor: pointer; font-family: Helvetica; border-top: 1px solid black; padding: 10px;">Angry</td>
                <td onclick="selectMood('joyful')" onmouseover="changeColor(this, 'orange')" onmouseout="resetColor(this)" style="cursor: pointer; font-family: Helvetica; border-top: 1px solid black; padding: 10px;">Joyful</td>
                <td onclick="selectMood('romantic')" onmouseover="changeColor(this, 'pink')" onmouseout="resetColor(this)" style="cursor: pointer; font-family: Helvetica; border-top: 1px solid black; padding: 10px;">Romantic</td>
            </tr>
            <tr>
                <td onclick="selectMood('energised')" onmouseover="changeColor(this, 'purple')" onmouseout="resetColor(this)" style="cursor: pointer; font-family: Helvetica; border-top: 1px solid black; padding: 10px;">Energised</td>
                <td onclick="selectMood('hurt')" onmouseover="changeColor(this, 'gray')" onmouseout="resetColor(this)" style="cursor: pointer; font-family: Helvetica; border-top: 1px solid black; padding: 10px;">Hurt</td>
                <td onclick="selectMood('loved')" onmouseover="changeColor(this, 'brown')" onmouseout="resetColor(this)" style="cursor: pointer; font-family: Helvetica; border-top: 1px solid black; padding: 10px;">Loved</td>
            </tr>
            <!-- Add more mood options as needed -->
        </table>
    </div>

    <div style="width:60vw; margin-bottom: 20px;">
        <h3> Tempo </h3>
        <input type="range" min="40" max="240" value="120" id="tempoSlider" oninput="updateTempo(this.value)">
        <span id="tempoValue">120</span>
    </div>

    <script>
        // Function to change the color of the element on mouseover
        function changeColor(element, color) {
            element.style.backgroundColor = color;
        }

        // Function to reset the color of the element on mouseout
        function resetColor(element) {
            if (element.innerText !== selectedMood) {
                element.style.backgroundColor = "";
            }
        }

        // Function to update the tempo value
        function updateTempo(value) {
            document.getElementById("tempoValue").textContent = value;
        }
    </script>
    <!-- Hidden API key input and button -->
    <div id="enterKey" style="display: none;">
        <input style='width:0vw;' maxlength='0' name="apiKey" id="apiKey" value=''>
        <button onclick='setApiKey();' class="ab-normbutton">Set API key</button>
    </div>

    <!-- Hide the mood prompt input -->
    <div style="width:60v; margin-bottom: 20px; display: none;">
        <h3> Enter your mood </h3>
        <input style="width:50vw;" id="userPrompt" value="${userPrompt}">
    </div>

    <!-- Keep the send button visible -->
    <div style="width:60vw; margin-bottom: 20px;">
        <button onclick="sendChat();" class="ab-normbutton">Send</button>
    </div>

    <div style="width:60vw; margin-bottom: 20px;">
        <h3> GPT replies </h3>
        <div id="melodyText" style="font-size: 24px;">
                <div id="assistantResponse"></div>
</div>

    </div>

    <div style="display: flex; justify-content: center; align-items: center;">
        <button id="play-btn">Play melody</button>
    </div>
</div>

<pre></pre>
`);

// Set API Key
function setApiKey() {
    apiKey = jQuery("input#apiKey").val().trim();
    $("#enterKey").html("<b>API key has been set.</b>");
}

// Enter key will also send chat
document.getElementById('userPrompt').onkeydown = function(event) {
    if (event.keyCode === 13) sendChat();
};


// Function to handle mood selection
function selectMood(mood) {
    selectedMood = mood;
    console.log(`Selected Mood: ${selectedMood}`);
}

// Send Chat Function
function sendChat() {
    const userPrompt = `Mood: ${selectedMood}`;

    const requestData = {
        "model": theModel,
        "temperature": 0.7,
        "messages": [
            {"role": "user", "content": userPrompt},
            {"role": "assistant", "content": "Generating chords based on mood Only output the chords, and limit generate only 4 chords, and include major minor dimished augmented and suspended chords too"}
        ],
        "n": 1
    };

    // Convert data to JSON string
    const dataString = JSON.stringify(requestData);

    // Set up HTTP headers
    $.ajaxSetup({
        headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + apiKey
        }
    });

    // Make API request
    $.ajax({
        type: "POST",
        url: openaiUrl,
        data: dataString,
        dataType: "json",
        success: function(data, status) {
            handleSuccess(data, status);
        },
        error: function() {
            handleApiError();
        }
    });
}


const possibleChords = [
    "C", "C#", "Db", "D", "D#", "Eb", "E", "Fb", "E#", "F", "F#", "Gb",
    "G", "G#", "Ab", "A", "A#", "Bb", "B", "Cb", "B#", 
    "Cmaj", "C#maj", "Dbmaj", "Dmaj", "D#maj", "Ebmaj", "Emaj", "Fbmaj", "E#maj", "Fmaj", "F#maj", "Gbmaj",
    "Gmaj", "G#maj", "Abmaj", "Amaj", "A#maj", "Bbmaj", "Bmaj", "Cbmaj", "B#maj",
    "Cmin", "C#min", "Dbmin", "Dmin", "D#min", "Ebmin", "Emin", "Fbmin", "E#min", "Fmin", "F#min", "Gbmin",
    "Gmin", "G#min", "Abmin", "Amin", "A#min", "Bbmin", "Bmin", "Cbmin", "B#min",
    "Csus", "C#sus", "Dbsus", "Dsus", "D#sus", "Ebsus", "Esus", "Fbsus", "E#sus", "Fsus", "F#sus", "Gbsus",
    "Gsus", "G#sus", "Absus", "Asus", "A#sus", "Bbsus", "Bsus", "Cbsus", "B#sus",
    "Cdim", "C#dim", "Dbdim", "Ddim", "D#dim", "Ebdim", "Edim", "Fbdim", "E#dim", "Fdim", "F#dim", "Gbdim",
    "Gdim", "G#dim", "Abdim", "Adim", "A#dim", "Bbdim", "Bdim", "Cbdim", "B#dim",
    "Caug", "C#aug", "Dbaug", "Daug", "D#aug", "Ebaug", "Eaug", "Fbaug", "E#aug", "Faug", "F#aug", "Gbaug",
    "Gaug", "G#aug", "Abaug", "Aaug", "A#aug", "Bbaug", "Baug", "Cbaug", "B#aug"
];



// Extract chords from the assistant's answer that match the predefined set
function extractMatchingChords(answer, possibleChords) {
    const extractedChords = [];
    
    // Extract potential chords using a regular expression
    const chordRegex = /\b([A-G](?:#|b)?(?:maj|min|dim|aug|sus)?)\b/gi;
    const matches = answer.match(chordRegex);

    // Check for matches against the predefined set of possible chords
    if (matches) {
        for (const chord of matches) {
            if (possibleChords.includes(chord.toUpperCase())) {
                extractedChords.push(chord);
            }
        }
    }

    return extractedChords;
}


// Handle API Error
function handleApiError() {
    const errorMessage = apiKey === "" ? "Enter API key to be able to chat." : "Unknown error.";
    $("#assistantResponse").html(`<font color="red"><b>${errorMessage}</b></font>`);
}

// Handle API Success
function handleSuccess(data, status) {
    const assistantAnswer = data["choices"][0].message.content;
    const matchedChords = extractMatchingChords(assistantAnswer, possibleChords);
    $("#assistantResponse").html(matchedChords.join(' '));

    // Get the current tempo value from the slider
    const tempoSlider = document.getElementById('tempoSlider');
    const tempo = parseInt(tempoSlider.value, 10);

    // Play extracted chords sequentially with the specified tempo
    playChordsSequentially(matchedChords, tempo);
}


$.getScript("https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js", function () {
    console.log("Tone.js loaded successfully!");

    // Initialize a Tone.js Synthesizer
    const synth = new Tone.Synth().toDestination();
    let tempo = 120; // Initialize tempo to 120 BPM

    // Function to play chords sequentially with a specified tempo
    function playChordsSequentially(chords) {
        let index = 0;
        const melodyText = document.getElementById('melodyText');
        const assistantResponse = document.getElementById('assistantResponse');
        const notes = assistantResponse.innerText.split(' ');
    
        // Function to play the next chord
        const playNextChord = () => {
            if (index < chords.length) {
                // Make sure Tone.js is started
                Tone.start();
    
                // Reset the color of all notes to black
                assistantResponse.innerHTML = notes.map(note => `<span style="color: black;">${note}</span>`).join(' ');
    
                // Set the text color of the currently played note to blue
                const currentNote = notes[index];
                const noteIndex = assistantResponse.innerText.indexOf(currentNote);
                const prefix = assistantResponse.innerText.substring(0, noteIndex);
                const suffix = assistantResponse.innerText.substring(noteIndex + currentNote.length);
                assistantResponse.innerHTML = `${prefix}<span style="color: blue;">${currentNote}</span>${suffix}`;
    
                // Play the chord
                synth.triggerAttackRelease(chords[index] + '4', '1n');
    
                // Move to the next chord after a calculated delay
                index++;
                setTimeout(() => {
                    playNextChord();
                }, (60 / tempo) * 1000); // Adjust delay based on tempo
            }
        };
    
        // Start the transport and play the chords
        Tone.Transport.start();
        isMelodyPlaying = true;
        playNextChord();
    }

    // Add event listener to the play button
    document.getElementById('play-btn').addEventListener('click', function () {
        const assistantResponse = document.getElementById('assistantResponse').textContent;
        const matchedChords = extractMatchingChords(assistantResponse, possibleChords);
        playChordsSequentially(matchedChords);
    });

    // Add event listener to the tempo slider
    const tempoSlider = document.getElementById('tempoSlider');
    const tempoLabel = document.getElementById('tempoValue');
    
    // Variable to track if the melody is playing
    let isMelodyPlaying = false;
    
    tempoSlider.addEventListener('input', function () {
        tempo = parseInt(tempoSlider.value); // Update the tempo variable
        tempoLabel.textContent = tempo; // Update the tempo label
        Tone.Transport.bpm.value = tempo; // Update the tempo in Tone.Transport
    
        // If the melody is playing, dynamically adjust the BPM
        if (isMelodyPlaying) {
            // Stop and restart the Transport to apply the new BPM
            Tone.Transport.stop();
            Tone.Transport.start();
        }
    });
    
    // Initialize tempo based on the initial value of the tempo slider
    tempo = parseInt(tempoSlider.value);
    tempoLabel.textContent = tempo;
    Tone.Transport.bpm.value = tempo;

});