Skip to content

Commit c2a02ed

Browse files
authored
Merge pull request #5 from yuma-m/feature/scale
Support scale expression to generate chord
2 parents 7db13a7 + fdd558f commit c2a02ed

File tree

8 files changed

+99
-19
lines changed

8 files changed

+99
-19
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ python:
55
- "3.4"
66
- "3.5"
77
- "3.6"
8+
- "3.7"
9+
- "3.8"
810

911
before_install:
1012
- sudo apt-get -qq update
@@ -15,6 +17,7 @@ install:
1517
- pip install .
1618

1719
script:
20+
- pip install parameterized
1821
- python setup.py test
1922

2023
deploy:

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## TBD
2+
3+
- Support scale expression to generate chord
4+
15
## 0.1.5
26

37
- Support triangle wave.

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ $ pip install pyaudio
4444

4545
```python
4646
# Play C major
47-
>>> chord = [261.626, 329.628, 391.996]
47+
>>> chord = ["C3", "E3", "G3"]
48+
>>> player.play_wave(synthesizer.generate_chord(chord, 3.0))
49+
50+
# You can also specify frequencies to play just intonation
51+
>>> chord = [440.0, 550.0, 660.0]
4852
>>> player.play_wave(synthesizer.generate_chord(chord, 3.0))
4953
```
5054

@@ -64,7 +68,7 @@ $ pip install pyaudio
6468
>>> from synthesizer import Writer
6569
>>> writer = Writer()
6670

67-
>>> chord = [261.626, 329.628, 391.996]
71+
>>> chord = ["C4", "E4", "G4"]
6872
>>> wave = synthesizer.generate_chord(chord, 3.0)
6973
>>> writer.write_wave("path/to/your.wav", wave)
7074
```

example/play_chord.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,42 @@
66
from synthesizer import Player, Synthesizer, Waveform
77

88

9-
BASE = 261.626 # C4
10-
11-
129
def main():
1310
player = Player()
1411
player.open_stream()
1512

1613
print("play major chord")
1714
synthesizer = Synthesizer(osc1_waveform=Waveform.sine, osc1_volume=1.0, use_osc2=False)
18-
chord = [BASE, BASE * 2.0 ** (4 / 12.0), BASE * 2.0 ** (7 / 12.0)]
15+
chord = ["C4", "E4", "G4"]
1916
player.play_wave(synthesizer.generate_chord(chord, 3.0))
2017
time.sleep(0.5)
2118

2219
print("play minor chord")
23-
chord = [BASE, BASE * 2.0 ** (3 / 12.0), BASE * 2.0 ** (7 / 12.0)]
20+
chord = ["C4", "Eb4", "G4"]
2421
player.play_wave(synthesizer.generate_chord(chord, 3.0))
2522
time.sleep(0.5)
2623

2724
print("play sus4 chord")
28-
chord = [BASE, BASE * 2.0 ** (5 / 12.0), BASE * 2.0 ** (7 / 12.0)]
25+
chord = ["C4", "F4", "G4"]
2926
player.play_wave(synthesizer.generate_chord(chord, 3.0))
3027
time.sleep(0.5)
3128

3229
print("play 7th chord")
33-
chord = [BASE, BASE * 2.0 ** (4 / 12.0), BASE * 2.0 ** (7 / 12.0), BASE * 2.0 ** (10 / 12.0)]
30+
chord = ["C4", "E4", "G4", "Bb4"]
3431
player.play_wave(synthesizer.generate_chord(chord, 3.0))
3532
time.sleep(0.5)
3633

3734
print("play add9 chord")
38-
chord = [BASE, BASE * 2.0 ** (4 / 12.0), BASE * 2.0 ** (7 / 12.0), BASE * 2.0 ** (14 / 12.0)]
35+
chord = ["C4", "E4", "G4", "D5"]
3936
player.play_wave(synthesizer.generate_chord(chord, 3.0))
4037
time.sleep(0.5)
4138

4239
print("play chord sequence")
43-
chord = [BASE * 2.0 ** (2 / 12.0), BASE * 2.0 ** (5 / 12.0), BASE * 2.0 ** (9 / 12.0), BASE * 2.0 ** (12 / 12.0)]
40+
chord = ["D4", "F4", "A4", "C5"]
4441
player.play_wave(synthesizer.generate_chord(chord, 1.0))
45-
chord = [BASE * 2.0 ** (2 / 12.0), BASE * 2.0 ** (7 / 12.0), BASE * 2.0 ** (11 / 12.0)]
42+
chord = ["D4", "G4", "B4"]
4643
player.play_wave(synthesizer.generate_chord(chord, 1.0))
47-
chord = [BASE, BASE * 2.0 ** (4 / 12.0), BASE * 2.0 ** (7 / 12.0), BASE * 2.0 ** (12 / 12.0)]
44+
chord = ["E4", "G4", "C5"]
4845
player.play_wave(synthesizer.generate_chord(chord, 1.0))
4946

5047

requirements.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
enum34==1.1.6
2-
numpy==1.13.3
3-
PyAudio==0.2.11
4-
scipy==0.19.1
1+
enum34>=1.1.6
2+
numpy>=1.13.3
3+
PyAudio>=0.2.11
4+
scipy>=0.19.1

synthesizer/frequency.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
A1_FREQ = 55.0
2+
3+
BASE_FREQUENCY = {
4+
"C": A1_FREQ * 2 ** (-9 / 12.0),
5+
"C#": A1_FREQ * 2 ** (-8 / 12.0),
6+
"Db": A1_FREQ * 2 ** (-8 / 12.0),
7+
"D": A1_FREQ * 2 ** (-7 / 12.0),
8+
"D#": A1_FREQ * 2 ** (-6 / 12.0),
9+
"Eb": A1_FREQ * 2 ** (-6 / 12.0),
10+
"E": A1_FREQ * 2 ** (-5 / 12.0),
11+
"F": A1_FREQ * 2 ** (-4 / 12.0),
12+
"F#": A1_FREQ * 2 ** (-3 / 12.0),
13+
"Gb": A1_FREQ * 2 ** (-3 / 12.0),
14+
"G": A1_FREQ * 2 ** (-2 / 12.0),
15+
"G#": A1_FREQ * 2 ** (-1 / 12.0),
16+
"Ab": A1_FREQ * 2 ** (-1 / 12.0),
17+
"A": A1_FREQ,
18+
"A#": A1_FREQ * 2 ** (1 / 12.0),
19+
"Bb": A1_FREQ * 2 ** (1 / 12.0),
20+
"B": A1_FREQ * 2 ** (2 / 12.0),
21+
}
22+
23+
24+
def frequency_from_scale(scale):
25+
""" Calculate frequency from pitch
26+
27+
:param str scale: e.g. C4, Ab3, D#5
28+
:rtype: float
29+
:return: Frequency of pitch
30+
"""
31+
base, octave = scale[:-1], scale[-1]
32+
octave = int(octave)
33+
if base not in BASE_FREQUENCY:
34+
return ValueError("Unknown note: {}".format(base))
35+
return BASE_FREQUENCY[base] * 2 ** (octave - 1)

synthesizer/synthesizer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import scipy.signal
55
from enum import Enum
66

7+
from .frequency import frequency_from_scale
8+
79

810
class Waveform(Enum):
911
sine = "sine"
@@ -89,18 +91,20 @@ def _generate_wave(self, phases):
8991
def generate_constant_wave(self, frequency, length):
9092
u""" generate wave with constant frequency
9193
92-
:param float frequency: frequency of wave
94+
:param (float|str) frequency: frequency or scale of wave
9395
:param float length: length of wave (seconds)
9496
:rtype: numpy.array
9597
:return: normalized wave
9698
"""
99+
if isinstance(frequency, str):
100+
frequency = frequency_from_scale(frequency)
97101
phases = np.cumsum(2.0 * np.pi * frequency / self._rate * np.ones(int(self._rate * float(length))))
98102
return self._generate_wave(phases)
99103

100104
def generate_chord(self, freqs, length):
101105
u""" generate wave consists of multiple frequencies
102106
103-
:param list[float] freqs: list of frequencies
107+
:param list[float] freqs: list of frequencies or scales
104108
:param length: legnth of wave (seconds)
105109
:rtype: numpy.array
106110
:return: normalized wave

test/test_frequency.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from nose.tools import assert_almost_equal
4+
from parameterized import parameterized
5+
6+
from synthesizer.frequency import frequency_from_scale
7+
8+
9+
@parameterized([
10+
("A0", 27.5),
11+
("A#0", 29.135),
12+
("Bb0", 29.135),
13+
("B0", 30.868),
14+
("C1", 32.703),
15+
("C#1", 34.648),
16+
("Db1", 34.648),
17+
("D1", 36.708),
18+
("D#1", 38.891),
19+
("Eb1", 38.891),
20+
("E1", 41.203),
21+
("F1", 43.654),
22+
("F#1", 46.249),
23+
("Gb1", 46.249),
24+
("G1", 48.999),
25+
("G#1", 51.913),
26+
("Ab1", 51.913),
27+
("A1", 55),
28+
("C3", 130.813),
29+
("A4", 440),
30+
("E5", 659.255),
31+
])
32+
def test_frequency_from_scale(scale, frequency):
33+
assert_almost_equal(frequency_from_scale(scale), frequency, places=3)

0 commit comments

Comments
 (0)