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;
});