2011-07-12 57 views
0

我正在將腳趾頭浸入音樂編程中(我甚至不知道這是否合適),並且我想先編寫一個可以播放簡單曲調的程序,例如「Mary had A Little Lamb」或「祝你生日快樂」。是否有與平臺無關的Beep功能?

我使用OS X 10.5。是否有一個簡單的API用於播放某個特定的頻率,類似於Window的Beep功能,但通過我的Macbook揚聲器播放?

我想用C++或Java編寫我的程序,但我願意接受其他建議。另外,只要它是開源的,我不介意涉及第三方庫的解決方案。如果解決方案要真正實現平臺無關性,那就太好了,但現在我只需要讓它在我的Macbook上運行即可。

回答

3

jMusic是一個相當不錯的庫,它可以在Java中運行,並且可以在Mac OS X上運行。您可以查看website。它有完整的文件和製作音樂的分步說明。這裏是一個簡單的做一個筆記的樣本:

import jm.music.data.*; 
import jm.JMC; 
import jm.util.*; 

public class Dot01 implements JMC { 
    public static void main(String[] args) { 
     Note n; 
     n = new Note(C4, QUARTER_NOTE); 
     Phrase phr = new Phrase(); 
     phr.addNote(n); 

     View.notate(phrase); 
    } 
} 
3

我不能保證它可以在Mac上工作。在Mac上聽過Java聲音。是完全搞砸了。話雖這麼說..

/* 
<applet 
    code='org.pscode.beeper.Beeper' 
    width='300' 
    height='300'> 
<param name='samplerate' value='11025'> 
<param name='fpw' value='40'> 
<param name='addharmonic' value='true'> 
<param name='autoloop' value='true'> 
<param name='loopcount' value='10'> 

<param name='volume' value='45'> 
</applet> 
*/ 
package org.pscode.beeper; 

import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import javax.swing.event.*; 
import javax.swing.border.*; 
import java.text.DecimalFormat; 

import javax.sound.sampled.*; 

import java.io.ByteArrayInputStream; 

/** Beeper presents a small, loopable tone that can be heard 
by clicking on the Code Key. It uses a Clip to loop the sound, 
as well as for access to the Clip's gain control. 
@author Andrew Thompson 
@version 2009-12-19 
@license LGPL */ 
public class Beeper extends JApplet { 

    BeeperPanel bp; 

    public void init() { 
     bp = new BeeperPanel(); 
     getContentPane().add(bp); 
     validate(); 

     String sampleRate = getParameter("samplerate"); 
     if (sampleRate!=null) { 
      try { 
       int sR = Integer.parseInt(sampleRate); 
       bp.setSampleRate(sR); 
      } catch(NumberFormatException useDefault) { 
      } 
     } 

     String fpw = getParameter("fpw"); 
     if (fpw!=null) { 
      try { 
       int fPW = Integer.parseInt(fpw); 
       JSlider slider = bp.getFramesPerWavelengthSlider(); 
       slider.setValue(fPW); 
      } catch(NumberFormatException useDefault) { 
      } 
     } 

     boolean harmonic = (getParameter("addharmonic")!=null); 
     bp.setAddHarmonic(harmonic); 

     bp.setUpSound(); 

     if (getParameter("autoloop")!=null) { 
      String loopcount = getParameter("loopcount"); 
      if (loopcount!=null) { 
       try { 
        Integer lC = Integer.parseInt(loopcount); 
        bp.loop(lC.intValue()); 
       } catch(NumberFormatException doNotLoop) { 
       } 
      } 
     } 
    } 

    public void stop() { 
     bp.loopSound(false); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      public void run() { 
       JFrame f = new JFrame("Beeper"); 
       f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       BeeperPanel BeeperPanel = new BeeperPanel(); 
       f.setContentPane(BeeperPanel); 
       f.pack(); 
       try { 
        f.setMinimumSize(f.getSize()); 
       } catch(Exception ignore) { 
       } 
       f.setLocationRelativeTo(null); 
       f.setVisible(true); 
      } 
     }); 
    } 
} 

/** The main UI of Beeper. */ 
class BeeperPanel extends JPanel { 

    JComboBox sampleRate; 
    JSlider framesPerWavelength; 
    JLabel frequency; 
    JCheckBox harmonic; 
    Clip clip; 

    DecimalFormat decimalFormat = new DecimalFormat("###00.00"); 

    BeeperPanel() { 
     super(new BorderLayout()); 
     // Use current OS look and feel. 
     try { 
      UIManager.setLookAndFeel(
       UIManager.getSystemLookAndFeelClassName()); 
//    "com.sun.java.swing.plaf.motif.MotifLookAndFeel"); 
//    "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); 
      SwingUtilities.updateComponentTreeUI(this); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
     setPreferredSize(new Dimension(300,300)); 

     JPanel options = new JPanel(); 
     BoxLayout bl = new BoxLayout(options,BoxLayout.Y_AXIS); 
     options.setLayout(bl); 

     Integer[] rates = { 
      new Integer(8000), 
      new Integer(11025), 
      new Integer(16000), 
      new Integer(22050) 
     }; 
     sampleRate = new JComboBox(rates); 
     sampleRate.setToolTipText("Samples per second"); 
     sampleRate.setSelectedIndex(1); 
     JPanel pSampleRate = new JPanel(new BorderLayout()); 
     pSampleRate.setBorder(new TitledBorder("Sample Rate")); 
     pSampleRate.add(sampleRate); 
     sampleRate.addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent ae) { 
       setUpSound(); 
      } 
     }); 
     options.add(pSampleRate); 

     framesPerWavelength = new JSlider(JSlider.HORIZONTAL,10,200,25); 
     framesPerWavelength.setPaintTicks(true); 
     framesPerWavelength.setMajorTickSpacing(10); 
     framesPerWavelength.setMinorTickSpacing(5); 
     framesPerWavelength.setToolTipText("Frames per Wavelength"); 
     framesPerWavelength.addChangeListener(new ChangeListener(){ 
      public void stateChanged(ChangeEvent ce) { 
       setUpSound(); 
      } 
     }); 

     JPanel pFPW = new JPanel(new BorderLayout()); 
     pFPW.setBorder(new TitledBorder("Frames per Wavelength")); 

     pFPW.add(framesPerWavelength); 
     options.add(pFPW); 

     JPanel bottomOption = new JPanel(new BorderLayout(4,4)); 
     harmonic = new JCheckBox("Add Harmonic", false); 
     harmonic.setToolTipText(
      "Add harmonic to second channel, one octave up"); 
     harmonic.addActionListener(new ActionListener(){ 
      public void actionPerformed(ActionEvent ae) { 
       setUpSound(); 
      } 
     }); 
     bottomOption.add(harmonic, BorderLayout.WEST); 

     frequency = new JLabel(); 
     bottomOption.add(frequency, BorderLayout.CENTER); 

     options.add(bottomOption); 

     add(options, BorderLayout.NORTH); 

     JPanel play = new JPanel(new BorderLayout(3,3)); 
     play.setBorder(new EmptyBorder(4,4,4,4)); 
     JButton bPlay = new JButton("Code Key"); 
     bPlay.setToolTipText("Click to make tone!"); 
     Dimension preferredSize = bPlay.getPreferredSize(); 
     bPlay.setPreferredSize(new Dimension(
      (int)preferredSize.getWidth(), 
      (int)preferredSize.getHeight()*3)); 

     // TODO comment out to try KeyListener! 
     //bPlay.setFocusable(false); 
     bPlay.addKeyListener(new KeyAdapter(){ 
      @Override 
      public void keyPressed(KeyEvent ke) { 
       loopSound(true); 
      } 
     }); 
     bPlay.addMouseListener(new MouseAdapter() { 
       @Override 
       public void mousePressed(MouseEvent me) { 
        loopSound(true); 
       } 

       @Override 
       public void mouseReleased(MouseEvent me) { 
        loopSound(false); 
       } 
      }); 
     play.add(bPlay); 

     try { 
      clip = AudioSystem.getClip(); 

      final FloatControl control = (FloatControl) 
       clip.getControl(FloatControl.Type.MASTER_GAIN); 

      final JSlider volume = new JSlider(
       JSlider.VERTICAL, 
       (int)control.getMinimum(), 
       (int)control.getMaximum(), 
       (int)control.getValue() 
       ); 
      volume.setToolTipText("Volume of beep"); 
      volume.addChangeListener(new ChangeListener(){ 
       public void stateChanged(ChangeEvent ce) { 
        control.setValue(volume.getValue()); 
       } 
      }); 
      play.add(volume, BorderLayout.EAST); 
     } catch(Exception e) { 
      e.printStackTrace(); 
     } 

     add(play, BorderLayout.CENTER); 

     setUpSound(); 
    } 

    public void loop(int loopcount) { 
     if (clip!=null) { 
      clip.loop(loopcount); 
     } 
    } 

    public void setAddHarmonic(boolean addHarmonic) { 
     harmonic.setSelected(addHarmonic); 
    } 

    /** Provides the slider for determining the # of frames per wavelength, 
    primarily to allow easy adjustment by host classes. */ 
    public JSlider getFramesPerWavelengthSlider() { 
     return framesPerWavelength; 
    } 

    /** Sets the sample rate to one of the four 
    allowable rates. Is ignored otherwise. */ 
    public void setSampleRate(int sR) { 
     switch (sR) { 
      case 8000: 
       sampleRate.setSelectedIndex(0); 
       break; 
      case 11025: 
       sampleRate.setSelectedIndex(1); 
       break; 
      case 16000: 
       sampleRate.setSelectedIndex(2); 
       break; 
      case 22050: 
       sampleRate.setSelectedIndex(3); 
       break; 
      default: 
     } 
    } 

    /** Sets label to current frequency settings. */ 
    public void setFrequencyLabel() { 
     float freq = getFrequency(); 
     if (harmonic.isSelected()) { 
      frequency.setText( 
       decimalFormat.format(freq) + 
       "(/" + 
       decimalFormat.format(freq*2f) + 
       ") Hz"); 
     } else { 
      frequency.setText(decimalFormat.format(freq) + " Hz"); 
     } 
    } 

    /** Generate the tone and inform the user of settings. */ 
    public void setUpSound() { 
     try { 
      generateTone(); 
      setFrequencyLabel(); 
     } catch(Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    /** Provides the frequency at current settings for 
    sample rate & frames per wavelength. */ 
    public float getFrequency() { 
     Integer sR = (Integer)sampleRate.getSelectedItem(); 
     int intST = sR.intValue(); 
     int intFPW = framesPerWavelength.getValue(); 

     return (float)intST/(float)intFPW; 
    } 

    /** Loops the current Clip until a commence false is passed. */ 
    public void loopSound(boolean commence) { 
     if (commence) { 
      clip.setFramePosition(0); 
      clip.loop(Clip.LOOP_CONTINUOUSLY); 
     } else { 
      clip.stop(); 
     } 
    } 

    /** Generates a tone, and assigns it to the Clip. */ 
    public void generateTone() 
     throws LineUnavailableException { 
     if (clip!=null) { 
      clip.stop(); 
      clip.close(); 
     } else { 
      clip = AudioSystem.getClip(); 
     } 
     boolean addHarmonic = harmonic.isSelected(); 

     int intSR = ((Integer)sampleRate.getSelectedItem()).intValue(); 
     int intFPW = framesPerWavelength.getValue(); 

     float sampleRate = (float)intSR; 

     // oddly, the sound does not loop well for less than 
     // around 5 or so, wavelengths 
     int wavelengths = 20; 
     byte[] buf = new byte[2*intFPW*wavelengths]; 
     AudioFormat af = new AudioFormat(
      sampleRate, 
      8, // sample size in bits 
      2, // channels 
      true, // signed 
      false // bigendian 
      ); 

     int maxVol = 127; 
     for(int i=0; i<intFPW*wavelengths; i++){ 
      double angle = ((float)(i*2)/((float)intFPW))*(Math.PI); 
      buf[i*2]=getByteValue(angle); 
      if(addHarmonic) { 
       buf[(i*2)+1]=getByteValue(2*angle); 
      } else { 
       buf[(i*2)+1] = buf[i*2]; 
      } 
     } 

     try { 
      byte[] b = buf; 
      AudioInputStream ais = new AudioInputStream( 
       new ByteArrayInputStream(b), 
       af, 
       buf.length/2); 

      clip.open(ais); 
     } catch(Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    /** Provides the byte value for this point in the sinusoidal wave. */ 
    private static byte getByteValue(double angle) { 
     int maxVol = 127; 
     return (new Integer(
      (int)Math.round(
      Math.sin(angle)*maxVol))). 
      byteValue(); 
    } 
} 

enter image description here

+0

這工作在Mac上,只要它產生的聲音從揚聲器。我很欣賞答案。 – FishBasketGordo

+0

感謝您的確認。也許這只是「角落案例」,不起作用 - 或者蘋果近期可以改善聲音支持。無論哪種方式,我有一個小應用程序。其中我很自豪(一款MP3公爵盒),現在看起來它可能會在Mac上使用。鑑於這個例子的工作。 :-) –

相關問題