SOURCE

console 命令行工具 X clear

                    
>
console
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>音频波形可视化器</title>
    <style>
      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
        color: #fff;
        margin: 0;
        padding: 20px;
        min-height: 100vh;
      }
      .container {
        max-width: 900px;
        margin: 0 auto;
        padding: 20px;
      }
      .header {
        text-align: center;
        margin-bottom: 20px;
      }
      h1 {
        font-size: 32px;
        text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
        margin-bottom: 5px;
      }
      .subtitle {
        color: #aaccff;
        font-size: 18px;
        margin-bottom: 20px;
      }
      .visualizer-container {
        background: rgba(0, 10, 30, 0.7);
        border-radius: 12px;
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
        padding: 20px;
        margin-bottom: 25px;
        backdrop-filter: blur(5px);
        border: 1px solid rgba(64, 128, 255, 0.3);
      }
      .canvas-wrapper {
        display: flex;
        justify-content: center;
        margin: 0 auto;
      }
      canvas {
        background: rgba(5, 15, 35, 0.6);
        border-radius: 8px;
        width: 100%;
        max-width: 800px;
        box-shadow: 0 0 20px rgba(64, 128, 255, 0.2);
        height: 300px;
      }
      .controls {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
        gap: 20px;
        margin-top: 25px;
      }
      .control-group {
        background: rgba(0, 10, 30, 0.7);
        border-radius: 10px;
        padding: 15px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        backdrop-filter: blur(5px);
        border: 1px solid rgba(64, 128, 255, 0.2);
      }
      .control-title {
        font-weight: 600;
        margin-bottom: 12px;
        color: #4facfe;
        font-size: 18px;
      }
      .control-item {
        margin-bottom: 15px;
      }
      label {
        display: block;
        margin-bottom: 5px;
        font-size: 14px;
      }
      .input-range {
        width: 100%;
        height: 8px;
        -webkit-appearance: none;
        border-radius: 4px;
        background: rgba(100, 150, 255, 0.2);
        outline: none;
      }
      .input-range::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: 18px;
        height: 18px;
        border-radius: 50%;
        background: #4facfe;
        cursor: pointer;
        box-shadow: 0 0 5px rgba(79, 172, 254, 0.8);
      }
      .value-display {
        background: rgba(64, 128, 255, 0.25);
        padding: 5px 10px;
        border-radius: 5px;
        font-size: 14px;
        display: inline-block;
        min-width: 40px;
        text-align: center;
        margin-top: 5px;
      }
      .btn-group {
        display: flex;
        gap: 10px;
        margin-top: 10px;
      }
      button {
        flex: 1;
        background: linear-gradient(to right, #4facfe, #00f2fe);
        color: white;
        border: none;
        padding: 12px;
        border-radius: 6px;
        font-weight: bold;
        cursor: pointer;
        transition: all 0.3s ease;
        font-size: 16px;
      }
      button:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(79, 172, 254, 0.5);
      }
      button:active {
        transform: translateY(0);
      }
      button:disabled {
        background: #666;
        cursor: not-allowed;
        transform: none;
        box-shadow: none;
      }
      #audio-source {
        width: 100%;
        padding: 10px;
        background: rgba(0, 10, 30, 0.6);
        border: 1px solid rgba(64, 128, 255, 0.4);
        border-radius: 6px;
        color: white;
        margin-bottom: 15px;
      }
      .status {
        background: rgba(0, 10, 30, 0.7);
        padding: 10px;
        border-radius: 6px;
        text-align: center;
        margin-top: 20px;
        font-size: 14px;
        color: #aaccff;
      }
      .mode-selector {
        display: flex;
        gap: 10px;
        margin-top: 15px;
      }
      .mode-btn {
        flex: 1;
        background: rgba(64, 128, 255, 0.2);
        border: 1px solid rgba(64, 128, 255, 0.5);
        text-align: center;
        padding: 12px;
        border-radius: 6px;
        cursor: pointer;
        transition: all 0.2s;
      }
      .mode-btn.active {
        background: rgba(79, 172, 254, 0.5);
        border-color: #4facfe;
        box-shadow: 0 0 10px rgba(79, 172, 254, 0.4);
      }
      @media (max-width: 600px) {
        .controls {
          grid-template-columns: 1fr;
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="header">
        <h1>音频波形可视化器</h1>
        <div class="subtitle">动态柱状图效果,带间距、圆角与平滑渐弱</div>
      </div>

      <div class="visualizer-container">
        <div class="canvas-wrapper">
          <canvas id="visualizer"></canvas>
        </div>

        <div class="controls">
          <div class="control-group">
            <div class="control-title">波形设置</div>

            <div class="control-item">
              <label for="spacing-control"
                >柱间距 (<span id="spacing-value">3</span>px)</label
              >
              <input
                type="range"
                id="spacing-control"
                class="input-range"
                min="0"
                max="10"
                value="3"
              />
            </div>

            <div class="control-item">
              <label for="roundness-control"
                >圆角半径 (<span id="roundness-value">4</span>px)</label
              >
              <input
                type="range"
                id="roundness-control"
                class="input-range"
                min="0"
                max="12"
                value="4"
              />
            </div>

            <div class="control-item">
              <label for="width-control"
                >柱宽 (<span id="width-value">8</span>px)</label
              >
              <input
                type="range"
                id="width-control"
                class="input-range"
                min="2"
                max="20"
                value="8"
              />
            </div>
          </div>

          <div class="control-group">
            <div class="control-title">外观设置</div>

            <div class="control-item">
              <label for="height-control"
                >振幅 (<span id="height-value">100</span>%)</label
              >
              <input
                type="range"
                id="height-control"
                class="input-range"
                min="40"
                max="200"
                value="100"
              />
            </div>

            <div class="control-item">
              <label for="color-mode">颜色模式</label>
              <select id="color-mode" class="input-range">
                <option value="gradient">渐变蓝</option>
                <option value="energy">能量红</option>
                <option value="ocean">海洋绿</option>
                <option value="purple">紫罗兰</option>
              </select>
            </div>

            <div class="control-item">
              <label for="sensitivity"
                >灵敏度 (<span id="sensitivity-value">50</span>%)</label
              >
              <input
                type="range"
                id="sensitivity"
                class="input-range"
                min="20"
                max="100"
                value="50"
              />
            </div>
          </div>

          <div class="control-group">
            <div class="control-title">音频源</div>

            <div class="control-item">
              <label for="audio-source">选择音频源</label>
              <select id="audio-source">
                <option value="test">测试音效</option>
                <option value="mic">麦克风输入</option>
              </select>
            </div>

            <div class="mode-selector">
              <div class="mode-btn active" data-mode="sine">正弦波</div>
              <div class="mode-btn" data-mode="peak">峰值波</div>
              <div class="mode-btn" data-mode="pulse">脉冲波</div>
            </div>

            <div class="btn-group">
              <button id="start-btn">开始</button>
              <button id="reset-btn">重置参数</button>
            </div>

            <div class="status">点击"开始"按钮以启动可视化效果</div>
          </div>
        </div>
      </div>
    </div>

    <script>
      // 获取DOM元素
      const canvas = document.getElementById("visualizer");
      const ctx = canvas.getContext("2d");
      const startBtn = document.getElementById("start-btn");
      const resetBtn = document.getElementById("reset-btn");

      // 设置canvas大小为容器大小
      function resizeCanvas() {
        canvas.width = canvas.offsetWidth;
        canvas.height = canvas.offsetHeight;
      }

      // 添加窗口大小变化监听
      window.addEventListener("resize", resizeCanvas);
      resizeCanvas();

      // 获取控制元素
      const spacingControl = document.getElementById("spacing-control");
      const roundnessControl = document.getElementById("roundness-control");
      const widthControl = document.getElementById("width-control");
      const heightControl = document.getElementById("height-control");
      const sensitivityControl = document.getElementById("sensitivity");
      const colorModeSelect = document.getElementById("color-mode");

      // 初始化可视化参数
      let visualizationParams = {
        barSpacing: 3,
        barRoundness: 4,
        barWidth: 8,
        barHeightMultiplier: 1.0,
        sensitivity: 0.5,
        isAnimating: false,
        animationId: null,
        audioContext: null,
        analyser: null,
        audioSource: "test",
        mode: "sine",
      };

      // 更新显示值的函数
      function updateDisplayValues() {
        document.getElementById("spacing-value").textContent =
          visualizationParams.barSpacing;
        document.getElementById("roundness-value").textContent =
          visualizationParams.barRoundness;
        document.getElementById("width-value").textContent =
          visualizationParams.barWidth;
        document.getElementById("height-value").textContent = Math.round(
          visualizationParams.barHeightMultiplier * 100
        );
        document.getElementById("sensitivity-value").textContent = Math.round(
          visualizationParams.sensitivity * 100
        );
      }

      // 从UI初始化参数
      function initParamsFromUI() {
        visualizationParams.barSpacing = parseInt(spacingControl.value);
        visualizationParams.barRoundness = parseInt(roundnessControl.value);
        visualizationParams.barWidth = parseInt(widthControl.value);
        visualizationParams.barHeightMultiplier =
          parseInt(heightControl.value) / 100;
        visualizationParams.sensitivity =
          parseInt(sensitivityControl.value) / 100;
        visualizationParams.mode =
          document.querySelector(".mode-btn.active").dataset.mode;

        updateDisplayValues();
      }

      // 颜色模式映射
      const colorModes = {
        gradient: {
          low: "#3a7bd5",
          mid: "#00d2ff",
          high: "#ffffff",
        },
        energy: {
          low: "#ff416c",
          mid: "#ff4b2b",
          high: "#fad961",
        },
        ocean: {
          low: "#00b09b",
          mid: "#96c93d",
          high: "#3df0f0",
        },
        purple: {
          low: "#654ea3",
          mid: "#a367dc",
          high: "#f4d0ff",
        },
      };

      // 重置参数
      resetBtn.addEventListener("click", () => {
        spacingControl.value = 3;
        roundnessControl.value = 4;
        widthControl.value = 8;
        heightControl.value = 100;
        sensitivityControl.value = 50;
        colorModeSelect.value = "gradient";

        document
          .querySelectorAll(".mode-btn")
          .forEach((btn) => btn.classList.remove("active"));
        document
          .querySelector('.mode-btn[data-mode="sine"]')
          .classList.add("active");

        initParamsFromUI();
        startBtn.textContent = "开始";
        document.querySelector(".status").textContent = "参数已重置";
      });

      // 添加控制监听器
      spacingControl.addEventListener("input", function () {
        visualizationParams.barSpacing = parseInt(this.value);
        updateDisplayValues();
      });

      roundnessControl.addEventListener("input", function () {
        visualizationParams.barRoundness = parseInt(this.value);
        updateDisplayValues();
      });

      widthControl.addEventListener("input", function () {
        visualizationParams.barWidth = parseInt(this.value);
        updateDisplayValues();
      });

      heightControl.addEventListener("input", function () {
        visualizationParams.barHeightMultiplier = parseInt(this.value) / 100;
        updateDisplayValues();
      });

      sensitivityControl.addEventListener("input", function () {
        visualizationParams.sensitivity = parseInt(this.value) / 100;
        updateDisplayValues();
      });

      // 模式选择器
      document.querySelectorAll(".mode-btn").forEach((btn) => {
        btn.addEventListener("click", function () {
          document
            .querySelectorAll(".mode-btn")
            .forEach((b) => b.classList.remove("active"));
          this.classList.add("active");
          visualizationParams.mode = this.dataset.mode;
        });
      });

      // 创建音频可视化柱状图
      function drawBars(dataArray, barCount) {
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 计算可以容纳的柱数
        const totalBarWidth =
          visualizationParams.barWidth + visualizationParams.barSpacing;
        barCount = Math.min(barCount, Math.floor(canvas.width / totalBarWidth));

        const centerX = canvas.width / 2;
        const halfBarCount = Math.floor(barCount / 2);
        const colorMode = colorModes[colorModeSelect.value];

        // 创建从中心向两边展开的柱子
        for (let i = 0; i < barCount; i++) {
          const barIndex =
            i < halfBarCount ? halfBarCount - i - 1 : i - halfBarCount;
          const value = dataArray[barIndex] / 256;

          // 应用灵敏度调整
          let barHeight =
            Math.pow(value, 1 + (1 - visualizationParams.sensitivity) * 2) *
            canvas.height *
            visualizationParams.barHeightMultiplier *
            0.8;

          // 从中心向两边高度递减效果
          const distanceFromCenter = Math.abs(i - halfBarCount);
          barHeight =
            barHeight * (1 - (0.4 * distanceFromCenter) / halfBarCount);

          const barWidth = visualizationParams.barWidth;
          const barRoundness = Math.min(
            visualizationParams.barRoundness,
            barHeight / 2
          );

          // 从中心开始的位置计算
          const x = centerX - halfBarCount * totalBarWidth + i * totalBarWidth;
          const y = canvas.height - barHeight;

          // 创建渐变颜色(能量越大颜色越亮)
          const gradient = ctx.createLinearGradient(x, y, x, canvas.height);
          const gradientValue = Math.min(1, value * 3);

          gradient.addColorStop(0, colorMode.high);
          gradient.addColorStop(0.6, colorMode.mid);
          gradient.addColorStop(1, colorMode.low);

          // 绘制圆角矩形
          ctx.fillStyle = gradient;

          if (barRoundness > 0) {
            // 绘制圆角矩形
            ctx.beginPath();
            ctx.moveTo(x + barRoundness, y);
            ctx.lineTo(x + barWidth - barRoundness, y);
            ctx.arcTo(
              x + barWidth,
              y,
              x + barWidth,
              y + barRoundness,
              barRoundness
            );
            ctx.lineTo(x + barWidth, canvas.height);
            ctx.lineTo(x, canvas.height);
            ctx.lineTo(x, y + barRoundness);
            ctx.arcTo(x, y, x + barRoundness, y, barRoundness);
            ctx.closePath();
            ctx.fill();
          } else {
            // 直接绘制矩形
            ctx.fillRect(x, y, barWidth, barHeight);
          }
        }
      }

      // 生成正弦波测试数据
      function generateSineData() {
        const data = [];
        const barsCount = 128;
        const time = Date.now() * 0.002;

        for (let i = 0; i < barsCount; i++) {
          // 正弦波基础 + 噪声 + 峰值
          let value = Math.sin(i * 0.2 + time);
          value += Math.sin(i * 0.1 + time * 0.5) * 0.3;
          value += Math.sin(i * 0.05 + time * 0.2) * 0.2;
          value += Math.random() * 0.1;

          // 转换到0-255范围
          const normalized = (value + 2) * 40; // 缩放值
          data.push(Math.min(255, Math.max(0, normalized)));
        }

        return data;
      }

      // 生成峰值波测试数据
      function generatePeakData() {
        const data = [];
        const barsCount = 128;
        const time = Date.now() * 0.001;
        const beat =
          Math.sin(time * 2) > 0.8 ? Math.sin(time * 30) * 0.3 + 0.7 : 0;

        for (let i = 0; i < barsCount; i++) {
          // 中心位置峰值
          const centerValue = Math.max(
            0,
            1 - Math.abs(i - barsCount / 2) / (barsCount / 4)
          );
          let value = centerValue;

          // 添加节奏点
          if (i > barsCount / 2 - 6 && i < barsCount / 2 + 6) {
            value += beat;
          }

          // 添加噪声
          value += Math.sin(i * 0.2 + time) * 0.1;
          value += Math.random() * 0.05;

          const normalized = value * 200;
          data.push(Math.min(255, Math.max(0, normalized)));
        }

        return data;
      }

      // 生成脉冲波测试数据
      function generatePulseData() {
        const data = [];
        const barsCount = 128;
        const time = Date.now() * 0.001;
        const pulse = Math.abs(Math.sin(time)) > 0.9 ? 1 : 0;

        for (let i = 0; i < barsCount; i++) {
          // 基础波
          let value = Math.sin(i * 0.3) * 0.5 + 0.5;

          // 中心位置
          if (Math.abs(i - barsCount / 2) < 8) {
            value = Math.min(1, value + pulse * 0.7);
          }

          // 随机脉冲
          if (Math.random() > 0.99) {
            value = 1;
          }

          const normalized = value * 200;
          data.push(Math.min(255, Math.max(0, normalized)));
        }

        return data;
      }

      // 基于选择的模式生成数据
      function generateVisualizerData() {
        switch (visualizationParams.mode) {
          case "peak":
            return generatePeakData();
          case "pulse":
            return generatePulseData();
          case "sine":
          default:
            return generateSineData();
        }
      }

      // 开始/停止可视化
      startBtn.addEventListener("click", function () {
        if (!visualizationParams.isAnimating) {
          visualizationParams.isAnimating = true;
          startBtn.textContent = "停止";
          document.querySelector(".status").textContent = "可视化效果运行中...";

          initParamsFromUI();
          animate();
        } else {
          visualizationParams.isAnimating = false;
          startBtn.textContent = "开始";
          document.querySelector(".status").textContent = "可视化已停止";

          if (visualizationParams.animationId) {
            cancelAnimationFrame(visualizationParams.animationId);
          }

          // 停止音频上下文
          if (visualizationParams.audioContext) {
            if (
              visualizationParams.audioSource === "mic" &&
              visualizationParams.audioContext.state !== "closed"
            ) {
              visualizationParams.audioContext.close();
            }
            visualizationParams.audioContext = null;
          }
        }
      });

      // 动画循环
      function animate() {
        if (!visualizationParams.isAnimating) return;

        let dataArray;
        const barCount = 64; // 64个柱状条(左右各32个)

        if (visualizationParams.audioSource === "mic") {
          // 真实音频数据处理
          if (!visualizationParams.audioContext) {
            initAudio();
          }

          if (visualizationParams.analyser) {
            const bufferLength = visualizationParams.analyser.frequencyBinCount;
            dataArray = new Uint8Array(bufferLength);
            visualizationParams.analyser.getByteFrequencyData(dataArray);
          } else {
            dataArray = generateVisualizerData();
          }
        } else {
          // 模拟数据
          dataArray = generateVisualizerData();
        }

        drawBars(dataArray, barCount);
        visualizationParams.animationId = requestAnimationFrame(animate);
      }

      // 音频初始化(麦克风)
      function initAudio() {
        visualizationParams.audioContext = new (window.AudioContext ||
          window.webkitAudioContext)();
        visualizationParams.analyser =
          visualizationParams.audioContext.createAnalyser();
        visualizationParams.analyser.fftSize = 256;

        if (visualizationParams.audioSource === "mic") {
          navigator.mediaDevices
            .getUserMedia({ audio: true })
            .then(function (stream) {
              const source =
                visualizationParams.audioContext.createMediaStreamSource(
                  stream
                );
              source.connect(visualizationParams.analyser);
            })
            .catch(function (err) {
              console.error("麦克风访问失败:", err);
              document.querySelector(".status").textContent =
                "麦克风访问失败,使用测试音效";
              visualizationParams.audioSource = "test";
            });
        }
      }

      // 设置音频源选择
      document
        .getElementById("audio-source")
        .addEventListener("change", function () {
          visualizationParams.audioSource = this.value;
          if (visualizationParams.isAnimating && this.value === "mic") {
            startBtn.click(); // 停止当前
            setTimeout(() => startBtn.click(), 100); // 重新开始
          }
        });

      // 初始化参数显示
      initParamsFromUI();
    </script>
  </body>
</html>