# Project export: AcousticPR

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2025
- Tagline: Virtual speech therapist that uses multidimensional speech analysis and personalized insights to build communication habits that empower confidence, clarity, and mental well-being through every word.
- Devpost: https://devpost.com/software/acousticpr
- GitHub: https://github.com/natelincyber/AcousticAI.git
- Team: 1 GitHub contributor(s) — natelincyber (8 commits)

## Devpost submission (written by the team)

### Inspiration

Effective communication is a critical life skill—one that influences everything from relationships to professional opportunities. Yet, many people struggle with speech anxiety, filler words, stuttering, and unclear emotional expression. We were inspired to build AcousticPR after seeing friends and colleagues with brilliant ideas struggle to express themselves due to poor communication habits. AcousticPR offers a data-driven practice environment to help users break unproductive habits, build confidence, and strengthen their public speaking skills. By acting like a personal speech therapist, it not only improves communication but also supports your personal and social mental well-being. Your voice is your life. Take control of it today.

### What it does

AcousticPR is a personalized speech analytics and coaching platform. It allows users to upload voice recordings, then analyzes their communication across several dimensions: Transcription & Word Usage Transcription & Word Usage Emotion Analysis Emotion Analysis Pitch Variation & Pace Pitch Variation & Pace Stuttering & Filler Word Detection Stuttering & Filler Word Detection Top Word Frequency & Repetition Top Word Frequency & Repetition Using these metrics, it delivers actionable feedback, highlighting emotional tone, disfluencies, pacing, and more. Users receive tailored llm-driven insights and visual graphs to guide improvement, making communication practice measurable and motivating.

### How we built it

We used a full-stack architecture focused on performance, usability, and advanced AI/ML: Frontend: React + Tailwind.js for a clean, responsive, and gamified UI Frontend: React + Tailwind.js for a clean, responsive, and gamified UI Backend: Flask + Socket.IO to support real-time feedback and REST APIs Backend: Flask + Socket.IO to support real-time feedback and REST APIs File Handling: .webm audio + wave for efficient client-to-server transfer (even for long recordings) File Handling: .webm audio + wave for efficient client-to-server transfer (even for long recordings) Speech Processing Pipeline Transcription: Utilized OpenAI’s Whisper API for high-accuracy, punctuated speech-to-text. Emotion Analysis: Used SpeechBrain’s emotion-diarization-wavlm-large model, trained on diverse datasets (IEMOCAP, MELD, MSP-IMPROV, MOSEI, RAVDESS), for detecting emotional tone in voice. We quantized the model for faster inference and ran it with GPU parallelism to support near real-time feedback. Audio Feature Extraction: Pitch analysis via Librosa Pitch analysis via Librosa Speech rate computed from total syllables and duration Speech rate computed from total syllables and duration Disfluencies (stuttering, filler words, etc.) detected with custom regex-based NLP pipelines Disfluencies (stuttering, filler words, etc.) detected with custom regex-based NLP pipelines Insight Generation: Used ChatGPT-4o mini via OpenAI API to turn raw data into qualitative, structured feedback Used ChatGPT-4o mini via OpenAI API to turn raw data into qualitative, structured feedback Used LlamaIndex with Pydantic to organize metrics and generate repeatable, prompt-engineered insights Used LlamaIndex with Pydantic to organize metrics and generate repeatable, prompt-engineered insights Visualization: Integrated Chart.js to plot user metrics and progress in a visually engaging format

### Challenges we ran into

Latency in heavy ML models: Emotion detection and transcription are both computationally intensive. To resolve this, we parallelized processing tasks using thread pools and ran all ML inference on GPUs. Unstructured-to-structured NLP: Turning audio into truly helpful feedback (not just raw stats) required careful prompt engineering, pedantic data modeling, and experimentation with LLMs. Handling large audio uploads: Transferring multi-minute recordings quickly and reliably was solved using .webm compression and efficient upload handling via Flask. Consistency in Feedback: We needed to balance personalized advice with repeatable metrics, which required multiple design iterations on our LLM + LlamaIndex pipeline.

### Accomplishments we're proud of

Achieving near real-time feedback on 5+ minute audio files with GPU-accelerated, parallelized processing Achieving near real-time feedback on 5+ minute audio files with GPU-accelerated, parallelized processing Training and deploying a custom emotion analysis model from multiple datasets Training and deploying a custom emotion analysis model from multiple datasets Seamlessly integrating LLM insights into our app to deliver genuinely actionable advice Seamlessly integrating LLM insights into our app to deliver genuinely actionable advice Designing a gamified UI that makes practicing communication feel fun Designing a gamified UI that makes practicing communication feel fun

### What we learned

Speech is a really complex signal, both acoustically and emotionally. Analyzing it meaningfully requires a blend of audio processing, NLP, and creativity. Speech is a really complex signal, both acoustically and emotionally. Analyzing it meaningfully requires a blend of audio processing, NLP, and creativity. Optimization matters: parallel processing, quantization, and efficient file handling were key to creating a fast, smooth UX. Optimization matters: parallel processing, quantization, and efficient file handling were key to creating a fast, smooth UX. LLMs are really powerful, but only when guided by strong structure and quality prompts. LLMs are really powerful, but only when guided by strong structure and quality prompts.

### What's next

Live Feedback Mode: Enable users to receive analysis and coaching in real-time as they speak. Camera based face and posture tracking for real time visual emotion indicators AcousticPR is more than just a speech analyzer. It’s a personal communication coach designed to help you unlock confident, clear expression while improving your mental well-being through self-awareness and growth.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 108 recognized source files, 264 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code
- LlamaIndex (technology) — claimed on Devpost, not found in the code
- PyTorch (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 120)

```
.gitignore
app.py
app/api/status/route.ts
app/api/upload/route.ts
lib/analysis.ts
lib/audio-features.ts
lib/constants.ts
lib/emotion.ts
lib/ffmpeg.ts
lib/memory-store.ts
lib/whisper.ts
next-env.d.ts
package.json
reactapp/.gitignore
reactapp/bun.lockb
reactapp/components.json
reactapp/eslint.config.js
reactapp/index.html
reactapp/package.json
reactapp/postcss.config.js
reactapp/public/recorder-worker.js
reactapp/public/robots.txt
reactapp/README.md
reactapp/src/App.css
reactapp/src/App.tsx
reactapp/src/components/AudioVisualization.jsx
reactapp/src/components/AudioWaves.jsx
reactapp/src/components/cassette/Background.jsx
reactapp/src/components/cassette/Buttons.jsx
reactapp/src/components/cassette/Cassette.jsx
reactapp/src/components/cassette/Center.jsx
reactapp/src/components/cassette/index.jsx
reactapp/src/components/cassette/Timebox.tsx
reactapp/src/components/cassette/TrackTitle.jsx
reactapp/src/components/CassetteTape.tsx
reactapp/src/components/charts/EmotionsChart.tsx
reactapp/src/components/charts/FillerWordsChart.tsx
reactapp/src/components/charts/FrequentWordsChart.tsx
reactapp/src/components/charts/RecordingButton.tsx
reactapp/src/components/charts/SpeechAdvice.tsx
reactapp/src/components/charts/SpeechRateChart.tsx
reactapp/src/components/charts/StutterWordsChart.tsx
reactapp/src/components/charts/TranscriptDisplay.tsx
reactapp/src/components/recorder/audio-recorder.tsx
reactapp/src/components/recorder/recorder.js
reactapp/src/components/RecordingControls.tsx
reactapp/src/components/TranscriptionDisplay.tsx
reactapp/src/components/ui/accordion.tsx
reactapp/src/components/ui/alert-dialog.tsx
reactapp/src/components/ui/alert.tsx
reactapp/src/components/ui/aspect-ratio.tsx
reactapp/src/components/ui/avatar.tsx
reactapp/src/components/ui/badge.tsx
reactapp/src/components/ui/breadcrumb.tsx
reactapp/src/components/ui/button.tsx
reactapp/src/components/ui/calendar.tsx
reactapp/src/components/ui/card.tsx
reactapp/src/components/ui/carousel.tsx
reactapp/src/components/ui/chart.tsx
reactapp/src/components/ui/checkbox.tsx
reactapp/src/components/ui/collapsible.tsx
reactapp/src/components/ui/command.tsx
reactapp/src/components/ui/context-menu.tsx
reactapp/src/components/ui/dialog.tsx
reactapp/src/components/ui/drawer.tsx
reactapp/src/components/ui/dropdown-menu.tsx
reactapp/src/components/ui/form.tsx
reactapp/src/components/ui/hover-card.tsx
reactapp/src/components/ui/input-otp.tsx
reactapp/src/components/ui/input.tsx
reactapp/src/components/ui/label.tsx
reactapp/src/components/ui/menubar.tsx
reactapp/src/components/ui/navigation-menu.tsx
reactapp/src/components/ui/pagination.tsx
reactapp/src/components/ui/popover.tsx
reactapp/src/components/ui/progress.tsx
reactapp/src/components/ui/radio-group.tsx
reactapp/src/components/ui/resizable.tsx
reactapp/src/components/ui/scroll-area.tsx
reactapp/src/components/ui/select.tsx
reactapp/src/components/ui/separator.tsx
reactapp/src/components/ui/sheet.tsx
reactapp/src/components/ui/sidebar.tsx
reactapp/src/components/ui/skeleton.tsx
reactapp/src/components/ui/slider.tsx
reactapp/src/components/ui/sonner.tsx
reactapp/src/components/ui/switch.tsx
reactapp/src/components/ui/table.tsx
reactapp/src/components/ui/tabs.tsx
reactapp/src/components/ui/textarea.tsx
reactapp/src/components/ui/toast.tsx
reactapp/src/components/ui/toaster.tsx
reactapp/src/components/ui/toggle-group.tsx
reactapp/src/components/ui/toggle.tsx
reactapp/src/components/ui/tooltip.tsx
reactapp/src/components/ui/use-toast.ts
reactapp/src/components/visualization/sketch.js
reactapp/src/components/Wrapper.js
reactapp/src/core/ThemeContext.js
reactapp/src/hooks/use-mobile.tsx
reactapp/src/hooks/use-toast.ts
reactapp/src/index.css
reactapp/src/lib/utils.ts
reactapp/src/main.tsx
reactapp/src/pages/Index.tsx
reactapp/src/pages/NotFound.tsx
reactapp/src/vite-env.d.ts
reactapp/tailwind.config.ts
reactapp/tsconfig.app.json
reactapp/tsconfig.json
reactapp/tsconfig.node.json
reactapp/vite.config.ts
requirements.txt
tsconfig.json
types/fluent-ffmpeg.d.ts
types/wav-decoder.d.ts
util/data.py
util/emotionClassifier.py
util/nlp.py
util/transcript.py
```

### Dependencies

- package.json: @types/formidable@^3.4.5, @types/react@19.1.2, fluent-ffmpeg@^2.1.3, formidable@^3.5.2, next@^15.3.0, onnxruntime-node@^1.21.0, openai@^4.93.0, pitchfinder@^2.3.2, ts-node@^10.9.2, typescript@^4.9.5, wav-decoder@^1.3.0
- reactapp/package.json: @eslint/js@^9.9.0, @hookform/resolvers@^3.9.0, @radix-ui/react-accordion@^1.2.0, @radix-ui/react-alert-dialog@^1.1.1, @radix-ui/react-aspect-ratio@^1.1.0, @radix-ui/react-avatar@^1.1.0, @radix-ui/react-checkbox@^1.1.1, @radix-ui/react-collapsible@^1.1.0, @radix-ui/react-context-menu@^2.2.1, @radix-ui/react-dialog@^1.1.2, @radix-ui/react-dropdown-menu@^2.1.1, @radix-ui/react-hover-card@^1.1.1, @radix-ui/react-label@^2.1.0, @radix-ui/react-menubar@^1.1.1, @radix-ui/react-navigation-menu@^1.2.0, @radix-ui/react-popover@^1.1.1, @radix-ui/react-progress@^1.1.0, @radix-ui/react-radio-group@^1.2.0, @radix-ui/react-scroll-area@^1.1.0, @radix-ui/react-select@^2.1.1, @radix-ui/react-separator@^1.1.0, @radix-ui/react-slider@^1.2.0, @radix-ui/react-slot@^1.1.0, @radix-ui/react-switch@^1.1.0, @radix-ui/react-tabs@^1.1.0, @radix-ui/react-toast@^1.2.1, @radix-ui/react-toggle@^1.1.0, @radix-ui/react-toggle-group@^1.1.0, @radix-ui/react-tooltip@^1.1.4, @tailwindcss/typography@^0.5.15, @tanstack/react-query@^5.56.2, @types/node@^22.5.5, @types/react@^18.3.3, @types/react-dom@^18.3.0, @types/styled-components@^5.1.34, @vitejs/plugin-react-swc@^3.5.0, autoprefixer@^10.4.20, caniuse-lite@^1.0.30001713, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@^1.0.0, date-fns@^3.6.0, embla-carousel-react@^8.3.0, eslint@^9.9.0, eslint-plugin-react-hooks@^5.1.0-rc.0, eslint-plugin-react-refresh@^0.4.9, framer-motion@^12.6.5, globals@^15.9.0, input-otp@^1.2.4, lovable-tagger@^1.1.7, lucide-react@^0.462.0, next-themes@^0.3.0, p5@^1.11.3, popmotion@^8.7.4, postcss@^8.4.47, react@^18.3.1, react-day-picker@^8.10.1, react-dom@^18.3.1, react-hook-form@^7.53.0, react-p5-wrapper@^4.1.2, react-resizable-panels@^2.1.3, react-router-dom@^6.26.2, recharts@^2.12.7, socket.io-client@^4.8.1, sonner@^1.5.0, styled-components@^6.1.17, styled-normalize@^8.0.6, tailwind-merge@^2.5.2, tailwindcss@^3.4.11, tailwindcss-animate@^1.0.7, typescript@^5.5.3, typescript-eslint@^8.0.1, vaul@^0.9.3, vite@^5.4.18, zod@^3.23.8

### Recent commits (newest first)

- updated constants
- finished next.js version of backend
- next.js backend
- analysis
- api
- modified:   ../.gitignore
- DONE
- req
- requirements.txt
- nlp done
- changes
- changes
- initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "acousticai",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "test": "ts-node --esm tests/test-backend.ts"
  },
  "dependencies": {
    "fluent-ffmpeg": "^2.1.3",
    "formidable": "^3.5.2",
    "next": "^15.3.0",
    "onnxruntime-node": "^1.21.0",
    "openai": "^4.93.0",
    "pitchfinder": "^2.3.2",
    "wav-decoder": "^1.3.0"
  },
  "devDependencies": {
    "@types/formidable": "^3.4.5",
    "@types/react": "19.1.2",
    "ts-node": "^10.9.2",
    "typescript": "^4.9.5"
  }
}

```

### requirements.txt

```
��a b s l - p y = = 2 . 2 . 2  
 a i o h a p p y e y e b a l l s   @   f i l e : / / / C : / b / a b s _ e 9 f m 5 m k 6 a k / c r o o t / a i o h a p p y e y e b a l l s _ 1 7 3 4 4 6 9 4 2 9 3 5 0 / w o r k  
 a i o h t t p   @   f i l e : / / / C : / b / a b s _ d 4 _ g d j f f a v / c r o o t / a i o h t t p _ 1 7 3 4 6 8 7 1 4 6 7 8 8 / w o r k  
 a i o s i g n a l   @   f i l e : / / / t m p / b u i l d / 8 0 7 5 4 a f 9 / a i o s i g n a l _ 1 6 3 7 8 4 3 0 6 1 3 7 2 / w o r k  
 a n n o t a t e d - t y p e s   @   f i l e : / / / C : / b / a b s _ 0 d m a o y h h j 3 / c r o o t / a n n o t a t e d - t y p e s _ 1 7 0 9 5 4 2 9 6 8 3 1 1 / w o r k  
 a n y i o   @   f i l e : / / / C : / b / a b s _ 3 1 g r v 7 a 7 b l / c r o o t / a n y i o _ 1 7 2 9 1 2 1 2 9 0 9 9 2 / w o r k  
 a s t u n p a r s e = = 1 . 6 . 3  
 a t t r s   @   f i l e : / / / C : / b / a b s _ 8 9 h m q u z 5 g a / c r o o t / a t t r s _ 1 7 3 4 5 3 3 1 3 0 8 1 0 / w o r k  
 a u d i o r e a d = = 3 . 0 . 1  
 b a n k s   @   f i l e : / / / h o m e / c o n d a / f e e d s t o c k _ r o o t / b u i l d _ a r t i f a c t s / b a n k s _ 1 7 4 3 4 4 3 7 1 9 0 8 9 / w o r k  
 b e a u t i f u l s o u p 4   @   f i l e : / / / C : / b / a b s _ d 5 w y t g _ p 0 w / c r o o t / b e a u t i f u l s o u p 4 - s p l i t _ 1 7 1 8 0 2 9 8 3 3 7 4 9 / w o r k  
 b i d i c t = = 0 . 2 3 . 1  
 b l i n k e r   @   f i l e : / / / C : / b / a b s _ b 1 i 8 7 k h t o b / c r o o t / b l i n k e r _ 1 7 3 7 4 4 8 7 3 2 0 9 5 / w o r k  
 b l i s = = 1 . 3 . 0  
 B o t t l e n e c k   @   f i l e : / / / C : / b / a b s _ 8 1 6 h r 2 k h p 1 / c r o o t / b o t t l e n e c k _ 1 7 3 1 0 5 8 6 4 8 1 1 0 / w o r k  
 B r o t l i   @   f i l e : / / / C : / b / a b s _ c 4 1 5 a u x 9 r a / c r o o t / b r o t l i - s p l i t _ 1 7 3 6 1 8 2 8 0 3 9 3 3 / w o r k  
 c a t a l o g u e = = 2 . 0 . 1 0  
 c e r t i f i   @   f i l e : / / / C : / b / a b s _ 8 a 9 4 4 p 1 _ g n / c r o o t / c e r t i f i _ 1 7 3 8 6 2 3 7 5 3 4 2 1 / w o r k / c e r t i f i  
 c f f i = = 1 . 1 7 . 1  
 c h a r s e t - n o r m a l i z e r   @   f i l e : / / / c r o o t / c h a r s e t - n o r m a l i z e r _ 1 7 2 1 7 4 8 3 4 9 5 6 6 / w o r k  
 c l i c k   @   f i l e : / / / C : / b / a b s _ 4 2 r b j n g f y s / c r o o t / c l i c k _ 1 7 4 4 2 7 1 6 0 6 9 8 3 / w o r k  
 c l o u d p a t h l i b = = 0 . 2 1 . 0  
 c o l o r a m a   @   f i l e : / / / C : / c i _ 3 1 1 / c o l o r a m a _ 1 6 7 6 4 2 2 3 1 0 9 6 5 / w o r k  
 c o n f e c t i o n = = 0 . 1 . 5  
 c y m e m = = 2 . 0 . 1 1  
 d a t a c l a s s e s - j s o n   @   f i l e : / / / C : / b / a b s _ 3 9 5 k r n b k i f / c r o o t / d a t a c l a s s e s - j s o n _ 1 7 1 5 2 6 4 8 1 6 3 9 4 / w o r k  
 d a t a s e t s   @   f i l e : / / / C : / b / a b s _ 7 d z k y 4 p x j 4 / c r o o t / d a t a s e t s _ 1 7 4 1 3 6 8 0 2 1 7 6 9 / w o r k  
 d e c o r a t o r   @   f i l e : / / / o p t / c o n d a / c o n d a - b l d / d e c o r a t o r _ 1 6 4 3 6 3 8 3 1 0 8 3 1 / w o r k  
 D e p r e c a t e d   @   f i l e : / / / C : / c i _ 3 1 1 / d e p r e c a t e d _ 1 6 7 6 4 4 9 5 3 7 4 4 1 / w o r k  
 d i l l   @   f i l e : / / / C : / b / a b s _ f 7 9 e g 2 7 d 2 q / c r o o t / d i l l _ 1 7 1 5 0 9 4 7 3 5 2 9 5 / w o r k  
 d i r t y j s o n   @   f i l e : / / / h o m e / c o n d a / f e e d s t o c k _ r o o t / b u i l d _ a r t i f a c t s / d i r t y j s o n _ 1 7 3 4 6 0 2 1 9 3 2 0 2 / w o r k  
 d i s t l i b = = 0 . 3 . 8  
 d i s t r o   @   f i l e : / / / C : / b / a b s _ 7 1 x r 3 6 u a 5 r / c r o o t / d i s t r o _ 1 7 1 4 4 8 8 2 8 2 6 7 6 / w o r k  
 e n _ c o r e _ w e b _ s m   @   h t t p s : / / g i t h u b . c o m / e x p l o s i o n / s p a c y - m o d e l s / r e l e a s e s / d o w n l o a d / e n _ c o r e _ w e b _ s m - 3 . 8 . 0 / e n _ c o r e _ w e b _ s m - 3 . 8 . 0 - p y 3 - n o n e - a n y . w h l # s h a 2 5 6 = 1 9 3 2 4 2 9 d b 7 2 7 d 4 b f f 3 d e e d 6 b 3 4 c f c 0 5 d f 1 7 7 9 4 f 4 a 5 2 e e b 2 6 c f 8 9 2 8 f 7 c 1 a 0 f b 8 5  
 e v a l _ t y p e _ b a c k p o r t   @   f i l e : / / / h o m e / c o n d a / f e e d s t o c k _ r o o t / b u i l d _ a r t i f a c t s / e v a l _ t y p e _ b a c k p o r t _ 1 7 3 4 8 5 7 7 2 7 8 3 6 / w o r k  
 f i l e l o c k   @   f i l e : / / / C : / b / a b s _ 7 5 8 1 8 7 j 2 8 1 / c r o o t / f i l e l o c k _ 1 7 4 4 2 8 1 4 0 4 8 5 0 / w o r k  
 f i l e t y p e   @   f i l e : / / / h o m e / c o n d a / f e e d s t o c k _ r o o t / b u i l d _ a r t i f a c t s / f i l e t y p e _ 1 6 6 7 4 4 5 7 7 8 5 6 3 / w o r k  
 F l a s k   @   f i l e : / / / C : / b / a b s _ a 6 m 3 7 u z 5 7 8 / c r o o t / f l a s k _ 1 7 3 7 4 5 4 3 3 5 6 6 2 / w o r k  
 f l a s k - c o r s = = 5 . 0 . 1  
 F l a s k - L o g i n   @   f i l e : / / / C : / b / a b s _ 8 5 3 c 5 n 6 v t x / c r o o t / f l a s k - l o g i n _ 1 7 1 6 5 5 2 5 6 0 7 2 2 / w o r k  
 F l a s k - S o c k e t I O = = 5 . 5 . 1  
 F l a s k - S Q L A l c h e m y   @   f i l e : / / / C : / b / a b s _ d 5 a r j u b k 1 y / c r o o t / f l a s k - s q l a l c h e m y _ 1 7 1 2 5 6 7 3 0 0 7 8 1 / w o r k  
 f l a t b u f f e r s = = 2 5 . 2 . 1 0  
 f r o z e n l i s t   @   f i l e : / / / C : / b / a b s _ 0 6 c t m b 1 z e o / c r o o t / f r o z e n l i s t _ 1 7 3 0 9 0 3 1 1 3 4 6 3 / w o r k  
 f s s p e c   @   f i l e : / / / C : / b / a b s _ 0 4 2 8 j j 7 f l h / c r o o t / f s s p e c _ 1 7 3 6 2 7 4 6 9 1 8 3 7 / w o r k  
 g a s t = = 0 . 6 . 0  
 g o o g l e - p a s t a = = 0 . 2 . 0  
 g r e e n l e t   @   f i l e : / / / C : / b / a b s _ b b l _ 1 8 a 2 t m / c r o o t / g r e e n l e t _ 1 7 3 3 8 6 0 0 8 1 9 7 7 / w o r k  
 g r i f f e   @   f i l e : / / / h o m e / c o n d a / f e e d s t o c k _ r o o t / b u i l d _ a r t i f a c t s / g r i f f e _ 1 7 4 3 5 7 9 6 8 6 2 6 6 / w o r k  
 g r p c i o = = 1 . 7 1 . 0  
 h 1 1   @   f i 
[truncated — 16140 more characters]
```

### reactapp/package.json

```
{
  "name": "vite_react_shadcn_ts",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:dev": "vite build --mode development",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.0",
    "@radix-ui/react-accordion": "^1.2.0",
    "@radix-ui/react-alert-dialog": "^1.1.1",
    "@radix-ui/react-aspect-ratio": "^1.1.0",
    "@radix-ui/react-avatar": "^1.1.0",
    "@radix-ui/react-checkbox": "^1.1.1",
    "@radix-ui/react-collapsible": "^1.1.0",
    "@radix-ui/react-context-menu": "^2.2.1",
    "@radix-ui/react-dialog": "^1.1.2",
    "@radix-ui/react-dropdown-menu": "^2.1.1",
    "@radix-ui/react-hover-card": "^1.1.1",
    "@radix-ui/react-label": "^2.1.0",
    "@radix-ui/react-menubar": "^1.1.1",
    "@radix-ui/react-navigation-menu": "^1.2.0",
    "@radix-ui/react-popover": "^1.1.1",
    "@radix-ui/react-progress": "^1.1.0",
    "@radix-ui/react-radio-group": "^1.2.0",
    "@radix-ui/react-scroll-area": "^1.1.0",
    "@radix-ui/react-select": "^2.1.1",
    "@radix-ui/react-separator": "^1.1.0",
    "@radix-ui/react-slider": "^1.2.0",
    "@radix-ui/react-slot": "^1.1.0",
    "@radix-ui/react-switch": "^1.1.0",
    "@radix-ui/react-tabs": "^1.1.0",
    "@radix-ui/react-toast": "^1.2.1",
    "@radix-ui/react-toggle": "^1.1.0",
    "@radix-ui/react-toggle-group": "^1.1.0",
    "@radix-ui/react-tooltip": "^1.1.4",
    "@tanstack/react-query": "^5.56.2",
    "caniuse-lite": "^1.0.30001713",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.0.0",
    "date-fns": "^3.6.0",
    "embla-carousel-react": "^8.3.0",
    "framer-motion": "^12.6.5",
    "input-otp": "^1.2.4",
    "lucide-react": "^0.462.0",
    "next-themes": "^0.3.0",
    "p5": "^1.11.3",
    "popmotion": "^8.7.4",
    "react": "^18.3.1",
    "react-day-picker": "^8.10.1",
    "react-dom": "^18.3.1",
    "react-hook-form": "^7.53.0",
    "react-p5-wrapper": "^4.1.2",
    "react-resizable-panels": "^2.1.3",
    "react-router-dom": "^6.26.2",
    "recharts": "^2.12.7",
    "socket.io-client": "^4.8.1",
    "sonner": "^1.5.0",
    "styled-components": "^6.1.17",
    "styled-normalize": "^8.0.6",
    "tailwind-merge": "^2.5.2",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^0.9.3",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@eslint/js": "^9.9.0",
    "@tailwindcss/typography": "^0.5.15",
    "@types/node": "^22.5.5",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@types/styled-components": "^5.1.34",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "autoprefixer": "^10.4.20",
    "eslint": "^9.9.0",
    "eslint-plugin-react-hooks": "^5.1.0-rc.0",
    "eslint-plugin-react-refresh": "^0.4.9",
    "globals": "^15.9.0",
    "lovable-tagger": "^1.1.7",
    "postcss": "^8.4.47",
    "tailwindcss": "^3.4.11",
    "typescript": "^5.5.3",
    "typescript-eslint": "^8.0.1",
    "vite": "^5.4.18"
  }
}

```

### app.py

```python
from flask import Flask, send_from_directory, request, jsonify
from flask_socketio import SocketIO, emit
from flask_cors import CORS
import os
from moviepy.editor import AudioFileClip
from util.emotionClassifier import EmotionDiarizer
from util.transcript import transcribe_webm_file
from util.nlp import run_full_analysis
from dotenv import load_dotenv

load_dotenv()


app = Flask(__name__, static_folder="reactapp/dist", static_url_path="")
CORS(app)
socketio = SocketIO(app, cors_allowed_origins="*")

UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)


diarizer = EmotionDiarizer()

# ------------------ Wav Transcription ------------------

def extract_audio_with_moviepy(webm_path, wav_path):
    clip = AudioFileClip(webm_path)
    clip.write_audiofile(
        wav_path,
        fps=16000,
        nbytes=2,
        codec='pcm_s16le',
        ffmpeg_params=["-ac", "1"]
    )


# ------------------ Flask Routes ------------------

@app.route('/')
def serve_index():
    return send_from_directory(app.static_folder, 'index.html')

@app.route('/<path:path>')
def serve_static(path):
    try:
        return send_from_directory(app.static_folder, path)
    except:
        return send_from_directory(app.static_folder, 'index.html')

@app.route('/upload', methods=['POST'])
def upload_and_transcribe():
    if 'audio' not in request.files:
        return jsonify({"error": "No file part in request"}), 400
    
    file = request.files['audio']
    if file.filename == '':
        return jsonify({"error": "No selected file"}), 400
    if not file.filename.endswith('.webm'):
        return jsonify({"error": "Only .webm files are allowed"}), 400
    
    sid = request.args.get("sid")

    webm_path, wav_path = save_file(file, sid)
    socketio.start_background_task(full_audio_pipeline, webm_path=webm_path, wav_path=wav_path, sid=sid)


    return jsonify({
        "message": "File processed successfully!",
    }), 200

    
def full_audio_pipeline(webm_path, wav_path, sid):
    try:
        socketio.emit("transcription_status", {"status": "started"}, to=sid)
        transcript = transcribe_webm_file(webm_path)
        socketio.emit("transcription_status", {"status": "completed"}, to=sid)
        
        socketio.emit("emotion_status", {"status": "started"}, to=sid)
        emotion = diarizer.analyze(wav_path)
        socketio.emit("emotion_status", {"status": "completed"}, to=sid)

        result = run_full_analysis(transcript, wav_path, emotion)

        # Emit final result to frontend
        socketio.emit("analysis_result", {
            "status": "completed",
            "data": result
        }, to=sid)

    except Exception as e:
        socketio.emit("analysis_result", {
            "status": "error",
            "error": str(e)
        }, to=sid)


def save_file(file, sid):
    filename = file.filename
    webm_path = os.path.join(UPLOAD_FOLDER, filename)
    wav_path = os.path.join(UPLOAD_FOLDER, filename.rsplit('.', 1)[0] + '.wav')
    print(wav_path)

    file.save(webm_path)

    try:
        extract_audio_with_moviepy(webm_path, wav_path)
    except Exception as e:
        return jsonify({"error": str(e)}), 500

    socketio.emit("upload_status", {
        "status": "uploaded",
        "filename": file.filename
    }, to=sid)
    
    return webm_path, wav_path


if __name__ == '__main__':
    socketio.run(app, debug=True)



```

### reactapp/src/main.tsx

```typescript
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'

createRoot(document.getElementById("root")!).render(<App />);

```

### reactapp/src/App.tsx

```typescript
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import NotFound from "./pages/NotFound";

const queryClient = new QueryClient();

const App = () => (
  <QueryClientProvider client={queryClient}>
    <TooltipProvider>
      <Toaster />
      <Sonner />
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Index />} />
          {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
          <Route path="*" element={<NotFound />} />
        </Routes>
      </BrowserRouter>
    </TooltipProvider>
  </QueryClientProvider>
);

export default App;

```

### app/api/status/route.ts

```typescript
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({ status: 'API is up and running' });
}

```

### app/api/upload/route.ts

```typescript
import { NextResponse } from 'next/server';
import { runFullAnalysis } from '../../../lib/analysis';

export const config = {
  api: {
    bodyParser: false,
  },
};

export async function POST(req: Request) {
  const formData = await req.formData();
  const audioFile = formData.get('audio') as File | null;


  if (!audioFile) {
    return NextResponse.json({ error: 'No audio file provided' }, { status: 400 });
  }
  if (!audioFile.name.endsWith('.webm')) {
    return NextResponse.json({ error: 'Only .webm files are allowed' }, { status: 400 });
  }

  try {
    const arrayBuffer = await audioFile.arrayBuffer();
    
    const fileBuffer = Buffer.from(arrayBuffer);
    
    const result = await runFullAnalysis(fileBuffer);
    
    return NextResponse.json({ status: 'completed', data: result });
  } catch (error: any) {
    console.error('Error processing file:', error);
    return NextResponse.json({ status: 'error', error: error.message }, { status: 500 });
  }
}

```

### reactapp/src/pages/Index.tsx

```typescript
import { motion, useScroll, useTransform } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import CassetteTape from "@/components/CassetteTape";

const Index = () => {
  const containerRef = useRef(null);
  const [fadeOpacity, setFadeOpacity] = useState(1);

  const { scrollY } = useScroll();
  const translateY = useTransform(scrollY, [0, 200], [0, -150]); // 👈 Changed from -250 to -150
  const opacity = useTransform(scrollY, [0, 200], [1, 0]);

  useEffect(() => {
    const handleScroll = () => {
      const scrollY = window.scrollY;
      const maxScroll = 200;
      const newOpacity = Math.max(1 - scrollY / maxScroll, 0);
      setFadeOpacity(newOpacity);
    };

    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <div className="min-h-screen flex flex-col bg-gradient-to-br from-[#1E1E2F] to-[#121218] overflow-x-hidden">
      <header className="container mx-auto py-6 px-4 flex justify-between items-center relative z-10">
        {/* Header content */}
      </header>

      <main
        className="flex-1 container mx-auto px-4 py-12 md:py-20 flex flex-col items-center relative z-10"
        ref={containerRef}
      >
        <div className="relative w-full flex flex-col items-center overflow-visible">
          {/* Hero Text */}
          <motion.div className="text-center mb-12 max-w-3xl" style={{ opacity }}>
            <h1 className="text-4xl md:text-6xl font-extrabold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-red-600 to-gray-300">
              Sound Confident. Speak with Power.
            </h1>
            <p className="text-xl text-red-100 mb-8 max-w-2xl mx-auto">
              Get real-time analysis of your speech patterns and actionable feedback to improve your delivery and tone.
            </p>
            <div className="flex justify-center gap-4 mb-8">
              <span className="px-4 py-1 bg-red-800/30 text-red-200 border border-red-600/30 rounded-full text-sm">
                Voice Analysis
              </span>
              <span className="px-4 py-1 bg-gray-800/30 text-gray-200 border border-gray-700/30 rounded-full text-sm">
                Speech Coaching
              </span>
              <span className="px-4 py-1 bg-gray-800/30 text-gray-200 border border-gray-700/30 rounded-full text-sm">
                Instant Feedback
              </span>
            </div>
          </motion.div>

          {/* Cassette Recorder */}
          <motion.div
            className="w-full px-4 z-20"
            style={{ translateY }}
          >
            <CassetteTape vizColor={"dark"}  />
          </motion.div>
        </div>

        {/* CTA */}
        <p className="mt-32 text-center text-red-200 bg-slate-800/50 px-6 py-3 rounded-full backdrop-blur-sm shadow-inner max-w-lg mx-auto border border-red-500/20">
          Record your voice and get instant feedback on your confidence level.
        </p>
      </main>
    </div>
  );
};

export default Index;

```

### reactapp/src/components/cassette/index.jsx

```javascript
import Background from "./Background";
import Buttons from "./Buttons";
import Cassette from "./Cassette";
import Center from "./Center";
import Timebox from "./Timebox";
import TrackTitle from "./TrackTitle";

export { Background, Buttons, Cassette, Center, Timebox, TrackTitle };

```

[100 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]