Clean up code and rename to yue

This commit is contained in:
Anthony Wang 2023-03-16 22:59:41 -04:00
parent 4691199323
commit 0da46c8a33
Signed by: a
GPG key ID: 42A5B952E6DD8D38
11 changed files with 165 additions and 203 deletions

2
.gitignore vendored
View file

@ -1,2 +0,0 @@
/music
/music.s32

View file

@ -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.

View file

@ -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))

View file

@ -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)

View file

@ -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)

View file

@ -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])

View file

@ -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)

View file

@ -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))

69
yue.py Normal file
View file

@ -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)))

View file

@ -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)
'())))