Skip to content

Commit

Permalink
Osad eini sample
Browse files Browse the repository at this point in the history
  • Loading branch information
MustafaAlotbah committed Sep 10, 2024
1 parent c78f29e commit a77acb1
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 25 deletions.
142 changes: 125 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,54 @@

**py_guitar_synth** is a Python package for generating realistic guitar music from tab sheets by modeling the physical and musical characteristics of guitar strings and performance techniques. The project allows users to convert guitar tabs into audio, simulating strokes, note durations, transitions, and string properties.

<p align="center">
<img src="docs/logo.png" alt="Logo" width="150"/>
</p>

## Features

- Convert guitar tab sheets into realistic audio signals.
- Model various string characteristics such as inharmonicity, decay rates, and vibrato.
- Apply impulse response convolution to simulate room acoustics.
- Add custom echo effects to audio output.
- Support for multiple instruments (guitar, violin, piano).
- Model various string characteristics such as _inharmonicity_, _decay rates_, and _vibrato_.
- Apply _impulse response convolution_ to simulate room acoustics.
- Add custom _echo effects_ to audio output.
- Support for multiple instruments (classical guitar, violin, piano).
- Built-in JSON-based configuration for instruments.
- Easily extendable for new instruments and custom sounds.

[Check out the documentation here](/docs/index.rst)

## Play Samples

**Osad Eini with a classical guitar**

<audio controls>
<source src="media/osad_eini_violin.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>

**Osad Eini with a violin**

<audio controls>
<source src="/media/osad_eini_violin.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>

## Installation

You can clone this repository and install the package locally:

```bash
git clone https://github.com/your-repo-url/py_guitar_synth.git
git clone https://github.com/mustafaalotbah/py_guitar_synth.git
cd py_guitar_synth
pip install .
```

## Dependencies

- Python 3.6+
- Python 3.9+
- `numpy`
- `soundfile`
- `sounddevice`

Ensure all dependencies are installed by running:

Expand Down Expand Up @@ -100,25 +121,112 @@ signal = generate_guitar_signal_from_sheet(
)
```

### Music Sheet Tab Format

`py_guitar_synth` supports a structured format for music sheets, which includes both metadata and the tab itself.
The metadata section provides information about the piece, such as the title, author, tempo (in beats per minute), and any capo settings.
The tab section follows, specifying the notes for each string in traditional guitar tab notation.

**Example**

```text
Title: Agua Marina
Author: Paco Cepero
bpm: 90
capo fret: 2
# This is a comment
e |----3-2-3-2!3!2!-0++---| |----3-2-3-2!3!2!-0++-| |-0-2-0-2-0!2!0!------|
B |-0---------------------| |-0-------------------| |-----------------4++-|
G |-----------------------| |---------------------| |---------------------|
D |-----------------2+r---| |-----------------2+r-| |-----------------1+r-|
A |-----------------------| |---------------------| |---------------------|
E |-----------------------| |---------------------| |---------------------|
```

#### Metadata Section:

- **Title**: The title of the composition. In this example, it is _Agua Marina_.
- **Author**: The composer or arranger, here _Paco Cepero_, known for his contributions to flamenco music.
- **bpm (beats per minute)**: The tempo of the piece, which dictates the pacing of the performance. A tempo of `90 bpm` indicates a moderately slow pace, typical for expressive styles like ballads or slower flamenco pieces.
- **Capo Fret**: The fret position where the capo is placed. The capo at the second fret transposes all notes up by two semitones, effectively raising the pitch of the entire performance without altering the fingering patterns.

#### Tablature Section:

The tablature (or tab) notation directly corresponds to the guitar fretboard and specifies both pitch (fret number) and duration (note value) for each note.

- **Strings (e, B, G, D, A, E)**: These represent the six strings of the guitar, with `e` being the high e string (1st string) and `E` the low E string (6th string).

- **Fret Numbers**: The numbers indicate which fret to press on a given string. For example, `3` means pressing the 3rd fret.

- **Rhythmic Symbols**:

- **`!`**: Marks shorter rhythmic values, such as eighth notes (`!`) or sixteenth notes (`!!`), providing finer rhythmic granularity.
- **`++`**: Indicates longer note values like whole notes (`++`) or half notes (`+`), corresponding to sustained pitches that resonate longer.
- **`r`**: The "let ring" indicator, meaning the note should continue to resonate after being plucked, producing a sustained tone.
- **Bars (`|----|`)**: The vertical bars divide the tab into readable measures but serve only as a visual guide. The width of each section between bars has no impact on timing or structure in the `py_guitar_synth` system, meaning the spacing is purely for organizational clarity. The actual note durations are encoded using symbols such as `!` or `++`.

## Command-Line Interface (CLI)

You can use the package from the command line with the guitar_synth command to generate a guitar signal.
The `py_guitar_synth` package allows users to generate realistic guitar or other instrument sounds from a musical sheet, either predefined or from a file.
The command-line interface (CLI) provides various options to customize the output, including instrument choice, sound effects, and more.

#### Usage

```shell
python -m py_guitar_synth [-h] [-i {classical_guitar,violin,piano}] [-s SHEET] [-p PLUCK_POSITION] [--sr SR] [--no-convolution] [--ir-file IR_FILE] [--no-echo] [--echo-delay ECHO_DELAY] [--echo-decay ECHO_DECAY]
```


```shell
guitar_synth --instrument path/to/instrument.json --sheet path/to/tab.txt --output output.wav
python -m py_guitar_synth --instrument path/to/instrument.json --sheet path/to/tab.txt --output output.wav
```

#### CLI Options:

- `--instrument` : Path to the JSON file defining the instrument's properties (e.g., guitar, violin).
- `--sheet` : Path to the guitar tab file or the name of a predefined sheet.
- `--output` : Path where the generated WAV file will be saved.
- `--convolve` : Whether to apply an impulse response (room simulation). Default is `True`.
- `--impulse-response` : Path to a custom impulse response WAV file for convolution.
- `--echo` : Whether to add an echo effect. Default is `True`.
- `--echo-delay` : Set the delay time for the echo (default: 0.2 seconds).
- `--echo-decay` : Set the decay factor for the echo (default: 0.2).
- `--pluck-position` : Set the pluck position on the string (default: 0.7).
- **`-i {classical_guitar, violin, piano}` / `--instrument {classical_guitar, violin, piano}`**:
This flag allows you to specify the instrument for the synthesis. The tool supports three default instruments:

- `classical_guitar`: The default instrument, which simulates the sound of a nylon-string classical guitar.
- `violin`: A simulation of the violin, capturing the unique characteristics of string-based sound generation.
- `piano`: Simulates piano keys, focusing on producing hammer-strike sounds instead of string plucks.

- **`-s SHEET` / `--sheet SHEET`**:
This option allows you to specify either a predefined sheet or the path to a custom tab file.

- Predefined options include `law_bass_f_aini`, `agua_marina` and `osad_eini`, which are stored in the tool as examples.
- If you prefer to use your custom music, provide the file path to a valid guitar tab sheet. The tool will parse the file and synthesize the corresponding sound.

- **`-p PLUCK_POSITION` / `--pluck_position PLUCK_POSITION`**:
The `pluck_position` parameter represents the point along the length of the string where it is plucked, and it significantly influences the harmonic content of the sound produced.
For instance, plucking near the bridge emphasizes higher harmonics, while plucking closer to the midpoint (e.g., 0.5) suppresses some harmonics.

- The value is a floating point number between `0` and `1`, where `0` represents plucking near the bridge (brighter sound), and `1` represents plucking near the neck (mellower sound).
- The default value is `0.7`, which corresponds to plucking closer to the middle of the string, providing a balanced sound.
- **`--sr SR` / `--sampling-rate SR`**:
This option controls the sample rate (`SR`) of the generated audio, in Hertz. The sample rate determines the audio quality and the size of the generated file.

- The default value is `44100 Hz`, which is standard CD quality, but users can increase this for higher fidelity or reduce it for faster processing and smaller file sizes.
- **`--no-convolution`**:
By default, the tool applies an impulse response convolution to the audio, which simulates the acoustic properties of an environment (e.g., a guitar body, concert hall). This flag disables that effect, resulting in a raw, "dry" sound that has no added reverberation or acoustic characteristics.

- **`--ir-file IR_FILE`**:
This option allows you to provide a custom impulse response (IR) file in WAV format. An impulse response is a way to simulate how sound behaves in a specific acoustic space, like a room or inside an instrument's body.

- If you have a custom impulse response, you can specify its path here to customize the acoustic environment applied to the sound synthesis. If this flag is not used, the default impulse response (if convolution is enabled) will be applied.
- **`--no-echo`**:
This flag disables the echo effect applied to the sound. The echo effect simulates a delay or reverberation, adding depth and realism to the generated sound.

- Disabling this will result in a more direct, clean sound without any trailing reflections.
- **`--echo-delay ECHO_DELAY`**:
If echo is enabled (by default), this option allows you to set the delay time between the original sound and the first echo. The value represents time in seconds (e.g., `0.2` seconds by default).

- A higher delay creates a more pronounced echo, simulating a larger room or a greater distance between reflective surfaces.
- **`--echo-decay ECHO_DECAY`**:
The `echo_decay` parameter controls how quickly the echo fades out after being heard. A value closer to `0` results in a faster fade, while higher values produce a longer-lasting echo. The default decay factor is `0.2`.

- This gives control over how much reverb or echo is applied, allowing for subtle or more prominent effects, depending on the user’s preference.

Example usage:

Expand Down
26 changes: 23 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
py_guitar_synth
Py Guiter Synthesizer
===============

Welcome to the `py_guitar_synth` project documentation! This project focuses on synthesizing realistic guitar sounds based on guitar tab sheets, simulating the physical and musical characteristics of strings and performance techniques.

.. image:: /docs/logo.png
:alt: py_guitar_synth Logo
:align: center
:scale: 10%

.. contents:: Table of Contents
:depth: 3
:local:
Expand Down Expand Up @@ -105,11 +110,26 @@ Notes played simultaneously across different strings can be written in a vertica
This represents an E minor chord.

Example:
A chord can also be played while the hand is gradually plucking the strings, resulting in a technique known as arpeggiation or a rolled chord.
This technique involves plucking each string in quick succession rather than striking all the strings at once.
In the following example, the "r" symbol indicates that each note should "let ring," meaning the string should continue to resonate after being plucked.

.. code-block:: bash
e|-3!-3r--| # Eighth note followed by let ring
e |----------------------3++r-|
B |------------------3!!r-----|
G |--------------0!!r---------|
D |----------0!!r-------------|
A |-----2!!r------------------|
E |-3!!r----------------------|
This notation represents a G major chord, but instead of strumming all the strings simultaneously, the player gradually plucks each string,
starting from the low E string to the high e string. As each note is played, the previous ones continue to resonate due to the "r" (letRing=True),
allowing for a smooth, cascading effect as the chord is built up across the strings.

The numbers represent the frets, while the symbols (e.g., ++, !!) indicate the duration of each note, from longer whole notes (++) to shorter sixteenth notes (!!).
This rolling technique creates a more textured and dynamic sound, often used in classical and fingerstyle guitar playing.


Custom Instrument Creation
---------------------------
Expand Down
Binary file added docs/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/osad_eini_classical_guitar.mp3
Binary file not shown.
Binary file added media/osad_eini_violin.mp3
Binary file not shown.
26 changes: 22 additions & 4 deletions py_guitar_synth/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import argparse
import sounddevice as sd
import threading

from py_guitar_synth import (
default_classical_guitar,
default_violine,
Expand All @@ -23,6 +25,7 @@
'agua_marina': agua_marina
}


def main():
parser = argparse.ArgumentParser(
description="py_guitar_synth: A tool for synthesizing guitar audio from tab sheets."
Expand Down Expand Up @@ -86,6 +89,8 @@ def main():
# Load the selected instrument and sheet
instrument = INSTRUMENTS[args.instrument]

print(f"Reading Sheet {args.sheet}")

# Load the sheet, either from predefined sheets or from a file
if args.sheet in SHEETS:
sheet = SHEETS[args.sheet]
Expand All @@ -110,10 +115,23 @@ def main():
echo_decay=args.echo_decay
)

# Play the generated audio using sounddevice
print(f"Playing {args.sheet} with {args.instrument}...")
sd.play(signal, args.sr)
sd.wait()
def play():
# Play the generated audio using sounddevice
print(f"Playing '{sheet.title}' by '{sheet.author}' with {args.instrument}...")
sd.play(signal, args.sr)
sd.wait()

# Start the playback in a separate thread
play_thread = threading.Thread(target=play)
play_thread.start()

# Main thread continues to run
try:
while play_thread.is_alive():
play_thread.join(timeout=1)
except KeyboardInterrupt:
sd.stop()
print("Playback stopped.")


if __name__ == '__main__':
Expand Down
50 changes: 50 additions & 0 deletions py_guitar_synth/assets/osad_eini.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# This Music sheet is written by Mustafa Alotbah
#

Title: Osad Eini
Author: Amr Diab
bpm: 55
capo fret: 3

e |-----------------------| |-----------------------| |----------------------------|
B |--0!1!0r-----0!2r-2++--| |-1-1!0---0-0!1!3!1-1++-| |-1-1!0-1!-3+-3!2!!1!!0-0++--|
G |---------2-0-0!2r-2++--| |-------2---------------| |----------------------------|
D |-2-----------0+r-------| |-----------------------| |----------------------------|
A |-----------------------| |-----------------------| |----------------------------|
E |-----------------------| |-----------------------| |----------------------------|

e |-----------------------| |-----------------------| |----------------------------|
B |-0-0!1!0r-----0!2++r---| |-----------------------| |----------------------------|
G |----------2-0-0!2++r---| |-2-2!0--2!4+-4!5!4-4++-| |4!-4!2-2!0-0-0!2++-2!4!2-2++|
D |--------------0++r-----| |-----------------------| |----------------------------|
A |-----------------------| |-----------------------| |----------------------------|
E |-----------------------| |-----------------------| |----------------------------|

e |-----------------------| |-----------------0++r--| |----------------------------|
B |-0-0!1!0r-----0!2++r---| |-------------0!!r------| |-0!!1!!3!!5++-5!-5!3-3!1-1++|
G |----------2-0-0!2++r---| |---------2!!r----------| |----------------------------|
D |--------------0++r-----| |-----2!!r--------------| |----------------------------|
A |-----------------------| |-0!!r------------------| |----------------------------|
E |-----------------------| |-----------------------| |----------------------------|

e |-----------------------| |-----------------------| |----------------------------|
B |-3!1!0-1-1!3++---------| |-3!-3!1-1!0-0+---------| |---0-0!1!3!1++--------------|
G |-----------------------| |-----------------------| |-2--------------------------|
D |-----------------------| |-----------------------| |----------------------------|
A |-----------------------| |-----------------------| |----------------------------|
E |-----------------------| |-----------------------| |----------------------------|

e |-----------------------| |-----------------------| |----------------------------|
B |--10--0-1-3-3!5++------| |-0-1-3-3!5++--5++------| |-5!-5!6!5!-3!5!3!r----------|
G |-----2-----------------| |-----------------------| |-------------------5++------|
D |-----------------------| |-----------------------| |----------------------------|
A |-----------------------| |-----------------------| |----------------------------|
E |-----------------------| |-----------------------| |----------------------------|

e |-----------------------| |-----------------------| |----------------------------|
B |-3!1!0-1-1!3++---------| |-1!-3!5!3!-1!3!1!------| |----------------------------|
G |-----------------------| |------------------4++--| |----------------------------|
D |-----------------------| |-----------------------| |----------------------------|
A |-----------------------| |-----------------------| |----------------------------|
E |-----------------------| |-----------------------| |----------------------------|

6 changes: 5 additions & 1 deletion py_guitar_synth/sheets.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@

# Load the 'agua_marina.txt' tab file
with resources.as_file(resources.files('py_guitar_synth.assets').joinpath('agua_marina.txt')) as f:
agua_marina = parse_guitar_tab_from_file(str(f))
agua_marina = parse_guitar_tab_from_file(str(f))

# Load the 'agua_marina.txt' tab file
with resources.as_file(resources.files('py_guitar_synth.assets').joinpath('osad_eini.txt')) as f:
osad_eini = parse_guitar_tab_from_file(str(f))
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ classifiers = [
dependencies = [
"numpy",
"soundfile",
"sounddevice",
"argparse"
]

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
numpy
soundfile
sounddevice
argparse
importlib_resources; python_version < "3.10"
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
'numpy',
'argparse',
'soundfile',
'sounddevice',
],
include_package_data=True,
entry_points={
Expand Down

0 comments on commit a77acb1

Please sign in to comment.