Java equivalent of C# system.beep?

后端 未结 8 1287
半阙折子戏
半阙折子戏 2021-01-01 10:31

I am working on a Java program, and I really need to be able to play a sound by a certain frequency and duration, similarly to the c# method System.Beep, I know how to use i

相关标签:
8条回答
  • 2021-01-01 10:57

    I don't think there's a way to play tunes1 with "beep" in portable2 Java. You'll need to use the javax.sound.* APIs I think ... unless you can find a third-party library that simplifies things for you.

    If you want to go down this path, then this page might give you some ideas.


    1 - Unless your users are all tone-deaf. Of course you can do things like beeping in Morse code ... but that's not a tune.

    2 - Obviously, you could make native calls to a Windows beep function. But that would not be portable.

    0 讨论(0)
  • 2021-01-01 11:02

    I've hacked together a function that works for me. It uses a bunch of stuff from javax.sound.sampled. I've geared it to work with the audio format my system automatically gives a new Clip from AudioSystem.getClip(). There's probably all kinds of ways it could be made more robust and more efficient.

    /**
     * Beeps.  Currently half-assumes that the format the system expects is
     * "PCM_SIGNED unknown sample rate, 16 bit, stereo, 4 bytes/frame, big-endian"
     * I don't know what to do about the sample rate.  Using 11025, since that
     * seems to be right, by testing against A440.  I also can't figure out why
     * I had to *4 the duration.  Also, there's up to about a 100 ms delay before
     * the sound starts playing.
     * @param freq
     * @param millis 
     */
    public static void beep(double freq, final double millis) {
        try {
            final Clip clip = AudioSystem.getClip();
            AudioFormat af = clip.getFormat();
    
            if (af.getSampleSizeInBits() != 16) {
                System.err.println("Weird sample size.  Dunno what to do with it.");
                return;
            }
    
            //System.out.println("format " + af);
    
            int bytesPerFrame = af.getFrameSize();
            double fps = 11025;
            int frames = (int)(fps * (millis / 1000));
            frames *= 4; // No idea why it wasn't lasting as long as it should.
    
            byte[] data = new byte[frames * bytesPerFrame];
    
            double freqFactor = (Math.PI / 2) * freq / fps;
            double ampFactor = (1 << af.getSampleSizeInBits()) - 1;
    
            for (int frame = 0; frame < frames; frame++) {
                short sample = (short)(0.5 * ampFactor * Math.sin(frame * freqFactor));
                data[(frame * bytesPerFrame) + 0] = (byte)((sample >> (1 * 8)) & 0xFF);
                data[(frame * bytesPerFrame) + 1] = (byte)((sample >> (0 * 8)) & 0xFF);
                data[(frame * bytesPerFrame) + 2] = (byte)((sample >> (1 * 8)) & 0xFF);
                data[(frame * bytesPerFrame) + 3] = (byte)((sample >> (0 * 8)) & 0xFF);
            }
            clip.open(af, data, 0, data.length);
    
            // This is so Clip releases its data line when done.  Otherwise at 32 clips it breaks.
            clip.addLineListener(new LineListener() {                
                @Override
                public void update(LineEvent event) {
                    if (event.getType() == Type.START) {
                        Timer t = new Timer((int)millis + 1, new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent e) {
                                clip.close();
                            }
                        });
                        t.setRepeats(false);
                        t.start();
                    }
                }
            });
            clip.start();
        } catch (LineUnavailableException ex) {
            System.err.println(ex);
        }
    }
    

    EDIT: Apparently somebody's improved my code. I haven't tried it yet, but give it a go: https://gist.github.com/jbzdak/61398b8ad795d22724dd

    0 讨论(0)
提交回复
热议问题