Last active
February 27, 2021 23:18
-
-
Save Alwinfy/63bd69c2c2bf5113f5b9dee8489e1c5b to your computer and use it in GitHub Desktop.
who needs wav libraries anyway: the repeat update
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ; Squished with the following vimscript command: | |
| ; %s/;.*//g | %s/\n/ /g | %s/\s\+/ /g | %s/\s*\([][()]\)\s*/\1/g | %s/notes-per-min/nm/g | %s/to-bytes/b/g | %s/sample-rate/sr/g | %s/bytes-per-sample/bps/g | %s/write-wav/ww/g | %s/note-freqs/nf/g | %s/note-lengt | |
| h/nl/g | %s/note/n/g | |
| (require racket/flonum) | |
| (define sample-rate 8000) | |
| (define bytes-per-sample 1) ; 8bit audio | |
| (define (to-bytes num len) | |
| (list->bytes | |
| (let loop ([num num] [len len]) | |
| (if (zero? len) | |
| null | |
| (cons (bitwise-and num 255) (loop (arithmetic-shift num -8) (sub1 len))))))) | |
| (define (write-wav body sample-rate bps out) | |
| ; Header | |
| (write-bytes | |
| (bytes-append | |
| #"RIFF" | |
| ; Remaining size | |
| (~> body stream-length (* bps) (+ 36) (to-bytes 4)) | |
| #"WAVEfmt " | |
| (to-bytes 16 4) ; header size | |
| (to-bytes 1 2) ; PCM=1 | |
| (to-bytes 1 2) ; 1 channel | |
| (to-bytes sample-rate 4) ; SR | |
| ; byte-rate=SR*chans*bytes/sample | |
| (to-bytes (* sample-rate bps) 4) | |
| ; block alignment=chans*bytes/sample | |
| (to-bytes bps 2) | |
| (to-bytes (* 8 bps) 2) ; bits/sample | |
| #"data" | |
| ; size=samples*chans*bytes/sample | |
| (to-bytes (* (stream-length body) bps) 4)) | |
| out) | |
| ; Body | |
| (for ([sample body]) | |
| (write-bytes (to-bytes sample bps) out))) | |
| (define middle-a 440.0) | |
| (define (freq n) (if n (~> n (/ 12) ((curry expt 2)) (* middle-a)) 0.0)) | |
| (define vars (make-hash)) | |
| (define (note-freqs notes) | |
| (append* | |
| (for/list ([note notes]) | |
| (match note | |
| [(list 'save id notes ...) #:when (symbol? id) (begin (hash-set! vars id (note-freqs notes)) null)] | |
| [(list notes ...) `(,(list->vector (map freq notes)))] | |
| [id #:when (hash-has-key? vars id) (hash-ref vars id)] | |
| [(list* notes ..1 repeat) (apply append (build-list repeat (const (note-freqs notes))))] | |
| [_ `(,(vector (freq note)))])))) | |
| (define args | |
| (let loop ([data (open-input-string string-args)]) | |
| (let ([val (read data)]) | |
| (if (eof-object? val) null (cons val (loop data)))))) | |
| (define-values (notes-per-min notes) | |
| (match args | |
| [(cons npm notes) (values npm (list->vector (note-freqs notes)))] | |
| [null (values #f #())])) | |
| (define valcap (~>> bytes-per-sample (* 8) (arithmetic-shift 1) sub1 ->fl)) | |
| (define note-length (* (/ 60 (or notes-per-min 1)) sample-rate)) | |
| (define period (/ (* 2 pi) sample-rate)) | |
| (cond | |
| [(not notes-per-min) "Usage: !!doots [notes per minute] [notes ...]\nEach note's a number of halftones above middle A. #f means silence.\n`(1 2 . 3)` is shorthand for `1 2 1 2 1 2`.\n`(3 7 10)` is a C chord.\n`(save arpeggio 3 7 10) arpeggio #f arpeggio` expands to `3 7 10 #f 3 7 10`."] | |
| [(< notes-per-min (vector-length notes)) "keep it under a minute you animals"] | |
| [else | |
| (make-attachment | |
| (call-with-output-bytes | |
| (curry write-wav | |
| (for/stream ([idx (in-range (* note-length (vector-length notes)))]) | |
| (match-let ([note (vector-ref notes (exact-truncate (/ idx note-length)))]) | |
| (~> | |
| (for/sum ([i note]) | |
| (~> i | |
| (fl* period (->fl idx)) | |
| flsin (fl+ 1.0) (fl* 0.5 valcap))) | |
| (/ (vector-length note)) | |
| exact-truncate))) | |
| sample-rate bytes-per-sample)) | |
| "doot.wav" 'audio/wav)]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is very fun ty