-
Notifications
You must be signed in to change notification settings - Fork 6
/
learn-music4.rkt
215 lines (179 loc) · 6.22 KB
/
learn-music4.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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#lang racket
#|
Show notes, then play them, see if you get them right!
Press any key if you find the note easy, you'll then
get less time to play this note next time.
Challenge: How to avoid practicing mistakes or quick
corrections, wait for the player to remember first?
DONE:
- Blue colouring for easy notes only considers default set
- Play easy-notes less often
- Show the note played, on the last play
TODO:
- Merge in updates from learn-music-phrases code
- Fix display of extenders that should be hidden by
stave lines
- Sort easy-notes for better display, or show them on
the stave?
- Save easy-notes for next time, so that the player
doesn't need to edit the source code
|#
(require srfi/1)
(require 2htdp/universe 2htdp/image)
(require 2htdp/image)
(require rsound)
(require rsound/piano-tones)
;; What notes do we want to practice?
(define NOTES
'(e2 f2 g2 a3 b3 c3 d3 e3 f3 g3 a4 b4 c4 d4 e4 f4 g4))
;; We need MIDI numbers to play them, these are the standard set
(define PIANO-MIDI-NOTES
'(52 53 55 57 59 60 62 64 65 67 69 71 72 74 76 77 79))
;; Guitar midi notes are one octave lower
(define MIDI-NOTES
(map (λ (x) (- x 12)) PIANO-MIDI-NOTES))
;; We want to show the open string notes differently
(define OPEN-STRINGS
'(e2 a3 d3 g3 b4 e4))
;; The initial set of easy notes for *me* to play - change this
;; to suit your needs
;; When recalling note names: '(a3 c4 g4 e4 c3 a4 b4 f3)
(define EASY-NOTES
'())
;; '(a3 c4 g4 e4 c3 a4 b4 f3))
;; How likely to skip easy phrases (0-1)?
(define SKIP-EASY-NOTES 1)
;; The canvas
(define WIDTH 400)
(define HEIGHT 300)
(define G-CLEF (bitmap "GClef.png"))
;; How many seconds between notes? Change this to suit your needs
(define TICK-RATE 2)
(define PIX-PER-NOTE 11)
(define PIX-BETWEEN-LINES (* 2 PIX-PER-NOTE))
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(define-syntax-rule (times-repeat n fn)
(for/list ([i (in-range n)])
fn))
(define (random-choice list)
(list-ref list (random (length list))))
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(define (note-index a-note)
(list-index (curry equal? a-note) NOTES))
(define above/align-left
((curry above/align) "left"))
(define (stave)
(apply above/align-left
(cons (line 300 0 "black")
(times-repeat 4
(above/align-left
(line 0 20 "black")
(line 300 0 "black")
)))))
(define (note-pos-relative-b4 a-note)
;; b4 is the middle of the stave
;; b4 = 0, a4 = -1, c4 = 1, etc
(- (note-index a-note) PIX-PER-NOTE))
(define (note-y-pos a-note)
(* PIX-PER-NOTE (note-pos-relative-b4 a-note)))
(define (extender-line)
(line 30 0 "black"))
(define (extenders a-note-pos)
;; Draw extenders from b4 up or down to note
;; the first few will be obscured by the 5 stave lines
;; Use absolute value of note pos:
(if (< a-note-pos 0) (extenders (- 0 a-note-pos))
(cond
[(= a-note-pos 0) (extender-line)]
;; No lines at odd note positions
[(odd? a-note-pos)
(extenders (sub1 a-note-pos))]
[(overlay/align/offset
"left" "top"
(extender-line)
0 PIX-BETWEEN-LINES
(extenders (sub1 a-note-pos)))])))
(define (extenders-above a-note)
;; Are the extenders above the stave (or below)?
(>= (note-pos-relative-b4 a-note) 0))
(define (note-img a-note)
;; A note, with a hint for open strings
(circle 10
"solid"
(if (member a-note OPEN-STRINGS) "darkgreen" "black")))
(define (show-note a-note)
;; Show the note on the stave with extenders and the G-Clef
(overlay/offset
(scale 0.53 G-CLEF)
120 -6
(place-image/align
(extenders (note-pos-relative-b4 a-note))
(/ WIDTH 2) (/ HEIGHT 2) "middle"
(if (extenders-above a-note) "bottom" "top")
(overlay/offset
(note-img a-note)
0 (note-y-pos a-note)
(overlay
(stave) (empty-scene WIDTH HEIGHT "white"))))))
(define (play-note a-note)
(play (piano-tone
(list-ref MIDI-NOTES (note-index a-note)))))
(define (play-and-show-note a-note)
(play-note a-note)
(show-note a-note))
;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; big-bang world
(struct world (note plays easy-notes) #:transparent)
(define (next-random-note last-note easy-notes)
;; Next random note, but not last-note
;; also play easy-notes less often
(define note (random-choice NOTES))
(if (or (eq? note last-note)
(and (member note easy-notes)
(< (random) SKIP-EASY-NOTES)))
(next-random-note last-note easy-notes)
note))
(define (play-note-times a-note easy-notes)
(if (member a-note easy-notes) 2 4))
(define (next-note w)
;; Play the next note, but first check if we've finished
;; playing this note. If we have, pick a new one.
(cond
[(zero? (world-plays w))
(let* ((note (next-random-note (world-note w) (world-easy-notes w)))
(plays (play-note-times note (world-easy-notes w))))
(next-note (world note plays (world-easy-notes w))))]
[else
(play-note (world-note w))
(world (world-note w) (sub1 (world-plays w)) (world-easy-notes w))]))
(define (easy-note w a-key)
;; The user finds the current note easy - stop playing it
;; and add it to the set
(let ((note (world-note w))
(easy-notes (world-easy-notes w)))
(world note 0
(if (member note easy-notes)
easy-notes
(cons note easy-notes)))))
(define (render-scene w)
(define scene
(place-image/align
(above/align "left"
(text (string-append "Easy notes: "
(string-join (map symbol->string (world-easy-notes w)) ", "))
15 "black")
(text "Press any key to add current note" 15 "black"))
5 5 "left" "top"
(show-note (world-note w))))
(if (= 0 (world-plays w))
(place-image/align
(text (symbol->string (world-note w)) 50 "black")
(- WIDTH 8) HEIGHT "right" "bottom"
scene)
scene))
(define (go)
(big-bang (world (random-choice NOTES) 0 EASY-NOTES)
(on-tick next-note TICK-RATE)
(on-key easy-note)
(to-draw render-scene)))
(go)