朗读 错误代码edit icon

Fork(复制)
下载
嵌入
BUG反馈
index.html
现在支持上传本地图片了!
index.html
            
            <!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>《将进酒》节奏检测工具</title>
    <style>
        body {
            font-family: "Microsoft YaHei", sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #f5f5f5;
            padding: 20px;
        }
        .poem-box {
            background: linear-gradient(135deg, #f8d7da, #cfe8fc);
            border-radius: 15px;
            padding: 20px;
            margin-bottom: 20px;
            width: 80%;
            max-width: 600px;
        }
        .line {
            font-size: 1.2em;
            margin: 10px 0;
            padding-left: 20px;
            position: relative;
        }
        .line::before {
            content: "";
            position: absolute;
            left: 0;
            width: 5px;
            height: 80%;
            background-color: #6c757d;
            opacity: 0.3;
        }
        .control-box {
            display: flex;
            gap: 20px;
            margin-bottom: 30px;
        }
        button {
            padding: 10px 20px;
            font-size: 1em;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            background-color: #007bff;
            color: white;
        }
        button:hover {
            background-color: #0056b3;
        }
        #waveform {
            width: 100%;
            height: 150px;
            border: 1px solid #dee2e6;
            margin: 20px 0;
            background-color: white;
        }
        .score {
            font-size: 1.5em;
            color: #28a745;
            font-weight: bold;
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <div class="poem-box">
        <h2>《将进酒》节奏检测</h2>
        <div class="line" data-time="0.0-1.5">君不见黄河之水天上来</div>
        <div class="line" data-time="1.5-3.0">奔流到海不复回</div>
        <div class="line" data-time="3.0-4.5">君不见高堂明镜悲白发</div>
        <div class="line" data-time="4.5-6.0">朝如青丝暮成雪</div>
        <!-- 可继续添加完整诗句,data-time为预设节奏区间(秒) -->
    </div>

    <div class="control-box">
        <button id="recordBtn">开始录音</button>
        <button id="playBtn" disabled>播放录音</button>
        <button id="analyzeBtn" disabled>分析节奏</button>
    </div>

    <canvas id="waveform"></canvas>
    <div class="score" id="scoreDisplay"></div>
    <div id="feedback"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/recorderjs/0.10.3/recorder.js"></script>
    <script>
        const poemLines = Array.from(document.querySelectorAll('.line'));
        const presetRhythm = poemLines.map(line => ({
            text: line.textContent,
            timeRange: line.dataset.time.split('-').map(Number)
        }));

        let recorder, audioContext, audioBlob, audioStream;
        const recordBtn = document.getElementById('recordBtn');
        const playBtn = document.getElementById('playBtn');
        const analyzeBtn = document.getElementById('analyzeBtn');
        const waveformCanvas = document.getElementById('waveform');
        const scoreDisplay = document.getElementById('scoreDisplay');
        const feedback = document.getElementById('feedback');
        let isRecording = false;

        // 初始化录音
        async function initRecorder() {
            try {
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
                const input = audioContext.createMediaStreamSource(audioStream);
                recorder = new Recorder(input);
                recordBtn.disabled = false;
            } catch (err) {
                console.error('录音权限申请失败:', err);
                alert('请允许麦克风权限以使用录音功能');
            }
        }

        // 开始/停止录音
        recordBtn.addEventListener('click', () => {
            if (!isRecording) {
                // 开始录音
                recorder.clear();
                recorder.record();
                isRecording = true;
                recordBtn.textContent = '停止录音';
                playBtn.disabled = true;
                analyzeBtn.disabled = true;
            } else {
                // 停止录音
                recorder.stop();
                isRecording = false;
                recordBtn.textContent = '开始录音';
                
                // 保存录音数据
                recorder.exportWAV(blob => {
                    audioBlob = blob;
                    playBtn.disabled = false;
                    analyzeBtn.disabled = false;
                    
                    // 绘制波形图
                    drawWaveform(blob);
                });
            }
        });

        // 播放音频
        playBtn.addEventListener('click', () => {
            if (!audioBlob) return;
            
            const audioUrl = URL.createObjectURL(audioBlob);
            const audio = new Audio(audioUrl);
            audio.play();
        });

        // 分析节奏
        analyzeBtn.addEventListener('click', async () => {
            if (!audioBlob) return alert('请先录制音频');
            
            // 实际应用中应调用真实的语音识别API
            const userRhythm = await analyzeAudio(audioBlob);
            const accuracy = calculateAccuracy(presetRhythm, userRhythm);
            
            // 显示评分
            scoreDisplay.textContent = `节奏准确率:${accuracy.toFixed(2)}%`;
            feedback.textContent = accuracy > 85 ? '节奏准确!' : '需加强重音与停顿练习';
            
            // 高亮显示朗读效果
            highlightLines(userRhythm);
        });

        // 分析音频(模拟)
        async function analyzeAudio(blob) {
            // 实际应用中应替换为真实的语音识别API
            return new Promise(resolve => {
                setTimeout(() => {
                    // 模拟识别结果,这里使用预设节奏作为参考
                    const userTime = presetRhythm.map((line, i) => {
                        const duration = line.timeRange[1] - line.timeRange[0];
                        // 添加一些随机波动模拟真实情况
                        const offset = (Math.random() - 0.5) * 0.5;
                        return {
                            start: line.timeRange[0] + offset,
                            end: line.timeRange[0] + duration + offset
                        };
                    });
                    resolve(userTime);
                }, 1000);
            });
        }

        // 计算准确率
        function calculateAccuracy(preset, user) {
            const total = preset.length;
            let match = 0;
            
            for (let i = 0; i < total; i++) {
                const p = preset[i];
                const u = user[i];
                
                if (u && 
                    u.start >= p.timeRange[0] - 0.3 && 
                    u.end <= p.timeRange[1] + 0.3) {
                    match++;
                }
            }
            
            return (match / total) * 100;
        }

        // 高亮显示朗读效果
        function highlightLines(userRhythm) {
            poemLines.forEach((line, i) => {
                const lineData = presetRhythm[i];
                const userData = userRhythm[i];
                
                if (!userData) return;
                
                const isMatch = 
                    userData.start >= lineData.timeRange[0] - 0.3 && 
                    userData.end <= lineData.timeRange[1] + 0.3;
                
                if (isMatch) {
                    line.style.backgroundColor = '#d4edda'; // 绿色背景表示节奏正确
                } else {
                    line.style.backgroundColor = '#f8d7da'; // 红色背景表示节奏错误
                }
                
                // 3秒后重置颜色
                setTimeout(() => {
                    line.style.backgroundColor = '';
                }, 3000);
            });
        }

        // 绘制波形图
        function drawWaveform(blob) {
            const ctx = waveformCanvas.getContext('2d');
            ctx.clearRect(0, 0, waveformCanvas.width, waveformCanvas.height);
            
            const audioContext = new (window.AudioContext || window.webkitAudioContext)();
            
            const reader = new FileReader();
            reader.onload = function(e) {
                audioContext.decodeAudioData(e.target.result, function(buffer) {
                    const data = buffer.getChannelData(0);
                    const step = Math.ceil(data.length / 1000);
                    const waveform = [];
                    
                    for (let i = 0; i < 1000; i++) {
                        let min = 1.0;
                        let max = -1.0;
                        for (let j = 0; j < step; j++) {
                            const value = data[i * step + j];
                            if (value < min) min = value;
                            if (value > max) max = value;
                        }
                        waveform.push({ min, max });
                    }
                    
                    // 绘制波形
                    ctx.beginPath();
                    ctx.strokeStyle = '#007bff';
                    ctx.lineWidth = 2;
                    
                    const width = waveformCanvas.width;
                    const height = waveformCanvas.height;
                    
                    for (let i = 0; i < waveform.length; i++) {
                        const x = (i / waveform.length) * width;
                        const minY = height / 2 + waveform[i].min * height / 2;
                        const maxY = height / 2 + waveform[i].max * height / 2;
                        
                        if (i === 0) {
                            ctx.moveTo(x, minY);
                        } else {
                            ctx.lineTo(x, minY);
                        }
                        ctx.lineTo(x, maxY);
                    }
                    
                    ctx.stroke();
                });
            };
            
            reader.readAsArrayBuffer(blob);
        }

        // 初始化
        initRecorder();
    </script>
</body>
</html>


        
编辑器加载中
预览
控制台