Minim:可播放的(Playable)

[ javadoc | 在线实例 ]

很简单,就是CD唱机的功能。

播放与暂停

手册原文说了一大堆,基本都是废话(其实我早发现这哥们废话多了),播放与暂停是什么玩意,没有不懂的吧?

示范代码(在线实例

import ddf.minim.*;

Minim minim;
AudioPlayer groove;
WaveformRenderer waveform;

void setup()
{
  size(512, 200, P3D);

  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3", 2048);

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  groove.addListener(waveform);

  textFont(createFont("Arial", 12));
  textMode(SCREEN);
}

void draw()
{
  background(0);
  // see waveform.pde for an explanation of how this works
  waveform.draw();

  if ( groove.isPlaying() )
  {
    text("The player is playing.", 5, 15);
  }
  else
  {
    text("The player is not playing.", 5, 15);
  }
}

void keyPressed()
{
  if ( key == 'l' )
  {
    groove.loop(1);
  }
  if ( key == 'p' )
  {
    groove.play();
  }
  if ( key == 's' )
  {
    groove.pause();
  }
}

void stop()
{
  // always close Minim audio classes when you are done with them
  groove.close();
  // always stop Minim before exiting.
  minim.stop();

  super.stop();
}

重置

rewind()将使你的播放头回到声音最初,它不会改变playable的属性,即,如果你之前设为循环播放,则rewind之后仍然是循环播放。

import ddf.minim.*;

Minim minim;
AudioPlayer groove;
WaveformRenderer waveform;

void setup()
{
  size(512, 200, P3D);

  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3", 2048);
  groove.loop();

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  groove.addListener(waveform);
}

void draw()
{
  background(0);
  // see waveform.pde for an explanation of how this works
  waveform.draw();
}

void keyPressed()
{
  if ( key == 'r' ) groove.rewind();
}

void stop()
{
  // always close Minim audio classes when you are done with them
  groove.close();
  // always stop Minim before exiting.
  minim.stop();

  super.stop();
}

循环

void loop()
void loop(int numLoops)

没引数的将无限循环,引数为循环次数,记得循环1次,则音频将被播放2次,循环2次,音频播放3次,以此类推。你可以使用isLooping()检查一个声音是否被设为了循环播放。你可以使用loopCount()侦测剩余循环的次数。如果你在声音循环次数之前呼叫了play()则剩余的循环命令将被取消,歌曲从头播放到尾然后停止。这在你想取消循环而不想中途切断声音时很有效。

import ddf.minim.*;

Minim minim;
AudioPlayer groove;
WaveformRenderer waveform;
int loopcount;

void setup()
{
  size(512, 200, P3D);

  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3", 2048);

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  groove.addListener(waveform);

  textFont(createFont("Arial", 12));
  textMode(SCREEN);
}

void draw()
{
  background(0);
  // see waveform.pde for an explanation of how this works
  waveform.draw();

  text("The player has " + groove.loopCount() + " loops left. Is playing: " + groove.isPlaying() + ", Is looping: " + groove.isLooping(), 5, 15);
}

void keyPressed()
{
  String keystr = String.valueOf(key);
  int num = int(keystr);
  if ( num > 0 && num < 10 )
  {
    groove.loop(num);
    loopcount = num;
  }
}

void stop()
{
  // always close Minim audio classes when you are done with them
  groove.close();
  // always stop Minim before exiting.
  minim.stop();

  super.stop();
}

循环点

你可以呼叫setLoopPoints(int start, int stop)以循环Playable对象的一部分,start和stop是以毫秒表述的时间,这在AudioSnippet里使用是最好的,因为曲子已经实现读入了内存,而在AudioPlayer中,则会有可察觉的滞后,尤以mp3文件明显。

import ddf.minim.*;

Minim minim;
AudioSnippet snip;

int loopBegin;
int loopEnd;

void setup()
{
  size(512, 200, P3D);
  textMode(SCREEN);
  minim = new Minim(this);
  snip = minim.loadSnippet("groove.mp3");
  snip.loop();

  textFont(loadFont("ArialMT-14.vlw"));
}

void draw()
{
  background(0);
  fill(255);
  text("Loop Count: " + snip.loopCount(), 5, 20);
  text("Looping: " + snip.isLooping(), 5, 40);
  text("Playing: " + snip.isPlaying(), 5, 60);
  int p = snip.position();
  int l = snip.length();
  text("Position: " + p, 5, 80);
  text("Length: " + l, 5, 100);
  float x = map(p, 0, l, 0, width);
  stroke(255);
  line(x, height/2 - 50, x, height/2 + 50);
  float lbx = map(loopBegin, 0, snip.length(), 0, width);
  float lex = map(loopEnd, 0, snip.length(), 0, width);
  stroke(0, 255, 0);
  line(lbx, 0, lbx, height);
  stroke(255, 0, 0);
  line(lex, 0, lex, height);
}

void mousePressed()
{
  int ms = (int)map(mouseX, 0, width, 0, snip.length());
  if ( mouseButton == RIGHT )
  {
    snip.setLoopPoints(loopBegin, ms);
    loopEnd = ms;
  }
  else
  {
    snip.setLoopPoints(ms, loopEnd);
    loopBegin = ms;
  }
}

void stop()
{
  // always close Minim audio classes when you are done with them
  snip.close();
  minim.stop();

  super.stop();
}

Cueing(抱歉我实在不知道这词儿怎么翻)

import ddf.minim.*;

Minim minim;
AudioPlayer groove;
WaveformRenderer waveform;

void setup()
{
  size(512, 200, P3D);

  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3", 2048);
  groove.loop();

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  groove.addListener(waveform);
}

void draw()
{
  background(0);
  // see waveform.pde for an explanation of how this works
  waveform.draw();
}

void keyPressed()
{
  if ( key == 'c' )
  {
    groove.cue(5000);
  }
}

void stop()
{
  // always close Minim audio classes when you are done with them
  groove.close();
  // always stop Minim before exiting.
  minim.stop();

  super.stop();
}

快退快进

skip(100):快进100毫秒,skip(-200):快退200毫秒。如果文件没有处于播放状态,则快进快退不会触发播放。如果快进快退出现错误,播放头将不会改变位置。如果快退位置小于零或快进位置大于歌曲长度,则播放头将位于文件头或尾。

import ddf.minim.*;

Minim minim;
AudioPlayer groove;
WaveformRenderer waveform;

void setup()
{
  size(512, 200, P3D);

  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3", 2048);
  groove.loop();

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  groove.addListener(waveform);
}

void draw()
{
  background(0);
  // see waveform.pde for an explanation of how this works
  waveform.draw();
}

void keyPressed()
{
  if ( key == 'f' )
  {
    groove.skip(100);
  }
  if ( key == 'r' )
  {
    groove.skip(-500);
  }
}

void stop()
{
  // always close Minim audio classes when you are done with them
  groove.close();
  // always stop Minim before exiting.
  minim.stop();

  super.stop();
}

位置和长度

方法position以毫秒返回当前播放头的位置。方法length以毫秒返回音频文件的长度。下例结合了这两种方法将播放头位置视觉化:

import ddf.minim.*;

Minim minim;
AudioPlayer groove;
WaveformRenderer waveform;

void setup()
{
  size(512, 200, P3D);

  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3", 2048);

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  groove.addListener(waveform);
  groove.loop();
}

void draw()
{
  background(0);
  // see waveform.pde for an explanation of how this works
  waveform.draw();

  float x = map(groove.position(), 0, groove.length(), 0, width);
  stroke(255, 0, 0);
  line(x, height/2 - 30, x, height/2 + 30);
}

void stop()
{
  // always close Minim audio classes when you are done with them
  groove.close();
  // always stop Minim before exiting.
  minim.stop();

  super.stop();
}

音频Meta数据

你可以通过呼叫getMetaData()由一个音频文件得到一个AudioMetaData对象。随即,你便可以呼叫以下方法:

String album()
String author()
String comment()
String composer()
String copyright()
String date()
String disc()
String encoded()
String fileName()
String genre()
int length()
String orchestra()
String publisher()
String title()
int track()

如果没有信息可用,以上方法将返回一个空的列或-1,基于返回类型。

import ddf.minim.*;

Minim minim;
AudioPlayer groove;
AudioMetaData meta;

void setup()
{
  size(512, 256, P3D);

  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3");
  meta = groove.getMetaData();

  textFont( loadFont("serif.vlw") );
  textMode(SCREEN);
}

int ys = 15;
int yi = 15;

void draw()
{
  background(0);
  int y = ys;
  text("File Name: " + meta.fileName(), 5, y);
  text("Length (in milliseconds): " + meta.length(), 5, y+=yi);
  text("Title: " + meta.title(), 5, y+=yi);
  text("Author: " + meta.author(), 5, y+=yi);
  text("Album: " + meta.album(), 5, y+=yi);
  text("Date: " + meta.date(), 5, y+=yi);
  text("Comment: " + meta.comment(), 5, y+=yi);
  text("Track: " + meta.track(), 5, y+=yi);
  text("Genre: " + meta.genre(), 5, y+=yi);
  text("Copyright: " + meta.copyright(), 5, y+=yi);
  text("Disc: " + meta.disc(), 5, y+=yi);
  text("Composer: " + meta.composer(), 5, y+=yi);
  text("Orchestra: " + meta.orchestra(), 5, y+=yi);
  text("Publisher: " + meta.publisher(), 5, y+=yi);
  text("Encoded: " + meta.encoded(), 5, y+=yi);
}

void stop()
{
  // always close Minim audio classes when you are done with them
  groove.close();
  // always stop Minim before exiting
  minim.stop();

  super.stop();
}

Minim:控制器

[ javadoc | 范例 ]

像之前的章节里提到的,JavaSound使用Line在你的系统和程序间传递音频。每条线路都可以通过它进行声相、平衡、增益和音量调节。优点是你无须在你的合成类里执行以上的控制,但缺点是在回放音频时,只有当你的软件接到采样后这些控制才会起作用。这意味着你在播放一个立体声音频时,即使将平衡调至极右,你也不会在你的采样中看到任何差别。换句话说,当你以为左声道的值为0的时候,它仍将保存原始音频文件左声道内的一起实际存在的东西。另外,在监听音频输入的时候,设置一个控制将被反映入采样缓冲器中,因为它将在你的软件收到音频之前起效。

打印控制

并非所有控制在所有线路上都可用。你可以使用方法printControls打印出一个控制器上可用的控制,同时包括那些控制的范围。

示范代码在线范例

import ddf.minim.*;

Minim minim;
AudioOutput out;

void setup()
{
  size(512, 200);
  minim = new Minim(this);
  out = minim.getLineOut();
  out.printControls();
}

void draw()
{
  background(0);
}

void stop()
{
  // 日完记得关闭Minim音频类
  out.close();
  minim.stop();

  super.stop();
}

询问控制

Controller提供方法hasControl,这样你可以在尝试使用一个控制前测定它是否存在。hasControl的引数为Control.Type。控制器包括三个静态成员(member),他们是BALANCE, GAIN, PAN, MUTE, SAMPLE_RATE和VOLUME。

示范代码在线范例

import ddf.minim.*;

Minim minim;
AudioOutput out;

void setup()
{
  size(512, 200);
  minim = new Minim(this);
  out = minim.getLineOut();

  textFont(createFont("Arial", 12));
}

void draw()
{
  background(0);

  if ( out.hasControl(Controller.PAN) )
  {
    text("The output has a pan control.", 5, 15);
  }
  else
  {
    text("The output doesn't have a pan control.", 5, 15);
  }

  if ( out.hasControl(Controller.VOLUME) )
  {
    text("The output has a volume control.", 5, 30);
  }
  else
  {
    text("The output doesn't have a volume control.", 5, 30);
  }

  if ( out.hasControl(Controller.SAMPLE_RATE) )
  {
    text("The output has a sample rate control.", 5, 45);
  }
  else
  {
    text("The output doesn't have a sample rate control.", 5, 45);
  }

  if ( out.hasControl(Controller.BALANCE) )
  {
    text("The output has a balance control.", 5, 60);
  }
  else
  {
    text("The output doesn't have a balance control.", 5, 60);
  }

  if ( out.hasControl(Controller.MUTE) )
  {
    text("The output has a mute control.", 5, 75);
  }
  else
  {
    text("The output doesn't have a mute control.", 5, 75);
  }

  if ( out.hasControl(Controller.GAIN) )
  {
    text("The output has a gain control.", 5, 90);
  }
  else
  {
    text("The output doesn't have a gain control.", 5, 105);
  }
}

void stop()
{
  // 日完记得XXXXXX
  out.close();
  minim.stop();

  super.stop();
}

获取和设置控制器

当你知道了你可以使用什么控制后,你可以使用适当的方法get(获取)和set(设置)操作它们。

setBalance(float value)
getBalance()
setGain(float value)
getGain()
setPan(float value)
getPan()
setVolume(float value)
getVolume()

平衡的范围是-1~1,增益常从-80~6,声相也是从-1~1,音量的范围作者说他自己也不知道。。。从所有的使用上,增益基本上等同于音量。以下是操作一个输出的增益的例子。其他控制的例子可以在这里找到。

示范代码在线范例

import ddf.minim.*;
import ddf.minim.signals.*;

Minim minim;
AudioOutput out;
Oscillator  osc;
WaveformRenderer waveform;

void setup()
{
  size(512, 200);
  minim = new Minim(this);
  out = minim.getLineOut();

  // see the example AudioOutput >> SawWaveSignal for more about this class
  osc = new SawWave(100, 0.2, out.sampleRate());
  // see the example Polyphonic >> addSignal for more about this
  out.addSignal(osc);

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  out.addListener(waveform);

  textFont(createFont("Arial", 12));
}

void draw()
{
  background(0);
  // see waveform.pde for more about this
  waveform.draw();

  if ( out.hasControl(Controller.GAIN) )
  {
    // 将鼠标位置映射到可听到的增益范围内
    float val = map(mouseX, 0, width, 6, -48);
    // 如果一个增益控制不可用,这里将什么都不做
    out.setGain(val);
    // 如果一个增益控制不可用,这里将输出0
    text("The current gain is " + out.getGain() + ".", 5, 15);
  }
  else
  {
    text("The output doesn't have a gain control.", 5, 15);
  }
}

void stop()
{
  // 日完记得XXXXX
  out.close();
  minim.stop();

  super.stop();
}

变化控制

因为反映的是一系列连续值的范围,所以所有这些控制都被称为浮点控制。Controller为持续改变这些控制之一的值提供方法。这被称为变化(shifting)。shifting的方法如下:

shiftBalance(float from, float to, int ms)
shiftGain(float from, float to, int ms)
shiftPan(float from, float to, int ms)
shiftVolume(float from, float to, int ms)

很明显的,from是一个值开始的点,to是目标值,ms是以毫秒计的shift作用时间。

示范代码在线范例

import ddf.minim.*;
import ddf.minim.signals.*;

Minim minim;
AudioOutput out;
WaveformRenderer waveform;
SawWave saw;

void setup()
{
  size(512, 200);
  minim = new Minim(this);
  out = minim.getLineOut();

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  out.addListener(waveform);

  // see the example AudioOutput >> SawWaveSignal for more about this
  saw = new SawWave(100, 0.2, out.sampleRate());
  // see the example Polyphonic >> addSignal for more about this
  out.addSignal(saw);

  textFont(createFont("Arial", 12));
}

void draw()
{
  background(0);
  // see waveform.pde for more about this
  waveform.draw();

  if ( out.hasControl(Controller.PAN) )
  {
    text("The current pan value is " + out.getPan() + ".", 5, 15);
  }
  else
  {
    text("The output doesn't have a pan control.", 5, 15);
  }

  if ( out.hasControl(Controller.VOLUME) )
  {
    text("The current volume value is " + out.getVolume() + ".", 5, 30);
  }
  else
  {
    text("The output doesn't have a volume control.", 5, 30);
  }

  if ( out.hasControl(Controller.BALANCE) )
  {
    text("The current balance value is " + out.getBalance() + ".", 5, 45);
  }
  else
  {
    text("The output doesn't have a balance control.", 5, 45);
  }

  if ( out.hasControl(Controller.GAIN) )
  {
    text("The current gain value is " + out.getGain() + ".", 5, 60);
  }
  else
  {
    text("The output doesn't have a gain control.", 5, 60);
  }
}

void keyReleased()
{
  if ( key == 'v' ) out.shiftVolume(0, 1, 2000);
  if ( key == 'g' ) out.shiftGain(-40, 0, 2000);
  if ( key == 'b' ) out.shiftBalance(-1, 1, 2000);
  if ( key == 'p' ) out.shiftPan(1, -1, 2000);
}

void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  minim.stop();

  super.stop();
}

静音

如下

isMuted()
mute()
unmute()

再次,如果静音不是一个可用的控制,静音和非静音将不起任何作用,并会在PDE控制台输出错误信息。

示范代码在线范例

import ddf.minim.*;
import ddf.minim.signals.*;

Minim minim;
AudioOutput out;
WaveformRenderer waveform;
SawWave saw;

void setup()
{
  size(512, 200);
  minim = new Minim(this);
  out = minim.getLineOut();

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  out.addListener(waveform);

  // see the example AudioOutput >> SawWaveSignal for more about this
  saw = new SawWave(100, 0.2, out.sampleRate());
  // see the example Polyphonic >> addSignal for more about this
  out.addSignal(saw);

  textFont(createFont("Arial", 12));
}

void draw()
{
  background(0);
  // see waveform.pde for more about this
  waveform.draw();

  if ( out.hasControl(Controller.MUTE) )
  {
    if (mousePressed)
    {
      out.mute();
    }
    else
    {
      out.unmute();
    }
    if ( out.isMuted() )
    {
      text("The output is muted.", 5, 15);
    }
    else
    {
      text("The output is not muted.", 5, 15);
    }
  }
  else
  {
    text("The output doesn't have a mute control.", 5, 15);
  }
}

void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  minim.stop();

  super.stop();
}

直接使用一个控制

使用控制更直接的方式是使用Controller的方法getControls。这个方法返回Control对象的数组,你可以使用方法getType测定一个控制的类型。

示范代码在线范例

import ddf.minim.*;
import ddf.minim.signals.*;
import javax.sound.sampled.Control;

Minim minim;
AudioOutput out;
Control[] controls;

void setup()
{
  size(512, 200);
  minim = new Minim(this);
  out = minim.getLineOut();
  controls = out.getControls();

  textFont(createFont("Arial", 12));
}

void draw()
{
  background(0);

  for ( int i = 0; i < controls.length; i++ )
  {
    text("Control " + (i+1) + " is a " + controls[i].toString() + ".", 5, 15 + i*15);
  }
}

void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  minim.stop();

  super.stop();
}

所有这些方法都返回适当的FloatControl。以下是使用pan()而不是setPan进入声相控制的例子:

示范代码在线范例

import ddf.minim.*;
import ddf.minim.signals.*;

Minim minim;
AudioOutput out;
Oscillator  osc;
WaveformRenderer waveform;

void setup()
{
  size(512, 200);
  minim = new Minim(this);
  out = minim.getLineOut();

  // see the example AudioOutput >> SawWaveSignal for more about this class
  osc = new SawWave(100, 0.2, out.sampleRate());
  // see the example Polyphonic >> addSignal for more about this
  out.addSignal(osc);

  waveform = new WaveformRenderer();
  // see the example Recordable >> addListener for more about this
  out.addListener(waveform);

  textFont(createFont("Arial", 12));
}

void draw()
{
  background(0);
  // see waveform.pde for more about this
  waveform.draw();

  if ( out.hasControl(Controller.PAN) )
  {
    // map the mouse position to the range of the pan
    float val = map(mouseX, 0, width, out.pan().getMinimum(), out.pan().getMaximum());
    out.pan().setValue(val);
    text("The current pan is " + out.pan().getValue() + ".", 5, 15);
  }
  else
  {
    text("There is no pan control for this output.", 5, 15);
  }
}

void stop()
{
  // always close Minim audio classes when you are finished with them
  out.close();
  minim.stop();

  super.stop();
}

要细讲FloatControl的所有方法实在太多,所以还是仔细读读javadoc或者研究一下在线范例们吧。

FloatControl

Minim:AudioSource(音源)

[ javadoc | 范例 ]

AudioSource定义了三个AudioBuffer成员,同时也可执行Recordable和Effectable界面。你无法直接创建一个AudioSource,它仅仅是为AudioPlayer, AudioSample, AudioOuput和AudioInput提供普通功能而存在。

采样缓冲器

三个采样缓冲器被命名为left, right和mix。它们持续更新左通道,右通道及左右混合声道的音源。即使在播放单声道音频时,三个通道都是可用的并且包含同样的采样。每个采样缓冲器都是一个AudioBuffer对象。

AudioBuffer: Get和Size

两个你在AudioBuffer中使用最频繁的方法是get(int i)和size()。方法size返回缓冲器的长度。方法get返回采样缓冲器第i个采样的浮点值,这个值将会在-1~1的范围内。因此,这常被认为是一个标准化的浮点采样。一个采样即对一个音源在某一时刻的振幅测量值。要听见一个声音,振幅必须时刻处于变化。最简单的变化辨识方波(square wave)。方法get并不做任何范围检查。因此,如果你询问一个位置小于零或大等于size返回值的采样,你将会得到一个ArrayOutOfBounds的错误。下例示范了如何使用get和size来为一个音频信号绘制波形。

范例在线看

import ddf.minim.*;

Minim minim;
AudioPlayer groove;

void setup()
{
  size(512, 200, P3D);
  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3");
  groove.loop();
}

void draw()
{
  background(0);
  stroke(255);
  // 我们用50乘以值才能看到波形
  for ( int i = 0; i < groove.bufferSize() - 1; i++ )
  {
    float x1 = map(i, 0, groove.bufferSize(), 0, width);
    float x2 = map(i+1, 0, groove.bufferSize(), 0, width);
    line(x1, height/4 - groove.left.get(i)*50, x2, height/4 - groove.left.get(i+1)*50);
    line(x1, 3*height/4 - groove.right.get(i)*50, x2, 3*height/4 - groove.right.get(i+1)*50);
  }
}

void stop()
{
  // 记得关闭音频类
  groove.close();
  // 记得停止Minim
  minim.stop();

  super.stop();
}

AudioBuffer:Level(水平)

AudioBuffer的方法level返回当前缓冲器的音量水平。这个值将始终处于0~1之间,但你可能会发现返回的值常比你的预期要小。

范例在线看

import ddf.minim.*;
import ddf.minim.signals.*;

Minim minim;
AudioPlayer groove;

void setup()
{
  size(200, 200, P3D);
  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3");
  groove.loop();
  rectMode(CORNERS);
}

void draw()
{
  background(0);
  fill(255);
  // 绘制当前左右采样缓冲器的音量水平
  // level()返回一个0~1之间的值, 因此我们将它放大
  rect(0, height, width/2, height - groove.left.level()*1000);
  rect(width/2, height, width, height - groove.right.level()*1000);
}

void stop()
{
  // 记得关闭音频类
  groove.close();
  // 记得停止Minim
  minim.stop();

  super.stop();
}

AudioBuffer: toArray(到数组)

第四个也是最后一个AudioBuffer可用的方法是toArray()。这个方法以一个浮动数组的形式返回一个缓冲器内值的副本。toArray返回的浮动数组长度将一直与缓冲器的大小相同。数组内的值也将一直在-1~1的范围内变化,除非你使用了AudioOutput的信号混合产生一个超过这个范围的采样值。如果那样的话你必须注意,因为这会产生声音的失真。你可以用toArray为在AudioBuffer内的音频绘制波形,并且这是这么做的首选方法。这是由于线路方面的原因。当有一个新的采样缓冲时,实际的音频输入输出发生在他自己的线路上并最后返回总线(你的sketch)。因此,当使用get来绘制波形的时候,在绘制波形的途中,在缓冲器中的采样很有可能已经改变了,这将造成一个看起来并不连续的波形。而当你使用toArray的时候,你给的是当前缓冲器里内容的副本并且是有保障的,因此在整个过程中不会出现采样的改变。

范例在线看

import ddf.minim.*;

Minim minim;
AudioPlayer groove;

void setup()
{
  size(512, 200, P3D);
  minim = new Minim(this);
  groove = minim.loadFile("groove.mp3");
  groove.loop();
}

void draw()
{
  background(0);
  stroke(255);
  float[] left = groove.left.toArray();
  float[] right = groove.right.toArray();
  // 我们仅循环至left.length - 1 因为我们正进入循环内i+1的目录
  for ( int i = 0; i < left.length - 1; i++ )
  {
    float x1 = map(i, 0, groove.bufferSize(), 0, width);
    float x2 = map(i+1, 0, groove.bufferSize(), 0, width);
    // 我们将这些值乘以50以更好的看到波形
    line(x1, height/4 - left[i]*50, x2, height/4 - left[i+1]*50);
    line(x1, 3*height/4 - right[i]*50, x2, 3*height/4 - right[i+1]*50);
  }
}

void stop()
{
  // 记得关闭音频类
  groove.close();
  // 记得停掉Minim
  minim.stop();

  super.stop();
}

关闭一个AudioSource

当你用完一个对象后,你应到呼叫close来关闭Minim里的全部音频输入输出。这使得用来回放/监听的线路完全的停止并释放那个线路占用的资源。你可以在除方法stop以外的其他地方停止一个音源,就像你在为groove配置一个新的播放器之前你必须呼叫close。如果你不这么做,上一个播放器的线路仍将继续执行而且你没有办法使其停止。

Minim:层级

Minim主要基于两个类建立:Controller 和 AudioSource,并由四个interface(界面)定义:Playable, Effectable, Polyphonic, 和 Recordable。在Java中,一个界面被定义为一个仅含函数定义的类。即,它仅含有一堆功能而并不实际执行其中任一。

在JavaSound里,当一个音频在你的系统和软件间传送时,他都会经过一条Line(线),这是JavaSound API里定义的一个界面。一条线可以控制例如声相、音量和平衡等,这些控制可以实时改变声音。你可以经由基础类Controller、AudioSnippet以及源自Controller的AudioSource来获取这些控制。

AudioSource定义了三个AudioBuffer:left, right, 和 mix。这三个采样缓冲器(一个采样缓冲器就是一个浮动数组)包含左通道、右通道和左右混合通道。AudioPlayer, AudioOutput, AudioInput, 和 AudioSample都来自AudioSource,这意味着它们都像数据成员般继承这些缓冲器,同时意味着它们都能提供进入采样的通路。

AudioSource同样执行四个界面中的两个:Recordable(可录制) 和 Effectable(可加效果)。因此,上述四个来自AudioSource的类同样可以Recordable 和 Effectable。

最后,因为方法遗传的层叠性,这四个来自AudioSource的类同样继承Controller。这么想,比如说:AudioPlayer是一个AudioSource同时也是一个Controller。因此,你能用AudioSource或Controller做的所有事,同样可以用AudioPlayer来完成。

Minim:Minim

[ javadoc | 范例 ]

Minim是你正在阅读的音频库的名字。Minim是在能提供一堆方法(method)给你获取系统音频资源的库里的一个类。要使用它,你必须在setup()之前申明一个Minm的变量,而后例示一个Minim对象。为了使Minm能够辨识一些东西并能够进入你sketch的data文件夹,你必须这么做。

示范代码

import ddf.minim.*;

Minim minim;

void setup()
{
  size(100, 100);

  minim = new Minim(this);
}

void draw()
{
}

获取Minim的音频资源

你可以用Minim做四件事:播放一个音频文件,播放合成音频,监听音频输入,向硬盘内录制音频。所有这些都是由库内不同的类操作的,这些类的范例通过呼叫Minim适当的方法而获得。

载入一个音频文件

有三个不同的类可用来播放一个音频文件,每种都用来配合特定的回放类型。接下来的课程会详尽的讲解每个类,现在你只需要知道他们是:AudioSnippet, AudioSample, 和 AudioPlayer。你通过呼叫如下Minim方法创建它们:

loadSnippet(String filename)
loadSample(String filename)
loadSample(String filename, int bufferSize)
loadFile(String filename)
loadFile(String filename, int bufferSize)

loadSnippet 返回 AudioSnippet,loadSample 返回 AudioSample, loadFile 返回 AudioPlayer。在任何情况下,filename(文件名)可以是你sketch里data文件夹下的音频文件的绝对路径,也可以是网路上的音频文件链接。bufferSize是你希望的缓冲器大小,这个数字越小,音频的延迟越少。

示范代码

import ddf.minim.*;

Minim minim;
AudioPlayer in;

void setup()
{
  size(512, 200);
  // 例示一个Minim对象
  minim = new Minim(this);
  // 载入一个文件,默认采样率为1024
  in = minim.loadFile("music.mp3");
  // 播放这个文件
  in.play();
}

void draw()
{
  // 做爱做的事
}

void stop()
{
  // 别忘记关闭音频输入输出类
  in.close();
  // 别忘记停止你的Minim对象
  minim.stop();

  super.stop();
}

获得一个AudioOutput(音频输出)

Minim为回放合成音频提供AudioOutput类。这是实时经由你程序创建并反馈到扬声器的音频。AudioOutput支持单声道及立体声,并且可指定你需要的合成类型。方法如下:

getLineOut()
getLineOut(int type)
getLineOut(int type, int bufferSize)
getLineOut(int type, int bufferSize, float sampleRate)
getLineOut(int type, int bufferSize, float sampleRate, int bitDepth)

type指出你想要单声道还是立体声。你应当使用Minim.MONO或Minim.STEREO作为这个引数的值,这个引数的默认值为Minim.STEREO。bufferSize如上,即你缓冲器的大小。sampleRate是你合成音频的采样率,默认值为44100(CD音频质量)。bitDepth是你合成音频的比特率,目前仅支持8和16两个值,默认值为16(CD音频质量)。注意有时你填入的值将超出你的设备允许范围,这时,这个方法将会返回null,发生这种情况并不意味着你完全无法获得一个输出,而只是表明你想要的是一个无法实现的配置。

示范代码

import ddf.minim.*;
import ddf.minim.signals.*;

Minim minim;
AudioOutput out;
SineWave sine;

void setup()
{
  size(512, 200);
  // 例示一个Minim对象
  minim = new Minim(this);
  // 通过Minim获得一个线性输出, 
  // 默认采样率为44100,采样深度16
  out = minim.getLineOut(Minim.STEREO, 512);
  // 创建一个振荡器, 频率440 Hz, 振幅0.5, 
  // 采样率44100以符合线性输出
  sine = new SineWave(440, 0.5, 44100);
  // 将振荡器加到输出上
  out.addSignal(sine);
}

void draw()
{
  // 做爱做的事
}

void stop()
{
  // always closes audio I/O classes
  out.close();
  // always stop your Minim object
  minim.stop();

  super.stop();
}

获得一个AudioInput(音频输入)

Minim为监听用户当前录音源提供了AudioInput类。目前我们无法指定这个输入的来源,比如麦克或线性输入。

getLineIn()
getLineIn(int type)
getLineIn(int type, int bufferSize)
getLineIn(int type, int bufferSize, float sampleRate)
getLineIn(int type, int bufferSize, float sampleRate, int bitDepth)

type指出你想要单声道还是立体声。你应当使用Minim.MONO或Minim.STEREO作为这个引数的值,这个引数的默认值为 Minim.STEREO。bufferSize如上,即你缓冲器的大小。sampleRate是你合成音频的采样率,默认值为44100(CD音频质 量)。bitDepth是你合成音频的比特率,目前仅支持8和16两个值,默认值为16(CD音频质量)。注意有时你填入的值将超出你的设备允许范围,这 时,这个方法将会返回null,发生这种情况并不意味着你完全无法获得一个输出,而只是表明你想要的是一个无法实现的配置。

示范代码

import ddf.minim.*;

Minim minim;
AudioInput in;

void setup()
{
  size(512, 200);
  // instatiate a Minim object
  minim = new Minim(this);
  // get a line out from Minim, 
  // default sample rate is 44100, bit depth is 16
  in = minim.getLineIn(Minim.STEREO, 512);
}

void draw()
{
  // do whatever you are going to do
}

void stop()
{
  // 别忘记关闭音频I/O类
  in.close();
  // 别忘记停止你的Minim对象
  minim.stop();

  super.stop();
}

创建一个AudioRecorder(音频录制机)

Minim为录制音频数据到硬盘提供AudioRecorder类。细节的讨论将后续说明,以下是获取一个AudioRecorder的方法:

createRecorder(Recordable source, String filename, boolean buffered)

source是一个你希望作为录音源的可记录对象。filename是要保存的音频文件的名字,包括扩展名。buffered表明你希望或不希望在录音过程中使用缓冲器。

示范代码

import ddf.minim.*;

Minim minim;
AudioInput in;
AudioRecorder fout;

void setup()
{
  size(512, 400);

  minim = new Minim(this);

  // 获得一个立体声线性输入: 缓冲器512
  in = minim.getLineIn(Minim.STEREO, 512);
  // 获取一个录音机 
  // 并使用缓冲录音的方式将声音录入指定文件名及类型的文件
  fout = minim.createRecorder(in, "myrecording.wav", true);
}

void draw()
{
  // 做爱做的事
}

void keyReleased()
{
  // 可以设置控制录音起始的键盘按键
  // 然后存至硬盘
}

void stop()
{
  // always close audio I/O classes
  in.close();
  // always stop your Minim object
  minim.stop();

  super.stop();
}

设置系统混音器(Mixer)

Javasound(Java声音)通过使用一个叫做Mixer(混音器)的对象提供进入电脑系统特定输入、输出口的能力。你可以在创建资源之前设置输入输出混音器来指定特定的输入或输出。AudioPlayer, AudioSnippet, AudioSample, 和 AudioOuput都使用输出混音器来获得音频输出。AudioInput则使用输入混音器。例如,如果你有一块多通道声卡,你可以为你的音频输出指定它特定的输出口。

示范代码在线示例

import ddf.minim.*;
// 需要SineWave信号包
import ddf.minim.signals.*;
import controlP5.*;
// 为使用Mixer 和 Mixer.Info对象,我们需要导入这个
import javax.sound.sampled.*;

Minim minim;
AudioOutput out;
// 一个展示整个音频系统混音器的info对象的数组。
// 我们用它组装我们的混音器下拉菜单,并让用户选择想要的混音器。
Mixer.Info[] mixerInfo;

// 给我们输出的一个信号
SineWave sine;

ControlP5 gui;

void setup()
{
  size(512, 275);

  minim = new Minim(this);
  gui = new ControlP5(this);

  ScrollList mixers = gui.addScrollList("Mixers", 10, 10, 475, 280);
  mixers.setLabel("Choose A Mixer");

  mixerInfo = AudioSystem.getMixerInfo();

  for(int i = 0; i < mixerInfo.length; i++)
  {
    controlP5.Button b = mixers.addItem("item"+i, i);
    b.setLabel(mixerInfo[i].getName());
  }

  sine = new SineWave(220, 0.3, 44100);

}

void draw()
{
  background(0);

  //gui.draw();

  if ( out != null )
  {
    stroke(255);
    // 绘制波形
    for(int i = 0; i < out.bufferSize() - 1; i++)
    {
      line(i, 50 + out.left.get(i)*50, i+1, 50 + out.left.get(i+1)*50);
      line(i, 150 + out.right.get(i)*50, i+1, 150 + out.right.get(i+1)*50);
    }
  }
}

public void controlEvent(ControlEvent theEvent)
{
  int mixerIndex = (int)theEvent.controller().value();

  println("User chose " + theEvent.controller().label());
  println("Using mixer info " + mixerInfo[mixerIndex].getName());

  Mixer mixer = AudioSystem.getMixer(mixerInfo[mixerIndex]);

  minim.setOutputMixer(mixer);

  if ( out != null )
  {
    out.close();
  }

  out = minim.getLineOut(Minim.STEREO);

  if ( out != null )
  {
    out.addSignal(sine);
  }
}

void stop()
{
  // 完事后记得关闭Minim音频类
  if ( out != null )
  {
    out.close();
  }
  minim.stop();

  super.stop();
}

同样的,你可以指定你声卡特定的输入:

示范代码在线示例

import ddf.minim.*;
import controlP5.*;
// need to import this so we can use Mixer and Mixer.Info objects
import javax.sound.sampled.*;

Minim minim;
AudioInput in;
// an array of info objects describing all of 
// the mixers the AudioSystem has. we'll use
// this to populate our gui scroll list and
// also to obtain an actual Mixer when the
// user clicks on an item in the list.
Mixer.Info[] mixerInfo;

ControlP5 gui;

void setup()
{
  size(512, 275);

  minim = new Minim(this);
  gui = new ControlP5(this);

  ScrollList mixers = gui.addScrollList("Mixers", 10, 10, 475, 280);
  mixers.setLabel("Choose A Mixer");

  mixerInfo = AudioSystem.getMixerInfo();

  for(int i = 0; i < mixerInfo.length; i++)
  {
    controlP5.Button b = mixers.addItem("item"+i, i);
    b.setLabel(mixerInfo[i].getName());
  }

}

void draw()
{
  background(0);

  //gui.draw();

  if ( in != null )
  {
    stroke(255);
    // draw the waveforms
    for(int i = 0; i < in.bufferSize() - 1; i++)
    {
      line(i, 50 + in.left.get(i)*50, i+1, 50 + in.left.get(i+1)*50);
      line(i, 150 + in.right.get(i)*50, i+1, 150 + in.right.get(i+1)*50);
    }
  }
}

public void controlEvent(ControlEvent theEvent)
{
  int mixerIndex = (int)theEvent.controller().value();

  println("User chose " + theEvent.controller().label());
  println("Using mixer info " + mixerInfo[mixerIndex].getName());

  Mixer mixer = AudioSystem.getMixer(mixerInfo[mixerIndex]);

  if ( in != null )
  {
    in.close();
  }

  minim.setInputMixer(mixer);

  in = minim.getLineIn(Minim.STEREO);

}

void stop()
{
  // always close Minim audio classes when you are done with them
  if ( in != null )
  {
    in.close();
  }
  minim.stop();

  super.stop();
}

Minim:快速入门指南

欲开始使用Minim,首先你得建立一个Minim对象,这样你便能载入音频文件或者建立声音输入与输出。而后,在你退出程序前,你必须关闭你从Minim获得的所有I/O类然后关闭你的Minim范本。音频输入/输出类包括AudioPlayer, AudioSample, AudioSnippet, AudioInput, 和 AudioOutput。达到这个目的的一个很好的方式就是在你关闭所有音频类的位置定义一个停止(stop)方法(method)。以下是以上几点的一个范例(部分):

Minim minim;
AudioPlayer player;
AudioInput input;

void setup()
{
  size(100, 100);

  minim = new Minim(this);
  player = minim.loadFile("song.mp3");
  input = minim.getLineIn();
}

void draw()
{
  // 做你想做的
}

void stop()
{
  // 你从Minim.loadFile()获得的音频播放器
  player.close();
  // 你从Minim.getLineIn()获得的音频输入
  input.close();
  minim.stop();

  // 这些称作停止方法(stop method) 
  // you are overriding by defining your own
  // 它必须被呼叫用以完成你的程常规的清理工作 
  super.stop();
}

Ess及Sonia的用户应当对这些协定非常熟悉。做所有这些是因为全部音频输入/输出都在独立的线路工作,并且他们必须被允许以常规的方式结束。你同样可以在执行你程序的时候关闭一个音频类以释放它的资源。

Continue reading Minim:快速入门指南