生成AI Claude 3.5 sonnetで作曲させ、再生もしたい!
前書き
1つ前の記事で、リアルタイムボイスチェンジャーのwebアプリをClaude 3.5 sonnetで作成させ、期待通りのものを生成することができました。
Claude 3.5 sonnetはテキストのやり取りのみですが、javascriptモジュールを指定してwebのコードを生成させることで、単なるテキストを超えた成果物を生成させることができます。
たとえば、プレゼン用のjavascriptモジュールを指定すれば、入力テキストを要約してブラウザで動作するスライドショーなども実現できたりします。
ということは、生成AIで作曲させ、再生用のjavascriptモジュールを指示してHTMLコードを生成させれば、作曲から再生まで生成AIで行えることになります。
さっそく作成!
利用する再生用のjavascriptモジュールは、ボイスチェンジャーwebアプリ作成時にも利用したTone.jsを使ってもらいます。
曲調は”メタリカ風に”と指示してみました。
生成されたwebアプリ画面イメージ
生成されたHTMLをブラウザで開くと、プレイボタンが表示され、生成AIが作った曲が再生されます。(HTMLコードはページ最後に掲載)
作成された曲のHTML
さーーて、どんな曲ができましたかね。
気になる方は、以下をファイルに保存しブラウザで開くと曲を聞けます。
メタリカ風なのかといわれると、素人がMIDIで初めて作ったような感じになっちゃってますが、目的であった、自動で作曲させた曲の再生まで生成AIで実現できました!
音源がしょぼいですが、もしかするとギターやドラムのサンプル音源をダウンロードして利用するようにしたらよくなるのかもしれません。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>16-bar Melodic Metallica-style Song</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #222;
color: #fff;
}
#playButton {
font-size: 24px;
padding: 10px 20px;
background-color: #f00;
color: #fff;
border: none;
cursor: pointer;
margin-bottom: 20px;
}
#progressBar {
width: 300px;
height: 20px;
background-color: #444;
border-radius: 10px;
overflow: hidden;
}
#progress {
width: 0%;
height: 100%;
background-color: #f00;
transition: width 0.1s linear;
}
</style>
</head>
<body>
<button id="playButton">Play 16-bar Melodic Metallica-style Song</button>
<div id="progressBar">
<div id="progress"></div>
</div>
<script>
const playButton = document.getElementById('playButton');
const progressBar = document.getElementById('progress');
// Create instruments
const distortion = new Tone.Distortion(0.8).toDestination();
const guitar = new Tone.PolySynth(Tone.Synth, {
oscillator: {
type: "sawtooth"
},
envelope: {
attack: 0.01,
decay: 0.1,
sustain: 0.3,
release: 0.1
}
}).connect(distortion);
const bass = new Tone.Synth().toDestination();
const kickDrum = new Tone.MembraneSynth().toDestination();
const snareDrum = new Tone.NoiseSynth({
noise: { type: 'white' },
envelope: { attack: 0.005, decay: 0.1, sustain: 0.05, release: 0.1 }
}).toDestination();
// Create sequences
const guitarPart = new Tone.Sequence((time, chord) => {
guitar.triggerAttackRelease(chord, "8n", time);
}, [
// Verse 1 (4 bars)
["E4", "G4", "B4"], ["E4", "G4", "B4"], ["G4", "B4", "D5"], ["G4", "B4", "D5"],
["A4", "C5", "E5"], ["A4", "C5", "E5"], ["G4", "B4", "D5"], ["G4", "B4", "D5"],
["E4", "G4", "B4"], ["E4", "G4", "B4"], ["G4", "B4", "D5"], ["G4", "B4", "D5"],
["A4", "C5", "E5"], ["A4", "C5", "E5"], ["B4", "D5", "F#5"], ["B4", "D5", "F#5"],
// Pre-Chorus (4 bars)
["C5", "E5", "G5"], ["C5", "E5", "G5"], ["B4", "D5", "F#5"], ["B4", "D5", "F#5"],
["A4", "C5", "E5"], ["A4", "C5", "E5"], ["G4", "B4", "D5"], ["G4", "B4", "D5"],
["C5", "E5", "G5"], ["C5", "E5", "G5"], ["B4", "D5", "F#5"], ["B4", "D5", "F#5"],
["A4", "C5", "E5"], ["G4", "B4", "D5"], ["F#4", "A4", "C#5"], ["F#4", "A4", "C#5"],
// Chorus (4 bars)
["E5", "G5", "B5"], ["E5", "G5", "B5"], ["D5", "F#5", "A5"], ["D5", "F#5", "A5"],
["C5", "E5", "G5"], ["C5", "E5", "G5"], ["B4", "D5", "F#5"], ["B4", "D5", "F#5"],
["E5", "G5", "B5"], ["E5", "G5", "B5"], ["D5", "F#5", "A5"], ["D5", "F#5", "A5"],
["C5", "E5", "G5"], ["C5", "E5", "G5"], ["B4", "D5", "F#5"], ["A4", "C#5", "E5"],
// Bridge (4 bars)
["F#4", "A4", "C#5"], ["F#4", "A4", "C#5"], ["E4", "G4", "B4"], ["E4", "G4", "B4"],
["D4", "F#4", "A4"], ["D4", "F#4", "A4"], ["C#4", "E4", "G#4"], ["C#4", "E4", "G#4"],
["F#4", "A4", "C#5"], ["F#4", "A4", "C#5"], ["E4", "G4", "B4"], ["E4", "G4", "B4"],
["D4", "F#4", "A4"], ["D4", "F#4", "A4"], ["E4", "G4", "B4"], ["E4", "G4", "B4"]
], "4n");
const bassPart = new Tone.Sequence((time, note) => {
if (Array.isArray(note)) {
note.forEach((n, index) => {
bass.triggerAttackRelease(n, "16n", time + index * Tone.Time("64n"));
});
} else {
bass.triggerAttackRelease(note, "8n", time);
}
}, [
// Verse 1
"E2", ["E2", "G2"], "A2", ["G2", "A2"],
"E2", ["E2", "G2"], "A2", "B2",
"E2", ["E2", "G2"], "A2", ["G2", "A2"],
"E2", ["E2", "G2"], "A2", "B2",
// Pre-Chorus
"C3", ["C3", "B2"], "A2", ["A2", "G2"],
"C3", ["C3", "B2"], "A2", ["G2", "F#2"],
"C3", ["C3", "B2"], "A2", ["A2", "G2"],
"C3", ["C3", "B2"], "A2", ["G2", "F#2"],
// Chorus
"E2", ["E2", "F#2"], "G2", ["G2", "A2"],
"B2", ["B2", "A2"], "G2", ["G2", "F#2"],
"E2", ["E2", "F#2"], "G2", ["G2", "A2"],
"B2", ["B2", "A2"], "G2", "F#2",
// Bridge
"F#2", ["F#2", "G2"], "A2", ["A2", "B2"],
"C#3", ["C#3", "B2"], "A2", "G2",
"F#2", ["F#2", "G2"], "A2", ["A2", "B2"],
"C#3", ["C#3", "B2"], "A2", "G2"
], "4n");
const drumPart = new Tone.Sequence((time, pattern) => {
if (pattern.kick) kickDrum.triggerAttackRelease("C2", "8n", time);
if (pattern.snare) snareDrum.triggerAttackRelease("8n", time);
if (pattern.hihat) {
const hihat = new Tone.NoiseSynth({
noise: { type: 'white' },
envelope: { attack: 0.001, decay: 0.05, sustain: 0.005, release: 0.05 }
}).toDestination();
hihat.triggerAttackRelease("32n", time);
}
}, [
{ kick: true, snare: false, hihat: true }, { kick: false, snare: false, hihat: true },
{ kick: false, snare: true, hihat: true }, { kick: false, snare: false, hihat: true },
{ kick: true, snare: false, hihat: true }, { kick: false, snare: false, hihat: true },
{ kick: false, snare: true, hihat: true }, { kick: false, snare: false, hihat: true },
{ kick: true, snare: false, hihat: true }, { kick: false, snare: false, hihat: true },
{ kick: false, snare: true, hihat: true }, { kick: false, snare: false, hihat: true },
{ kick: true, snare: false, hihat: true }, { kick: false, snare: true, hihat: true },
{ kick: true, snare: false, hihat: true }, { kick: false, snare: true, hihat: true }
], "8n");
// Set up the transport
Tone.Transport.bpm.value = 140;
Tone.Transport.loop = true;
Tone.Transport.loopEnd = "16m";
// Update progress bar
function updateProgressBar() {
const progress = (Tone.Transport.ticks / (16 * 4 * 4 * 128)) * 100;
progressBar.style.width = `${progress}%`;
requestAnimationFrame(updateProgressBar);
}
playButton.addEventListener('click', async () => {
await Tone.start();
if (Tone.Transport.state !== "started") {
guitarPart.start(0);
bassPart.start(0);
drumPart.start(0);
Tone.Transport.start();
playButton.textContent = "Stop";
updateProgressBar();
} else {
Tone.Transport.stop();
playButton.textContent = "Play 16-bar Melodic Metallica-style Song";
progressBar.style.width = "0%";
}
});
</script>
</body>
</html>