# Audio Waveform

> A customizable audio waveform visualization component with playback progress and interactive seeking capabilities.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/audio-waveform
- Markdown: https://ui.ahmedbna.com/docs/components/audio-waveform.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/audio-waveform.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/audio-waveform.json
- Install: `npx bna-ui add audio-waveform`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`, `text`, `view`
- Preview recording: https://demo.ahmedbna.com/0039-audio-waveform-demo.MP4

---

**Example:** A basic audio waveform with playback controls

```tsx
// components/demo/audio-waveform/audio-waveform-demo.tsx
import { AudioWaveform } from '@/components/ui/audio-waveform';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useEffect, useState } from 'react';

export function AudioWaveformDemo() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [progress, setProgress] = useState(0);

  // Simulate audio playback progress
  useEffect(() => {
    let interval: ReturnType<typeof setInterval>;
    if (isPlaying) {
      interval = setInterval(() => {
        setProgress((prev) => {
          if (prev >= 100) {
            setIsPlaying(false);
            return 0;
          }
          return prev + 2;
        });
      }, 100);
    }
    return () => clearInterval(interval);
  }, [isPlaying]);

  const handleSeek = (position: number) => {
    setProgress(position);
  };

  const togglePlayback = () => {
    setIsPlaying(!isPlaying);
  };

  return (
    <View style={{ gap: 16 }}>
      <AudioWaveform
        isPlaying={isPlaying}
        progress={progress}
        showProgress={true}
        interactive={true}
        onSeek={handleSeek}
        height={60}
        barCount={40}
      />

      <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
        <Button onPress={togglePlayback} style={{ flex: 0 }}>
          {isPlaying ? 'Pause' : 'Play'}
        </Button>
        <Text variant='caption'>{Math.round(progress)}% Complete</Text>
      </View>
    </View>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add audio-waveform
```

### Manual

**1.** Install the following dependencies:

```bash
npx expo install react-native-reanimated
```

**2.** Copy and paste the following code into your project.

```tsx
// components/ui/audio-waveform.tsx
import { useColor } from '@/hooks/useColor';
import React, { useEffect, useMemo } from 'react';
import { StyleSheet, View, ViewStyle } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  cancelAnimation,
  interpolate,
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withRepeat,
  withSequence,
  withTiming,
} from 'react-native-reanimated';

export interface AudioWaveformProps {
  data?: number[];
  isPlaying?: boolean;
  progress?: number;
  onSeek?: (position: number) => void;
  onSeekStart?: () => void;
  onSeekEnd?: () => void;
  style?: ViewStyle;
  height?: number;
  barCount?: number;
  barWidth?: number;
  barGap?: number;
  activeColor?: string;
  inactiveColor?: string;
  animated?: boolean;
  showProgress?: boolean;
  interactive?: boolean;
}

// FIX: The Bar component now manages its own animation state.
const Bar = React.memo(
  ({
    value,
    height,
    width,
    isActive,
    showProgress,
    activeColor,
    inactiveColor,
    opacity,
    isPlaying,
    animated,
  }: {
    value: number;
    height: number;
    width: number;
    isActive: boolean;
    showProgress: boolean;
    activeColor: string;
    inactiveColor: string;
    opacity: number;
    isPlaying: boolean;
    animated: boolean;
  }) => {
    // Each Bar has its own shared value, created at the top level. This is correct.
    const animatedValue = useSharedValue(value);

    const animatedStyle = useAnimatedStyle(() => {
      return {
        height: interpolate(
          animatedValue.value,
          [0, 1],
          [4, height * 0.9],
          'clamp'
        ),
      };
    });

    // This effect handles animations when the bar's data or playing state changes.
    useEffect(() => {
      if (isPlaying && animated && !showProgress) {
        // "Live" animation effect
        const randomDuration = 200 + Math.random() * 200;
        animatedValue.value = withRepeat(
          withSequence(
            withTiming(value * (0.9 + Math.random() * 0.2), {
              duration: randomDuration,
            }),
            withTiming(value * (0.9 + Math.random() * 0.2), {
              duration: randomDuration,
            })
          ),
          -1,
          true
        );
      } else {
        // Animate to the new static value
        cancelAnimation(animatedValue);
        animatedValue.value = withTiming(value, {
          duration: animated ? 250 : 0,
        });
      }

      return () => {
        cancelAnimation(animatedValue);
      };
    }, [value, isPlaying, animated, showProgress, animatedValue]);

    return (
      <Animated.View
        style={[
          styles.bar,
          {
            width,
            backgroundColor:
              isActive || !showProgress ? activeColor : inactiveColor,
            opacity,
          },
          animatedStyle,
        ]}
      />
    );
  }
);

export function AudioWaveform({
  data,
  isPlaying = false,
  progress = 0,
  onSeek,
  onSeekStart,
  onSeekEnd,
  style,
  height = 60,
  barCount = 50,
  barWidth = 3,
  barGap = 2,
  activeColor,
  inactiveColor,
  animated = true,
  showProgress = false,
  interactive = false,
}: AudioWaveformProps) {
  const primaryColor = useColor('destructive');
  const mutedColor = useColor('textMuted');

  const finalActiveColor = activeColor || primaryColor;
  const finalInactiveColor = inactiveColor || mutedColor;

  // FIX: This now just memoizes the raw data array, not an array of hooks.
  const waveformData = useMemo(
    () => data || generateSampleWaveform(barCount),
    [data, barCount]
  );

  const totalWidth = barCount * barWidth + (barCount - 1) * barGap;

  const getProgressLinePosition = () => {
    const progressRatio = Math.max(0, Math.min(100, progress)) / 100;
    if (progressRatio === 0) return 0;
    if (progressRatio === 1) return totalWidth - 1;
    const exactBarPosition = progressRatio * barCount;
    const barIndex = Math.floor(exactBarPosition);
    const barProgress = exactBarPosition - barIndex;
    let position = barIndex * (barWidth + barGap);
    position += barProgress * barWidth;
    return Math.min(position, totalWidth - 1);
  };

  const handleSeek = (x: number) => {
    if (!onSeek) return;
    const clampedX = Math.max(0, Math.min(totalWidth, x));
    const seekPercentage = (clampedX / totalWidth) * 100;
    onSeek(seekPercentage);
  };

  const panGesture = Gesture.Pan()
    .enabled(interactive)
    .hitSlop({ top: 12, bottom: 12 })
    .onStart((event) => {
      if (onSeekStart) runOnJS(onSeekStart)();
      runOnJS(handleSeek)(event.x);
    })
    .onUpdate((event) => {
      runOnJS(handleSeek)(event.x);
    })
    .onEnd(() => {
      if (onSeekEnd) runOnJS(onSeekEnd)();
    });

  const handleAccessibilityAction = (event: {
    nativeEvent: { actionName: string };
  }) => {
    if (!interactive || !onSeek) return;
    const clampedProgress = Math.max(0, Math.min(100, progress));
    switch (event.nativeEvent.actionName) {
      case 'increment':
        onSeek(Math.min(100, clampedProgress + 5));
        break;
      case 'decrement':
        onSeek(Math.max(0, clampedProgress - 5));
        break;
    }
  };

  return (
    <View style={[styles.container, { height }, style]}>
      <GestureDetector gesture={panGesture}>
        <View
          style={[styles.waveform, { width: totalWidth }]}
          accessible={interactive}
          accessibilityRole={interactive ? 'adjustable' : undefined}
          accessibilityValue={
            interactive
              ? { min: 0, max: 100, now: Math.round(progress) }
              : undefined
          }
          accessibilityActions={
            interactive
              ? [
                  { name: 'increment', label: 'increment' },
                  { name: 'decrement', label: 'decrement' },
                ]
              : undefined
          }
          onAccessibilityAction={handleAccessibilityAction}
        >
          {/* FIX: We now map over the data array and pass the value to each Bar */}
          {waveformData.map((value, index) => {
            const progressRatio = progress / 100;
            const barProgress = (index + 0.5) / barCount;
            const isActive = showProgress
              ? barProgress <= progressRatio
              : false;

            let opacity = 1;
            if (showProgress && barProgress > progressRatio) {
              const distanceFromProgress = barProgress - progressRatio;
              opacity = Math.max(0.3, 1 - distanceFromProgress * 2);
            }

            return (
              <View
                key={index}
                style={[
                  styles.barContainer,
                  {
                    width: barWidth,
                    marginRight: index < barCount - 1 ? barGap : 0,
                  },
                ]}
              >
                <Bar
                  value={value}
                  height={height}
                  width={barWidth}
                  isActive={isActive}
                  showProgress={showProgress}
                  activeColor={finalActiveColor}
                  inactiveColor={finalInactiveColor}
                  opacity={opacity}
                  isPlaying={isPlaying}
                  animated={animated}
                />
              </View>
            );
          })}
          {showProgress && (
            <View
              style={[
                styles.progressLine,
                {
                  left: getProgressLinePosition(),
                  height: height * 0.95,
                  backgroundColor: finalActiveColor,
                },
              ]}
            />
          )}
        </View>
      </GestureDetector>
    </View>
  );
}

// Helper function remains the same
function generateSampleWaveform(barCount: number): number[] {
  return Array.from({ length: barCount }, (_, i) => {
    const wave1 = Math.sin((i / barCount) * Math.PI * 4) * 0.3;
    const wave2 = Math.sin((i / barCount) * Math.PI * 8) * 0.15;
    const wave3 = Math.sin((i / barCount) * Math.PI * 2) * 0.2;
    const noise = (Math.random() - 0.5) * 0.2;
    const base = 0.4;
    const peak = Math.random() < 0.1 ? Math.random() * 0.3 : 0;
    return Math.max(
      0.1,
      Math.min(0.95, base + wave1 + wave2 + wave3 + noise + peak)
    );
  });
}

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
    position: 'relative',
  },
  waveform: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    position: 'relative',
  },
  barContainer: {
    justifyContent: 'center',
    alignItems: 'center',
    height: '100%',
  },
  bar: {
    borderRadius: 1.5,
    minHeight: 4,
  },
  progressLine: {
    position: 'absolute',
    width: 2,
    borderRadius: 1,
    opacity: 0.9,
    top: '2.5%',
    zIndex: 10,
  },
});
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import { AudioWaveform } from '@/components/ui/audio-waveform';
```

### Basic Usage

```tsx
function MyComponent() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [progress, setProgress] = useState(0);

  return (
    <AudioWaveform
      isPlaying={isPlaying}
      progress={progress}
      showProgress={true}
      height={60}
    />
  );
}
```

### With Custom Data

```tsx
function MyComponent() {
  const audioData = [0.2, 0.5, 0.8, 0.3, 0.6, 0.9, 0.4, 0.7, 0.1, 0.5];

  return (
    <AudioWaveform
      data={audioData}
      isPlaying={isPlaying}
      progress={progress}
      showProgress={true}
      onSeek={(position) => setProgress(position)}
      interactive={true}
    />
  );
}
```

### Interactive Seeking

```tsx
function AudioPlayer() {
  const [progress, setProgress] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  const [isSeeking, setIsSeeking] = useState(false);

  const handleSeek = (position: number) => {
    setProgress(position);
    // Update your audio player position here
  };

  return (
    <AudioWaveform
      isPlaying={isPlaying}
      progress={progress}
      showProgress={true}
      interactive={true}
      onSeek={handleSeek}
      onSeekStart={() => setIsSeeking(true)}
      onSeekEnd={() => setIsSeeking(false)}
      activeColor='#007AFF'
      inactiveColor='#E5E5E7'
    />
  );
}
```

## Examples

#### Default

**Example:** A basic audio waveform with playback progress

```tsx
// components/demo/audio-waveform/audio-waveform-demo.tsx
import { AudioWaveform } from '@/components/ui/audio-waveform';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useEffect, useState } from 'react';

export function AudioWaveformDemo() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [progress, setProgress] = useState(0);

  // Simulate audio playback progress
  useEffect(() => {
    let interval: ReturnType<typeof setInterval>;
    if (isPlaying) {
      interval = setInterval(() => {
        setProgress((prev) => {
          if (prev >= 100) {
            setIsPlaying(false);
            return 0;
          }
          return prev + 2;
        });
      }, 100);
    }
    return () => clearInterval(interval);
  }, [isPlaying]);

  const handleSeek = (position: number) => {
    setProgress(position);
  };

  const togglePlayback = () => {
    setIsPlaying(!isPlaying);
  };

  return (
    <View style={{ gap: 16 }}>
      <AudioWaveform
        isPlaying={isPlaying}
        progress={progress}
        showProgress={true}
        interactive={true}
        onSeek={handleSeek}
        height={60}
        barCount={40}
      />

      <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
        <Button onPress={togglePlayback} style={{ flex: 0 }}>
          {isPlaying ? 'Pause' : 'Play'}
        </Button>
        <Text variant='caption'>{Math.round(progress)}% Complete</Text>
      </View>
    </View>
  );
}
```

#### Recording Mode

**Example:** An animated waveform for recording visualization

```tsx
// components/demo/audio-waveform/audio-waveform-recording.tsx
import { AudioWaveform } from '@/components/ui/audio-waveform';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useEffect, useState } from 'react';

export function AudioWaveformRecording() {
  const [isRecording, setIsRecording] = useState(false);
  const [recordingData, setRecordingData] = useState<number[]>([]);
  const [duration, setDuration] = useState(0);

  // Simulate recording with real-time audio levels
  useEffect(() => {
    let interval: ReturnType<typeof setInterval>;
    if (isRecording) {
      interval = setInterval(() => {
        // Generate random audio level (simulating microphone input)
        const newLevel = Math.max(
          0.1,
          Math.random() * 0.9 + Math.sin(Date.now() / 200) * 0.2
        );

        setRecordingData((prev) => {
          const newData = [...prev, newLevel];
          // Keep only the last 50 data points
          return newData.slice(-50);
        });

        setDuration((prev) => prev + 0.1);
      }, 100);
    }
    return () => clearInterval(interval);
  }, [isRecording]);

  const toggleRecording = () => {
    if (!isRecording) {
      setRecordingData([]);
      setDuration(0);
    }
    setIsRecording(!isRecording);
  };

  const formatDuration = (seconds: number) => {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins}:${secs.toString().padStart(2, '0')}`;
  };

  return (
    <View style={{ gap: 16 }}>
      <View
        style={{
          backgroundColor: isRecording ? '#FF3B30' : '#34C759',
          padding: 12,
          borderRadius: 12,
          opacity: 0.1,
        }}
      />

      <AudioWaveform
        data={recordingData}
        isPlaying={isRecording}
        animated={true}
        showProgress={false}
        height={60}
        barCount={50}
        activeColor={isRecording ? '#FF3B30' : '#34C759'}
        style={{
          backgroundColor: isRecording
            ? 'rgba(255, 59, 48, 0.05)'
            : 'rgba(52, 199, 89, 0.05)',
          borderRadius: 8,
          paddingHorizontal: 8,
        }}
      />

      <View
        style={{
          flexDirection: 'row',
          alignItems: 'center',
          justifyContent: 'space-between',
        }}
      >
        <Button
          onPress={toggleRecording}
          variant={isRecording ? 'destructive' : 'default'}
        >
          {isRecording ? 'Stop Recording' : 'Start Recording'}
        </Button>
        <Text variant='caption'>{formatDuration(duration)}</Text>
      </View>

      {recordingData.length > 0 && !isRecording && (
        <Text variant='caption' style={{ textAlign: 'center', opacity: 0.6 }}>
          Tap play to preview your recording
        </Text>
      )}
    </View>
  );
}
```

#### Interactive Seeking

**Example:** A waveform with touch-based seeking functionality

```tsx
// components/demo/audio-waveform/audio-waveform-interactive.tsx
import { AudioWaveform } from '@/components/ui/audio-waveform';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useEffect, useState } from 'react';

export function AudioWaveformInteractive() {
  const [isPlaying, setIsPlaying] = useState(false);
  const [progress, setProgress] = useState(0);
  const [isSeeking, setIsSeeking] = useState(false);

  // Sample audio data - more realistic pattern
  const audioData = [
    0.2, 0.4, 0.3, 0.6, 0.8, 0.5, 0.7, 0.9, 0.4, 0.3, 0.5, 0.7, 0.6, 0.8, 0.9,
    0.7, 0.5, 0.3, 0.4, 0.6, 0.8, 0.9, 0.7, 0.5, 0.4, 0.6, 0.8, 0.7, 0.5, 0.3,
    0.4, 0.6, 0.9, 0.8, 0.6, 0.4, 0.2, 0.3, 0.5, 0.7,
  ];

  // Auto-play simulation
  useEffect(() => {
    let interval: ReturnType<typeof setInterval>;
    if (isPlaying && !isSeeking) {
      interval = setInterval(() => {
        setProgress((prev) => {
          if (prev >= 100) {
            setIsPlaying(false);
            return 100;
          }
          return prev + 1;
        });
      }, 100);
    }
    return () => clearInterval(interval);
  }, [isPlaying, isSeeking]);

  const handleSeek = (position: number) => {
    setProgress(position);
  };

  const handleSeekStart = () => {
    setIsSeeking(true);
  };

  const handleSeekEnd = () => {
    setIsSeeking(false);
  };

  const togglePlayback = () => {
    setIsPlaying(!isPlaying);
  };

  const formatTime = (percentage: number) => {
    const totalSeconds = 180; // 3 minutes total
    const currentSeconds = (percentage / 100) * totalSeconds;
    const minutes = Math.floor(currentSeconds / 60);
    const seconds = Math.floor(currentSeconds % 60);
    return `${minutes}:${seconds.toString().padStart(2, '0')}`;
  };

  return (
    <View style={{ gap: 16 }}>
      <AudioWaveform
        data={audioData}
        isPlaying={isPlaying}
        progress={progress}
        showProgress={true}
        interactive={true}
        onSeek={handleSeek}
        onSeekStart={handleSeekStart}
        onSeekEnd={handleSeekEnd}
        height={80}
        barCount={40}
        barWidth={4}
        barGap={2}
        activeColor='#007AFF'
        inactiveColor='#E5E5E7'
      />

      <View
        style={{
          flexDirection: 'row',
          justifyContent: 'space-between',
          alignItems: 'center',
          marginTop: 12,
        }}
      >
        <Text variant='caption' style={{ opacity: 0.6 }}>
          {formatTime(progress)}
        </Text>
        <Text variant='caption' style={{ opacity: 0.6 }}>
          3:00
        </Text>
      </View>

      <View style={{ flexDirection: 'row', gap: 12, alignItems: 'center' }}>
        <Button onPress={togglePlayback}>{isPlaying ? 'Pause' : 'Play'}</Button>

        <Button
          variant='outline'
          onPress={() => setProgress(0)}
          disabled={progress === 0}
        >
          Reset
        </Button>

        {isSeeking && (
          <Text variant='caption' style={{ color: '#007AFF' }}>
            Seeking...
          </Text>
        )}
      </View>
    </View>
  );
}
```

#### Custom Styling

**Example:** A waveform with custom colors and dimensions

```tsx
// components/demo/audio-waveform/audio-waveform-styled.tsx
import { AudioWaveform } from '@/components/ui/audio-waveform';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function AudioWaveformStyled() {
  const [isPlaying1, setIsPlaying1] = useState(false);
  const [isPlaying2, setIsPlaying2] = useState(false);
  const [isPlaying3, setIsPlaying3] = useState(false);
  const [progress1, setProgress1] = useState(35);
  const [progress2, setProgress2] = useState(60);
  const [progress3, setProgress3] = useState(80);

  const musicData = [
    0.1, 0.3, 0.5, 0.4, 0.6, 0.8, 0.7, 0.9, 0.6, 0.4, 0.5, 0.7, 0.8, 0.9, 0.7,
    0.5, 0.3, 0.4, 0.6, 0.8, 0.9, 0.7, 0.5, 0.4, 0.6, 0.8, 0.7, 0.5, 0.3, 0.4,
  ];

  const voiceData = [
    0.2, 0.4, 0.3, 0.5, 0.4, 0.6, 0.5, 0.7, 0.4, 0.3, 0.5, 0.4, 0.6, 0.5, 0.4,
    0.3, 0.4, 0.5, 0.6, 0.4, 0.3, 0.5, 0.4, 0.3, 0.4, 0.5, 0.4, 0.3, 0.2, 0.3,
  ];

  const podcastData = [
    0.3, 0.5, 0.4, 0.6, 0.5, 0.4, 0.6, 0.7, 0.5, 0.4, 0.6, 0.5, 0.7, 0.6, 0.5,
    0.4, 0.5, 0.6, 0.7, 0.5, 0.4, 0.6, 0.5, 0.4, 0.5, 0.6, 0.5, 0.4, 0.3, 0.4,
  ];

  return (
    <View style={{ gap: 24 }}>
      {/* Music Style - Vibrant gradient colors */}
      <View
        style={{
          padding: 16,
          backgroundColor: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
          borderRadius: 16,
          shadowColor: '#667eea',
          shadowOffset: { width: 0, height: 4 },
          shadowOpacity: 0.3,
          shadowRadius: 8,
        }}
      >
        <Text
          variant='body'
          style={{ color: 'white', marginBottom: 12, fontWeight: 'bold' }}
        >
          🎵 Music Track
        </Text>
        <AudioWaveform
          data={musicData}
          isPlaying={isPlaying1}
          progress={progress1}
          showProgress={true}
          interactive={true}
          onSeek={setProgress1}
          height={60}
          barCount={30}
          barWidth={4}
          barGap={3}
          activeColor='#FFD700'
          inactiveColor='rgba(255, 255, 255, 0.3)'
        />
        <View
          style={{
            flexDirection: 'row',
            justifyContent: 'space-between',
            marginTop: 12,
          }}
        >
          <Button
            onPress={() => setIsPlaying1(!isPlaying1)}
            style={{ backgroundColor: 'rgba(255, 255, 255, 0.2)' }}
          >
            {isPlaying1 ? '⏸️' : '▶️'}
          </Button>
          <Text style={{ color: 'white', opacity: 0.8 }}>2:15 / 3:45</Text>
        </View>
      </View>

      {/* Voice Message Style - Clean and minimal */}
      <View
        style={{
          padding: 16,
          backgroundColor: 'teal',
          borderRadius: 12,
          borderLeftWidth: 4,
          borderLeftColor: '#34C759',
        }}
      >
        <Text variant='body' style={{ marginBottom: 12, fontWeight: '600' }}>
          🎙️ Voice Message
        </Text>
        <AudioWaveform
          data={voiceData}
          isPlaying={isPlaying2}
          progress={progress2}
          showProgress={true}
          interactive={true}
          onSeek={setProgress2}
          height={40}
          barCount={25}
          barWidth={3}
          barGap={2}
          activeColor='#34C759'
          inactiveColor='#E5E5E7'
        />
        <View
          style={{
            flexDirection: 'row',
            justifyContent: 'space-between',
            alignItems: 'center',
            marginTop: 8,
          }}
        >
          <Button
            onPress={() => setIsPlaying2(!isPlaying2)}
            variant='outline'
            style={{
              borderColor: '#34C759',
              borderRadius: 20,
              paddingHorizontal: 16,
            }}
          >
            {isPlaying2 ? 'Pause' : 'Play'}
          </Button>
          <Text variant='caption'>0:45</Text>
        </View>
      </View>

      {/* Podcast Style - Professional dark theme */}
      <View
        style={{
          padding: 16,
          backgroundColor: '#1C1C1E',
          borderRadius: 12,
          borderWidth: 1,
          borderColor: '#2C2C2E',
        }}
      >
        <Text
          variant='body'
          style={{ color: 'white', marginBottom: 12, fontWeight: 'bold' }}
        >
          🎧 Podcast Episode
        </Text>
        <AudioWaveform
          data={podcastData}
          isPlaying={isPlaying3}
          progress={progress3}
          showProgress={true}
          interactive={true}
          onSeek={setProgress3}
          height={70}
          barCount={35}
          barWidth={2}
          barGap={1}
          activeColor='#FF6B6B'
          inactiveColor='#48484A'
        />
        <View
          style={{
            flexDirection: 'row',
            justifyContent: 'space-between',
            alignItems: 'center',
            marginTop: 12,
          }}
        >
          <View style={{ flexDirection: 'row', gap: 8 }}>
            <Button
              onPress={() => setIsPlaying3(!isPlaying3)}
              style={{
                backgroundColor: '#FF6B6B',
                borderRadius: 8,
                paddingHorizontal: 12,
              }}
            >
              {isPlaying3 ? '⏸️' : '▶️'}
            </Button>
            <Button
              variant='outline'
              style={{
                borderColor: '#48484A',
                borderRadius: 8,
              }}
            >
              1.5x
            </Button>
          </View>
          <Text style={{ color: 'white', opacity: 0.6 }}>45:30 / 58:15</Text>
        </View>
      </View>
    </View>
  );
}
```

#### Real-time Data

**Example:** A waveform that updates with real-time audio data

```tsx
// components/demo/audio-waveform/audio-waveform-realtime.tsx
import { AudioWaveform } from '@/components/ui/audio-waveform';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useEffect, useState } from 'react';

export function AudioWaveformRealtime() {
  const [isActive, setIsActive] = useState(false);
  const [realtimeData, setRealtimeData] = useState<number[]>([]);
  const [frequency, setFrequency] = useState(1);
  const [amplitude, setAmplitude] = useState(0.5);

  // Simulate real-time audio data with different patterns
  useEffect(() => {
    let interval: ReturnType<typeof setInterval>;
    if (isActive) {
      interval = setInterval(() => {
        const time = Date.now() / 1000;

        // Generate different wave patterns based on frequency and amplitude
        let newLevel = 0;

        if (frequency === 1) {
          // Sine wave
          newLevel = Math.abs(Math.sin(time * 2) * amplitude);
        } else if (frequency === 2) {
          // Multiple frequencies combined
          newLevel = Math.abs(
            ((Math.sin(time * 3) +
              Math.sin(time * 1.5) +
              Math.sin(time * 0.8)) /
              3) *
              amplitude
          );
        } else {
          // Random with trend
          newLevel = Math.max(
            0,
            Math.min(1, Math.random() * amplitude + Math.sin(time * 0.5) * 0.3)
          );
        }

        setRealtimeData((prev) => {
          const newData = [...prev, newLevel];
          // Keep only the last 60 data points for smooth animation
          return newData.slice(-60);
        });
      }, 50);
    }
    return () => clearInterval(interval);
  }, [isActive, frequency, amplitude]);

  const resetData = () => {
    setRealtimeData([]);
  };

  const patternButtons = [
    { id: 1, label: 'Sine Wave', color: '#007AFF' },
    { id: 2, label: 'Complex', color: '#34C759' },
    { id: 3, label: 'Random', color: '#FF9500' },
  ];

  return (
    <View style={{ gap: 16 }}>
      <View
        style={{
          padding: 16,
          backgroundColor: isActive
            ? 'rgba(0, 122, 255, 0.05)'
            : 'rgba(142, 142, 147, 0.05)',
          borderRadius: 12,
          borderWidth: 2,
          borderColor: isActive ? '#007AFF' : '#E5E5E7',
          borderStyle: isActive ? 'solid' : 'dashed',
        }}
      >
        <Text variant='body' style={{ marginBottom: 12, fontWeight: 'bold' }}>
          Real-time Audio Visualization
        </Text>

        <AudioWaveform
          data={realtimeData}
          isPlaying={isActive}
          animated={true}
          showProgress={false}
          height={80}
          barCount={60}
          barWidth={2}
          barGap={1}
          activeColor={
            frequency === 1
              ? '#007AFF'
              : frequency === 2
                ? '#34C759'
                : '#FF9500'
          }
          inactiveColor='#E5E5E7'
        />

        <View
          style={{
            flexDirection: 'row',
            justifyContent: 'space-between',
            alignItems: 'center',
            marginTop: 12,
          }}
        >
          <Text variant='caption' style={{ opacity: 0.6 }}>
            {realtimeData.length} data points
          </Text>
          <Text variant='caption' style={{ opacity: 0.6 }}>
            {isActive ? 'Live' : 'Stopped'}
          </Text>
        </View>
      </View>

      {/* Controls */}
      <View style={{ gap: 12 }}>
        <View style={{ flexDirection: 'row', gap: 8 }}>
          <Button
            onPress={() => setIsActive(!isActive)}
            style={{
              backgroundColor: isActive ? '#FF3B30' : '#34C759',
              flex: 1,
            }}
          >
            {isActive ? 'Stop Stream' : 'Start Stream'}
          </Button>
          <Button
            onPress={resetData}
            variant='outline'
            disabled={realtimeData.length === 0}
          >
            Clear
          </Button>
        </View>

        {/* Pattern Selection */}
        <View>
          <Text variant='caption' style={{ marginBottom: 8, opacity: 0.7 }}>
            Wave Pattern:
          </Text>
          <View style={{ flexDirection: 'row', gap: 8 }}>
            {patternButtons.map((pattern) => (
              <Button
                key={pattern.id}
                onPress={() => setFrequency(pattern.id)}
                variant={frequency === pattern.id ? 'default' : 'outline'}
                style={{
                  flex: 1,
                  backgroundColor:
                    frequency === pattern.id ? pattern.color : 'transparent',
                  borderColor: pattern.color,
                }}
              >
                <Text
                  style={{
                    color: frequency === pattern.id ? 'white' : pattern.color,
                    fontSize: 12,
                  }}
                >
                  {pattern.label}
                </Text>
              </Button>
            ))}
          </View>
        </View>

        {/* Amplitude Control */}
        <View>
          <Text variant='caption' style={{ marginBottom: 8, opacity: 0.7 }}>
            Amplitude: {Math.round(amplitude * 100)}%
          </Text>
          <View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
            <Button
              size='icon'
              variant='outline'
              onPress={() => setAmplitude(Math.max(0.1, amplitude - 0.1))}
            >
              -
            </Button>
            <View
              style={{
                flex: 1,
                height: 4,
                backgroundColor: '#E5E5E7',
                borderRadius: 2,
                overflow: 'hidden',
              }}
            >
              <View
                style={{
                  width: `${amplitude * 100}%`,
                  height: '100%',
                  backgroundColor: '#007AFF',
                }}
              />
            </View>
            <Button
              size='icon'
              variant='outline'
              onPress={() => setAmplitude(Math.min(1, amplitude + 0.1))}
            >
              +
            </Button>
          </View>
        </View>
      </View>

      <View
        style={{
          padding: 12,
          backgroundColor: 'rgba(255, 149, 0, 0.05)',
          borderRadius: 8,
        }}
      >
        <Text variant='caption' style={{ textAlign: 'center', opacity: 0.7 }}>
          Simulates real-time audio input with different wave patterns
        </Text>
      </View>
    </View>
  );
}
```

#### Compact Size

**Example:** A smaller waveform suitable for chat messages

```tsx
// components/demo/audio-waveform/audio-waveform-compact.tsx
import { AudioWaveform } from '@/components/ui/audio-waveform';
import { Button } from '@/components/ui/button';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function AudioWaveformCompact() {
  const [isPlaying1, setIsPlaying1] = useState(false);
  const [isPlaying2, setIsPlaying2] = useState(false);
  const [isPlaying3, setIsPlaying3] = useState(false);
  const [progress1, setProgress1] = useState(20);
  const [progress2, setProgress2] = useState(45);
  const [progress3, setProgress3] = useState(70);

  // Compact chat message data
  const messageData1 = [
    0.3, 0.5, 0.4, 0.6, 0.3, 0.4, 0.5, 0.3, 0.4, 0.6, 0.5, 0.3, 0.4, 0.5, 0.3,
  ];
  const messageData2 = [
    0.2, 0.4, 0.6, 0.5, 0.3, 0.5, 0.4, 0.6, 0.3, 0.4, 0.5, 0.4, 0.3, 0.2, 0.3,
  ];
  const messageData3 = [
    0.4, 0.6, 0.5, 0.7, 0.4, 0.3, 0.5, 0.6, 0.4, 0.5, 0.6, 0.5, 0.4, 0.3, 0.4,
  ];

  const MessageBubble = ({
    data,
    isPlaying,
    setIsPlaying,
    progress,
    setProgress,
    duration,
    sent = false,
  }: {
    data: number[];
    isPlaying: boolean;
    setIsPlaying: (playing: boolean) => void;
    progress: number;
    setProgress: (progress: number) => void;
    duration: string;
    sent?: boolean;
  }) => (
    <View
      style={{
        flexDirection: 'row',
        alignItems: 'center',
        alignSelf: sent ? 'flex-end' : 'flex-start',
        backgroundColor: sent ? '#007AFF' : '#F0F0F0',
        borderRadius: 18,
        padding: 8,
        maxWidth: '80%',
        gap: 8,
      }}
    >
      <Button
        onPress={() => setIsPlaying(!isPlaying)}
        variant='ghost'
        size='icon'
      >
        {isPlaying ? '⏸️' : '▶️'}
      </Button>

      <View style={{ flex: 1 }}>
        <AudioWaveform
          data={data}
          isPlaying={isPlaying}
          progress={progress}
          showProgress={true}
          interactive={true}
          onSeek={setProgress}
          height={24}
          barCount={15}
          barWidth={2}
          barGap={1}
          activeColor={sent ? 'white' : '#007AFF'}
          inactiveColor={sent ? 'rgba(255, 255, 255, 0.4)' : '#C7C7CC'}
        />
      </View>

      <Text
        style={{
          color: sent ? 'rgba(255, 255, 255, 0.8)' : '#8E8E93',
          fontSize: 11,
          minWidth: 28,
        }}
      >
        {duration}
      </Text>
    </View>
  );

  return (
    <View style={{ gap: 16 }}>
      <View style={{ gap: 12 }}>
        <MessageBubble
          data={messageData1}
          isPlaying={isPlaying1}
          setIsPlaying={setIsPlaying1}
          progress={progress1}
          setProgress={setProgress1}
          duration='0:12'
          sent={false}
        />

        <MessageBubble
          data={messageData2}
          isPlaying={isPlaying2}
          setIsPlaying={setIsPlaying2}
          progress={progress2}
          setProgress={setProgress2}
          duration='0:08'
          sent={true}
        />

        <MessageBubble
          data={messageData3}
          isPlaying={isPlaying3}
          setIsPlaying={setIsPlaying3}
          progress={progress3}
          setProgress={setProgress3}
          duration='0:15'
          sent={false}
        />
      </View>
    </View>
  );
}
```

## API Reference

### AudioWaveform

The main AudioWaveform component.

| Prop            | Type                         | Default     | Description                                                       |
| --------------- | ---------------------------- | ----------- | ----------------------------------------------------------------- |
| `data`          | `number[]`                   | -           | Audio amplitude data (0-1 range). Auto-generated if not provided. |
| `isPlaying`     | `boolean`                    | `false`     | Whether the audio is currently playing.                           |
| `progress`      | `number`                     | `0`         | Current playback progress (0-100).                                |
| `onSeek`        | `(position: number) => void` | -           | Callback fired when user seeks to a position.                     |
| `onSeekStart`   | `() => void`                 | -           | Callback fired when seeking starts.                               |
| `onSeekEnd`     | `() => void`                 | -           | Callback fired when seeking ends.                                 |
| `style`         | `ViewStyle`                  | -           | Additional styles for the container.                              |
| `height`        | `number`                     | `60`        | Height of the waveform in pixels.                                 |
| `barCount`      | `number`                     | `50`        | Number of bars in the waveform.                                   |
| `barWidth`      | `number`                     | `3`         | Width of each bar in pixels.                                      |
| `barGap`        | `number`                     | `2`         | Gap between bars in pixels.                                       |
| `activeColor`   | `string`                     | theme color | Color for played/active portion.                                  |
| `inactiveColor` | `string`                     | theme color | Color for unplayed/inactive portion.                              |
| `animated`      | `boolean`                    | `true`      | Whether to animate the waveform during playback.                  |
| `showProgress`  | `boolean`                    | `false`     | Whether to show progress indicator.                               |
| `interactive`   | `boolean`                    | `false`     | Whether to enable touch-based seeking.                            |

## Usage Patterns

### Audio Player Integration

```tsx
function AudioPlayer({ audioUrl }: { audioUrl: string }) {
  const [isPlaying, setIsPlaying] = useState(false);
  const [progress, setProgress] = useState(0);
  const [duration, setDuration] = useState(0);
  const [audioData, setAudioData] = useState<number[]>([]);

  // Load audio data from file
  useEffect(() => {
    loadAudioWaveform(audioUrl).then(setAudioData);
  }, [audioUrl]);

  const handleSeek = (position: number) => {
    const newTime = (position / 100) * duration;
    // Seek audio to newTime
    setProgress(position);
  };

  return (
    <View>
      <AudioWaveform
        data={audioData}
        isPlaying={isPlaying}
        progress={progress}
        showProgress={true}
        interactive={true}
        onSeek={handleSeek}
        height={80}
        activeColor='#007AFF'
      />
      <Button onPress={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </Button>
    </View>
  );
}
```

### Voice Message

```tsx
function VoiceMessage({ duration, audioData }: VoiceMessageProps) {
  const [isPlaying, setIsPlaying] = useState(false);
  const [progress, setProgress] = useState(0);

  return (
    <View style={styles.voiceMessage}>
      <TouchableOpacity onPress={() => setIsPlaying(!isPlaying)}>
        <Icon name={isPlaying ? 'pause' : 'play'} />
      </TouchableOpacity>

      <AudioWaveform
        data={audioData}
        isPlaying={isPlaying}
        progress={progress}
        showProgress={true}
        interactive={true}
        height={32}
        barCount={30}
        barWidth={2}
        barGap={1}
        style={{ flex: 1, marginHorizontal: 12 }}
      />

      <Text>{formatDuration(duration)}</Text>
    </View>
  );
}
```

### Recording Visualization

```tsx
function RecordingView() {
  const [isRecording, setIsRecording] = useState(false);
  const [recordingData, setRecordingData] = useState<number[]>([]);

  // Update waveform with real-time audio levels
  useEffect(() => {
    if (isRecording) {
      const interval = setInterval(() => {
        const newLevel = getCurrentAudioLevel(); // Your audio level function
        setRecordingData((prev) => [...prev.slice(-49), newLevel]);
      }, 100);

      return () => clearInterval(interval);
    }
  }, [isRecording]);

  return (
    <View>
      <AudioWaveform
        data={recordingData}
        isPlaying={isRecording}
        animated={true}
        showProgress={false}
        height={60}
        barCount={50}
        activeColor='#FF3B30'
      />
      <Button onPress={() => setIsRecording(!isRecording)}>
        {isRecording ? 'Stop' : 'Record'}
      </Button>
    </View>
  );
}
```

## Accessibility

The AudioWaveform component follows accessibility best practices:

- Proper touch target sizes for interactive elements
- Screen reader support for progress information
- Respects system accessibility settings
- Keyboard navigation support where applicable

## Performance

The component is optimized for performance:

- Uses `react-native-reanimated` for smooth native animations
- Efficient rendering with minimal re-renders
- Configurable bar count to balance detail vs performance
- Native driver animations where possible

## Theming

The AudioWaveform automatically adapts to your app's theme:

- Uses theme colors by default for active/inactive states
- Supports both light and dark modes
- Customizable colors through props
- Respects theme opacity values

## Data Format

Audio data should be provided as an array of numbers between 0 and 1:

```tsx
const audioData = [
  0.1, // Very quiet
  0.3, // Quiet
  0.5, // Medium
  0.8, // Loud
  1.0, // Maximum
];
```

If no data is provided, the component generates realistic sample data automatically.
