D&DJ was created to help dungeon masters incorporate music and sounds into their storytelling. The idea is to prepare the music and sounds you want to use for specific scenes in advance and then being able to play them without any issues.
For the discord version click here.
- Prepare a configuration file defining the music and sounds you want to use
- Music
- Supports MP3 files and links to YouTube videos
- Supports next, start_at, end_at and individual volume parameters
- One only piece of music can be played at a time
- Sound
- Supports WAV and OGG files
- Supports end_at and individual volume parameters
- Repeat a sound X times or for an infinite number of times
- Repeat a sound every X milliseconds or every interval X-Y
- Each sound can only be played once at a time
- Different sounds can be played at the same time
- Start the server with the config (and connect the server to the speakers)
- The server acts as the media player
- Visit the hosted web page from a device in the same network (e.g., phone)
- The web page displays the available music and sounds (as specified in the config)
- Play music, stop the music, change the volume or play sounds
Desktop View (Music Tab) | Mobile (Sound Tab) |
---|---|
The server is an asynchronous server that uses aiohttp. Clients can connect to the server via the hosted web page and they then connect via websockets. Every connected client will always know what music is currently being played, what the volume is and will be notified when a change occurs.
The web page shows the clients the preconfigured music and sounds that are available and allows them to request to play a music track, stop the music, change the volume or play sounds. The server will receive these requests and fulfill them using python-vlc (VLC media player bindings) for the music and pygame for the sounds.
If the music is a link to a YouTube video, pafy will get the link to the audio stream and pass it to the VLC media player.
First, you need to have Python installed, version 3.7.
Next, set up a virtual environment.
- Change the directory to this project's root and type
python -m venv venv
to create a virtual environment namedvenv
- Activate the virtual environment (
(venv)
should appear before the directory)- On Windows:
venv\Scripts\activate
- On Linux:
source venv/bin/activate
- On Windows:
- Install the requirements with
pip install -r requirements.txt
- Use the
requirements-dev.txt
if you are a developer and runpre-commit install
.
- Use the
Whenever you want to execute the program from the terminal, make sure the virtual environment is active.
There are two additional dependencies you have to install:
- The VLC media player is used to play the music, so make sure to have it installed.
- File types and conversions are handled by pydub, which requires
FFmpeg being installed. Make sure to add the
/bin
folder of FFmpeg to yourPATH
on Windows.
Run the example by typing python start_server.py examples/death_house/config.yaml
.
Now you can visit 127.0.0.1:8080
in your browser and start playing around with it. It is an example configuration for running (a variation of)
Death House.
The second example at examples/wolves_of_welton/
is based on
The Wolves of Welton by Winghorn Press
(and it uses too much music for my taste).
Credits to Tabletop Audio for some of the music/ambience in the examples. The audio is licensed under CC BY-NC-ND 4.0.
Sound Credits (licensed under CC BY 3.0)
- Monster footsteps: "Monster footsteps on wood" by Suburbanwizard at Freesound.org
- Grinding noise: "brick becomes slab.wav" by Timbre at Freesound.org
The configuration file is a YAML
file. It contains two sets of configurations, one for the music
and one for the sound.
music:
# content ...
sound:
# content ...
The music configuration is divided into a hierarchical structure.
At the root is the music
element that defines at least the master volume
and a list of groups
that will be used.
### music > root config ###
music:
volume: 20 # master value from 0 (mute) to 100 (max)
directory: path/to/dir # (Optional) used if all files are in the same dir
sort: true # (Optional, default=true) whether to sort the groups alphabetically
groups: [] # a list of groups
A group
can for example be a scene in the story. It has a name
and defines a collection
of track_lists
(i.e., playlists).
### music > group config ###
music:
# ...
groups:
- name: Scene 1 - Travel
directory: path/to/dir # (Optional) used if all files of a group are in the same dir
sort: true # (Optional, default=true) whether to sort the tracklists alphabetically
track_lists: [] # a list of tracklists
- name: Scene 2 - Arrival
# ...
A track_list
also has at least a name
and defines a collection of tracks
that
it will play.
### music > track_list config ###
music:
# ...
groups:
- name: Scene 1 - Travel
# ...
track_lists:
- name: Spooky Music
directory: path/to/dir # (Optional) used if all files of a tracklist are in the same dir
volume: 50 # (Optional, default=100) volume for this tracklist where 0 is mute and 100 is max
loop: true # (Optional, default=true) whether to loop if all tracks have been played
shuffle: true # (Optional, default=true) whether to shuffle the tracks before playing them all
next: Forest Ambience # (Optional) name of the next tracklist to play
tracks: [] # a list of tracks
Finally, a track
refers to a music file or YouTube link. In the simplest case it is only a filename (link),
but you can further configure it. Every file type that the VLC media player supports should work.
Keep in mind that streaming the audio from YouTube will introduce a delay of a second or two to build up the connection etc.
### music > track config ###
music:
# ...
groups:
- name: Scene 1 - Travel
# ...
track_lists:
- name: Forest Ambience
# ...
tracks:
- forest_ambience_1.mp3 # can either be a filename
- https://www.youtube.com/watch?v=HAw37tUHcOo # or a YouTube video
- file: forest_ambience_2.mp3 # or more specific (`file` can be a filename or link)
start_at: 0:0:10 # (Optional) the format is %H:%M:%S
end_at: 0:0:20 # (Optional) the format is %H:%M:%S
The sound
configuration is very similar to the music
configuration.
At the root is the sound
element that defines at least the volume
and a list of groups
that will be used.
### sound > root config ###
sound:
volume: 1 # value from 0 (mute) to 1 (max) (master volume, each sound has its own volume)
directory: path/to/dir # (Optional) used if all files are in the same dir
sort: true # (Optional, default=true) whether to sort the groups alphabetically
groups: [] # a list of groups
A group
has at minimum a name
and defines a collection
of sounds
(e.g., footstep sounds).
### sound > group config ###
sound:
# ...
groups:
- name: Meele Attacks
directory: path/to/dir # (Optional) used if all files of a group are in the same dir
sort: true # (Optional, default=true) whether to sort the sounds alphabetically
sounds: [] # a list of sounds
- name: Footsteps
# ...
A sound
is not a single sound file, but a collection of sound files. If the sound
is being played, one of its
associated files will be played at random.
For example, a sound named Sword Hit may list several different sound files, each being a variant of a sword hit. When Sword Hit is being played, one of the files will be played at random to introduce a bit of variety.
### sound > sound config ###
sound:
# ...
groups:
- name: Meele Attacks
# ...
sounds:
- name: Sword Hit
directory: path/to/dir # (Optional) used if all files of a sound are in the same dir
volume: 0.5 # (Optional, default=1) value from 0 (mute) to 1 (max)
repeat_count: 3 # (Optional, default=1) #times to play the sound (includes initial, 0 = ∞)
repeat_delay: 5000-7000 # (Optional, default=0) delay in ms, single number or interval (not on initial)
files: [] # a list of sound files
Finally, a sound file
is in the simplest case just a filename, but it also can be a more
specific configuration. Note that only .ogg
and .wav
files are supported and
pygame requires them to have a signed 16-bit sample format. Fear not, files will be converted automatically
if they appear to be incompatible.
### sound > file config ###
sound:
# ...
groups:
- name: Meele Attacks
# ...
sounds:
- name: Sword Hit
# ...
files:
- sword_hit_1.ogg # can either be a filename
- file: sword_hit_2.wav # or a more specific config
end_at: 0:0:2 # (Optional) the format is %H:%M:%S
If you want to, you can always include other YAML
files in your main config file.
### main_config.yaml ###
# ...
sound:
# ...
groups:
- !include path/to/footsteps_config.yaml
- !include path/to/spells_config.yaml
### footsteps_config.yaml ###
name: Footsteps
directory: path/to/footsteps/dir
sounds:
- name: Dry Leaves
files:
- footsteps_dry_leaves.wav
- name: Monster
files:
- footsteps_monster.wav
If you want to access the web page from a different device, e.g., your phone, you have to look up the IP address of the host computer and host the server on this IP address.
On Windows you can open the command line and enter ipconfig
as command to view your IPv4 address.
On Linux you can use ifconfig
. For simplicity, assume your IP address is 192.168.1.1 in the following.
Run the start_server.py
script as follows: python start_server.py --host "192.168.1.1" "path/to/config.yaml"
Now you can visit the url 192.168.1.1:8080
from any device that is in the same network as the host computer.
Here is a bit of advice I would give. You may agree or disagree with it, see what works for you.
The first point is to not overuse music. Use music to emphasize special situations and changes in the atmosphere. By that I mean to play a single track that fits, not a whole playlist. By doing so the music feels much more impactful when it plays.
For example, when the party meets the villain for the first time, you can play some dark super villain music (maybe even give the villain his own soundtrack). Or when something sad happens or a sad story is being told, emphasize the sadness by underlying it with sad music. Keep in mind that the music is not the focus and should be quiet enough to not disturb while talking. Also, use music without any vocals since they will be distracting.
Think you of the situations you expect to arise in your story and how you can use music to emphasize some of them.
Second, I recommend to use ambience as default background "music" instead of using music.
For example, when the party is in a forest, play some forest ambience to set the scene and make the journey more immersive. When the party enters a tavern, switch to tavern ambience.
As the DM you will know the locations that will appear in your story, so you can prepare appropiate ambience for them.
This aspect is more difficult, because it can be very challenging to find the sound effects you want and that fit your situation. Anyway, I like to incorporate sound effects into my descriptions of what is happening.
If there is an explosion, play the sound of the explosion. If the party is in the forest and hears footsteps, play the sound of footsteps on leaves.
For sound files the only supported formats are .wav
and .ogg
.
If you happen to have a different format or simply want to convert your audio files into a different format,
you can use the scripts/convert_file.py
script. This script uses pydub to perform the conversion.
Note that working with formats other than .wav
requires having
FFmpeg installed.
When converting a .wav
file, the sample format will be set to 16-bit, which is the format pygame supports.
python convert_file.py path/to/file-or-dir format
- The first argument is the file to convert or a directory (will convert every file in the directory)
- The second argument is the output format (e.g.,
ogg
orwav
) - The following arguments are optional and only work for files and not directories
- use
--start X
to have the audio start atX
milliseconds - use
--end X
to have the audio end atX
milliseconds - use
--out X
to change the output name toX
(name excludes extension)
- use
The converted file(s) will be in the same directory as the source file(s) and have the same name (except the extension).
Example:
python convert_file.py --start 5000 --end 8000 --out new_sound path/to/sound.wav ogg
This converts sound.wav
located at path/to/
to new_sound.ogg
located at path/to/
and new_sound.ogg
consists of the part starting at 5000 ms and ending at 8000 ms.
Here is small collection of sources where you can discover great music.
-
Adrian von Ziegler (Fantasy Music)
-
BrunuhVille (Fantasy Music)
-
Derek & Brandon Fiechter (Fantasy Music)
-
Lukas King (Piano Music)
-
Michael Ghelfi (Fantasy Music and Ambience)
-
Peter Crowley's Fantasy Dream (Fantasy Music)
-
Peter Grundy (Fantasy Music)
-
Secession Studios (Cinematic Music)
-
Sword Coast Soundscapes (Ambience, Campaign-Specific Ambience)
-
Tabletop Audio (Music and Ambience)
-
Vindsvept (Fantasy Music)
Finding sounds is quite challenging. So far I make use of the following free websites and hope they have what I need.
- Freesound (Collaborative collection of sounds released under CC license)
- GameSounds.xyz (Collection of royalty free sounds)
- Phanary (No downloads, but has a searchable collection and lists the original sources)
- Zapsplat (Free sound effects)
If you can spend some coin, there is also the option to buy professional sound packs that fulfill your needs.
If you are a mad scientist, you can also try to extract the sound files from your favorite RPG video game.
In order to run the tests simply type the command pytest
.
In order to generate a coverage report type the following:
coverage run -m pytest
coverage html
The coverage report will be available in coverage_html/index.html
.
This repository uses black as code formatter combined with flake8 and isort.
All will be run as pre-commit hooks and on Travis CI.
Given that hooks are not allowed to modify the staged changes, you have to commit twice if files have to be formatted. The first commit will fail as it formats the files and the second commit will verify that the files are properly formatted and allow the commit.