Repository: gradio-app/gradio

Issue:
[Gradio 4] Adjust waveforms depending on the sound volume range in `gr.Audio`
- [x] I have searched to see if a similar issue already exists.

For loud audio input, the waveform appears like this, and is almost meaningless. So, it would be nice if the display scale of the waveform could be changed according to the sound volume of the input audio.

![](https://github.com/gradio-app/gradio/assets/25161192/21b782ef-0005-4f15-8b2b-d2e74733e7fe)




Discussion:

Resolving patch:
diff --git a/gradio/templates.py b/gradio/templates.py
--- a/gradio/templates.py
+++ b/gradio/templates.py
@@ -326,7 +326,7 @@ def __init__(
 
 class Microphone(components.Audio):
     """
-    Sets: source="microphone"
+    Sets: sources=["microphone"]
     """
 
     is_template = True


Test patch:
diff --git a/js/app/test/audio_debugger.spec.ts b/js/app/test/audio_debugger.spec.ts
new file mode 100644
--- /dev/null
+++ b/js/app/test/audio_debugger.spec.ts
@@ -0,0 +1,39 @@
+import { test } from "@gradio/tootils";
+
+// we cannot currently test the waveform canvas with playwright (https://github.com/microsoft/playwright/issues/23964)
+// so this test covers the interactive elements around the waveform canvas
+
+test("audio waveform", async ({ page }) => {
+	await page.getByRole("button", { name: "Interface" }).click();
+	await page.getByRole("button", { name: "cantina.wav" }).click();
+	await page
+		.getByTestId("waveform-x")
+		.getByLabel("Adjust playback speed to 1.5x")
+		.click();
+	await page.getByLabel("Adjust playback speed to 2x").click();
+
+	await page
+		.getByTestId("waveform-x")
+		.getByLabel("Skip forward by 0.15 seconds")
+		.click();
+	await page
+		.getByTestId("waveform-x")
+		.getByLabel("Skip backwards by 0.15 seconds")
+		.click();
+	await page.getByLabel("Trim audio to selection").click();
+	await page.getByRole("button", { name: "Trim" }).click();
+	await page.getByLabel("Reset audio").click();
+	await page.getByRole("button", { name: "Submit" }).click();
+	await page
+		.getByTestId("waveform-output")
+		.getByLabel("Adjust playback speed to 1.5x")
+		.click();
+	await page
+		.getByTestId("waveform-output")
+		.getByLabel("Skip backwards by 0.15 seconds")
+		.click();
+	await page
+		.getByTestId("waveform-output")
+		.getByLabel("Skip forward by 0.15 seconds")
+		.click();
+});
diff --git a/js/audio/audio.test.ts b/js/audio/audio.test.ts
--- a/js/audio/audio.test.ts
+++ b/js/audio/audio.test.ts
@@ -1,6 +1,6 @@
-import { test, describe, assert, afterEach } from "vitest";
+import { test, describe, assert, afterEach, vi } from "vitest";
 import { cleanup, render } from "@gradio/tootils";
-import Audio from "./Index.svelte";
+import Audio from "./";
 import type { LoadingStatus } from "@gradio/statustracker";
 import { setupi18n } from "../app/src/i18n";
 import ResizeObserver from "resize-observer-polyfill";
@@ -18,6 +18,18 @@ const loading_status: LoadingStatus = {
 	show_progress: "full"
 };
 
+const default_values = {
+	loading_status,
+	label: "music",
+	value: {
+		url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
+		path: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
+		orig_name: "SoundHelix-Song-1.mp3"
+	},
+	root: "",
+	proxy_url: "",
+	show_label: true
+};
 describe("Audio", () => {
 	setupi18n();
 
@@ -25,18 +37,86 @@ describe("Audio", () => {
 
 	test("renders audio component", async () => {
 		const { getAllByTestId } = await render(Audio, {
-			loading_status,
-			label: "music",
-			value: {
-				url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
-				path: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
-				orig_name: "SoundHelix-Song-1.mp3"
-			},
-			root: "",
-			proxy_url: "",
-			theme_mode: "dark"
+			...default_values,
+			interactive: true,
+			sources: ["microphone", "upload"],
+			pending: false,
+			streaming: false
 		});
 
 		assert.exists(getAllByTestId("waveform-music"));
 	});
+
+	test("renders audio component with audio controls", async () => {
+		const { getAllByTestId, getAllByLabelText, getAllByText } = await render(
+			Audio,
+			{
+				...default_values,
+				streaming: false,
+				pending: false,
+				sources: ["microphone"],
+				interactive: true
+			}
+		);
+
+		assert.exists(getAllByTestId("waveform-controls"));
+
+		assert.exists(getAllByLabelText("Trim audio to selection"));
+		assert.exists(getAllByLabelText("Reset audio"));
+		assert.exists(getAllByText("0:00"));
+		assert.exists(getAllByLabelText("audio.play"));
+		assert.exists(getAllByLabelText("Adjust volume"));
+		assert.exists(getAllByLabelText("Adjust playback speed to 1.5x"));
+		assert.exists(getAllByLabelText("Skip forward by 5 seconds"));
+	});
+
+	test("does not render with audio editing controls when not interactive", async () => {
+		const { getAllByTestId, queryByLabelText } = await render(Audio, {
+			...default_values,
+			streaming: false,
+			pending: false,
+			sources: ["microphone"],
+			interactive: false
+		});
+
+		assert.exists(getAllByTestId("waveform-controls"));
+		assert.notExists(queryByLabelText("Trim audio to selection"));
+		assert.notExists(queryByLabelText("Reset audio"));
+	});
+
+	test("renders source selection with correct selected source", async () => {
+		const { getByTestId, getByLabelText } = await render(Audio, {
+			...default_values,
+			streaming: false,
+			pending: false,
+			sources: ["microphone", "upload"],
+			interactive: true
+		});
+
+		assert.exists(getByTestId("source-select"));
+		assert.lengthOf(getByTestId("source-select").children, 2);
+		assert.exists(getByLabelText("Record audio"));
+
+		assert.equal(
+			getByLabelText("Record audio").classList.contains("selected"),
+			true
+		);
+
+		assert.equal(
+			getByLabelText("Upload file").classList.contains("selected"),
+			false
+		);
+	});
+
+	test("does not render source selection when upload is only source", async () => {
+		const { queryByTestId } = await render(Audio, {
+			...default_values,
+			streaming: false,
+			pending: false,
+			sources: ["upload"],
+			interactive: true
+		});
+
+		assert.notExists(queryByTestId("source-select"));
+	});
 });