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

继续阅读“Minim:快速入门指南”

Minim:关于

Minim是一个为在Proessing环境下工作的人们提供的易用的音频库(library),它基于JavaSound API、一点点Tritonus和Javazoom的MP3SPI开发。掩藏在其API之后的哲学是尽可能简单的将音频互动加入至你的sketch内,同时也为高级用户提供足够多的弹性。你无需做回调(callback)甚至无需直接操作采样数组(sample arrays),所有脏活累活都为你做好了。下载提供两种口味:1)仅仅是运行Minim必须的jar文件,2)包括javadocs、例子以及源码的完全版。Minim经LGPL许可发布。

安装:解压zip档到你sketchbook文件夹(在偏好设置里指定)的libraries文件夹内(如果不存在,则手动创建),承载它的文件夹应命名为minim(Processing现已自带Minim库)。

以下为Minim的一些特性:

  • 音频播放器(AudioPlayer):WAV, AIFF, AU, SND, 和 MP3文件的单声道或立体声回放。
  • 音频meta数据(AudioMetaData):一个装载文件meta数据的对象,例如ID3标签。
  • 音频录音机(AudioRecorder):经缓冲或直接将单声道或立体声音频录入硬盘。
  • 音频输入(AudioInput):单声道或立体声输入监听器。
  • 音频输出(AudioOutput):单声道或立体声合成。
  • 音频信号(AudioSignal):一个提供你写自己的声音合成类的简单界面。
  • 配备所有标准波形,粉噪音及白噪音生成器。此外,你还可以通过简单执行你自己的周期波形来扩展振荡器类。
  • 音频效果(AudioEffect):一个提供你写自己的音频效果的简单界面。
  • 配备低通(low pass)、高通(high pass)、带通(band pass)、陷波(notch)滤波器。此外,你还可以通过对你自己的IIR滤波器的简单执行来扩充IIRFilter类。
  • 可以方便的将信号和效果加于音频输入和输出上。
  • 提供一个可做频谱分析的FFT类。
  • 提供一个可做拍子侦测的BeatDetect类。

计划中的特性:

  • 更多的音频效果,如混响、延迟、移相、回响调制以及一点点破音。
  • 使用频主体过滤技术提供更好的滤波效果(frequency domain filtering techniques)。

要开始使用Minim,你可以进入下一课的学习,或去日它的javadocs。如果有任何问题或者发现任何bug,可以通过作者主页以及p5论坛联系他。

Processing:数组(下)

12. ok,现在来做100辆车吧:

pr9-3
pr9-3

Car[] cars = new Car[100]; // 由100个cars对象组成的数组!

void setup() {
size(200,200);
smooth();
for (int i = 0; i < cars.length; i ++ ) { // 用for循环初始化100辆车.
cars[i] = new Car(color(i*2),0,i*2,i/20.0);
}
}

void draw() {
background(255);
for (int i = 0; i < cars.length; i ++ ) { // 用for循环跑每一辆车.
cars[i].move();
cars[i].display();
}
}

当然,你的Car类还是放在一边,不需要改动任何参数,无论我们只跑1辆车,还是同时跑100辆,甚至1000辆车。

13. 当你设计你的类的时候,运用布尔变量来变换一个对象的属性是很方便的。例如,一个Car对象是跑还是不跑,Zoog可能高兴或不高兴。

pr9-4
pr9-4

14. 好的,来运用上边说的,继续来看超长晕眩代码。。。
// 一个stripes的数组
Stripe[] stripes = new Stripe[70];

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

// Initialize all Stripe objects
for (int i = 0; i < stripes.length; i ++ ) {
stripes[i] = new Stripe();
}
}

void draw() {

background(30);
// 移动并显示所有stripes数组
for (int i = 0; i < stripes.length; i ++ ) { // 检查鼠标是否位于条纹上 stripes[i].rollover(mouseX,mouseY); // 将鼠标坐标传递进入一个对象
stripes[i].move(); stripes[i].display();
}
}

class Stripe {
float x; // 条纹的水平坐标值
float speed; // 条纹的移动速度
float w; // 条纹的宽度
// 一个布尔变量持续捕捉对象的状态.
boolean mouse; // 条纹的状态(鼠标是否位于条纹上?)

Stripe() {
// 所有条纹始于0
x = 0;
// 所有条纹都有一个随机的正向移动速度
speed = random(1);
w = random(10,30);
mouse = false;
}

// 绘制条纹
void display() {
// 布尔变量决定条纹颜色
if (mouse) {
fill(255);
} else {
fill(255-x,100+w,x);
}

noStroke();
rect(x,0,w,height);
}

// 移动条纹
void move() {
x += speed;
if (x > width + 20)
x = -20;
}

// 检查点(mx,my)是否位于条纹内.
void rollover(int mx, int my) {
// 条纹左沿是x, 右沿是x + w
if (mx > x && mx < x + w) {
mouse = true;
} else {
mouse = false;
}
}
}

pr9-5
pr9-5

15. 数组有一些函数可用于控制它的大小,它们是:horten( ), concat( ), subset( ), append( ), splice( ), expand( ) 。此外还有可以改变数组顺序的函数,比如sort( )reverse( ) 。Daniel这里偷懒了,告诉我们这些所有的函数都能在pr的参考里找到(废话。。),然后举了这么一个例子(使用append( )增大数组):
Ball[] balls = new Ball[1]; // 我们从仅有一个元素的数组开始.
float gravity = 0.1;

void setup() {
size(200,200);
smooth();

// 初始化0号球
balls[0] = new Ball(50,0,16);
}

void draw() {
background(255);

// 更新并显示所有的球
for (int i = 0; i < balls.length; i ++ ) {
// 无论数组有多长,更新并显示所有的元素.
balls[i].gravity();
balls[i].move();
balls[i].display();
}
}

void mousePressed() {
// 一个新的ball对象
Ball b = new Ball(mouseX,mouseY,random(10,30)); // 在鼠标位置制造一个新对象.
balls = (Ball[]) append(balls,b);
// 这里,函数append() 在数组的末尾增加了一个元素.
// append() 使用了两个引数. 第一个是你希望附加的数组, 第二是你希望附加的东西.
// 你需要将append() 得到的结果重新分配给最初的数组.
// 另外, append()函数要求你通过向圆括号内填入数组数据类型((Ball[]))的方式再次清楚的表述数组里的数据类型。
//这一过程叫做投掷(casting)
}

class Ball {
float x;
float y;
float speed;
float w;

Ball(float tempX, float tempY, float tempW) {
x = tempX;
y = tempY;
w = tempW;
speed = 0;
}

void gravity() {
// 重力加速度
speed = speed + gravity;
}

void move() {
// 将速度加到y轴
y = y + speed;
// 如果小球到底
// 将速度反向
if (y > height) {
speed = speed * -0.95;
y = height;
}
}

void display() {
// 显示球
fill(random(255));
stroke(0);
ellipse(x,y,w,w);
}
}