View Javadoc

1   package net.sf.jack4j.examples;
2   
3   /*
4   Copyright (C) 2008 Ondrej Par
5   
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10  
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU Lesser General Public License for more details.
15  
16  You should have received a copy of the GNU Lesser General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  
20  */
21  
22  import java.io.BufferedReader;
23  import java.io.IOException;
24  import java.io.InputStreamReader;
25  import java.util.ArrayList;
26  import java.util.EnumSet;
27  import java.util.Iterator;
28  import java.util.List;
29  
30  import net.sf.jack4j.AbstractJackClient;
31  import net.sf.jack4j.JackException;
32  import net.sf.jack4j.JackLocalMidiPort;
33  import net.sf.jack4j.JackMidiPortBuffer;
34  import net.sf.jack4j.JackPortFlag;
35  
36  /**
37   * Very simple MIDI sequencer.
38   * 
39   * <p>
40   * See comments in the code that describe the usage of Jack4j library.
41   * 
42   * <p>
43   * To run the examples, you need to have the native Jack4j library in your
44   * system load path (LD_LIBRARY_PATH under Linux).
45   * 
46   * <p>
47   * This example client expects single command line parameter - client name.
48   * 
49   * @author repa
50   */
51  public class MidiSeq extends AbstractJackClient {
52  
53  	/**
54  	 * Simple structure that will contain single element of our loop.
55  	 */
56  	private static final class LoopNote {
57  
58  		public int noteNumber;
59  		public int durationSamples;
60  		public int pauseSamples;
61  	}
62  
63  	private JackLocalMidiPort midiOut;
64  	private List<LoopNote> loop;
65  
66  	private boolean running;
67  	private Iterator<LoopNote> loopIterator;
68  	private LoopNote currentLoopNote;
69  	private boolean playingNote;
70  	private int remainingSamples;
71  
72  	/**
73  	 * This is callback method, invoked by Jack client thread during each
74  	 * process cycle.
75  	 * 
76  	 * <p>
77  	 * This method produces MIDI events and stores them into <i>buffer</i>
78  	 * associated with the <i>output MIDI port</i>.
79  	 * 
80  	 * <p>
81  	 * Port was created earlier, during client initialization.
82  	 */
83  	@Override
84  	public int process(int bufferSize) throws Exception {
85  		if (!running) {
86  			return 0;
87  		}
88  
89  		// initialize the MIDI buffer into which we will store MIDI events
90  		JackMidiPortBuffer outBuffer = midiOut.initializeMidiBuffer(bufferSize);
91  		// Always call clearBuffer() on output MIDI ports, on the beginning of each process cycle:
92  		outBuffer.clearBuffer();
93  
94  		int sampleIdx = 0;
95  		for (;;) {
96  			if (remainingSamples < (bufferSize - sampleIdx)) {
97  				sampleIdx += remainingSamples;
98  
99  				// we finished "playing" current note or pause
100 				// generate next MIDI event;
101 				byte[] midiEventData;
102 				if (playingNote) {
103 					// note ends
104 					midiEventData = new byte[3];
105 					midiEventData[0] = (byte) 0x80; // NOTE OFF
106 					midiEventData[1] = (byte) currentLoopNote.noteNumber;
107 					midiEventData[2] = 64; // velocity
108 
109 					playingNote = false;
110 					remainingSamples = currentLoopNote.pauseSamples;
111 				} else {
112 					// pause ends, play next note
113 					if (!loopIterator.hasNext()) {
114 						// restart the loop
115 						loopIterator = loop.iterator();
116 					}
117 					currentLoopNote = loopIterator.next();
118 
119 					midiEventData = new byte[3];
120 					midiEventData[0] = (byte) 0x90; // NOTE ON
121 					midiEventData[1] = (byte) currentLoopNote.noteNumber;
122 					midiEventData[2] = 64; // velocity
123 
124 					playingNote = true;
125 					remainingSamples = currentLoopNote.durationSamples;
126 				}
127 
128 				// Write event into the output buffer.
129 
130 				// Each event "happens" on a certain time during current process cycle.
131 				// The "time" parameter has a value between 0 (happens at the beginning
132 				// of the current process cycle) and bufferSize (happens at the very end of the current
133 				// process cycle.
134 				System.out.println("Writing MIDI event, time " + sampleIdx + ", first byte is " + midiEventData[0]);
135 				outBuffer.eventWrite(sampleIdx, midiEventData);
136 			} else {
137 				// nothing happens until the end of current process cycle
138 				remainingSamples -= (bufferSize - sampleIdx);
139 				break;
140 			}
141 		}
142 
143 		// indicate that we finished properly
144 		return 0;
145 	}
146 
147 	public MidiSeq(String clientName, boolean useExactName, boolean canStartServer, String serverName)
148 	        throws JackException {
149 		// Call to super constructor will register us with Jack server
150 		super(clientName, useExactName, canStartServer, serverName);
151 
152 		// Setting default ThreadInit callback is necessary, 
153 		// otherwise other callbacks won't work properly (the Jack client thread
154 		// must be registered with JVM first)
155 		setDefaultThreadInitCallback();
156 
157 		// Set the native callback that will invoke our process(int) method.
158 		setDefaultProcessCallback();
159 
160 		// Add output MIDI port.
161 		midiOut = addMidiPort("in", EnumSet.of(JackPortFlag.IS_OUTPUT));
162 
163 		loop = null;
164 
165 		running = false;
166 
167 		// we are not activated yet, thus the callbacks won't be invoked
168 	}
169 
170 	/**
171 	 * Activation and main event loop.
172 	 */
173 	public void run() throws JackException, IOException {
174 		// Activate the client; until now, callbacks weren't invoked
175 		activate();
176 
177 		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
178 
179 		System.out.println("CONNECT THE OUTPUT PORT to some MIDI synthesizer.");
180 		System.out.println("You can use MidiSeq example client from Jack4j package.");
181 
182 		// Let the user enter the loop events
183 		loop = new ArrayList<LoopNote>();
184 		for (;;) {
185 			System.out.println("Enter MIDI note number (0-127) or enter to quit:");
186 			String midiNoteStr = in.readLine();
187 			if ("".equals(midiNoteStr)) {
188 				break;
189 			}
190 			int midiNote;
191 			try {
192 				midiNote = Integer.parseInt(midiNoteStr);
193 			} catch (NumberFormatException e) {
194 				System.out.println(e.getMessage());
195 				continue;
196 			}
197 			if ((midiNote < 0) || (midiNote > 127)) {
198 				System.out.println("Invalid MIDI note");
199 				continue;
200 			}
201 
202 			System.out.println("Enter note duration in seconds (floating point number):");
203 			String durationStr = in.readLine();
204 			float duration;
205 			try {
206 				duration = Float.parseFloat(durationStr);
207 			} catch (NumberFormatException e) {
208 				System.out.println(e.getMessage());
209 				continue;
210 			}
211 			if (duration <= 0) {
212 				System.out.println("Duration must be positive number");
213 				continue;
214 			}
215 
216 			System.out.println("Enter pause duration in seconds (floating point number):");
217 			String pauseDurationStr = in.readLine();
218 			float pauseDuration;
219 			try {
220 				pauseDuration = Float.parseFloat(pauseDurationStr);
221 			} catch (NumberFormatException e) {
222 				System.out.println(e.getMessage());
223 				continue;
224 			}
225 			if (pauseDuration < 0) {
226 				System.out.println("Duration must be positive number");
227 				continue;
228 			}
229 
230 			// store the note into the loop
231 			LoopNote loopNote = new LoopNote();
232 			loopNote.noteNumber = midiNote;
233 			loopNote.durationSamples = (int) (duration * getSampleRate());
234 			loopNote.pauseSamples = (int) (pauseDuration * getSampleRate());
235 
236 			loop.add(loopNote);
237 		}
238 
239 		if (!loop.isEmpty()) {
240 			loopIterator = loop.iterator();
241 			currentLoopNote = null;
242 			playingNote = false;
243 			remainingSamples = 0;
244 
245 			running = true;
246 
247 			// the client thread is playing now; let the user quit the app
248 			System.out.println("Playing; press ENTER to quit");
249 			in.readLine();
250 		}
251 
252 		// close the client (disconnect from Jack)
253 		close();
254 	}
255 
256 	public static void main(String[] args) throws Exception {
257 		if (args.length != 1) {
258 			throw new IllegalArgumentException("Expecting single parameter (client name)");
259 		}
260 		String clientName = args[0];
261 
262 		MidiSeq midiSeq = new MidiSeq(clientName, false, true, null);
263 
264 		midiSeq.run();
265 	}
266 }