aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Wang2023-03-16 22:59:41 -0400
committerAnthony Wang2023-03-16 22:59:41 -0400
commit0da46c8a33e7a9b61b564cce5c4c4526fe3d84dc (patch)
tree1c3948cdb3a6cd6119f2ede76279cde26e464eba
parent46911993234b7c9cc07546f56843b35621b7f811 (diff)
Clean up code and rename to yue
-rw-r--r--.gitignore2
-rw-r--r--README.md15
-rw-r--r--blend.nim (renamed from music.nim)61
-rw-r--r--blend.ogg (renamed from example.ogg)bin432160 -> 432160 bytes
-rw-r--r--blend.py (renamed from music.py)101
-rw-r--r--blend.scm (renamed from music.scm)4
-rw-r--r--keyboard.py39
-rw-r--r--lambeat.scm17
-rw-r--r--yue.nim (renamed from musiclib.nim)45
-rw-r--r--yue.py69
-rw-r--r--yue.scm (renamed from lib.scm)15
11 files changed, 165 insertions, 203 deletions
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 37c3b88..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/music
-/music.s32 \ No newline at end of file
diff --git a/README.md b/README.md
index eb1259f..87fbaae 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,9 @@
-# Lambeat
-Lambeat is a new way to make music using functional programming. It's heavily influenced by [Bytebeat](https://dollchan.net/bytebeat) and initially started out as an reimplementation of Bytebeat in Scheme. Since then, it's grown to be a delightful new way to make music with code.
+# yue
+
+yue is a programmatic music library, influenced by [Bytebeat](https://dollchan.net/bytebeat). It is designed to be low-level and enable you to build your own abstractions and functions. Think of it like an assembly language for music. There are three implementations in Python (PyPy recommended for speed), Scheme, and Nim. Check out this [sample code](blend.py) and [listen to it](blend.ogg)!
## Get started
-First, install [Sox](https://sox.sourceforge.net/) and clone this repo. Write some music in `music.scm`. Enjoy your music with `guile --fresh-auto-compile lambeat.scm | play -r 8000 -t s16 -`!
-For the Python version, use `pypy3 music.py | play -r 44100 -t s32 -` to listen and `pypy3 music.py | sox -r 44100 -t s32 - example.ogg` to save to a file.
+First, install [Sox](https://sox.sourceforge.net/) and clone this repo.
-For the nim version
-```
-nim c --mm:orc -d:release music.nim
-./music > music.s32
-play -r 44100 -t s32 music.s32
-```
+To run a program that uses yue, use `pypy file.py`, `guile --fresh-auto-compile file.scm`, or `nim c --mm:orc -d:release file.nim && ./file`, and pipe to `play -r 44100 -t s32 -`. If you would like to save to a file, pipe to `sox -r 44100 -t s32 - file.ogg` instead.
diff --git a/music.nim b/blend.nim
index 30c256c..df87253 100644
--- a/music.nim
+++ b/blend.nim
@@ -1,56 +1,6 @@
-import musiclib, std/[math, sugar]
+import yue, std/[sugar]
-# Number of times to sample each second
-const bitrate = 44100
-
-func osc_weird_pluck*(f, t: float): float =
- # I got this as a bug
- let w = 2 * PI * f
- let wxt = w * t
- let exp_swxt = math.exp(-0.001 * wxt)
- let y0 = 0.6 * math.sin(wxt)
- let y1 = 0.2 * math.sin(2 * wxt)
- let y2 = 0.05 * math.sin(3 * wxt)
- let y3 = (y0 + y1 + y2) * exp_swxt
- let y4 = (1+y3)*y3*y3 # this line is different
- y4 * (1 + 16 * t * math.exp(-6 * t))
-
-func osc_piano*(f, t: float): float =
- ## Returns the intensity of a tone of frequency f sampled at time t
- ## t starts at 0 (note start)
- # https://dsp.stackexchange.com/questions/46598/mathematical-equation-for-the-sound-wave-that-a-piano-makes
- # https://youtu.be/ogFAHvYatWs?t=254
- # return int(2**13*(1+square(t, 440*2**(math.floor(5*t)/12))))
- # Y = sum([math.sin(2 * i * math.pi * t * f) * math.exp(-0.0004 * 2 * math.pi * t * f) / 2**i for i in range(1, 4)])
- # Y += Y * Y * Y
- # Y *= 1 + 16 * t * math.exp(-6 * t)
- let w = 2 * PI * f
- let ewt = math.exp(-0.001 * w * t)
- var Y = 0.6 * math.sin(w * t) * ewt +
- 0.2 * math.sin(2 * w * t) * ewt +
- 0.05 * math.sin(3 * w * t) * ewt
-
- let Y2 = Y * (Y * Y + 1)
- Y2 * (1 + 16 * t * math.exp(-6 * t))
-
-func osc_pulse*(f, t: float, phasedrift: float = 0.0): float =
- let doublewidth = 1.0 / f
- let width = doublewidth / 2
- let phase: float = (t + doublewidth * phasedrift) mod doublewidth
- if phase < width: 1.0 else: -1.0
-
-func osc_saw*(f,t:float, phasedrift: float = 0.0):float =
- let doublewidth = 1.0 / f
- let width = doublewidth / 2
- let phase: float = (t + doublewidth * phasedrift) mod doublewidth
- if phase < width:
- -1.0 + 2.0 * phase / width
- else:
- 1.0 - 2.0 * (phase - width) / (1.0 - width)
-
-func freq*(octave, step: float): float =
- ## Returns the frequency of a note
- 55 * pow(2, (octave + step / 12 - 1))
+const GAIN_NORMAL = 0.22
var osc: OscFn = (f, t: float) => 0.0
@@ -58,10 +8,6 @@ proc p*(len, octave, step, vol: float = 1): Note =
## Note helper constructor
(len, freq(octave, step), vol, osc)
-#------- song region -------
-
-const GAIN_NORMAL = 0.22
-
osc = (f, t: float) => osc_piano(f, t) * GAIN_NORMAL
let intro = [
@@ -334,6 +280,9 @@ music.process(bass, 64)
music.process(outro, 72, 4)
music.sortByStart()
+# Number of times to sample each second
+const bitrate = 44100
+
# Print out music encoded in s32 to standard output
for i in (0 * bitrate ..< 84 * bitrate):
let bytes = cast[array[4, uint8]](music.at(i / bitrate))
diff --git a/example.ogg b/blend.ogg
index af1b409..af1b409 100644
--- a/example.ogg
+++ b/blend.ogg
Binary files differ
diff --git a/music.py b/blend.py
index 74197b6..b1c9df8 100644
--- a/music.py
+++ b/blend.py
@@ -1,10 +1,4 @@
-import bisect
-import math
-import struct
-import sys
-
-# Number of times to sample each second
-bitrate = 44100
+import yue
intro = [
(1,3,3),
@@ -250,81 +244,18 @@ outro = [
(16,3,7,2),
]
-def process(notes, start, speed=1, gain=1):
- """
- Adds a list of notes to the music list
- """
- t = start
- for note in notes:
- vol = 1
- if len(note) == 4:
- vol = note[3]
- start = min(t, t + note[0] / speed)
- end = max(t, t + note[0] / speed)
- music.append((start, end, note[1], note[2], vol * gain))
- t = end
-
-
-# Process all lists of notes
-music = []
-process(intro, 0, 4)
-process(melody, 8, 4)
-process(melody, 24, 4)
-process(bass, 24, gain=1.5)
-process(bass, 32, gain=1.5)
-process(melody, 40, 4)
-process(melody2, 40, 4)
-process(bass, 40, gain=1.5)
-process(bass, 48, gain=1.5)
-process(melody, 56, 4)
-process(melody3, 56, 4)
-process(bass, 56, gain=1.5)
-process(bass, 64, gain=1.5)
-process(outro, 72, 4)
-music.sort()
-
-
-def freq(octave, step):
- """
- Returns the frequency of a note
- """
- return 55 * 2 ** (octave + step / 12 - 1)
-
-
-def tone(f, t):
- """
- Returns the intensity of a tone of frequency f sampled at time t
- """
- # https://dsp.stackexchange.com/questions/46598/mathematical-equation-for-the-sound-wave-that-a-piano-makes
- # https://youtu.be/ogFAHvYatWs?t=254
- # return int(2**13*(1+square(t, 440*2**(math.floor(5*t)/12))))
- # Y = sum([math.sin(2 * i * math.pi * t * f) * math.exp(-0.0004 * 2 * math.pi * t * f) / 2**i for i in range(1, 4)])
- # Y += Y * Y * Y
- # Y *= 1 + 16 * t * math.exp(-6 * t)
- w = 2 * math.pi * f
- Y = 0.6 * math.sin(w * t) * math.exp(-0.001 * w * t)
- Y += 0.2 * math.sin(2 * w * t) * math.exp(-0.001 * w * t)
- Y += 0.05 * math.sin(3 * w * t) * math.exp(-0.001 * w * t)
- Y += Y * Y * Y
- Y *= 1 + 16 * t * math.exp(-6 * t)
- return Y
-
-
-def at(t):
- """
- Returns the total intensity of music sampled at time t
- """
- i = bisect.bisect(music, (t, 2**31))
- # This is actually pretty efficient ngl
- # Because people usually don't have that many overlapping notes
- ret = 0
- for j in range(max(i - 32, 0), i):
- m = music[j]
- # if m[0] + m[1] > t:
- ret += m[4] * tone(freq(m[2], m[3]), t - m[0])
- return int(2**28 * ret)
-
-
-# Print out music encoded in s32 to standard output
-for i in range(0 * bitrate, 84 * bitrate):
- sys.stdout.buffer.write(struct.pack('i', at(i / bitrate)))
+yue.process(intro, 0, 4, blend=1)
+yue.process(melody, 8, 4, blend=1)
+yue.process(melody, 24, 4, blend=1)
+yue.process(bass, 24, gain=1.5, blend=1)
+yue.process(bass, 32, gain=1.5, blend=1)
+yue.process(melody, 40, 4, blend=1)
+yue.process(melody2, 40, 4, blend=1)
+yue.process(bass, 40, gain=1.5, blend=1)
+yue.process(bass, 48, gain=1.5, blend=1)
+yue.process(melody, 56, 4, blend=1)
+yue.process(melody3, 56, 4, blend=1)
+yue.process(bass, 56, gain=1.5, blend=1)
+yue.process(bass, 64, gain=1.5, blend=1)
+yue.process(outro, 72, 4, blend=1)
+yue.play(0, 84)
diff --git a/music.scm b/blend.scm
index b779eee..1824ca1 100644
--- a/music.scm
+++ b/blend.scm
@@ -1,4 +1,4 @@
-(include "lib.scm")
+(include "yue.scm")
; https://musiclab.chromeexperiments.com/Song-Maker/song/6021372552937472
(define (music t)
@@ -73,3 +73,5 @@
(2 3 35 0.5)
(2 0 35.5 0.5)
))))
+
+(play 6 100)
diff --git a/keyboard.py b/keyboard.py
index 301c950..73892ea 100644
--- a/keyboard.py
+++ b/keyboard.py
@@ -1,51 +1,26 @@
import math
-import sounddevice as sd
import os
import sys
+import sounddevice as sd
+import yue
# Number of times to sample each second
bitrate = 44100
-def freq(octave, step):
- """
- Returns the frequency of a note
- """
- return 55 * 2 ** (octave + step / 12 - 1)
-
-
-def tone(f, t):
- """
- Returns the intensity of a tone of frequency f sampled at time t
- """
- # https://dsp.stackexchange.com/questions/46598/mathematical-equation-for-the-sound-wave-that-a-piano-makes
- # https://youtu.be/ogFAHvYatWs?t=254
- # return int(2**13*(1+square(t, 440*2**(math.floor(5*t)/12))))
- # Y = sum([math.sin(2 * i * math.pi * t * f) * math.exp(-0.0004 * 2 * math.pi * t * f) / 2**i for i in range(1, 4)])
- # Y += Y * Y * Y
- # Y *= 1 + 16 * t * math.exp(-6 * t)
- w = 2 * math.pi * f
- Y = 0.6 * math.sin(w * t) * math.exp(-0.001 * w * t)
- Y += 0.2 * math.sin(2 * w * t) * math.exp(-0.001 * w * t)
- Y += 0.05 * math.sin(3 * w * t) * math.exp(-0.001 * w * t)
- Y += Y * Y * Y
- Y *= 1 + 16 * t * math.exp(-6 * t)
- return Y
-
-
note = []
for i in range(0, 24):
- note.append([tone(freq(3, i), j / bitrate) / 4 for j in range(0, 3 * bitrate)])
+ note.append([yue.tone(yue.freq(3, i), j / bitrate) / 4 for j in range(0, 3 * bitrate)])
sd.default.samplerate = bitrate
-print('READY')
+print("READY")
while True:
- os.system('stty raw -echo')
+ os.system("stty raw -echo")
c = sys.stdin.read(1)
- os.system('stty -raw echo')
+ os.system("stty -raw echo")
- x = '`1234567890-~!@#$%^&*()_'.index(c)
+ x = "`1234567890-~!@#$%^&*()_".index(c)
print(x)
sd.play(note[x])
diff --git a/lambeat.scm b/lambeat.scm
deleted file mode 100644
index 5214b56..0000000
--- a/lambeat.scm
+++ /dev/null
@@ -1,17 +0,0 @@
-(use-modules (ice-9 binary-ports))
-(include "music.scm")
-
-; Bitrate is the number time to sample the music function each second
-(define bitrate 8000)
-
-; Get the music as a list sampled at the bitrate
-(define (play t end)
- (cons ((lambda (a)
- (let ((b (modulo (inexact->exact (round (* (+ a 2) 32768))) 65536))) cons
- (put-u8 (current-output-port) (modulo b 256))
- (put-u8 (current-output-port) (quotient b 256)))) (music t))
- (if (< t end)
- (play (+ t (/ 1 bitrate)) end)
- '())))
-
-(play 6 100)
diff --git a/musiclib.nim b/yue.nim
index c21cb95..51553a2 100644
--- a/musiclib.nim
+++ b/yue.nim
@@ -73,3 +73,48 @@ proc at*(music: openArray[ProcessedNote], t: float): int32 =
int32.low
else:
int32(ret)
+
+func osc_weird_pluck*(f, t: float): float =
+ # I got this as a bug
+ let w = 2 * PI * f
+ let wxt = w * t
+ let exp_swxt = math.exp(-0.001 * wxt)
+ let y0 = 0.6 * math.sin(wxt)
+ let y1 = 0.2 * math.sin(2 * wxt)
+ let y2 = 0.05 * math.sin(3 * wxt)
+ let y3 = (y0 + y1 + y2) * exp_swxt
+ let y4 = (1+y3)*y3*y3 # this line is different
+ y4 * (1 + 16 * t * math.exp(-6 * t))
+
+func osc_piano*(f, t: float): float =
+ ## Returns the intensity of a tone of frequency f sampled at time t
+ ## t starts at 0 (note start)
+ # https://dsp.stackexchange.com/questions/46598/mathematical-equation-for-the-sound-wave-that-a-piano-makes
+ # https://youtu.be/ogFAHvYatWs?t=254
+ let w = 2 * PI * f
+ let ewt = math.exp(-0.001 * w * t)
+ var Y = 0.6 * math.sin(w * t) * ewt +
+ 0.2 * math.sin(2 * w * t) * ewt +
+ 0.05 * math.sin(3 * w * t) * ewt
+
+ let Y2 = Y * (Y * Y + 1)
+ Y2 * (1 + 16 * t * math.exp(-6 * t))
+
+func osc_pulse*(f, t: float, phasedrift: float = 0.0): float =
+ let doublewidth = 1.0 / f
+ let width = doublewidth / 2
+ let phase: float = (t + doublewidth * phasedrift) mod doublewidth
+ if phase < width: 1.0 else: -1.0
+
+func osc_saw*(f,t:float, phasedrift: float = 0.0):float =
+ let doublewidth = 1.0 / f
+ let width = doublewidth / 2
+ let phase: float = (t + doublewidth * phasedrift) mod doublewidth
+ if phase < width:
+ -1.0 + 2.0 * phase / width
+ else:
+ 1.0 - 2.0 * (phase - width) / (1.0 - width)
+
+func freq*(octave, step: float): float =
+ ## Returns the frequency of a note
+ 55 * pow(2, (octave + step / 12 - 1))
diff --git a/yue.py b/yue.py
new file mode 100644
index 0000000..dda9e79
--- /dev/null
+++ b/yue.py
@@ -0,0 +1,69 @@
+import bisect
+import math
+import struct
+import sys
+
+# Number of times to sample each second
+bitrate = 44100
+music = []
+
+
+def process(notes, start, speed=1, gain=1, blend=0):
+ """
+ Adds a list of notes to the music list
+ """
+ t = start
+ for note in notes:
+ vol = 1
+ if len(note) == 4:
+ vol = note[3]
+ start = min(t, t + note[0] / speed)
+ end = max(t, t + note[0] / speed)
+ music.append((start, end + 16 * int(blend), note[1], note[2], vol * gain))
+ t = end
+
+
+def freq(octave, step):
+ """
+ Returns the frequency of a note
+ """
+ return 55 * 2 ** (octave + step / 12 - 1)
+
+
+def tone(f, t):
+ """
+ Returns the intensity of a tone of frequency f sampled at time t
+ https://dsp.stackexchange.com/questions/46598/mathematical-equation-for-the-sound-wave-that-a-piano-makes
+ https://youtu.be/ogFAHvYatWs?t=254
+ """
+ w = 2 * math.pi * f
+ Y = 0.6 * math.sin(w * t) * math.exp(-0.001 * w * t)
+ Y += 0.2 * math.sin(2 * w * t) * math.exp(-0.001 * w * t)
+ Y += 0.05 * math.sin(3 * w * t) * math.exp(-0.001 * w * t)
+ Y += Y * Y * Y
+ Y *= 1 + 16 * t * math.exp(-6 * t)
+ return Y
+
+
+def at(t):
+ """
+ Returns the total intensity of music sampled at time t
+ This is actually pretty efficient ngl
+ Because people usually don't have that many overlapping notes
+ """
+ i = bisect.bisect(music, (t, 2**31))
+ ret = 0
+ for j in range(max(i - 32, 0), i):
+ m = music[j]
+ if m[1] > t:
+ ret += m[4] * tone(freq(m[2], m[3]), t - m[0])
+ return int(2**28 * ret)
+
+
+def play(start, end):
+ """
+ Print music from the start time to end time encoded in s32 to standard output
+ """
+ music.sort()
+ for i in range(start * bitrate, end * bitrate):
+ sys.stdout.buffer.write(struct.pack("i", at(i / bitrate)))
diff --git a/lib.scm b/yue.scm
index 36a65c4..6b692ab 100644
--- a/lib.scm
+++ b/yue.scm
@@ -1,3 +1,8 @@
+(use-modules (ice-9 binary-ports))
+
+; Bitrate is the number time to sample the music function each second
+(define bitrate 8000)
+
; Triangle wave with a period of 1 second
(define (tri t)
(let ((m (floor-remainder (+ t (/ 1 4)) 1)))
@@ -20,3 +25,13 @@
; Gets the frequency of a particular pitch
(define (getfreq octave pitch)
(* 55 (ash 1 octave) (expt 2 (/ pitch 12))))
+
+; Get the music as a list sampled at the bitrate
+(define (play t end)
+ (cons ((lambda (a)
+ (let ((b (modulo (inexact->exact (round (* (+ a 2) 32768))) 65536))) cons
+ (put-u8 (current-output-port) (modulo b 256))
+ (put-u8 (current-output-port) (quotient b 256)))) (music t))
+ (if (< t end)
+ (play (+ t (/ 1 bitrate)) end)
+ '())))