/
sequencer.rkt
86 lines (75 loc) · 3.47 KB
/
sequencer.rkt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#lang racket
(require math/array racket/flonum racket/unsafe/ops)
(require "synth.rkt" "mixer.rkt")
(provide scale chord note sequence mix)
(define (base+relative-semitone->freq base relative-semitone)
(* 440 (expt (expt 2 1/12) -57)))
;; details at http://www.phy.mtu.edu/~suits/notefreqs.html
(define (note-freq note)
;; A4 (440Hz) is 57 semitones above C0, which is our base.
(* 440 (expt (expt 2 1/12) (- note 57))))
;; A note is represented using the number of semitones from C0.
(define (name+octave->note name octave)
(+ (* 12 octave)
(case name
[(C) 0] [(C# Db) 1] [(D) 2] [(D# Eb) 3] [(E) 4] [(F) 5] [(F# Gb) 6]
[(G) 7] [(G# Ab) 8] [(A) 9] [(A# Bb) 10] [(B) 11])))
;; Generates a scale of the given mode (major, minor, ...) starting at
;; `root' at `octave'. `duration' is the duration of an individual note.
;; Custom scales can be generated by giving a list of semitone intervals.
;; Returns a list of note + duration pairs
;; TODO add option for descending
(define (scale root octave duration mode . notes*)
(define root-note (name+octave->note root octave))
(define notes
(if (eq? mode 'custom)
notes*
(case mode
((major ionian) '(0 2 4 5 7 9 11 12))
((minor minor-natural aeolian) '(0 2 3 5 7 8 10 12))
((minor-harmonic mohammedan) '(0 2 3 5 7 8 11 12))
((minor-melodic) '(0 2 3 5 7 9 11 12))
((dorian) '(0 2 3 5 7 9 10 12))
((phrygian) '(0 1 3 5 7 8 10 12))
((lydian) '(0 2 4 6 7 9 11 12))
((mixolydian) '(0 2 4 5 7 9 10 12))
((locrian) '(0 1 3 5 6 8 10 12))
((major-arpeggio) '(0 4 7))
((minor-arpeggio) '(0 3 7))
((custom) notes))))
(for/list ([n (in-list notes)])
(cons (+ root-note n) duration)))
;; TODO probably leave duration out of this (and chord) for now, and have it
;; be part of a higher-level API
;; Similar to scale, but generates a chord.
;; Chords are pairs (listof note) + duration
(define (chord root octave duration type . notes*)
(define notes (apply scale root octave duration type notes*))
(cons (map car notes) duration))
;; Single note.
(define (note name octave duration)
(cons (name+octave->note name octave) duration))
;; Accepts notes or pauses, but not chords.
(define (synthesize-note note n-samples function)
(build-array (vector n-samples)
(if note
(function (note-freq note))
(lambda (x) 0.0)))) ; pause
;; repeats n times the sequence encoded by the pattern, at tempo bpm
;; pattern is a list of either single notes (note . duration) or
;; chords ((note ...) . duration) or pauses (#f . duration)
(define (sequence n pattern tempo function)
(define samples-per-beat (quotient (* fs 60) tempo))
(array-append*
(for*/list ([i (in-range n)] ; repeat the whole pattern
[note (in-list pattern)])
(if (list? (car note)) ; chord
(apply mix
(for/list ([x (in-list (car note))])
(list (synthesize-note x
(* samples-per-beat (cdr note))
function)
1))) ; all of equal weight
(synthesize-note (car note)
(* samples-per-beat (cdr note))
function)))))