DEV Community

Toluwaloju Kayode
Toluwaloju Kayode

Posted on Edited on

Midnight Stranger: Your Local spooky story teller

Hacktoberfest Open-Source AI Challenge: Ghost in the Machine Submission 👻🌼

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine

What I Built

Midnight Stranger is a horror radio that works with the internet switched off.

You open a page, press "Turn the radio on", and a glass dial lights up. There are four fictional stations. 88.1 is The Lighthouse, coastal dread told by a tired British voice. 93.7 is Route 13 Overnight, drivers and things in the rearview mirror. 101.3 is Frequency of the Dead, gentle ghost stories in the spirit of Día de los Muertos. 107.9 is Static Hours, strange signals and unexplained broadcasts. Drag the needle between stations and you get a burst of static, then whatever is on air.

Nothing on that dial is a recording. A local language model wrote each story a few minutes earlier. A local voice model read it aloud. The crackle, the sea rumble and the engine drone under the voice are generated from noise by a few lines of numpy. The captions follow the narrator word for word, paragraph by paragraph.

I read "Ghost in the Machine" literally. A radio is a machine, and the voice in it is a model running on the same laptop. If you unplug the router, the ghost keeps talking.

All the stories are fictional and kept PG-13.

Demo

Code

GitHub logo Toluwaloju0 / midnight-stranger

A repo for the fifth hacktoberfest challenge to create a ghost in a local machine. Mine is a ghost which tells you scary stories while you work

Midnight Stranger

An offline horror radio. A local language model writes short spooky stories, a local text-to-speech voice narrates them over procedurally generated ambience, and a browser "dial" lets you tune between fictional late-night stations. No cloud, no API keys, no internet needed once set up.

Built for the Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine.

Status: early development. Features below are marked Planned or Done. This README is updated as the project changes (see the Changelog).

Why open-weight AI

  • Works with the lights (and the internet) off. Everything runs on your own machine.
  • Private. No prompt, story or log leaves the computer.
  • Free to run. No API keys, no per-token cost.
  • Swappable. Change the model or voice with a config setting, or change how a station behaves by editing its prompt.

How it works

Browser dial UI -> FastAPI -> station queue ->
…



Check out the README for how to run the application on your local machine

How I Built It

The stack, all of it open and all of it local:

Writing the stories: Gemma 3 (4B) through Ollama, with gemma3:1b as a fallback
Reading them aloud: Piper TTS, three voices (en_GB-alan, en_US-lessac, en_US-amy)
Ambience and mixing: numpy, the standard wave module, no sample files
Service: FastAPI and uvicorn, bound to 127.0.0.1 by default
Radio page: One HTML file, one CSS file, one JS file. No framework, no CDN, no web fonts

Honest limits. The first story on a fresh install takes a couple of minutes because nothing has been written yet (the page says "No signal" and starts by itself when it arrives). The stories are uneven, and I curated the demo set by hand. The ambience is synthetic and I tuned it by ear on one pair of headphones. I developed and tested on Linux.

I wrote this with an AI coding assistant helping on the code. I chose what to build, ran everything on my own machine, and listened to the output.

Why Does Open Innovation Matter?

Three things in this project would not have survived a closed API, and one of them surprised me.

It runs on a plane. The whole premise is that the radio keeps working when the connection does not. A hosted model cannot make that promise, because the model is somewhere else. Here the model is a 3.4 GB file I downloaded once. The measured cost of every story is $0, and the only meter running is the laptop fan.

I could look inside it. When a story failed validation, I did not get an error from a service. I got the rejected text, wrote it to a log file, and read it. That is how I found the length problem above and the habits each station's prompt needed to avoid. Being able to swap the model, change a persona, or lower the temperature with one config line is the difference between tuning a machine and filing a ticket with one.

The licences shaped the project, out in the open. Piper is GPL, so this repository is GPL-3.0-or-later. I would rather have that written in the README than discovered later. I also want to be exact about Gemma: it is open-weight under Google's terms, not open source in the OSI sense, and the README says so. Open innovation is a spectrum, and part of using it responsibly is saying which part of it you stand on.

There is a quieter point too. A story radio that is private by construction is a different thing from one that promises privacy. No prompt, story or listening habit leaves the machine, because there is nowhere for it to go.

The Spirit of It

I built this project with the mind of creating spooky buy user friendly stories which can play on the background while the user listens without the application interrupting any of the users activities.

Prize Categories

Best use of Gemma: the whole project is built on Gemma3 and it runs the story generation, and writes all the stories

Top comments (0)