-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMelodyPlayer.java
222 lines (180 loc) · 5.83 KB
/
MelodyPlayer.java
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
/*
* c2017-c2023 Courtney Brown
*
* Class: MelodyPlayer
* Description: Sends a melody of midi notes to an external player/midi channel, revised 2024 for polyphonic playing
*
*/
package com.example;
import java.util.*;
// send the MIDI elsewhere to play the notes
public class MelodyPlayer {
MidiBusCRCP outputMidiBus; //sends midi notes to an external program (eg. Ableton, Logic, etc.)
int note_index = 0; // where we are in the notes
float notems; // the value of a quaver or 1/4 note in millis
float last_time; // the last time we called draw()
boolean play; // should we play?
float bpm; // beats per minute of melody
double start_time; //time at creation of the melody player in milliseconds
double playStart = 0;
double rhythm_multiplier; //determines note length and onset of the next note
ArrayList<Integer> melody; //list of pitches in order
ArrayList<Double> rhythm; //list of note lengths (& thus time before next note, in order)
ArrayList<Double> startTimes; //list of start times
boolean hasRhythm; //has there been a list of rhythms assigned?
boolean hasMelody; //has there been a list of pitches assigned?
boolean hasStart = false;
String outputBus; //bus to send MIDI to -- change if you have named it something else or you are using Windows
//list of playing notes to keep track off for note offs
ArrayList<Double> playingRhythms = new ArrayList();
ArrayList<Double> playingTimes = new ArrayList();
ArrayList<Integer> playingPitches = new ArrayList();
//constructor -- initializes data)
//input is the tempo - bpm (beats per minute) or how fast to play the music
MelodyPlayer(float tempo, String bus) {
reset();
setBPM(tempo);
play = true;
hasRhythm = false;
rhythm_multiplier = 0.5f; // default is 1/8 notes
start_time = System.currentTimeMillis();
outputBus = bus;
setupMidi();
}
void setMelody(ArrayList<Integer> m) {
melody = m;
hasMelody = true;
}
void setRhythm(ArrayList<Double> r) {
rhythm = r;
hasRhythm = true;
}
void setStartTimes(ArrayList<Double> r) {
startTimes = r;
hasStart = true;
}
// display all ports available to the MidiBus -- only output ports are relevant, however
//if OS X, best to choose the IAC bus we created (for mac/OS X) so can send to an external program (eg DAW/sampler)
//if Windows, why are you LIKE this??! -- TODO: install/document virtual port on Windows via 3rd party software
void listDevices()
{
MidiBusCRCP.listDevices();
}
//create the Midi port and bus to send the notes to
void setupMidi() {
outputMidiBus = new MidiBusCRCP(outputBus);
//or if on windows - use a windows virtual port
}
void setBPM(float tempo) {
bpm = tempo;
notems = (float) (((1.0 / (bpm / 60.0))) * 1000); //how many ms in a 1/4 note for this tempo
}
//time since creation of the melody player -- mimics the processing function millis()
int millis(){
double millisNow = System.currentTimeMillis()-start_time;
return (int) millisNow;
}
//send the melody out in MIDI messages, in correct timing, to the external program
void play()
{
int vel = 100; //midi velocity -- TODO: change/assign if want to vary
double cur_time = millis(); //what time is it now?
//send note off messages for any notes that have been playing
sendNoteOff(cur_time);
// just do nothing if there is no melody (pitches)
if (!hasMelody)
{
System.out.println("There is no melody in the notes given.");
return;
}
//if we're at the end of the melody also do nothing
if(atEndOfMelody())
{
return;
}
//playing the notes by sending the note ons at the corect times
if( note_index < startTimes.size() )
{
play = cur_time - playStart >= notems * startTimes.get(note_index); //should we send the note now? based on prev. note's duration
//send note on messages
if( play && note_index < startTimes.size() ) {
sendNoteOn(note_index, cur_time, vel);
note_index++;
boolean sameTime = true;
//send note-on messages for the notes that occur at the same time as well (eg. a chord, a dyad)
while( note_index < startTimes.size() && sameTime )
{
sameTime = (startTimes.get(note_index-1).equals(startTimes.get(note_index)));
if( sameTime )
{
sendNoteOn(note_index, cur_time, vel);
note_index++;
}
}
}
}
}
//send a note on message and then add to playing note arrays
void sendNoteOn(int note_index, double cur_time, int vel)
{
//System.out.println("note on:" + melody.get(note_index));
outputMidiBus.sendNoteOn(0, (int) melody.get(note_index), vel);
playingRhythms.add( rhythm.get(note_index));
playingTimes.add( cur_time );
playingPitches.add(melody.get(note_index));
}
//send note off messages for the playing notes in our buffers
void sendNoteOff(double cur_time)
{
for(int i=playingRhythms.size()-1; i>=0; i-- )
{
if( playingRhythms.get(i) * notems <= cur_time - playingTimes.get(i) )
{
outputMidiBus.sendNoteOff(0, (int) playingPitches.get(i), 0);
playingRhythms.remove(i);
playingPitches.remove(i);
playingTimes.remove(i);
}
}
}
public void noteOffAllNotes()
{
//TODO: implement
}
public ArrayList<Double> getRhythm() {
return rhythm;
}
public ArrayList<Integer> getMelody() {
return melody;
}
public ArrayList<Double> getStartTimes() {
return startTimes;
}
//reset note to 0
void reset() {
note_index = 0;
playStart = millis();
playingRhythms.clear();
playingPitches.clear();
playingTimes.clear();
}
//set to ends
void setToEnd()
{
note_index = melody.size();
playingRhythms.clear();
playingPitches.clear();
playingTimes.clear();
}
//have we reached the end of the melody?
boolean atEndOfMelody()
{
//reminder to fix this
return note_index >= melody.size() && playingRhythms.size()<=0 ;
}
//send note offs for all playing notes
void stopAllNotes()
{
sendNoteOff(Double.MAX_VALUE);
}
}