diff options
author | Anthony Wang | 2023-03-16 22:59:41 -0400 |
---|---|---|
committer | Anthony Wang | 2023-03-16 22:59:41 -0400 |
commit | 0da46c8a33e7a9b61b564cce5c4c4526fe3d84dc (patch) | |
tree | 1c3948cdb3a6cd6119f2ede76279cde26e464eba | |
parent | 46911993234b7c9cc07546f56843b35621b7f811 (diff) |
Clean up code and rename to yue
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | README.md | 15 | ||||
-rw-r--r-- | blend.nim (renamed from music.nim) | 61 | ||||
-rw-r--r-- | blend.ogg (renamed from example.ogg) | bin | 432160 -> 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.py | 39 | ||||
-rw-r--r-- | lambeat.scm | 17 | ||||
-rw-r--r-- | yue.nim (renamed from musiclib.nim) | 45 | ||||
-rw-r--r-- | yue.py | 69 | ||||
-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 @@ -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. @@ -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)) Binary files differ@@ -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) @@ -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) @@ -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)) @@ -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))) @@ -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) + '()))) |