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.EnumSet;
26  
27  import net.sf.jack4j.AbstractJackTransportClient;
28  import net.sf.jack4j.JackException;
29  import net.sf.jack4j.JackPositionBit;
30  import net.sf.jack4j.JackTransportState;
31  import net.sf.jack4j.TransportPosition;
32  
33  /**
34   * Example timebase master.
35   * 
36   * <p>
37   * This client supplies Jack transport mechanism with additional timebase
38   * information, such as bar/beat/tick numbers. Without the timebase master,
39   * other Jack clients would only get frame number information from Jack
40   * transport.
41   * 
42   * <p>
43   * See comments in the code that describe the usage of Jack4j library.
44   * 
45   * <p>
46   * To run the examples, you need to have the native Jack4j library in your
47   * system load path (LD_LIBRARY_PATH under Linux).
48   * 
49   * <p>
50   * This example client expects single command line parameter - client name.
51   * 
52   * @author repa
53   */
54  public class TimebaseMaster extends AbstractJackTransportClient {
55  
56  	private boolean timeReset = true;
57  	private double bpm = 120;
58  	private float signatureNumerator = 4;
59  	private float signatureDenominator = 4;
60  	private double ticksPerBeat = 96;
61  
62  	/**
63  	 * This callback computes the timebase information.
64  	 * 
65  	 * <p>
66  	 * It's called on the beginning of each process cycle.
67  	 * 
68  	 * @see net.sf.jack4j.JackTransportClient#timebaseCallback(net.sf.jack4j.JackTransportState,
69  	 *      int, net.sf.jack4j.TransportPosition, boolean)
70  	 */
71  	@Override
72  	public void timebaseCallback(JackTransportState state, int nframes, TransportPosition pos, boolean newPos) {
73  		if (newPos || timeReset) {
74  			// either someone requested new transport position, or this is first call to our callback
75  			// set the time signature and tempo values
76  			pos.setBeatsPerBar(signatureNumerator);
77  			pos.setBeatType(signatureDenominator);
78  			pos.setBeatsPerMinute(bpm);
79  			pos.setTicksPerBeat(ticksPerBeat);
80  
81  			timeReset = false;
82  		}
83  
84  		// fill in beat, bar, tick, and barStartTick properties of "pos" object
85  
86  		double minute = pos.getFrame() / (pos.getFrameRate() * 60.0);
87  		double ticksPerMinute = pos.getBeatsPerMinute() * pos.getTicksPerBeat();
88  		double realAbsoluteTick = minute * ticksPerMinute;
89  		int absoluteTick = (int) realAbsoluteTick;
90  		int absoluteBeat = (int) (absoluteTick / pos.getTicksPerBeat());
91  
92  		int barNumber = (int) (absoluteBeat / pos.getBeatsPerBar());
93  		int beatNumber = (int) (absoluteBeat - (barNumber * pos.getBeatsPerBar()));
94  		int tickNumber = (int) (absoluteTick - (absoluteBeat * pos.getTicksPerBeat()));
95  
96  		pos.setBar(barNumber + 1);
97  		pos.setBeat(beatNumber + 1);
98  		pos.setTick(tickNumber);
99  		pos.setBarStartTick(barNumber * pos.getBeatsPerBar() * pos.getTicksPerBeat());
100 
101 		double bbtOffsetTicks = realAbsoluteTick - absoluteTick;
102 		double bbtOffsetSecond = (bbtOffsetTicks / ticksPerMinute) * 60.0;
103 		pos.setBbtOffset((int) (bbtOffsetSecond * pos.getFrameRate()));
104 
105 		// inform other clients that bar/beat/tick fields are available (while others,
106 		// such as audio-video synchronization fields, are not available).
107 		pos.setValidBitsSet(EnumSet.of(JackPositionBit.POSITION_BBT, JackPositionBit.BBT_FRAME_OFFSET));
108 
109 	}
110 
111 	public TimebaseMaster(String clientName, boolean useExactName, boolean canStartServer, String serverName)
112 	        throws JackException {
113 		// Call to super constructor will register us with Jack server
114 		super(clientName, useExactName, canStartServer, serverName);
115 
116 		// Setting default ThreadInit callback is necessary, 
117 		// otherwise other callbacks won't work properly (the Jack client thread
118 		// must be registered with JVM first)
119 		setDefaultThreadInitCallback();
120 
121 		// we are not activated yet, thus the callbacks won't be invoked
122 	}
123 
124 	/**
125 	 * Activation and main event loop.
126 	 */
127 	public void run() throws JackException, IOException {
128 		// Activate the client; until now, callbacks weren't invoked
129 		activate();
130 
131 		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
132 
133 		System.out.println("Issue commands (\"h\" for help)");
134 		boolean cont = true;
135 		while (cont) {
136 			System.out.print("cmd> ");
137 
138 			String cmd = in.readLine();
139 			cmd = cmd.trim();
140 			if (cmd.isEmpty()) {
141 				continue;
142 			}
143 			char cmdChar = cmd.charAt(0);
144 
145 			try {
146 				switch (cmdChar) {
147 					case 'q':
148 						cont = false;
149 						break;
150 					case 'h':
151 						System.out.println("(h) print this help");
152 						System.out.println("(q) quit");
153 						System.out.println("(m) become timebase master");
154 						System.out.println("(r) release timebase");
155 						System.out.println("(p) start transport");
156 						System.out.println("(s) stop transport");
157 						System.out.println("(b) set time signature");
158 						System.out.println("(t) set tempo");
159 						break;
160 					case 'm':
161 						System.out.println("Registering as timebase master");
162 						setDefaultTimebaseCallback(false);
163 						break;
164 					case 'r':
165 						System.out.println("Releasing timebase");
166 						releaseTimebase();
167 						break;
168 					case 'p':
169 						System.out.println("Starting transport");
170 						startTransport();
171 						break;
172 					case 's':
173 						System.out.println("Stopping transport");
174 						stopTransport();
175 						break;
176 					case 'b':
177 						setTimeSignature(in);
178 						break;
179 					case 't':
180 						setTempo(in);
181 						break;
182 					default:
183 						System.err.println("Unknown command: " + cmd);
184 				}
185 			} catch (JackException e) {
186 				e.printStackTrace();
187 			}
188 		}
189 
190 		// close the client (disconnect from Jack)
191 		close();
192 	}
193 
194 	private void setTempo(BufferedReader in) throws IOException {
195 		System.out.print("Enter new tempo value (positive floating number): ");
196 
197 		String line = in.readLine();
198 		double newBpm;
199 		try {
200 			newBpm = Double.parseDouble(line);
201 		} catch (NumberFormatException e) {
202 			e.printStackTrace();
203 			return;
204 		}
205 
206 		if (newBpm > 0) {
207 			bpm = newBpm;
208 
209 			timeReset = true;
210 		} else {
211 			System.err.println("Invalid tempo (must be positive)");
212 		}
213 	}
214 
215 	private void setTimeSignature(BufferedReader in) throws IOException {
216 		System.out.print("Enter new signature numerator: ");
217 		String line = in.readLine();
218 		float newSignatureNumerator;
219 		try {
220 			newSignatureNumerator = Float.parseFloat(line);
221 		} catch (NumberFormatException e) {
222 			e.printStackTrace();
223 			return;
224 		}
225 
226 		System.out.print("Enter new signature denominator: ");
227 		line = in.readLine();
228 		float newSignatureDenominator;
229 		try {
230 			newSignatureDenominator = Float.parseFloat(line);
231 		} catch (NumberFormatException e) {
232 			e.printStackTrace();
233 			return;
234 		}
235 
236 		signatureNumerator = newSignatureNumerator;
237 		signatureDenominator = newSignatureDenominator;
238 
239 		timeReset = true;
240 	}
241 
242 	public static void main(String[] args) throws Exception {
243 		if (args.length != 1) {
244 			throw new IllegalArgumentException("Expecting single parameter (client name)");
245 		}
246 		String clientName = args[0];
247 
248 		TimebaseMaster timebaseMaster = new TimebaseMaster(clientName, false, true, null);
249 
250 		timebaseMaster.run();
251 	}
252 }