summaryrefslogtreecommitdiff
path: root/actions/menus/MainMenu/PlaybackMenu/ConvertMidiForBass
blob: c4fe670fb8062f3f3b4d9b9bedbfa0c5289e8618 (plain)
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
<?xml version="1.0"?>
<Denemo>
  <merge>
    <title>A Denemo Keymap</title>
    <author>AT, JRR, RTS</author>
    <map>
      <row>
        <after>ToggleImmediatePlayback</after>
        <action>ConvertMidiForBass</action>
        <scheme>;;;;;;;; DenemoConvert

(define (DenemoConvert)
(define MidiNoteStarts (make-vector 256 #f))

(defstruct note name start duration)
(define Notes '())

(if (d-RewindRecordedMidi)
    (let loop ((note #f)(tick 0))
      (set! note (d-GetRecordedMidiNote))
      (if note
	  (begin
	    (set! tick (d-GetRecordedMidiOnTick))
	    (if (&lt; tick 0)
		(let ((on (vector-ref MidiNoteStarts note)))
		  (if on
		      (begin 
			(set! Notes (cons (list (make-note 'name note 'start on 'duration (- (- tick) on))) Notes))
			(vector-set! MidiNoteStarts note #f)	
			(loop note tick))
		      (format #t "An off with no On\n")))
		(let ((on (vector-ref MidiNoteStarts note)))
		  (if on
		      (format #t "An on when already on\n")
		      (begin
			(vector-set! MidiNoteStarts note tick)
			(loop note tick)
		)))))
	  (begin         ;;;;;; finished processing the notes
	    (if (&gt; (length Notes) 0)
		(let ()
		    (define (add-note note)
		      (if (Note? note)
			  (begin
			  (eval-string (string-append "(d-InsertNoteInChord \"" (d-GetNoteForMidiKey (note.name note)) "\")")))
			  (format #t "\tNo note to add note ~a ~a to\n" (note.name note) (note.duration note))))
		    
		    (define (insert-note name dur)
		      (let ((base (duration::GuessBaseNoteInTicks dur)))
(format #t "have ~a ~a \n" base dur)
			(if base
			    (begin
			      (if (&gt; (- dur base) (- (* 2 base) dur))
				  (set! base (* base 2)))
			      (begin 
				;(format #t "Create note ~a ~a\n"  (d-GetNoteForMidiKey name)   (duration::ticks-&gt;denemo base))
				(eval-string (string-append  "(d-Insert" (duration::ticks-&gt;denemo base)")"))
				(d-PutNoteName (d-GetNoteForMidiKey name)))))))
		    
	    
		    (set! Notes (reverse Notes))
     
;;;;;; change the list of Notes into a list of chords
		    (let loop ((index 0))

;;;;;;;;;;; overlap decides if two notes should be a chord	 
		      (define (overlap n1 n2)
			(if (list? n1)
			    (set! n1 (car n1)))
			(&lt; (abs (- (note.start n1) (note.start n2))) 50))
;;;;;;;;;;;;;;;;;; end of overlap
		      
					;(format #t "Number of notes ~a\n" index)	      
		      (let ((note1 (list-ref Notes index))
			    (note2 #f))
			(if (&gt; (length Notes) (+ 1 index))
			    (begin
			      (set! note2 (list-ref Notes (+ 1 index)))
			      (if (overlap note1 (car note2))
				  (begin
				    (list-set! Notes index (cons (car note2) note1))
				    (set! Notes (delq note2 Notes)))
				  (begin
					;(list-set! Notes index (list note1))
				    (set! index (+ index 1))))
			      (loop index))))) ;;;;;;; end of changing Notes to list of chords


;;;loop through the chords, getting a good duration value, the duration from one to the next and inserting
		    (let loop ((index 0))
		      (if (&gt; (length Notes) (+ 1 index))
			  (let ((chord1 (list-ref Notes index))
				(chord2 #f)
				(duration #f))
			    (if (&gt; (length Notes) (+ 1 index))
			     (begin
			      (set! chord2 (list-ref Notes (+ 1 index)))
			      (set! duration (- (note.start (car chord2)) (note.start (car chord1))))
			      (format #t "With duration ~a\n" duration)
			      (insert-note (note.name (car chord1)) duration)
			       (for-each  add-note (cdr chord1))
			       (set! index (+ index 1))
			       (loop index))
			     (insert-note (note.name (car chord1)) (note.duration (car chord1)))))))
	    
		    (format #t "End of processing\n"))))));;;;;if rewind succeeded
		(format #t "No notes found in recording\n")))


;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;
(if (d-RewindRecordedMidi)
    (let loop ((note #f)(tick 0))
      (set! note (d-GetRecordedMidiNote))
      (if note
	  (begin
	    (set! tick (d-GetRecordedMidiOnTick))
	    (if tick
	      (begin
		(format #t "I have a note ~a ~a at ~a ticks\n" note (if (&lt; tick 0) "Off" "On") tick)
		(loop note tick)
		))))))
;;;;;;;;
;;;;;;;; DenemoConvertFromBass

(define (DenemoConvertFromBass)
(define MidiNoteStarts (make-vector 256 #f))
(defstruct note name start duration)
(define Notes '())

(if (d-RewindRecordedMidi)
    (let looprecordednotes ((note #f)(tick 0))
      (set! note (d-GetRecordedMidiNote))
      (if note
	  (begin
	    (set! tick (d-GetRecordedMidiOnTick))
	    (if (&lt; tick 0)
		(let ((on (vector-ref MidiNoteStarts note)))
		  (if on
		      (begin 
			(set! Notes (cons (list (make-note 'name note 'start on 'duration (- (- tick) on))) Notes))
			(vector-set! MidiNoteStarts note #f)	
			(looprecordednotes note tick))
		      (format #t "An off with no On\n")))
		(let ((on (vector-ref MidiNoteStarts note)))
		  (if on
		      (format #t "An on when already on\n")
		      (begin
			(vector-set! MidiNoteStarts note tick)
			(looprecordednotes note tick)
		)))))
	  (begin         ;;;;;; finished generating Notes as a list of lists each with one note struct in it
	    (if (&gt; (length Notes) 0)
		(let ()
;;;;;;; we have at least one note in the Notes list, define to procs to create a denemo chord and add a note to it
		    (define (add-note note)
		      (if (note? note)
			  (begin
			  (eval-string (string-append "(d-InsertNoteInChord \"" (d-GetNoteForMidiKey (note.name note)) "\")")))
			  (format #t "\tNo note to add note ~a ~a to\n" (note.name note) (note.duration note))))
		    
		    (define (insert-note name dur)
		      (let ((base (duration::GuessBaseNoteInTicks dur)))
			(format #t "have ~a ~a \n" base dur)
			(if base
			    (begin
			      (if (&gt; (- dur base) (- (* 2 base) dur))
				  (set! base (* base 2)))
			      (begin 
				;(format #t "Create note ~a ~a\n"  (d-GetNoteForMidiKey name)   (duration::ticks-&gt;denemo base))
				(eval-string (string-append  "(d-Insert" (duration::ticks-&gt;denemo base)")"))
				(d-PutNoteName (d-GetNoteForMidiKey name)))))))
;;;;;;;;;;;;;; end of defining procs
		    
;;; notes has been set up by pre-pending so it is backwards...	    
		    (set! Notes (reverse Notes))
     
                    ;;;;;; take the Notes and seek out bass notes, remove them and form chords, insert chords in staff above
		    (let () ;;;no loop here we drive it via the loopforbasskey
                      ;;;;;;;;;;; overlap decides if two notes should be a chord	 
		      (define (overlap n1 n2)
			(if (list? n1)
			    (set! n1 (car n1)))
			(&lt; (abs (- (note.start n1) (note.start n2))) 50))
                      ;;;;;;;;;;;;;;;;;; end of overlap
		      
					;(format #t "Number of notes ~a\n" index)

                      ;;;;;;;Step through notes in current (bass) staff, placing chords in (empty) staff above
		    
				(let loopforbasskey ()

;;;;;;;;;;;;; copy non-notes to the empty staff above and stop with the cursor on the first bass note
				(let loop ()
                                  ;;;;first copy anything that are not a bass note to the staff above
				  (if (or (Rest?) (Tupletmarker?))
				      (begin 
					(d-SetMark) (d-Copy) (d-PushPosition)(d-MoveToStaffUp) (GoToMeasureEnd)(d-Paste)(d-PopPosition))
				      (begin
					(if (and (not (Note?)) (d-NextObject))
					    (loop)))))

;;;;;;;;;;;;; cursor is on the first/next bass note (if any)
				(if (Note?)  
				    (let  ((bass-key (d-GetNoteAsMidi)) (bass-duration (d-GetDurationInTicks))) ;;;midi number and duration of current note
				      ;; now loop through the Notes list looking for a note the same MIDI key number, which should be before ticks move on too much... once found, consecutive notes are taken to make a chord or chords for the bass note, an
				      (define notes-belonging '());the notes belonging to the bass note
				      (define bass-note #f);a bass note (list) selected from Notes

;;;;;;;;;;;;;;;;;; three procs: make-chords takes a list which has each note in a separate list and puts all the (consecutive) ones that overlap into a single list, that is it turns a sequence of list-of-note into a list-of-notes which represents a chord. It returns the list.
		      
				      (define (make-chords notes)
					(let loop ((index 0))
					  (let ((note1 (list-ref notes index))
						(note2 #f))
					    (if (&gt; (length notes) (+ 1 index))
						(begin
						  (set! note2 (list-ref notes (+ 1 index)))
						  (if (overlap note1 (car note2))
						      (begin
							(list-set! notes index (cons (car note2) note1))
							(set! notes (delq note2 notes)) ;;so we loop with same index
							)
						      (set! index (+ 1 index)))
						  (loop index)))))

					notes)	
;;;;;;; insert-chord takes a list of note structs and puts a chord in the staff above with those notes.		
				      (define (insert-chord chord duration)
						(insert-note (note.name (car chord)) duration)
							(for-each  add-note (cdr chord)))

                                       ;;;;;;; insert-chords takes a list of lists of note structs and the duration of the denemo bass note they have been assigned to. It inserts a chord for each of the lists assigning durations to fit the bass note.		
	(define (insert-chords notes bass-duration)
                                        ;(disp "insert-chords called with " notes "\n")				   
					(let ((chords (make-chords notes)) (remaining bass-duration))
				    ;;;loop through the chords, getting a good duration value, the duration from one to the next and inserting
					  (let loop ((index 0))
					    (if (&gt; (length chords) (+ 1 index))
						(let ((chord1 (list-ref chords index))
						      (chord2 #f)
						      (duration #f))
						 
							(set! chord2 (list-ref chords (+ 1 index)));;move this into the init
							(set! duration (- (note.start (car chord1)) (note.start (car chord2))))
							(set! remaining (- remaining duration))
							(format #t "With duration ~a\n" duration)
							(insert-chord chord1 duration)
							(loop (+ index 1)))	     
						(begin
						  (if (= (length chords) (+ 1 index))
						      (begin
							
							(insert-chord (list-ref chords 0) remaining))
						     (d-WarningDialog "We have no chord")))))))


 (define (contains n1 n2)
(let (
      (n1.end (+ (note.start n1) (note.duration n1)))
      (n2.end (+ (note.start n2) (note.duration n2))))
     

   (disp "I test "	
	  (&lt; (/ (- n2.end n1.end) (note.duration n2)) 0.5)
	 " instead " 
	(&lt; (abs (- (note.start n1) (note.start n2))) 50) "\n" )
   (&lt; (/ (- n2.end n1.end) (note.duration n2)) 0.5)))




;;;;;;;;;;;;;;;;;;;;;;;;; now the actual processing to loop through Notes finding an equivalent to bass-key and processing the notes belong. These are removed from Notes and then the outer loop to move on in the bass staff is taken.				      

;;; first loop through Notes from the start (previous chords have been deleted) and seek the bass-note				      
					(let loopgetbassnote ((index 0))
;(disp "now index " index "bass-key " bass-key "\n")
					  (if (&gt; (length Notes) index)
						 (if (= bass-key (note.name (car (list-ref Notes index))))
						     (begin
						       (set! bass-note (list-ref Notes index))
						       (set! Notes (delq bass-note Notes))
						       )
						     (loopgetbassnote (+ index 1)))))
                                           ;;;; if bass-note then that note should have been removed from Notes, next get chord notes
                                        (set! notes-belonging '())
					(if bass-note					    
					      (let loop ((index 0))
						(if (&gt; (length Notes) index)
						    (let ( (next-note (list-ref Notes index)))
						    (if (contains (car bass-note) (car next-note))
							(begin
							  (set! notes-belonging (cons*  next-note notes-belonging))
							  (set! Notes (delq next-note Notes))
							  (loop index)))))))
;;;;;;;;;;;;;;;;;;; finished creating notes-belonging, all these notes are now removed from Notes
					      
					     
					      (if (&gt; (length notes-belonging) 0)
						  (begin
						    (d-PushPosition)
						    (d-StaffUp)
						    (GoToMeasureEnd)
;(disp "notes-belonging looks like this: " notes-belonging "\n")
						    (insert-chords notes-belonging bass-duration)
;(disp "insert-chords finished\n")
						    (d-PopPosition)
                                                    (if (d-NextObject)
							(loopforbasskey)
						    (d-WarningDialog "finished bass staff")))
						  
						  (begin
						  (d-WarningDialog "found no notes for a bass-note")
						  (disp "Bass note with no accompanying chord" bass-note))));;;if there is a note in the bass clef



					    (d-WarningDialog "No more bass notes")))))

		(begin
		  (d-WarningDialog "The Notes list is empty"))))))
				    
	
		(format #t "No notes found in recording\n")))


;;;;;;;;;;;;;;;;;;;;;;;;
(DenemoConvertFromBass)</scheme>
        <label>Convert MIDI chords over Bass</label>
        <tooltip>Takes a MIDI recording over a bass line and inserts the chords by matching the bass notes. Each chord must include the bass note.</tooltip>
      </row>
    </map>
  </merge>
</Denemo>