1 | /*
|
---|
2 | * GPLv2 or 3, Copyright (c) 2010 Andrzej Zaborowski
|
---|
3 | *
|
---|
4 | * This class simulates a car engine. What does a car engine do? It
|
---|
5 | * makes a pc-speaker-like buzz. The PC Speaker could only emit
|
---|
6 | * a (nearly) square wave and we simulate it here for maximum realism.
|
---|
7 | */
|
---|
8 | package wmsturbochallenge;
|
---|
9 |
|
---|
10 | import java.util.Timer;
|
---|
11 | import java.util.TimerTask;
|
---|
12 |
|
---|
13 | import javax.sound.sampled.AudioFormat;
|
---|
14 | import javax.sound.sampled.AudioSystem;
|
---|
15 | import javax.sound.sampled.DataLine;
|
---|
16 | import javax.sound.sampled.SourceDataLine;
|
---|
17 |
|
---|
18 | class EngineSound {
|
---|
19 | public EngineSound() {
|
---|
20 | rpm = 0.0;
|
---|
21 | }
|
---|
22 |
|
---|
23 | public void start() {
|
---|
24 | rpm = 0.3;
|
---|
25 | speed = 0.0;
|
---|
26 | n = 0;
|
---|
27 |
|
---|
28 | if (output != null)
|
---|
29 | stop();
|
---|
30 |
|
---|
31 | AudioFormat output_format =
|
---|
32 | new AudioFormat(S_RATE, 16, 1, true, true);
|
---|
33 | DataLine.Info info =
|
---|
34 | new DataLine.Info(SourceDataLine.class, output_format);
|
---|
35 |
|
---|
36 | /* Get the data line, open it and initialise the device */
|
---|
37 | try {
|
---|
38 | output = (SourceDataLine) AudioSystem.getLine(info);
|
---|
39 | output.open(output_format);
|
---|
40 | output.start();
|
---|
41 | frames_written = 0;
|
---|
42 | reschedule(0);
|
---|
43 | } catch (Exception e) {
|
---|
44 | output = null;
|
---|
45 | System.out.println("Audio not available: " +
|
---|
46 | e.getClass().getSimpleName());
|
---|
47 | }
|
---|
48 | }
|
---|
49 |
|
---|
50 | public void stop() {
|
---|
51 | rpm = 0.0;
|
---|
52 | n = 0;
|
---|
53 |
|
---|
54 | if (output == null)
|
---|
55 | return;
|
---|
56 |
|
---|
57 | tick.cancel();
|
---|
58 | tick.purge();
|
---|
59 |
|
---|
60 | output.stop();
|
---|
61 | output.flush();
|
---|
62 | output.close();
|
---|
63 | output = null;
|
---|
64 | }
|
---|
65 |
|
---|
66 | public void set_speed(double speed) {
|
---|
67 | /* This engine is equipped with an automatic gear box that
|
---|
68 | * switches gears when the RPM becomes too high or too low. */
|
---|
69 | double new_speed = Math.abs(speed);
|
---|
70 | double accel = new_speed - this.speed;
|
---|
71 | this.speed = new_speed;
|
---|
72 |
|
---|
73 | if (accel > 0.05)
|
---|
74 | accel = 0.05;
|
---|
75 | else if (accel < -0.05)
|
---|
76 | accel = -0.05;
|
---|
77 | rpm += accel;
|
---|
78 |
|
---|
79 | if (accel > 0.0 && rpm > 1.0 + n * 0.2 && speed > 0.0) {
|
---|
80 | rpm = 0.3 + n * 0.2;
|
---|
81 | n ++;
|
---|
82 | } else if (accel < 0.0 && rpm < 0.3) {
|
---|
83 | if (n > 0) {
|
---|
84 | rpm = 0.7 + n * 0.1;
|
---|
85 | n --;
|
---|
86 | } else
|
---|
87 | rpm = 0.2;
|
---|
88 | }
|
---|
89 | if (speed < 2.0)
|
---|
90 | n = 0;
|
---|
91 | }
|
---|
92 |
|
---|
93 | public boolean is_on() {
|
---|
94 | return output != null;
|
---|
95 | }
|
---|
96 |
|
---|
97 | protected double speed;
|
---|
98 | protected double rpm;
|
---|
99 | protected int n;
|
---|
100 |
|
---|
101 | protected SourceDataLine output = null;
|
---|
102 | protected long frames_written;
|
---|
103 | protected Timer tick = new Timer();
|
---|
104 |
|
---|
105 | /* Audio parameters. */
|
---|
106 | protected static final int S_RATE = 44100;
|
---|
107 | protected static final int MIN_BUFFER = 4096;
|
---|
108 | protected static final double volume = 0.3;
|
---|
109 |
|
---|
110 | protected class audio_task extends TimerTask {
|
---|
111 | @Override
|
---|
112 | public void run() {
|
---|
113 | if (output == null)
|
---|
114 | return;
|
---|
115 |
|
---|
116 | /* If more than a two buffers left to play,
|
---|
117 | * reschedule and try to wake up closer to the
|
---|
118 | * end of already written data. */
|
---|
119 | long frames_current = output.getLongFramePosition();
|
---|
120 | if (frames_current < frames_written - MIN_BUFFER * 2) {
|
---|
121 | reschedule(frames_current);
|
---|
122 | return;
|
---|
123 | }
|
---|
124 |
|
---|
125 | /* Build a new buffer */
|
---|
126 | /* double freq = 20 * Math.pow(1.3, rpm * 5.0); */
|
---|
127 | double freq = (rpm - 0.1) * 160.0;
|
---|
128 | int wavelen = (int) (S_RATE / freq);
|
---|
129 | int bufferlen = MIN_BUFFER - (MIN_BUFFER % wavelen) +
|
---|
130 | wavelen;
|
---|
131 | int value = (int) (0x7fff * volume);
|
---|
132 |
|
---|
133 | bufferlen *= 2;
|
---|
134 | byte[] buffer = new byte[bufferlen];
|
---|
135 | for (int b = 0; b < bufferlen; ) {
|
---|
136 | int j;
|
---|
137 | for (j = wavelen / 2; j > 0; j --) {
|
---|
138 | buffer[b ++] = (byte) (value >> 8);
|
---|
139 | buffer[b ++] = (byte) (value & 0xff);
|
---|
140 | }
|
---|
141 | value = 0x10000 - value;
|
---|
142 | for (j = wavelen - wavelen / 2; j > 0; j --) {
|
---|
143 | buffer[b ++] = (byte) (value >> 8);
|
---|
144 | buffer[b ++] = (byte) (value & 0xff);
|
---|
145 | }
|
---|
146 | value = 0x10000 - value;
|
---|
147 | }
|
---|
148 |
|
---|
149 | frames_written +=
|
---|
150 | output.write(buffer, 0, bufferlen) / 2;
|
---|
151 |
|
---|
152 | reschedule(frames_current);
|
---|
153 | }
|
---|
154 | }
|
---|
155 |
|
---|
156 | protected void reschedule(long frames) {
|
---|
157 | /* Send a new buffer as close to the end of the
|
---|
158 | * currently playing buffer as possible (aim at
|
---|
159 | * about half into the last frame). */
|
---|
160 | long delay = (frames_written - frames - MIN_BUFFER / 2) *
|
---|
161 | 1000 / S_RATE;
|
---|
162 | if (delay < 0)
|
---|
163 | delay = 0;
|
---|
164 | tick.schedule(new audio_task(), delay);
|
---|
165 | }
|
---|
166 | }
|
---|