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