用JS生成声音,实现钢琴演奏

基于WebAudio API来创建合成音乐效果

看演示效果
废话不多说,直接上代码

第一步:创建一个蜂鸣器,就是喇叭,能响就行

var BEEP = {
   

	VERSION: 3,

	//  Chrome, Opera, and Firefox already provide AudioContext()
	//  but Safari instead provides webkitAudioContext().
	//  Let’s just standardize this for our own sanity right here:
	AudioContext: window.AudioContext ? window.AudioContext : window.webkitAudioContext,

	//  We’ll keep track of Voices, Triggers, and Instruments
	// (but not Notes because we just throw those away left and right)
	//  so we can access and/or destroy them later even if unnamed.
	voices:      [],
	triggers:    [],
	instruments: [],

	//  Once the “DOM Content Loaded” event fires we’ll come back
	//  and set this to either an existing DOM Element with a #beep ID
	//  or create a new element and append it to the Body.
	domContainer: null,

	//  Destroy everything. EVERYTHING. DO IT. DO IT NOW.
	destroy: function(){
   
		while( this.voices.length ){
   
			this.voices.pop().destroy()
		}
		while( this.triggers.length ){
   
			this.triggers.pop().destroy()
		}
		while( this.instruments.length ){
   
			this.instruments.pop().destroy()
		}
	},

	//  Create a new Instrument from the code
	//  currently in the code editor.
	eval: function(){
   
		var code = document.getElementById( 'editor' ).value
		try {
   
			eval( code )	
		}
		catch( e ){
   
			console.log( 'OMFG', e )
		}
	},

	//  Right now just runs BEEP.eval() but in the near future
	//  we might have some more tricks up our sleeve...
	boot: function(){
   
		this.eval()
	},

	//  We need to tear everything down.
	//  Then build it right back up.
	reset: function(){
   
		this.destroy()
		this.boot()
	}
}

//  We might as well create an instance of AudioContext
//  that we can re-use over and over if necessary.
//  Why? Because the number of AudioContext instance is
//  limited by hardware. For example, my personal laptop
//  can only handle 6 of them at once!
BEEP.audioContext = new BEEP.AudioContext()

//  Once our DOM Content is ready for action
//  it’s time to GIVE IT ACTION. W000000000000T !
document.addEventListener( 'DOMContentLoaded', function(){
   
	BEEP.domContainer = document.getElementById( 'beep' )
	BEEP.boot()
})

第二步:根据音调的不同,创建所需的音符


BEEP.Note = function( params ){
   
	var that = this
 	if( typeof params === 'number' ) this.hertz = params
 	else if( typeof params === 'object' && params.hertz !== undefined ){
   
		Object.keys( params ).forEach( function( key ){
   
			that[ key ] = params[ key ]
		})
	}
	else return BEEP.Note.EDO12( params )
}

//  Common Western music has 12 notes per octave,
//  lettered A through G with modifier symbols for sharps and flats.
//  Let’s build a validator for Western music:
BEEP.Note.validateWestern = function( params ){
   
	var 
	NAMES   = [ 'A♭', 'A♮', 'B♭', 'B♮', 'C♮', 'C♯', 'D♮', 'E♭', 'E♮', 'F♮', 'F♯', 'G♮' ],
	LETTERS = 'ABCDEFG',
	SHARPS  = 'CF',
	FLATS   = 'EAB',
	temp

	if( typeof params === 'undefined' ) params = {
   }
	else if( typeof params === 'string' ){
   
		temp = params
		params = {
   }
		temp.split( '' ).forEach( function( p, i ){
   
			if( +p + '' !== 'NaN' ) params.octaveIndex = +p
			else if( '♭♮♯#'.indexOf( p ) !== -1 ){
   
				params.modifier = p
			}
			else if(( LETTERS + 'H' ).indexOf( p.toUpperCase() ) !== -1 ){
   
				if( p.toUpperCase() === 'H' ) params.letter = 'B'
				else if( p === 'b' && i > 0 ) params.modifier = '♭'
				else params.letter = p.toUpperCase()
			}
		})
	}

	//  What octave is this?
	if( params.octaveIndex === undefined 
		|| params.octaveIndex === ''
		|| +params.octaveIndex +'' === 'NaN' ) params.octaveIndex = 4
	params.octaveIndex = +params.octaveIndex
	if( params.octaveIndex < 0 ) params.octaveIndex = 0
	else if( params.octaveIndex > 7 ) params.octaveIndex = 7

	//  What’s this Note’s name?
	if( params.letter === undefined ) params.letter = 'A'
	params.letterIndex = LETTERS.indexOf( params.letter )
	if( params.modifier === undefined ) params.modifier = '♮'
	if( params.A === undefined ) params.A = 440.00

	//  Force the correct accidental symbols.
	if( params.modifier === 'b' ) params.modifier = '♭'
	if( params.modifier === '#' ) params.modifier = '♯'
	
	//  Handy function for redefining the letter
	//  when the letterIndex may have shifted.
	function setLetterByLetterIndex( params ){
   
		if( params.letterIndex < 0 ){
   
			params.letterIndex += LETTERS.length
			params.octaveIndex --
		}
		if( params.letterIndex >= LETTERS.length ){
   
			params.letterIndex -= LETTERS.length
			//  Next line commented out but left in as a reminder
			//  that it would cause G♯ conversion to A♭
			//  to jump up an entire octave for no good reason!
			//params.octaveIndex ++
		}
		params.letter = LETTERS.substr( params.letterIndex, 1 )
		return params
	}

	//  Force the correct sharp / flat categorization.
	//  Why does the Equal Temperament scale consider certain letters flat or sharp
	//  when they are mathematically equal?!
	//  Has to do with the delta between Equal Temperament and the Just Scale.
	//  Where Equal Temperament comes in higher than Just we call it sharp,
	//  and where it comes in lower than Just we call it flat:
	//  http://www.phy.mtu.edu/~suits/scales.html
	if( params.modifier === '♭' && FLATS.indexOf( params.letter ) === -1 ){
   
		params.letterIndex = LETTERS.indexOf( params.letter ) - 1
		params = setLetterByLetterIndex( params )
		if( SHARPS.indexOf( params.letter ) > -1 ) params.modifier = '♯'
		else params.modifier = '♮'
	}
	else if( params.modifier === '♯' && SHARPS.indexOf( params.letter ) === -1 ){
   
		params.letterIndex = LETTERS.indexOf( params.letter ) + 1
		params = setLetterByLetterIndex( params )
		if( FLATS.indexOf( params.letter ) > -1 ) params.modifier = '♭'
		else params.modifier = '♮'
	}
	
	//  Now that we’re certain the modifier is correct
	//  we can set convenience booleans.
	if( params.modifier === '♯' ) params.isSharp = true
	else if( params.modifier === '♭' ) params.isFlat = true
	else params.isNatural = true
	
	//  A final cleanse. Should test if this is still necessary...
	params = setLetterByLetterIndex( params )

	//  Penultimate bits...	
	params.name = params.letter + params.modifier
	params.nameSimple = params.letter
	if( params.modifier !== '♮' ) params.nameSimple += params.modifier
	params.nameIndex = NAMES.indexOf( params.name )
	params.pianoKeyIndex = params.octaveIndex * 12 + params.nameIndex
	if( params.nameIndex > 3 ) params.pianoKeyIndex -= 12

	//  What tuning method are we supposed to use? 
	if( params.tuning === undefined ) params.tuning = 'EDO12'
	
	//  We now have the majority of the Note ready for use.
	//  Everything except for ... the FREQUENCY of the Note!
	//  That will be decided based on the tuning method.
	return params
}

//  Does exactly what it says on the tin, man.
BEEP.Note.EDO12 = function( params ){
   
	params = BEEP.Note.validateWestern( params )
	params.hertz = params.A * Math.pow( Math.pow( 2, 1 / 12 ), params.pianoKeyIndex - 49 )
	params.tuning = 'EDO12'
	return new BEEP.Note( params )
}

//  The most mathematically beautiful tuning,
//  makes for sonically gorgeous experiences
//  ... Until you change keys!
BEEP.Note.JustIntonation = function( params, key ){
   
	var 
	that = this,
	relationshipIndex

	params = BEEP.Note.validateWestern( params )
	params.tuning = 'JustIntonation'
	params.key = new BEEP.Note.EDO12( key )

	//  This is Ptolemy’s “Intense Diatonic Scale” which is based on 
	//  Pythagorean tuning. It is but one example of Just Intonation.
	relationshipIndex = ( params.nameIndex - params.key.nameIndex ) % 12
	if( relationshipIndex < 0 ) relationshipIndex += 12
	params.hertz = [
		params.key.hertz,          //  Do  UNISON
		params.key.hertz * 16 / 15,//      minor     2nd
		params.key.hertz *  9 /  8,//  Re  MAJOR     2nd
		params.key.hertz *  6 /  5,//      minor     3rd
		params.key.hertz *  5 /  4,//  Mi  MAJOR     3rd
		params.key.hertz *  4 /  3,//  Fa  PERFECT   4th
		params.key.hertz * 45 / 32,//      augmented 4th
		params.key.hertz *  3 /  2,//  So  PERFECT   5th
		params.key.hertz *  8 /  5,//      minor     6th
		params.key.hertz *  5 /  3,//  La  MAJOR     6th
		params.key.hertz * 16 /  9,//      minor     7th (HD, baby!)
		params.key.hertz * 15 /  8,//  Ti  MAJOR     7th
		params.key.hertz *  2      //  Do  OCTAVE
	][ relationshipIndex ]

	//  If the key’s octave and our desired note’s octave were equal
	//  then we’d be done. Otherwise we’ve got to bump up or down our 
	//  note by whole octaves.
	params.hertz = params.hertz * Math.pow( 2, params.octaveIndex - params.key.octaveIndex )
	return new BEEP.Note( params )
}

第三步:设置高低音

BEEP.Voice = function( a, b ){
   
	//  Remember the ? will be validated by Note()
	//  so it could be an Object, String, Number, etc.
	//
	//      ( AudioContext, Note )
	//      ( AudioContext, ?    )
	//      ( GainNode,     Note )
	//      ( GainNode,     ?    )
	if( a instanceof BEEP.AudioContext || a instanceof GainNode ){
   
		if( a instanceof BEEP.AudioContext ){
   
			this.audioContext = a
			this.destination  = a.destination
		}
		else if( a instanceof GainNode ){
   
			this.audioContext = a.audioContext
			this.destination  = a
		}
		if( b instanceof BEEP.Note ) this.note = b
		else this.note = new BEEP.Note( b )//  Still ok if b === undefined.
	}

	//  Again, the ? will be validated by Note()
	//  so it could be an Object, String, Number, etc.
	//
	//      ( Note               )
	//      ( Note, AudioContext )
	//      ( Note, GainNode     )
	//      ( ?                  )
	//      ( ?,    AudioContext )
	//      ( ?,    GainNode     )
	else {
   
		if( a instanceof BEEP.Note ) this.note = a
		else this.note = new BEEP.Note( a )//  Still ok if a === undefined.
		if(  b instanceof BEEP.AudioContext ){
   
			this.audioContext = b
			this.destination  = b.destination
		}
		else if( b instanceof GainNode ){
   
			this.audioContext = b.audioContext
			this.destination  = b
		}
		else {
   
			this.audioContext = BEEP.audioContext
			this.destination  = this.audioContext.destination
		}
	}

	//  Create a Gain Node
	//  for turning this voice up and down.
	this.gainNode = this.audioContext.createGain()
	this.gainNode.gain.value = 0
	this.gainNode.connect( this.destination )
	this.gainHigh = this.note.gainHigh !== undefined ? this.note.gainHigh : 1

	//  Create an Oscillator
	//  for generating the sound.
	this.oscillator = this.audioContext.createOscillator()
	this.oscillator.connect( this.gainNode )
	this.oscillator.type = 'sine'
	this.oscillator.frequency.value = this.note.hertz

	//  Right now these do nothing; just here as a stand-in for the future.
	this.duration = Infinity
	this.attack   = 0
	this.decay    = 0
	this.sustain  = 0

	//  Because of “iOS reasons” we cannot begin playing a sound
	//  until the user has tripped an event.
	//  So we’ll use this boolean to trip this.oscillator.start(0)
	//  on the first use of this Voice instance.
	this.isPlaying = false

	//  Push a reference of this instance into BEEP’s library
	//  so we can access and/or destroy it later.
	BEEP.voices.push( this )
}

//  Voices are *always* emitting, so “playing” a Note
//  is really a matter of turning its amplitude up.
BEEP.Voice.prototype.play = function( params ){
   

	//  Let’s create that Note.
	//  The params will specify a frequency assignment method to use
	//  otherwise Note() will pick a default.
	if( params !== undefined ) this.note = new BEEP.Note( params )
	this.oscillator.frequency.value = this.note.hertz
	this.gainNode.gain.value = this.gainHigh || params.gainHigh || 1

	//  Oh, iOS. This “wait to play” shtick is for you.
	if( this.isPlaying === false ){
   
		this.isPlaying = true
		this.oscillator.start( 0 )
	}
	return this
}

BEEP.Voice.prototype.pause 
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值