0

Currenty, I am working on a text-to-speech program, and I recently added threads to play the sound. I've found that when enough threads are made, the GUI console no longer prints out and a "NegativeArraySizeException" is printed out in the Eclipse console. I couldn't find anything for this error involving just a string and not a normal array, so I'm not sure where to start fixing this...

The error in question is:

Exception in thread "Thread-99" java.lang.NegativeArraySizeException: -248
    at java.base/java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:89)
    at java.base/java.lang.StringBuilder.<init>(StringBuilder.java:119)
    at javafx.scene.control.TextArea$TextAreaContent.get(TextArea.java:102)
    at javafx.scene.control.TextArea$TextAreaContent.get(TextArea.java:311)
    at javafx.scene.control.TextArea$TextAreaContent.get(TextArea.java:88)
    at javafx.scene.control.TextInputControl$TextProperty.get(TextInputControl.java:1386)
    at javafx.scene.control.TextInputControl.updateSelectedText(TextInputControl.java:178)
    at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:1273)
    at javafx.scene.control.TextInputControl.filterAndSet(TextInputControl.java:1229)
    at javafx.scene.control.TextInputControl$TextProperty.doSet(TextInputControl.java:1480)
    at javafx.scene.control.TextInputControl$TextProperty.set(TextInputControl.java:1393)
    at javafx.scene.control.TextInputControl.setText(TextInputControl.java:361)
    at net.oijon.algonquin.gui.GUI$2$1.run(GUI.java:144)
    at java.base/java.lang.Thread.run(Thread.java:833)

The thread being run is:

String message = IPA.createAudio(IPA.getFileNames(insert.getText()), fileNameField.getText(), packField.getText());
console.setText(message);
System.out.println(message);

There's also the two methods of getFileNames and createAudio. Both return strings so I can put info messages on the GUI console, as I'm not sure how I'd go about just routing the actual console to it. There's probably a better way to do that, but it should be fine for now. getFileNames relies on three arrays of every IPA character. There's a list of all IPA sounds, a list of prediacritics, and a list of postdiacritics. These are:

static char[] ipaList = {'p', 'b', 't', 'd', 'ʈ', 'ɖ', 'c', 'ɟ', 'k', 'g', 'ɡ', 'q', 'ɢ', 'ʔ', 'm', 'ɱ', 'n', 'ɳ', 'ɲ', 'ŋ', 'ɴ', 'ʙ', 'r', 'ʀ', 'ⱱ', 'ɾ', 'ɽ', 'ɸ', 'β', 'f', 'v', 'θ', 'ð', 's', 'z', 'ʃ', 'ʒ', 'ʂ', 'ʐ', 'ç', 'ʝ', 'x', 'ɣ', 'χ', 'ʁ', 'ħ', 'ʕ', 'h', 'ɦ', 'ɬ', 'ɮ', 'ʋ', 'ɹ', 'ɻ', 'j', 'ɰ', 'l', 'ɭ', 'ʎ', 'ʟ', 'ʍ', 'w', 'ɥ', 'ʜ', 'ʢ', 'ʡ', 'ɕ', 'ʑ', 'ɺ', 'ɧ', 'i', 'y', 'ɨ', 'ʉ', 'ɯ', 'u', 'ɪ', 'ʏ', 'ʊ', 'e', 'ø', 'ɘ', 'ɵ', 'ɤ', 'o', 'ə', 'ɛ', 'œ', 'ɜ', 'ɞ', 'ʌ', 'ɔ', 'æ', 'ɐ', 'a', 'ɶ', 'ɑ', 'ɒ'};
static char[] preDiacriticList = {'ᵐ', 'ⁿ', 'ᶯ', 'ᶮ', 'ᵑ'};
static char[] postDiacriticList = {'̥', 'ː', '̊', '̬', 'ʰ', '̹', '̜', '̟', '̠', '̈', '̽', '̩', '̯', '˞', '̤', '̰', '̼', 'ʷ', 'ʲ', 'ˠ', 'ˤ', '̴', '̝', '̞', '̘', '̙', '̪', '̺', '̻','̃', 'ˡ', '̚', '-'};
        
//g and ɡ are the same sound, however two different points in unicode. as such, they need to both be in there to prevent disappearing chars

getFileNames:

public static String[] getFileNames(String input) {
        
        String[] fileNames = new String[input.length()];
        
   
        int inputLength = input.length();
        int currentFileName = 0;
        
        for (int i = 0; i < inputLength; i++) {
            char c = input.charAt(i);
            boolean isPreDiacritic = false;
            boolean isPostDiacritic = false;
            
            //handles spaces.
            if (c == ' ') {
                //if space, set to space.wav
                fileNames[currentFileName] = "space";
                currentFileName++;
            }
            
            
            for (int j = 0; j < postDiacriticList.length; j++) {
                if (c == postDiacriticList[j]) {
                    isPostDiacritic = true;
                    //shouldnt actually be a problem, but just in case...]
                    if (currentFileName != 0) {
                        //if diacritic, add to file name of previous char.
                        currentFileName--;
                        fileNames[currentFileName] += Character.toString(c);
                        currentFileName++;
                    } else {
                        System.err.println("Postdiacritic \'" + c + "\' attempted to be added to non-existant character! Skipping...");
                    }
                    
                }
            }
            
            for (int l = 0; l < preDiacriticList.length; l++) {
                if (c == preDiacriticList[l]) {
                    System.out.println(preDiacriticList[l]);
                    isPreDiacritic = true;
                    if (currentFileName != fileNames.length) {
                        //if prediacritic, add to file name of next char.
                        fileNames[currentFileName] = Character.toString(c);
                    } else {
                        System.err.println("Prediacritic \'" + c + "\' attempted to be added to non-existant character! Skipping...");
                    }
                }
            }
            
            //skips if the character was a diacritic, should speed things up...
            if (isPostDiacritic == false && isPreDiacritic == false) {
                for (int k = 0; k < ipaList.length; k++) {
                    if (c == ipaList[k]) {
                        //sets file name to character and goes to the next file name
                        //checks if null because if not, prediacritics would be overwritten.
                        if (fileNames[currentFileName] == null) {
                            fileNames[currentFileName] = Character.toString(c);
                        } else {
                            fileNames[currentFileName] += Character.toString(c);
                        }
                        
                        currentFileName++;
                    }
                }
            }
            
            //TODO: handle supersegmentals
        
        }
        return fileNames;
    }

and createAudio:

public static String createAudio(String[] fileNames, String name, String packName){
        
        String exception = "";
        long fileLength = 0;
        try {
            URL packURL = new File(System.getProperty("user.home") + "/AlgonquinTTS/packs/" + packName).toURI().toURL();
        } catch (MalformedURLException e1) {
            URL packURL = IPA.class.getResource("/" + packName);
            exception += "packURL is malformed! Getting resources from jar instead...";
            exception += e1.toString();
            e1.printStackTrace();
        }
        AudioInputStream allStreams[] = new AudioInputStream[fileNames.length];
        try {
            for (int i = 0; i < fileNames.length; i++) {
                URL url;
                File clipFile = new File(System.getProperty("user.home") + "/AlgonquinTTS/packs/" + packName + "/" + fileNames[i] + ".wav");
                try {
                    url = clipFile.toURI().toURL();
                } catch (MalformedURLException e1) {
                    url = IPA.class.getResource("/" + packName + "/" + fileNames[i] + ".wav");
                    exception += "url is malformed! Getting resources from jar instead...";
                    exception += e1.toString();
                    e1.printStackTrace();
                }
                if (clipFile.exists() == false) {
                    if (fileNames[i] != null) {
                        boolean foundValid = false;
                        for (int j = 0; j < fileNames[i].length(); j++) {
                            for (int k = 0; k < ipaList.length; k++) {
                                if (fileNames[i].charAt(j) == ipaList[k]) {
                                    foundValid = true;
                                    exception += "Invalid sound " + fileNames[i] + " detected! This usually means the sound hasn't been added yet. Reverting to " + fileNames[i].charAt(j) + "\n";
                                    fileNames[i] = Character.toString(fileNames[i].charAt(j));
                                }
                            }
                        }
                        if (foundValid == false) {
                            exception += "Invalid sound " + fileNames[i] + " detected! No valid replacement found, skipping...\n";
                            fileNames[i] = "space";
                        }
                    }
                    else {
                        fileNames[i] = "space";
                    }
                    
                }
                AudioInputStream ais = AudioSystem.getAudioInputStream(new File(
                        System.getProperty("user.home") + "/AlgonquinTTS/packs/" + packName + "/" + fileNames[i] + ".wav").getAbsoluteFile());
                allStreams[i] = ais;
            }
            for (int i = 1; i < allStreams.length; i++) {
                AudioInputStream temp = new AudioInputStream(
                        new SequenceInputStream(allStreams[0], allStreams[i]),
                            allStreams[0].getFormat(),
                            allStreams[0].getFrameLength() + allStreams[1].getFrameLength());
                allStreams[0] = temp;
            }
                
            AudioSystem.write(allStreams[0], AudioFileFormat.Type.WAVE, new File(System.getProperty("user.home") + 
                    "/AlgonquinTTS/" + name + ".wav"));
            exception += "Created file " + System.getProperty("user.home") + 
                    "/AlgonquinTTS/" + name + ".wav\n";
            Clip clip = AudioSystem.getClip();
            AudioInputStream ais = AudioSystem.getAudioInputStream(
                    new File(System.getProperty("user.home") + 
                            "/AlgonquinTTS/" + name + ".wav").getAbsoluteFile()
                    );
            clip.open(ais);
            clip.start();
            fileLength += clip.getMicrosecondLength();
            while(clip.getMicrosecondLength() != clip.getMicrosecondPosition())
            {
            }
            ais.close();
            exception += "Successfully played " + Arrays.toString(fileNames) + "\n";
            
        } catch (Exception e) {
            exception = e.toString();
            e.printStackTrace();
        }
        
        
        return exception;
    }
N3ther
  • 76
  • 1
  • 10
  • 2
    Did you edit the source code with the same encoding as the javac compiler used? Both UTF-8? And (far fetched) instead of `Character.toString(c)` try `String.format("%04x", (int)c);` – Joop Eggen Jul 21 '22 at 12:59
  • @JoopEggen changing `Character.toString(c)` to `String.format("%04x", (int)c);` seems to have fixed it – N3ther Jul 21 '22 at 13:12
  • 1
    Then check the encodings too. If you somehow got illegal UTF-8, then you could get a multibyte _continuation_ byte without _start_ byte. You could u-encode the chars `uXXXX` or for the moment use basic latin chars to try it out. – Joop Eggen Jul 21 '22 at 13:26
  • I think I figured out how the start byte is going missing, as I noticed that the exception only happens after 100% CPU usage has been hit and when there is noticeable distortion in the sound. Not sure if this is possible, but perhaps my computer hitting 100% CPU usage is causing it to drop the start byte? – N3ther Jul 21 '22 at 15:05
  • I was thinking on char encodings, a UTF/8 multibyte sequence for a char > 127 might be 0b11xxxxxx (startbyte) followed by one or more 0b10xxxxxx (continuation bytes). So encountering a standalone byte 0b10xxxxxx is illegal, missing a start. You might use the java tool `native2ascii` with parameters to encode your special chars. On a copy of your project. – Joop Eggen Jul 21 '22 at 15:12
  • 2
    Also consider that _hitting 100% CPU usage_ may be exposing a latent synchronization error. – trashgod Jul 21 '22 at 15:34

1 Answers1

0

A NegativeArraySizeException is thrown if an application tries to create an array with negative size.

It need not be your code directly, but use the stack trace as a hint where such an array might get created. I doubt you will find something like new int[-4]; but it might be more something like

int x = -4;
new int[x];

or even more obscure. If you find the offending situation, by reading the code you may understand either what needs to be changed, or in case it is not your own code understand how not to call it.

Queeg
  • 7,748
  • 1
  • 16
  • 42