生成AI Claude 3.5 sonnetでボイスチェンジャーwebアプリを作成する!
前書き
昨今、生成AIを利用したwebアプリ自動生成試行が多く行われています。今回は、Claude 3.5 sonnetを利用して、webブラウザで動作するヴォイスチェンジャーを自動生成してみます。
うまく生成できれば、youtube生配信時などで専用のアプリを入れなくてもこれで十分、となるかもしれません。
生成してもらった!
ということで、早速Claude と何ターンか対話して、30分ぐらいで以下機能を持つボイスチェンジャーができました!(HTMLコードはページの最後に)
・音量調整
・ミュート
・ピッチシフト(男性、女性の声ボタン付き)
・リバーブ
・スペクトルアナライザ
webアプリ画面
画面イメージは以下の通り。素晴らしい!きちんと動作します。
生成されたhtml
生成されたhtmlは次の通りです。HTMLにjavascript,cssも収めるよう指示したので、この1ファイルのみで動作します。(tone.jsという外部モジュールを組み込んでいます)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>リアルタイム音声エフェクトアプリ</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
background-color: #f0f4f8;
color: #333;
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #2c3e50;
margin-bottom: 20px;
font-size: 24px;
}
.control-panel {
display: flex;
flex-direction: column;
width: 100%;
}
.slider-container {
background-color: white;
padding: 10px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin-bottom: 10px;
}
input[type="range"] {
width: 100%;
margin-top: 5px;
}
button {
padding: 8px 16px;
font-size: 14px;
background-color: #3498db;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
margin: 5px;
}
button:hover {
background-color: #2980b9;
}
.button-group {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
#spectrum {
width: 100%;
height: 150px;
background-color: black;
margin-top: 20px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<h1>リアルタイム音声エフェクトアプリ</h1>
<div class="control-panel">
<div class="button-group">
<button id="startButton">マイク入力開始</button>
<button id="muteButton">ミュート</button>
</div>
<div class="slider-container">
<label for="volumeSlider">音量: <span id="volumeValue">0</span> dB</label>
<input type="range" id="volumeSlider" min="-60" max="0" step="1" value="0">
</div>
<div class="slider-container">
<label for="pitchSlider">ピッチシフト: <span id="pitchValue">0</span> 半音</label>
<input type="range" id="pitchSlider" min="-12" max="12" step="1" value="0">
</div>
<div class="button-group">
<button id="maleVoice">男性の声</button>
<button id="normalVoice">通常の声</button>
<button id="femaleVoice">女性の声</button>
</div>
<div class="slider-container">
<label for="reverbSlider">リバーブ: <span id="reverbValue">0.5</span></label>
<input type="range" id="reverbSlider" min="0" max="1" step="0.1" value="0.5">
</div>
</div>
<canvas id="spectrum"></canvas>
<script>
let mic, volume, reverb, pitchShift, analyser;
let canvas, canvasContext;
let isMuted = false;
async function startAudio() {
await Tone.start();
console.log('Audio is ready');
mic = new Tone.UserMedia();
volume = new Tone.Volume(0);
reverb = new Tone.Reverb({wet: 0.5});
pitchShift = new Tone.PitchShift(0);
analyser = new Tone.Analyser('fft', 1024);
mic.chain(volume, pitchShift, reverb, analyser);
analyser.toDestination();
try {
await mic.open();
console.log('Mic is open');
document.getElementById('startButton').textContent = 'マイク入力中';
document.getElementById('startButton').disabled = true;
drawSpectrum();
} catch (e) {
console.log('Could not open mic: ', e);
}
}
function drawSpectrum() {
requestAnimationFrame(drawSpectrum);
const spectrum = analyser.getValue();
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < spectrum.length; i++) {
const x = i / spectrum.length * canvas.width;
const height = (spectrum[i] + 140) * 2;
const hue = i / spectrum.length * 360;
canvasContext.fillStyle = `hsl(${hue}, 100%, 50%)`;
canvasContext.fillRect(x, canvas.height - height, canvas.width / spectrum.length, height);
}
}
document.getElementById('startButton').addEventListener('click', startAudio);
document.getElementById('muteButton').addEventListener('click', () => {
isMuted = !isMuted;
volume.mute = isMuted;
document.getElementById('muteButton').textContent = isMuted ? 'ミュート解除' : 'ミュート';
});
document.getElementById('volumeSlider').addEventListener('input', (e) => {
volume.volume.value = parseFloat(e.target.value);
document.getElementById('volumeValue').textContent = e.target.value;
});
document.getElementById('pitchSlider').addEventListener('input', (e) => {
pitchShift.pitch = parseFloat(e.target.value);
document.getElementById('pitchValue').textContent = e.target.value;
});
document.getElementById('reverbSlider').addEventListener('input', (e) => {
reverb.wet.value = parseFloat(e.target.value);
document.getElementById('reverbValue').textContent = e.target.value;
});
document.getElementById('maleVoice').addEventListener('click', () => {
pitchShift.pitch = -5;
document.getElementById('pitchSlider').value = -5;
document.getElementById('pitchValue').textContent = -5;
});
document.getElementById('normalVoice').addEventListener('click', () => {
pitchShift.pitch = 0;
document.getElementById('pitchSlider').value = 0;
document.getElementById('pitchValue').textContent = 0;
});
document.getElementById('femaleVoice').addEventListener('click', () => {
pitchShift.pitch = 5;
document.getElementById('pitchSlider').value = 5;
document.getElementById('pitchValue').textContent = 5;
});
window.onload = () => {
canvas = document.getElementById('spectrum');
canvasContext = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 150;
};
</script>
</body>
</html>
この記事が気に入ったらサポートをしてみませんか?