About the Buffalo Bark Machine

A 2018 text-to-speech demo, still alive in 2026

What This Is

You type some text, pick a "buffalo dialect" (really an Amazon Polly voice), click "Say it!", and a few seconds later a playable MP3 of that text in that voice shows up in the results table. Every post gets a UUID; you can search them back later by ID, or use * to list everything in the database.

Originally built in 2018 following an AWS reference architecture for serverless text-to-speech. Backend went dark sometime between then and now (the DynamoDB table got deleted at some point), revived in 2026 with the Lambdas migrated from Python 2.7 to 3.12.

Architecture at a Glance

1. HTTPS (POST + GET) 2a. POST {voice, text} 2b. GET ?postId=X 3. put_item (PROCESSING) 4. publish UUID 2c. scan / query 5. trigger w/ UUID 7. synthesize_speech 8. upload .mp3 6. query / 9. update_item Your Browser HTML + jQuery 3.7.1 API Gateway PostReaderAPI Lambda: NewPosts POST handler Lambda: GetPosts GET handler SNS topic New_Posts DynamoDB posts (id, voice, text, status, url) Lambda: ConvertToAudio SNS-triggered worker Amazon Polly text → mp3 stream S3 bucket pollymp3pail (public mp3s)
Client AWS edge / routing Compute Storage External

Single-headed arrows are one-way (publish / upload / trigger); double-headed arrows are request/response. The numbered sequence matches the "What happens when you click Say it!" steps below.

What Happens When You Click "Say it!"

  1. The browser POSTs {voice, text} as JSON to the API Gateway endpoint.
  2. API Gateway invokes PostReader_NewPosts. The Lambda generates a UUID, writes a new row to the posts DynamoDB table with status: "PROCESSING", then publishes the UUID as a message to the New_Posts SNS topic. Returns the UUID to the browser.
  3. SNS fans out the message to its only subscriber: PostReader_ConvertToAudio. (This decoupling means NewPosts can return the post ID immediately without waiting on Polly.)
  4. ConvertToAudio looks up the row in DynamoDB, splits the text into ~1,000-character blocks (Polly's per-call limit), and calls Polly.synthesize_speech on each block. The audio streams are concatenated into a single MP3 file in /tmp/.
  5. The MP3 is uploaded to S3 (pollymp3pail/<uuid>.mp3) with a public-read ACL, and the DynamoDB row is updated: status: "UPDATED" with the S3 URL.
  6. Later, when you "Search" by post ID (or *), PostReader_GetPosts queries / scans the table and returns the rows. The browser renders each row as a table entry; if a url is present it embeds an <audio> player pointing at the S3 MP3.

Tech Stack

Layer Tools
Frontend Static HTML + jQuery 3.7.1 (only the bark page uses jQuery; everything else on the site is plain JS)
API layer AWS API Gateway (REST, regional)
Compute 3 × AWS Lambda (Python 3.12) — PostReader_NewPosts, PostReader_GetPosts, PostReader_ConvertToAudio
Speech synthesis Amazon Polly (~40 voices across a dozen languages)
Message bus Amazon SNS topic New_Posts (one subscriber: the audio-worker Lambda)
Database Amazon DynamoDB table posts (on-demand billing, single hash key id)
Audio storage Amazon S3 bucket pollymp3pail — public objects, no CDN in front

Hosting & Cost

Entirely AWS free-tier at the volume this thing actually sees (single-digit requests per month, most months zero). The only line items that ever bill are pennies for S3 storage of accumulated MP3s and DynamoDB on-demand reads. Estimated cost: $0/month, rounding up.

Because everything is serverless / on-demand, there's nothing running when nobody's using it. Cold-start latency on the Lambdas is around 300–500ms; a full POST-to-MP3-ready round trip takes 3–6 seconds.

Source Code

All of it — HTML, client JS, Lambda sources, README — lives in one directory:
github.com/JackVance/bigolbuffalo/tree/main/src/BuffaloBarkMachine