Writing music audio files using sine, square, triangular and sawtooth wave
Architecture of the C++ implementation
main()
┌──────────────────────────┐
│ │
│ Write WAV │
│ header │
sigen.cpp │ │
│ │ │
┌──────────────────────────┐ │ ▼ │
│ │ │ │
│ play() Generate │ │ Parse parse() │
│ signals ◄─┼─┼── score │
│ │ │ │
│ │ │ │ │ │
│ ▼ │ │ │ │
│ │ │ │ │
│ filter() A/R filter │ │ │ │
│ │ │ │ │
│ │ │ │ │ │
│ ▼ │ │ ▼ │
│ │ │ │
│ lowpass() Low pass ──┼─┼─► Write │
│ filter │ │ notes │
│ │ │ │
└──────────────────────────┘ │ │ │
│ ▼ │
│ │
│ Insert data size │
│ in WAV header │
│ │
│ │ │
│ ▼ │
│ │
│ Playback │
│ with │
│ system Call │
│ │
└──────────────────────────┘
To use the C++ version:
make simple./simple sheets/<title>.wmusicfor debug mode:
make refresh DEBUG=1./simple sheets/<title>.wmusicThe wave module in the Python standard library provides a convenient interface to the WAV sound format. References: https://docs.python.org/3/library/wave.html https://www.tutorialspoint.com/read-and-write-wav-files-using-python-wave
(Note: WSL may need extra configuration to display GUI and play sound.)
To incorporate the C++ code as a backend, use pybind11:
sudo apt install python3-dev g++ cmake pybind11-devuv run setup.py build_ext --inplacewhere uv is recommended for dependency management.
To run the python script:
uv run main.pyor
uv run main.py guiTo use command-line interface:
uv run main.py cliTo generate WAV from score:
uv run main.py sheets/<title>.wmusicTo build a standalone app:
uv run pyinstaller main.py --onefile
# pyinstaller main.py --onefile --clean --noconsole --icon=icon.icoUse MSYS2 MINGW x64 terminal:
pacman -Syu
# Close terminal, reopen it (again in MinGW 64-bit)
pacman -SyuTo use inside VScode: In VScode, open Settings, search for 'terminal.integrated.profiles.windows' - Edit in settings.json - add:
"terminal.integrated.profiles.windows": {
"MSYS2 MinGW 64-bit": {
"path": "C:\\msys64\\usr\\bin\\bash.exe",
"args": ["--login", "-i"],
"env": {
"MSYSTEM": "MINGW64",
"CHERE_INVOKING": "1"
}
}
},
"terminal.integrated.defaultProfile.windows": "MSYS2 MinGW 64-bit"Restart VScode, open terminal.
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-python-pip mingw-w64-x86_64-pybind11 git
g++ --version
python --version
cmake --versionSet up .venv and dependencies (uv can be tricky in MSYS2 Mingw)
python -m venv .venv
source .venv/bin/activate
pip install --upgrade -r requirements.txtpython setup.py build_ext --inplaceTo run the python script:
python main.pyTo build a standalone app:
pyinstaller main.py --onefile
# pyinstaller main.py --onefile --clean --noconsole --icon=icon.icoPython TODO list:
- time different implementations main_cpp.py, main_list.py, main_np.py and compare with C++
- write tests
- add docs
- use C++ code as a backend
- add waveforms.py to store functions for each waveform
- add Entry with up/down for transpose
- use () to pass frequecies or chords
- add bpm and sample rate selection
- use numpy for faster performance
- the functions can be added to produce complex timber and polyphony
- add loudness: ff, f, fp, p, pp
- add dynamics: cresc, dim
- add timber
- GUI (maybe try webapp next? Electrojs?)
- add polyphony (fugue)
- live waveform plotting matplotlib like oscilloscope
- playsound on the go with Enter (plays the line just completed)
- threading, running playsound at background (already okay with vlc open)
C++ TODO list:
- consistent note length
- functional REPL
- triangle wave generation
- polyphony doesn't sound out of tune anymore?
- saw and square waves sounds bad, add LPF to soften it
Using uv for Python dependency management
Installing uv:
curl -LsSf https://astral.sh/uv/install.sh | shSyncing Python version, .venv, dependencies (optional command):
uv syncUsing .venv (optional command, as it should be automatic):
source ./venv/Scripts/activateSyncing and running project:
uv run main.pyLinting with ruff:
uvx ruff check .uvx ruff formatOther useful commands:
Creating 'pyproject.toml' and '.python-version:
uv initAdding package dependencies to 'pyproject.toml':
uv add <package>Adding package dependencies from 'requirements.txt' to 'pyproject.toml':
uv add -r requirements.txtAdding ruff and pytest as development dependencies:
uv add ruff --devuv add pytest --devGenerating 'requirements.txt' from a UV lock file:
uv export -o requirements.txt