Working sound playback with LWGJL finally! (still some bugs)

This commit is contained in:
Brendan Robert 2023-11-04 23:14:56 -05:00
parent e99bd01741
commit 580e760fae
8 changed files with 184 additions and 106 deletions

View File

@ -4,4 +4,4 @@
/.jace.conf
*.classpath
*.project
/src/main/resources/jace/data/sound/*.mp3
/src/main/resources/jace/data/sound/*.ogg

View File

@ -204,7 +204,7 @@
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
@ -215,6 +215,11 @@
<artifactId>lwjgl-openal</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-stb</artifactId>
<classifier>${lwjgl.natives}</classifier>
</dependency>
<dependency>
<groupId>com.badlogicgames.jlayer</groupId>
<artifactId>jlayer</artifactId>

View File

@ -60,7 +60,7 @@ public class SoundMixer extends Device {
* Sample playback rate
*/
@ConfigurableField(name = "Playback Rate", shortName = "freq")
public static int RATE = 48000;
public static int RATE = 44100;
@ConfigurableField(name = "Mute", shortName = "mute")
public static boolean MUTE = false;
@ -239,10 +239,11 @@ public class SoundMixer extends Device {
return;
}
isAlive = false;
performSoundOperation(()->AL10.alSourceStop(sourceId));
performSoundOperation(()->AL10.alDeleteSources(sourceId));
performSoundOperation(()->AL10.alDeleteBuffers(alternateBufferId));
performSoundOperation(()->AL10.alDeleteBuffers(currentBufferId));
performSoundOperation(()->{if (AL10.alIsSource(sourceId)) AL10.alSourceStop(sourceId);});
performSoundOperation(()->{if (AL10.alIsSource(sourceId)) AL10.alDeleteSources(sourceId);});
performSoundOperation(()->{if (AL10.alIsBuffer(alternateBufferId)) AL10.alDeleteBuffers(alternateBufferId);});
performSoundOperation(()->{if (AL10.alIsBuffer(currentBufferId)) AL10.alDeleteBuffers(currentBufferId);});
}
}

View File

@ -4,8 +4,6 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
@ -109,7 +107,7 @@ public class LawlessHacks extends Cheats {
}
}
private String getSongName(int number) {
public String getSongName(int number) {
Map<Integer, String> score = scores.get(currentScore);
if (score == null) {
return null;
@ -125,24 +123,24 @@ public class LawlessHacks extends Cheats {
return filename;
}
private Media getAudioTrack(int number) {
public Media getAudioTrack(int number) {
String filename = getSongName(number);
String pathStr = "/jace/data/sound/" + filename;
URL path = getClass().getResource(pathStr);
if (path == null) {
return null;
}
String resourcePath = path.toString();
System.out.println("Playing " + resourcePath);
if (resourcePath.startsWith("resource:")) {
resourcePath = Paths.get(resourcePath).toFile().getAbsolutePath();
System.out.println("Playing " + resourcePath);
}
// URL path = getClass().getResource(pathStr);
// if (path == null) {
// return null;
// }
// String resourcePath = path.toString();
// System.out.println("Playing " + resourcePath);
// if (resourcePath.startsWith("resource:")) {
// resourcePath = Paths.get(resourcePath).toFile().getAbsolutePath();
// System.out.println("Playing " + resourcePath);
// }
// Log path
try {
return new Media(resourcePath);
return new Media(pathStr);
} catch (IOException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Unable to load audio track " + resourcePath, e);
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Unable to load audio track " + pathStr, e);
return null;
}
}
@ -315,7 +313,7 @@ public class LawlessHacks extends Cheats {
Pattern COMMENT = Pattern.compile("\\s*[-#;']+.*");
Pattern LABEL = Pattern.compile("(8-)?[A-Za-z\\s\\-_]+");
Pattern ENTRY = Pattern.compile("([0-9]+)\\s+(.*)");
private final Map<String, Map<Integer, String>> scores = new HashMap<>();
public final Map<String, Map<Integer, String>> scores = new HashMap<>();
private final Set<Integer> autoResume = new HashSet<>();
private final Map<Integer, Long> lastTime = new HashMap<>();
private void readScores() {
@ -323,7 +321,7 @@ public class LawlessHacks extends Cheats {
readScores(data);
}
private void readScores(InputStream data) {
public void readScores(InputStream data) {
BufferedReader reader = new BufferedReader(new InputStreamReader(data));
reader.lines().forEach(line -> {
boolean useAutoResume = false;

View File

@ -1,83 +1,138 @@
package jace.lawless;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.net.URLDecoder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import org.lwjgl.stb.STBVorbis;
import org.lwjgl.stb.STBVorbisInfo;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import javafx.util.Duration;
public class Media {
long streamHandle = -1;
long totalSamples = 0;
int totalSamples = 0;
float totalDuration = 0;
long sampleRate = 0;
boolean isStereo = true;
ShortBuffer sampleBuffer = ShortBuffer.allocate(2);
ShortBuffer sampleBuffer;
public Media(String resourcePath) throws IOException {
// Load resource into memory
try (InputStream inputStream = getClass().getResourceAsStream(resourcePath)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
// Get caononical file path from relative resource path
String canonicalPath = URLDecoder.decode(getClass().getResource(resourcePath).getPath(), "UTF-8");
System.out.println("Loading media: " + canonicalPath);
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer error = stack.callocInt(1);
Long decoder = STBVorbis.stb_vorbis_open_filename(canonicalPath, error, null);
if (decoder == null || decoder <= 0) {
throw new RuntimeException("Failed to open Ogg Vorbis file. Error: " + getError(error.get(0)));
}
ByteBuffer byteBuffer = ByteBuffer.wrap(outputStream.toByteArray());
IntBuffer error = IntBuffer.allocate(1);
streamHandle = STBVorbis.stb_vorbis_open_memory(byteBuffer, error, null);
MemoryStack stack = MemoryStack.stackPush();
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
STBVorbis.stb_vorbis_get_info(streamHandle, info);
totalSamples = STBVorbis.stb_vorbis_stream_length_in_samples(streamHandle);
totalDuration = STBVorbis.stb_vorbis_stream_length_in_seconds(streamHandle);
STBVorbis.stb_vorbis_get_info(decoder, info);
totalSamples = STBVorbis.stb_vorbis_stream_length_in_samples(decoder);
totalDuration = STBVorbis.stb_vorbis_stream_length_in_seconds(decoder);
sampleRate = info.sample_rate();
isStereo = info.channels() == 2;
info.free();
sampleBuffer = MemoryUtil.memAllocShort(totalSamples);
STBVorbis.stb_vorbis_get_samples_short_interleaved(decoder, isStereo?2:1, sampleBuffer);
STBVorbis.stb_vorbis_close(decoder);
sampleBuffer.rewind();
} catch (RuntimeException ex) {
throw ex;
}
}
public String getError(int vorbisErrorCode) {
switch (vorbisErrorCode) {
case STBVorbis.VORBIS__no_error:
return "VORBIS_no_error";
case STBVorbis.VORBIS_need_more_data:
return "VORBIS_need_more_data";
case STBVorbis.VORBIS_invalid_api_mixing:
return "VORBIS_invalid_api_mixing";
case STBVorbis.VORBIS_outofmem:
return "VORBIS_outofmem";
case STBVorbis.VORBIS_feature_not_supported:
return "VORBIS_feature_not_supported";
case STBVorbis.VORBIS_too_many_channels:
return "VORBIS_too_many_channels";
case STBVorbis.VORBIS_file_open_failure:
return "VORBIS_file_open_failure";
case STBVorbis.VORBIS_seek_without_length:
return "VORBIS_seek_without_length";
case STBVorbis.VORBIS_unexpected_eof:
return "VORBIS_unexpected_eof";
case STBVorbis.VORBIS_seek_invalid:
return "VORBIS_seek_invalid";
case STBVorbis.VORBIS_invalid_setup:
return "VORBIS_invalid_setup";
case STBVorbis.VORBIS_invalid_stream:
return "VORBIS_invalid_stream";
case STBVorbis.VORBIS_missing_capture_pattern:
return "VORBIS_missing_capture_pattern";
case STBVorbis.VORBIS_invalid_stream_structure_version:
return "VORBIS_invalid_stream_structure_version";
case STBVorbis.VORBIS_continued_packet_flag_invalid:
return "VORBIS_continued_packet_flag_invalid";
case STBVorbis.VORBIS_incorrect_stream_serial_number:
return "VORBIS_incorrect_stream_serial_number";
case STBVorbis.VORBIS_invalid_first_page:
return "VORBIS_invalid_first_page";
case STBVorbis.VORBIS_bad_packet_type:
return "VORBIS_bad_packet_type";
case STBVorbis.VORBIS_cant_find_last_page:
return "VORBIS_cant_find_last_page";
case STBVorbis.VORBIS_seek_failed:
return "VORBIS_seek_failed";
case STBVorbis.VORBIS_ogg_skeleton_not_supported:
return "VORBIS_ogg_skeleton_not_supported";
default:
return "Unknown error code: " + vorbisErrorCode;
}
}
public void close() {
STBVorbis.stb_vorbis_close(streamHandle);
MemoryUtil.memFree(sampleBuffer);
}
public void seekToTime(Duration millis) {
int sampleNumber = (int) (millis.toMillis() * sampleRate / 1000);
STBVorbis.stb_vorbis_seek(streamHandle, sampleNumber);
sampleBuffer.position(sampleNumber * (isStereo ? 2 : 1));
}
public boolean isEnded() {
return STBVorbis.stb_vorbis_get_sample_offset(streamHandle) >= totalSamples;
return sampleBuffer.remaining() == 0;
}
public void restart() {
STBVorbis.stb_vorbis_seek_start(streamHandle);
sampleBuffer.rewind();
}
public short getNextLeftSample() {
// read next sample for left and right channels
int numSamples = STBVorbis.stb_vorbis_get_frame_short_interleaved(streamHandle, isStereo ? 2 : 1, sampleBuffer);
if (numSamples == 0) {
if (isEnded()) {
return 0;
}
return sampleBuffer.get(0);
return sampleBuffer.get();
}
public short getNextRightSample() {
return isStereo ? sampleBuffer.get(1) : sampleBuffer.get(0);
if (isEnded()) {
return 0;
}
return isStereo ? sampleBuffer.get() : sampleBuffer.get(sampleBuffer.position());
}
public java.time.Duration getCurrentTime() {
int sampleNumber = STBVorbis.stb_vorbis_get_sample_offset(streamHandle);
int sampleNumber = sampleBuffer.position() / (isStereo ? 2 : 1);
return java.time.Duration.ofMillis((long) (sampleNumber * 1000 / sampleRate));
}
public float getTotalDuration() {
return totalDuration;
}
}

View File

@ -13,7 +13,7 @@ public class MediaPlayer {
double vol = 1.0;
int repeats = 0;
int maxRepetitions = 1;
Status status = Status.STOPPED;
Status status = Status.NOT_STARTED;
Media song;
SoundBuffer playbackBuffer;
Executor executor = Executors.newSingleThreadExecutor();
@ -47,8 +47,9 @@ public class MediaPlayer {
playbackBuffer.shutdown();
} catch (InterruptedException | ExecutionException e) {
// Ignore exception on shutdown
} finally {
song.close();
}
song.close();
}
public void setCycleCount(int i) {
@ -72,12 +73,13 @@ public class MediaPlayer {
return;
} else if (status == Status.NOT_STARTED) {
repeats = 0;
status = Status.PLAYING;
if (playbackBuffer == null || !playbackBuffer.isAlive()) {
playbackBuffer = SoundMixer.createBuffer(true);
}
}
executor.execute(() -> {
status = Status.PLAYING;
System.out.println("Song playback thread started");
while (status == Status.PLAYING && (maxRepetitions == INDEFINITE || repeats < maxRepetitions)) {
if (song.isEnded()) {
if (maxRepetitions == INDEFINITE) {
@ -87,16 +89,15 @@ public class MediaPlayer {
if (repeats < maxRepetitions) {
song.restart();
} else {
System.out.println("Song ended");
this.stop();
break;
}
}
}
short leftSample = song.getNextLeftSample();
short rightSample = song.getNextRightSample();
try {
playbackBuffer.playSample((short) (leftSample * vol));
playbackBuffer.playSample((short) (rightSample * vol));
playbackBuffer.playSample((short) (song.getNextLeftSample() * vol));
playbackBuffer.playSample((short) (song.getNextRightSample() * vol));
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
this.stop();

View File

@ -4,8 +4,8 @@
---- Lines that contain an asterisk will use auto-resume, otherwise will always restart
Common
--- Sound effects start at number 129
129 bang.mp3
130 boom.mp3
--129 bang.ogg
--130 boom.ogg
-- 1: Theme (Copyright)
-- 2: Train station (intro), Fort Miller
@ -30,45 +30,45 @@ Common
---- Chiptune version uses the blips and bleeps that are the voice of a generation
8-bit Chipmusic
1 8 Bit Weapon - Lawless Legends Original Double Score - 02 Lawless Legends 8-Bit C64.mp3
2 8 Bit Weapon - Lawless Legends Original Double Score - 03 Fort Miller 8-Bit C64.mp3
3 8 Bit Weapon - Lawless Legends Original Double Score - 04 Tragedy in the West 8-Bit C64.mp3
4 8 Bit Weapon - Lawless Legends Original Double Score - 05 Victory! 8-Bit C64.mp3
5 8 Bit Weapon - Lawless Legends Original Double Score - 06 Grub Gulch 8-Bit C64.mp3
6 8 Bit Weapon - Lawless Legends Original Double Score - 07 Mines of Mystery 8-Bit C64.mp3
7 8 Bit Weapon - Lawless Legends Original Double Score - 08 Texas Flats 8-Bit C64.mp3
8 8 Bit Weapon - Lawless Legends Original Double Score - 09 Strange Tales 8-Bit C64.mp3
9 8 Bit Weapon - Lawless Legends Original Double Score - 10 Oro's Villa 8-Bit C64.mp3
10 8 Bit Weapon - Lawless Legends Original Double Score - 11 Tahnku Village 8-Bit C64.mp3
11 8 Bit Weapon - Lawless Legends Original Double Score - 12 Tahnku Sanctuary 8-Bit C64.mp3
12 8 Bit Weapon - Lawless Legends Original Double Score - 13 Mariposa 8-Bit C64.mp3
13 8 Bit Weapon - Lawless Legends Original Double Score - 14 Freemont Estate 8-Bit C64.mp3
14 8 Bit Weapon - Lawless Legends Original Double Score - 15 Fort Littlecreek 8-Bit C64.mp3
15 8 Bit Weapon - Lawless Legends Original Double Score - 16 Sacred Grove 8-Bit C64.mp3
16 8 Bit Weapon - Lawless Legends Original Double Score - 17 Poverty Flats 8-Bit C64.mp3
17 8 Bit Weapon - Lawless Legends Original Double Score - 18 Combat! 8-Bit C64.mp3
18 8 Bit Weapon - Lawless Legends Original Double Score - 19 Killed in Cold Blood 8-Bit C64.mp3
19 *8 Bit Weapon - Lawless Legends Original Double Score - 20 The Sierra Nevada Wilderness 8-Bit C64.mp3
20 *8 Bit Weapon - Lawless Legends Original Double Score - 20 The Sierra Nevada Wilderness 8-Bit C64.mp3
1 8 Bit Weapon - Lawless Legends Original Double Score - 02 Lawless Legends 8-Bit C64.ogg
2 8 Bit Weapon - Lawless Legends Original Double Score - 03 Fort Miller 8-Bit C64.ogg
3 8 Bit Weapon - Lawless Legends Original Double Score - 04 Tragedy in the West 8-Bit C64.ogg
4 8 Bit Weapon - Lawless Legends Original Double Score - 05 Victory! 8-Bit C64.ogg
5 8 Bit Weapon - Lawless Legends Original Double Score - 06 Grub Gulch 8-Bit C64.ogg
6 8 Bit Weapon - Lawless Legends Original Double Score - 07 Mines of Mystery 8-Bit C64.ogg
7 8 Bit Weapon - Lawless Legends Original Double Score - 08 Texas Flats 8-Bit C64.ogg
8 8 Bit Weapon - Lawless Legends Original Double Score - 09 Strange Tales 8-Bit C64.ogg
9 8 Bit Weapon - Lawless Legends Original Double Score - 10 Oro's Villa 8-Bit C64.ogg
10 8 Bit Weapon - Lawless Legends Original Double Score - 11 Tahnku Village 8-Bit C64.ogg
11 8 Bit Weapon - Lawless Legends Original Double Score - 12 Tahnku Sanctuary 8-Bit C64.ogg
12 8 Bit Weapon - Lawless Legends Original Double Score - 13 Mariposa 8-Bit C64.ogg
13 8 Bit Weapon - Lawless Legends Original Double Score - 14 Freemont Estate 8-Bit C64.ogg
14 8 Bit Weapon - Lawless Legends Original Double Score - 15 Fort Littlecreek 8-Bit C64.ogg
15 8 Bit Weapon - Lawless Legends Original Double Score - 16 Sacred Grove 8-Bit C64.ogg
16 8 Bit Weapon - Lawless Legends Original Double Score - 17 Poverty Flats 8-Bit C64.ogg
17 8 Bit Weapon - Lawless Legends Original Double Score - 18 Combat! 8-Bit C64.ogg
18 8 Bit Weapon - Lawless Legends Original Double Score - 19 Killed in Cold Blood 8-Bit C64.ogg
19 *8 Bit Weapon - Lawless Legends Original Double Score - 20 The Sierra Nevada Wilderness 8-Bit C64.ogg
20 *8 Bit Weapon - Lawless Legends Original Double Score - 20 The Sierra Nevada Wilderness 8-Bit C64.ogg
8-bit Orchestral samples
1 8 Bit Weapon - Lawless Legends Original Double Score - 21 Lawless Legends 8-Bit Samples.mp3
2 8 Bit Weapon - Lawless Legends Original Double Score - 22 Fort Miller 8-Bit Samples.mp3
3 8 Bit Weapon - Lawless Legends Original Double Score - 23 Tragedy in the West 8-Bit Samples.mp3
4 8 Bit Weapon - Lawless Legends Original Double Score - 24 Victory! 8-Bit Samples.mp3
5 8 Bit Weapon - Lawless Legends Original Double Score - 25 Grub Gulch 8-Bit Samples.mp3
6 8 Bit Weapon - Lawless Legends Original Double Score - 27 Mines of Mystery 8-Bit Samples.mp3
7 8 Bit Weapon - Lawless Legends Original Double Score - 26 Texas Flats 8-Bit Samples.mp3
8 8 Bit Weapon - Lawless Legends Original Double Score - 28 Strange Tales 8-Bit Samples.mp3
9 8 Bit Weapon - Lawless Legends Original Double Score - 29 Oro's Villa 8-Bit Samples.mp3
10 8 Bit Weapon - Lawless Legends Original Double Score - 30 Tahnku Village 8-Bit Samples.mp3
11 8 Bit Weapon - Lawless Legends Original Double Score - 31 Tahnku Sanctuary 8-Bit Samples.mp3
12 8 Bit Weapon - Lawless Legends Original Double Score - 32 Mariposa City 8-Bit Samples.mp3
13 8 Bit Weapon - Lawless Legends Original Double Score - 33 Freemont Estate 8-Bit Samples.mp3
14 8 Bit Weapon - Lawless Legends Original Double Score - 34 Fort Littlecreek 8-Bit Samples.mp3
15 8 Bit Weapon - Lawless Legends Original Double Score - 35 Sacred Grove 8-Bit Samples.mp3
16 8 Bit Weapon - Lawless Legends Original Double Score - 36 Poverty Flats 8-Bit Samples.mp3
17 8 Bit Weapon - Lawless Legends Original Double Score - 37 Combat! 8-Bit Samples.mp3
18 8 Bit Weapon - Lawless Legends Original Double Score - 38 Killed in Cold Blood 8-Bit Samples.mp3
19 *8 Bit Weapon - Lawless Legends Original Double Score - 39 The Sierra Nevada Wilderness 8-Bit Samples.mp3
20 *8 Bit Weapon - Lawless Legends Original Double Score - 39 The Sierra Nevada Wilderness 8-Bit Samples.mp3
1 8 Bit Weapon - Lawless Legends Original Double Score - 21 Lawless Legends 8-Bit Samples.ogg
2 8 Bit Weapon - Lawless Legends Original Double Score - 22 Fort Miller 8-Bit Samples.ogg
3 8 Bit Weapon - Lawless Legends Original Double Score - 23 Tragedy in the West 8-Bit Samples.ogg
4 8 Bit Weapon - Lawless Legends Original Double Score - 24 Victory! 8-Bit Samples.ogg
5 8 Bit Weapon - Lawless Legends Original Double Score - 25 Grub Gulch 8-Bit Samples.ogg
6 8 Bit Weapon - Lawless Legends Original Double Score - 27 Mines of Mystery 8-Bit Samples.ogg
7 8 Bit Weapon - Lawless Legends Original Double Score - 26 Texas Flats 8-Bit Samples.ogg
8 8 Bit Weapon - Lawless Legends Original Double Score - 28 Strange Tales 8-Bit Samples.ogg
9 8 Bit Weapon - Lawless Legends Original Double Score - 29 Oro's Villa 8-Bit Samples.ogg
10 8 Bit Weapon - Lawless Legends Original Double Score - 30 Tahnku Village 8-Bit Samples.ogg
11 8 Bit Weapon - Lawless Legends Original Double Score - 31 Tahnku Sanctuary 8-Bit Samples.ogg
12 8 Bit Weapon - Lawless Legends Original Double Score - 32 Mariposa City 8-Bit Samples.ogg
13 8 Bit Weapon - Lawless Legends Original Double Score - 33 Freemont Estate 8-Bit Samples.ogg
14 8 Bit Weapon - Lawless Legends Original Double Score - 34 Fort Littlecreek 8-Bit Samples.ogg
15 8 Bit Weapon - Lawless Legends Original Double Score - 35 Sacred Grove 8-Bit Samples.ogg
16 8 Bit Weapon - Lawless Legends Original Double Score - 36 Poverty Flats 8-Bit Samples.ogg
17 8 Bit Weapon - Lawless Legends Original Double Score - 37 Combat! 8-Bit Samples.ogg
18 8 Bit Weapon - Lawless Legends Original Double Score - 38 Killed in Cold Blood 8-Bit Samples.ogg
19 *8 Bit Weapon - Lawless Legends Original Double Score - 39 The Sierra Nevada Wilderness 8-Bit Samples.ogg
20 *8 Bit Weapon - Lawless Legends Original Double Score - 39 The Sierra Nevada Wilderness 8-Bit Samples.ogg

View File

@ -2,10 +2,28 @@ package jace;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
import jace.core.SoundMixer;
import jace.core.SoundMixer.SoundBuffer;
import jace.lawless.LawlessHacks;
import jace.lawless.Media;
public class SoundTest {
@Test
public void musicDecodeTest() {
// For every song in the music folder, decode it and print out the duration
// This is to make sure that the decoding is working properly
LawlessHacks lawless = Emulator.withComputer(LawlessHacks::new, null);
for (String score : lawless.scores.keySet()) {
lawless.changeMusicScore(score);
for (int track : lawless.scores.get(score).keySet()) {
System.out.println("Loading score %s, track %d".formatted(score, track));
Media m = lawless.getAudioTrack(track);
System.out.println("Duration: " + m.getTotalDuration());
}
}
}
// @Test
public void soundGenerationTest() {