Changes
14 changed files (+507/-545)
-
.gitignore (deleted)
-
@@ -1,2 +0,0 @@/music /music.s32
-
-
-
@@ -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.
-
-
blend.py (new)
-
@@ -0,0 +1,261 @@import yue intro = [ (1,3,3), (1,3,7), (1,3,10), (6,4,2), (1,3,1), (1,3,5), (1,3,8), (3,4,0), (1,2,11), (1,3,3), (1,3,6), (3,3,10), (1,2,8), (1,3,0), (1,3,3), (8,3,7), ] melody = [ (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,7), (2,4,8), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,1), (1,4,5), (2,4,8), (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,10), (2,4,3), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,10), (1,4,8), (2,4,10), (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,7), (2,4,8), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,1), (1,4,5), (2,4,1), (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,10), (1,4,8), (1,4,7), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,10), (1,4,8), (2,4,10), ] bass = [ (1,1,3), (1,1,10), (1,1,1), (1,1,8), (1,1,3), (1,2,3), (1,1,1), (1,1,10), ] melody2 = [ (1,0,0), (1,5,10), (1,5,8), (1,5,7), (1,5,8), (3,5,7,2), (1,5,3), (1,4,10), (6,5,1,2), (1/2,5,0,2), (1/2,5,1,2), (3,5,3,2), (1/2,5,10,2), (7/2,5,3,2), (8,0,0), (1,0,0), (1,5,3), (1,5,10), (1,5,10), (4/3,5,10), (4/3,5,8), (4/3,5,7), (1,0,0), (1,5,1), (1,5,8), (1,5,8), (4/3,5,8), (4/3,5,8), (4/3,5,10), (8,0,0), (1,0,0), (5,5,3,2), (2,5,10,2), ] melody3 = [ (1,0,0), (1,5,10), (1/2,5,8,2/3), (1/2,5,7,2/3), (1/4,5,8,1/2), (1/4,5,7,1/2), (1/4,5,8,1/2), (1/4,5,7,1/2), (1,5,8), (3,5,7,2), (1,5,3), (1,4,10), (1,5,1), (5,5,7,2), (1/2,5,7), (1/2,5,10), (1/4,5,7), (1/4,5,10), (1/4,5,7), (1/4,5,10), (1,6,3), (2,5,3,2), (1/2,6,3), (5/2,5,3,2), (1/2,5,10), (1/2,5,8), (1/2,5,7), (1/2,5,8), (1/2,5,7), (1/2,5,3), (1/2,4,10), (1/2,5,1), (1/2,5,0), (1/2,4,10), (1/2,4,8), (1/2,4,10), (1/2,5,3), (1/2,5,7), (1/2,5,3), (1/2,5,10), (4/3,5,7), (4/3,6,3), (4/3,6,3), (4/3,6,2), (4/3,5,10), (4/3,5,7), (3,5,5), (2,5,7), (2,5,8), (1,6,1), (1,5,3), (1,5,5), (2,5,7), (1,5,3), (1,5,8), (2,5,10), (3/2,6,0), (3/2,6,1), (5,6,3,2), ] outro = [ (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,7), (2,4,8), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,1), (1,4,5), (2,4,8), (1,2,11), (1,3,3), (1,3,6), (1,3,10), (1.5,3,11), (1.5,4,3), (3,4,8), (1.5,2,8), (1.5,3,0), (2,3,3), (16,3,7,2), ] 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,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])
-
-
lambeat.scm (deleted)
-
@@ -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)
-
-
lib.scm (deleted)
-
@@ -1,22 +0,0 @@; Triangle wave with a period of 1 second (define (tri t) (let ((m (floor-remainder (+ t (/ 1 4)) 1))) (if (< m 1/2) (- (* 4 m) 1) (- 3 (* 4 m))))) ; Square wave with a period of 1 second (define (square t) (let ((m (floor-remainder t 1))) (if (< m 0.5) 1 -1))) ; Creates a note (define (note freq start len) (lambda (t) ( if (or (< t start) (>= t (+ start len))) 0 (* 1/4 (tri (* t freq)))))) ; Gets the frequency of a particular pitch (define (getfreq octave pitch) (* 55 (ash 1 octave) (expt 2 (/ pitch 12))))
-
-
-
@@ -1,66 +1,12 @@import musiclib, std/[math, 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)) import yue, std/[sugar] 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 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
-
@@ -333,6 +279,9 @@ music.process(bass, 56)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):
-
-
music.py (deleted)
-
@@ -1,330 +0,0 @@import bisect import math import struct import sys # Number of times to sample each second bitrate = 44100 intro = [ (1,3,3), (1,3,7), (1,3,10), (6,4,2), (1,3,1), (1,3,5), (1,3,8), (3,4,0), (1,2,11), (1,3,3), (1,3,6), (3,3,10), (1,2,8), (1,3,0), (1,3,3), (8,3,7), ] melody = [ (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,7), (2,4,8), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,1), (1,4,5), (2,4,8), (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,10), (2,4,3), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,10), (1,4,8), (2,4,10), (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,7), (2,4,8), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,1), (1,4,5), (2,4,1), (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,10), (1,4,8), (1,4,7), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,10), (1,4,8), (2,4,10), ] bass = [ (1,1,3), (1,1,10), (1,1,1), (1,1,8), (1,1,3), (1,2,3), (1,1,1), (1,1,10), ] melody2 = [ (1,0,0), (1,5,10), (1,5,8), (1,5,7), (1,5,8), (3,5,7,2), (1,5,3), (1,4,10), (6,5,1,2), (1/2,5,0,2), (1/2,5,1,2), (3,5,3,2), (1/2,5,10,2), (7/2,5,3,2), (8,0,0), (1,0,0), (1,5,3), (1,5,10), (1,5,10), (4/3,5,10), (4/3,5,8), (4/3,5,7), (1,0,0), (1,5,1), (1,5,8), (1,5,8), (4/3,5,8), (4/3,5,8), (4/3,5,10), (8,0,0), (1,0,0), (5,5,3,2), (2,5,10,2), ] melody3 = [ (1,0,0), (1,5,10), (1/2,5,8,2/3), (1/2,5,7,2/3), (1/4,5,8,1/2), (1/4,5,7,1/2), (1/4,5,8,1/2), (1/4,5,7,1/2), (1,5,8), (3,5,7,2), (1,5,3), (1,4,10), (1,5,1), (5,5,7,2), (1/2,5,7), (1/2,5,10), (1/4,5,7), (1/4,5,10), (1/4,5,7), (1/4,5,10), (1,6,3), (2,5,3,2), (1/2,6,3), (5/2,5,3,2), (1/2,5,10), (1/2,5,8), (1/2,5,7), (1/2,5,8), (1/2,5,7), (1/2,5,3), (1/2,4,10), (1/2,5,1), (1/2,5,0), (1/2,4,10), (1/2,4,8), (1/2,4,10), (1/2,5,3), (1/2,5,7), (1/2,5,3), (1/2,5,10), (4/3,5,7), (4/3,6,3), (4/3,6,3), (4/3,6,2), (4/3,5,10), (4/3,5,7), (3,5,5), (2,5,7), (2,5,8), (1,6,1), (1,5,3), (1,5,5), (2,5,7), (1,5,3), (1,5,8), (2,5,10), (3/2,6,0), (3/2,6,1), (5,6,3,2), ] outro = [ (1,3,3), (1,3,7), (1,3,10), (1,4,2), (1,4,3), (1,4,7), (2,4,8), (1,3,1), (1,3,5), (1,3,8), (1,4,0), (1,4,1), (1,4,5), (2,4,8), (1,2,11), (1,3,3), (1,3,6), (1,3,10), (1.5,3,11), (1.5,4,3), (3,4,8), (1.5,2,8), (1.5,3,0), (2,3,3), (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)))
-
-
-
@@ -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 7 34.5 0.5)(2 3 35 0.5) (2 0 35.5 0.5) )))) (play 6 100)
-
-
musiclib.nim (deleted)
-
@@ -1,75 +0,0 @@import std/[algorithm, math, sugar, strformat, logging] type OscFn* = proc (f: float, t: float): float Note* = tuple len: float ## seconds freq: float vol: float osc: OscFn ProcessedNote* = tuple start: float ## absolute time in seconds stop: float ## absolute time in seconds freq: float vol: float osc: OscFn const HACK_LONGEST_NOTE = 16.0 func process*(music: var seq[ProcessedNote], notes: openArray[Note]; start_init: float, speed: float=1) = ## Adds a list of notes to the music list ## ## `notes` sequence of notes with no rests in between var start = start_init var t = start for note in notes: assert note.len >= 0.0 assert note.len <= HACK_LONGEST_NOTE, &"note too long: {note.len}" start = t let stop = t + note.len / speed music &= (start, stop, note.freq, note.vol, note.osc) t = stop func sortByStart*(music: var seq[ProcessedNote]) = music.sort((a, b) => cmp(a.start, b.start)) func bisect(music: openArray[ProcessedNote], x: float): int = ## Return the index where to insert item `x` in list `music` ## ## assumes `music` is sorted by `.start` music.lowerBound(x, (m, key) => cmp(m.start, key)) const GAIN_BIAS: float = pow(2.0, 31.0) proc at*(music: openArray[ProcessedNote], t: float): int32 = ## Returns the total intensity of music sampled at time t ## ## assumes `music` is sorted by `.start` var i: int = music.bisect(t) - 1 var ret: float = 0 while i >= 0: let m = music[i] assert m.start <= t if m.start + HACK_LONGEST_NOTE < t: break else: ret += m.vol * m.osc(m.freq, t - m.start) i -= 1 ret *= GAIN_BIAS # clip sample if ret >= int32.high.float: warn(&"audio clipping at t={t}") int32.high elif ret <= int32.low.float: warn(&"audio clipping at t={t}") int32.low else: int32(ret)
-
-
yue.nim (new)
-
@@ -0,0 +1,120 @@import std/[algorithm, math, sugar, strformat, logging] type OscFn* = proc (f: float, t: float): float Note* = tuple len: float ## seconds freq: float vol: float osc: OscFn ProcessedNote* = tuple start: float ## absolute time in seconds stop: float ## absolute time in seconds freq: float vol: float osc: OscFn const HACK_LONGEST_NOTE = 16.0 func process*(music: var seq[ProcessedNote], notes: openArray[Note]; start_init: float, speed: float=1) = ## Adds a list of notes to the music list ## ## `notes` sequence of notes with no rests in between var start = start_init var t = start for note in notes: assert note.len >= 0.0 assert note.len <= HACK_LONGEST_NOTE, &"note too long: {note.len}" start = t let stop = t + note.len / speed music &= (start, stop, note.freq, note.vol, note.osc) t = stop func sortByStart*(music: var seq[ProcessedNote]) = music.sort((a, b) => cmp(a.start, b.start)) func bisect(music: openArray[ProcessedNote], x: float): int = ## Return the index where to insert item `x` in list `music` ## ## assumes `music` is sorted by `.start` music.lowerBound(x, (m, key) => cmp(m.start, key)) const GAIN_BIAS: float = pow(2.0, 31.0) proc at*(music: openArray[ProcessedNote], t: float): int32 = ## Returns the total intensity of music sampled at time t ## ## assumes `music` is sorted by `.start` var i: int = music.bisect(t) - 1 var ret: float = 0 while i >= 0: let m = music[i] assert m.start <= t if m.start + HACK_LONGEST_NOTE < t: break else: ret += m.vol * m.osc(m.freq, t - m.start) i -= 1 ret *= GAIN_BIAS # clip sample if ret >= int32.high.float: warn(&"audio clipping at t={t}") int32.high elif ret <= int32.low.float: warn(&"audio clipping at t={t}") 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))
-
-
yue.py (new)
-
@@ -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)))
-
-
yue.scm (new)
-
@@ -0,0 +1,37 @@(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))) (if (< m 1/2) (- (* 4 m) 1) (- 3 (* 4 m))))) ; Square wave with a period of 1 second (define (square t) (let ((m (floor-remainder t 1))) (if (< m 0.5) 1 -1))) ; Creates a note (define (note freq start len) (lambda (t) ( if (or (< t start) (>= t (+ start len))) 0 (* 1/4 (tri (* t freq)))))) ; 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) '())))
-