updating sample player, does not rely on Piano.js and using FluidR3 soundfont for AI sound
This commit is contained in:
parent
7249b3eac7
commit
a0d4af1092
BIN
static/audio/string_ensemble/A0.mp3
Executable file
BIN
static/audio/string_ensemble/A0.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/A1.mp3
Executable file
BIN
static/audio/string_ensemble/A1.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/A2.mp3
Executable file
BIN
static/audio/string_ensemble/A2.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/A3.mp3
Executable file
BIN
static/audio/string_ensemble/A3.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/A4.mp3
Executable file
BIN
static/audio/string_ensemble/A4.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/A5.mp3
Executable file
BIN
static/audio/string_ensemble/A5.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/A6.mp3
Executable file
BIN
static/audio/string_ensemble/A6.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/A7.mp3
Executable file
BIN
static/audio/string_ensemble/A7.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/C1.mp3
Executable file
BIN
static/audio/string_ensemble/C1.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/C2.mp3
Executable file
BIN
static/audio/string_ensemble/C2.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/C3.mp3
Executable file
BIN
static/audio/string_ensemble/C3.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/C4.mp3
Executable file
BIN
static/audio/string_ensemble/C4.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/C5.mp3
Executable file
BIN
static/audio/string_ensemble/C5.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/C6.mp3
Executable file
BIN
static/audio/string_ensemble/C6.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/C7.mp3
Executable file
BIN
static/audio/string_ensemble/C7.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/C8.mp3
Executable file
BIN
static/audio/string_ensemble/C8.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Ds1.mp3
Executable file
BIN
static/audio/string_ensemble/Ds1.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Ds2.mp3
Executable file
BIN
static/audio/string_ensemble/Ds2.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Ds3.mp3
Executable file
BIN
static/audio/string_ensemble/Ds3.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Ds4.mp3
Executable file
BIN
static/audio/string_ensemble/Ds4.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Ds5.mp3
Executable file
BIN
static/audio/string_ensemble/Ds5.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Ds6.mp3
Executable file
BIN
static/audio/string_ensemble/Ds6.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Ds7.mp3
Executable file
BIN
static/audio/string_ensemble/Ds7.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Fs1.mp3
Executable file
BIN
static/audio/string_ensemble/Fs1.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Fs2.mp3
Executable file
BIN
static/audio/string_ensemble/Fs2.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Fs3.mp3
Executable file
BIN
static/audio/string_ensemble/Fs3.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Fs4.mp3
Executable file
BIN
static/audio/string_ensemble/Fs4.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Fs5.mp3
Executable file
BIN
static/audio/string_ensemble/Fs5.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Fs6.mp3
Executable file
BIN
static/audio/string_ensemble/Fs6.mp3
Executable file
Binary file not shown.
BIN
static/audio/string_ensemble/Fs7.mp3
Executable file
BIN
static/audio/string_ensemble/Fs7.mp3
Executable file
Binary file not shown.
5
static/audio/string_ensemble_README
Normal file
5
static/audio/string_ensemble_README
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
- Fluid-Soundfont
|
||||||
|
- Generated from [FluidR3_GM.sf2](http://www.musescore.org/download/fluid-soundfont.tar.gz) (141 MB uncompressed)
|
||||||
|
- Released under [Creative Commons Attribution 3.0 license](http://creativecommons.org/licenses/by/3.0/us/)
|
||||||
|
- Instrument names as .json file [here](http://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/names.json)
|
||||||
|
- URL prefix to fetch files: http://gleitz.github.io/midi-js-soundfonts/FluidR3_GM/
|
88
static/src/sound/Sampler.js
Normal file
88
static/src/sound/Sampler.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Frequency from 'Tone/type/Frequency'
|
||||||
|
import Buffers from 'Tone/core/Buffers'
|
||||||
|
import MultiPlayer from 'Tone/source/MultiPlayer'
|
||||||
|
import Tone from 'Tone/core/Tone'
|
||||||
|
import AudioBuffer from 'Tone/core/Buffer'
|
||||||
|
|
||||||
|
class Sampler{
|
||||||
|
constructor(baseUrl='', range=[21, 108]){
|
||||||
|
|
||||||
|
//all the notes of the piano sampled every 3rd note
|
||||||
|
const notes = [21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108]
|
||||||
|
|
||||||
|
const lowerIndex = notes.findIndex((note) => note >= range[0])
|
||||||
|
let upperIndex = notes.findIndex((note) => note >= range[1])
|
||||||
|
upperIndex = upperIndex === -1 ? upperIndex = notes.length : upperIndex + 1
|
||||||
|
|
||||||
|
const slicedNotes = notes.slice(lowerIndex, upperIndex)
|
||||||
|
|
||||||
|
this._urls = {}
|
||||||
|
slicedNotes.forEach(note => {
|
||||||
|
this._urls[note - 1] = baseUrl + Frequency(note, 'midi').toNote().replace('#', 's') + '.mp3'
|
||||||
|
this._urls[note] = baseUrl + Frequency(note, 'midi').toNote().replace('#', 's') + '.mp3'
|
||||||
|
this._urls[note + 1] = baseUrl + Frequency(note, 'midi').toNote().replace('#', 's') + '.mp3'
|
||||||
|
})
|
||||||
|
this._player = null
|
||||||
|
|
||||||
|
this._loaded = false
|
||||||
|
AudioBuffer.on('load', () => {
|
||||||
|
this._loaded = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
load(){
|
||||||
|
return new Promise(done => {
|
||||||
|
this._player = new MultiPlayer(this._urls, done).toMaster()
|
||||||
|
this._player.fadeOut = 0.2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
set volume(vol){
|
||||||
|
if (this._loaded){
|
||||||
|
this._player.volume.value = vol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDown(note, time){
|
||||||
|
if (this._loaded){
|
||||||
|
let pitch = this._midiToFrequencyPitch(note)
|
||||||
|
const duration = this._player.buffers.get(note).duration * 0.95
|
||||||
|
this._player.start(note, time, 0, duration - this._player.fadeOut, pitch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUp(note, time){
|
||||||
|
if (this._loaded){
|
||||||
|
this._player.stop(note, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_midiToFrequencyPitch(midi){
|
||||||
|
let mod = midi % 3
|
||||||
|
if (mod === 1){
|
||||||
|
return 1
|
||||||
|
} else if (mod === 2){
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Sampler}
|
62
static/src/sound/Sound.js
Normal file
62
static/src/sound/Sound.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Tone from 'Tone/core/Tone'
|
||||||
|
import PolySynth from 'Tone/instrument/PolySynth'
|
||||||
|
import Frequency from 'Tone/type/Frequency'
|
||||||
|
import MonoSynth from 'Tone/instrument/MonoSynth'
|
||||||
|
import {Sampler} from 'sound/Sampler'
|
||||||
|
|
||||||
|
class Sound {
|
||||||
|
constructor(){
|
||||||
|
|
||||||
|
this._range = [24, 108]
|
||||||
|
|
||||||
|
this._piano = new Sampler('audio/Salamander/', this._range)
|
||||||
|
|
||||||
|
this._synth = new Sampler('audio/string_ensemble/', this._range)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
load(){
|
||||||
|
return Promise.all([this._piano.load(), this._synth.load()])
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDown(note, time=Tone.now(), ai=false){
|
||||||
|
|
||||||
|
if (note >= this._range[0] && note <= this._range[1]){
|
||||||
|
this._piano.keyDown(note, time)
|
||||||
|
if (ai){
|
||||||
|
this._synth.volume = -8
|
||||||
|
this._synth.keyDown(note, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUp(note, time=Tone.now(), ai=false){
|
||||||
|
if (note >= this._range[0] && note <= this._range[1]){
|
||||||
|
time += 0.05
|
||||||
|
this._piano.keyUp(note, time)
|
||||||
|
if (ai){
|
||||||
|
this._synth.keyUp(note, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Sound}
|
21
static/third_party/Piano/LICENSE.md
vendored
21
static/third_party/Piano/LICENSE.md
vendored
@ -1,21 +0,0 @@
|
|||||||
[The MIT License](http://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
Copyright © 2016 Yotam Mann
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
3
static/third_party/Piano/README.md
vendored
3
static/third_party/Piano/README.md
vendored
@ -1,3 +0,0 @@
|
|||||||
A [Multisampled](https://en.wikipedia.org/wiki/Sample-based_synthesis#Multisampling) Piano at 5 velocity levels across 88 keys (sampled every third note) of a Yamaha C5. The sounds are from [Salamander Grand Piano](https://archive.org/details/SalamanderGrandPianoV3).
|
|
||||||
|
|
||||||
See [Main.js](https://github.com/tambien/Piano/blob/master/Main.js) for an example of how to use the API with either a MIDI file or MIDI keyboard.
|
|
41
static/third_party/Piano/src/Harmonics.js
vendored
41
static/third_party/Piano/src/Harmonics.js
vendored
@ -1,41 +0,0 @@
|
|||||||
import Salamander from './Salamander'
|
|
||||||
import PianoBase from './PianoBase'
|
|
||||||
import {noteToMidi, createSource, midiToFrequencyRatio} from './Util'
|
|
||||||
import Buffers from 'Tone/core/Buffers'
|
|
||||||
|
|
||||||
// the harmonics notes that Salamander has
|
|
||||||
const harmonics = [21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87]
|
|
||||||
|
|
||||||
export default class Harmonics extends PianoBase {
|
|
||||||
|
|
||||||
constructor(range=[21, 108]){
|
|
||||||
super()
|
|
||||||
|
|
||||||
const lowerIndex = harmonics.findIndex((note) => note >= range[0])
|
|
||||||
let upperIndex = harmonics.findIndex((note) => note >= range[1])
|
|
||||||
upperIndex = upperIndex === -1 ? upperIndex = harmonics.length : upperIndex
|
|
||||||
|
|
||||||
const notes = harmonics.slice(lowerIndex, upperIndex)
|
|
||||||
|
|
||||||
this._buffers = {}
|
|
||||||
|
|
||||||
for (let n of notes){
|
|
||||||
this._buffers[n] = Salamander.getHarmonicsUrl(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
start(note, gain, time){
|
|
||||||
let [midi, ratio] = midiToFrequencyRatio(note)
|
|
||||||
if (this._buffers.has(midi)){
|
|
||||||
const source = createSource(this._buffers.get(midi)).connect(this.output)
|
|
||||||
source.playbackRate.value = ratio
|
|
||||||
source.start(time, 0, undefined, gain, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
load(baseUrl){
|
|
||||||
return new Promise((success, fail) => {
|
|
||||||
this._buffers = new Buffers(this._buffers, success, baseUrl)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
124
static/third_party/Piano/src/Note.js
vendored
124
static/third_party/Piano/src/Note.js
vendored
@ -1,124 +0,0 @@
|
|||||||
import Tone from 'Tone/core/Tone'
|
|
||||||
import Salamander from './Salamander'
|
|
||||||
import PianoBase from './PianoBase'
|
|
||||||
import {noteToMidi, createSource, midiToFrequencyRatio} from './Util'
|
|
||||||
import Buffers from 'Tone/core/Buffers'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal class
|
|
||||||
*/
|
|
||||||
class Note extends Tone{
|
|
||||||
constructor(time, source, velocity, gain){
|
|
||||||
super()
|
|
||||||
//round the velocity
|
|
||||||
this._velocity = velocity
|
|
||||||
this._startTime = time
|
|
||||||
|
|
||||||
this.output = source
|
|
||||||
this.output.start(time, 0, undefined, gain, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
stop(time){
|
|
||||||
if (this.output.buffer){
|
|
||||||
|
|
||||||
// return the amplitude of the damper playback
|
|
||||||
let progress = (time - this._startTime) / this.output.buffer.duration
|
|
||||||
progress = (1 - progress) * this._velocity
|
|
||||||
// stop the buffer
|
|
||||||
this.output.stop(time, 0.2)
|
|
||||||
|
|
||||||
return Math.pow(progress, 0.5)
|
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps velocity depths to Salamander velocities
|
|
||||||
*/
|
|
||||||
const velocitiesMap = {
|
|
||||||
1 : [8],
|
|
||||||
2 : [6, 12],
|
|
||||||
3 : [1, 8, 15],
|
|
||||||
4 : [1, 5, 10, 15],
|
|
||||||
5 : [1, 4, 8, 12, 16],
|
|
||||||
6 : [1, 3, 7, 10, 13, 16],
|
|
||||||
7 : [1, 3, 6, 9, 11, 13, 16],
|
|
||||||
8 : [1, 3, 5, 7, 9, 11, 13, 15],
|
|
||||||
9 : [1, 3, 5, 7, 9, 11, 13, 15, 16],
|
|
||||||
10 : [1, 2, 3, 5, 7, 9, 11, 13, 15, 16],
|
|
||||||
11 : [1, 2, 3, 5, 7, 9, 11, 13, 14, 15, 16],
|
|
||||||
12 : [1, 2, 3, 4, 5, 7, 9, 11, 13, 14, 15, 16],
|
|
||||||
13 : [1, 2, 3, 4, 5, 7, 9, 11, 12, 13, 14, 15, 16],
|
|
||||||
14 : [1, 2, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15, 16],
|
|
||||||
15 : [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
16 : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
|
||||||
}
|
|
||||||
|
|
||||||
const notes = [21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages all of the hammered string sounds
|
|
||||||
*/
|
|
||||||
export default class Strings extends PianoBase {
|
|
||||||
|
|
||||||
constructor(range=[21, 108], velocities=1){
|
|
||||||
super()
|
|
||||||
|
|
||||||
const lowerIndex = notes.findIndex((note) => note >= range[0])
|
|
||||||
let upperIndex = notes.findIndex((note) => note >= range[1])
|
|
||||||
upperIndex = upperIndex === -1 ? upperIndex = notes.length : upperIndex + 1
|
|
||||||
|
|
||||||
const slicedNotes = notes.slice(lowerIndex, upperIndex)
|
|
||||||
|
|
||||||
this._buffers = velocitiesMap[velocities].slice()
|
|
||||||
|
|
||||||
this._buffers.forEach((vel, i) => {
|
|
||||||
this._buffers[i] = {}
|
|
||||||
slicedNotes.forEach((note) => {
|
|
||||||
this._buffers[i][note] = Salamander.getNotesUrl(note, vel)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_hasNote(note, velocity){
|
|
||||||
return this._buffers.hasOwnProperty(velocity) && this._buffers[velocity].has(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
_getNote(note, velocity){
|
|
||||||
return this._buffers[velocity].get(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
start(note, velocity, time){
|
|
||||||
|
|
||||||
let velPos = velocity * (this._buffers.length - 1)
|
|
||||||
let roundedVel = Math.round(velPos)
|
|
||||||
let diff = roundedVel - velPos
|
|
||||||
let gain = 1 - diff * 0.5
|
|
||||||
|
|
||||||
let [midi, ratio] = midiToFrequencyRatio(note)
|
|
||||||
|
|
||||||
if (this._hasNote(midi, roundedVel)){
|
|
||||||
let source = createSource(this._getNote(midi, roundedVel))
|
|
||||||
source.playbackRate.value = ratio
|
|
||||||
|
|
||||||
let retNote = new Note(time, source, velocity, gain).connect(this.output)
|
|
||||||
|
|
||||||
return retNote
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
load(baseUrl){
|
|
||||||
const promises = []
|
|
||||||
this._buffers.forEach((obj, i) => {
|
|
||||||
let prom = new Promise((success) => {
|
|
||||||
this._buffers[i] = new Buffers(obj, success, baseUrl)
|
|
||||||
})
|
|
||||||
promises.push(prom)
|
|
||||||
})
|
|
||||||
return Promise.all(promises)
|
|
||||||
}
|
|
||||||
}
|
|
64
static/third_party/Piano/src/Pedal.js
vendored
64
static/third_party/Piano/src/Pedal.js
vendored
@ -1,64 +0,0 @@
|
|||||||
import PianoBase from './PianoBase'
|
|
||||||
import Salamander from './Salamander'
|
|
||||||
import {createSource} from './Util'
|
|
||||||
import Buffers from 'Tone/core/Buffers'
|
|
||||||
|
|
||||||
export default class Pedal extends PianoBase {
|
|
||||||
constructor(load=true){
|
|
||||||
super()
|
|
||||||
|
|
||||||
this._downTime = Infinity
|
|
||||||
|
|
||||||
this._currentSound = null
|
|
||||||
|
|
||||||
this._buffers = null
|
|
||||||
|
|
||||||
this._loadPedalSounds = load
|
|
||||||
}
|
|
||||||
|
|
||||||
load(baseUrl){
|
|
||||||
if (this._loadPedalSounds){
|
|
||||||
return new Promise((success) => {
|
|
||||||
this._buffers = new Buffers({
|
|
||||||
up : 'pedalU1.mp3',
|
|
||||||
down : 'pedalD1.mp3'
|
|
||||||
}, success, baseUrl)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Squash the current playing sound
|
|
||||||
*/
|
|
||||||
_squash(time){
|
|
||||||
if (this._currentSound){
|
|
||||||
this._currentSound.stop(time, 0.1)
|
|
||||||
}
|
|
||||||
this._currentSound = null
|
|
||||||
}
|
|
||||||
|
|
||||||
_playSample(time, dir){
|
|
||||||
if (this._loadPedalSounds){
|
|
||||||
this._currentSound = createSource(this._buffers.get(dir))
|
|
||||||
this._currentSound.connect(this.output).start(time, 0, undefined, 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
down(time){
|
|
||||||
this._squash(time)
|
|
||||||
this._downTime = time
|
|
||||||
this._playSample(time, 'down')
|
|
||||||
}
|
|
||||||
|
|
||||||
up(time){
|
|
||||||
this._squash(time)
|
|
||||||
this._downTime = Infinity
|
|
||||||
this._playSample(time, 'up')
|
|
||||||
}
|
|
||||||
|
|
||||||
isDown(time){
|
|
||||||
return time > this._downTime
|
|
||||||
}
|
|
||||||
}
|
|
189
static/third_party/Piano/src/Piano.js
vendored
189
static/third_party/Piano/src/Piano.js
vendored
@ -1,189 +0,0 @@
|
|||||||
import Gain from 'Tone/core/Gain'
|
|
||||||
import Tone from 'Tone/core/Tone'
|
|
||||||
import Frequency from 'Tone/type/Frequency'
|
|
||||||
import Pedal from './Pedal'
|
|
||||||
import Note from './Note'
|
|
||||||
import Harmonics from './Harmonics'
|
|
||||||
import Release from './Release'
|
|
||||||
import Salamander from './Salamander'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class Multisampled Grand Piano using [Salamander Piano Samples](https://archive.org/details/SalamanderGrandPianoV3)
|
|
||||||
* @extends {Tone}
|
|
||||||
*/
|
|
||||||
export default class Piano extends Tone{
|
|
||||||
|
|
||||||
constructor(range=[21, 108], velocities=1, release=true){
|
|
||||||
|
|
||||||
super(0, 1)
|
|
||||||
|
|
||||||
this._loaded = false
|
|
||||||
|
|
||||||
this._heldNotes = new Map()
|
|
||||||
|
|
||||||
this._sustainedNotes = new Map()
|
|
||||||
|
|
||||||
this._notes = new Note(range, velocities).connect(this.output)
|
|
||||||
|
|
||||||
this._pedal = new Pedal(release).connect(this.output)
|
|
||||||
|
|
||||||
if (release){
|
|
||||||
this._harmonics = new Harmonics(range).connect(this.output)
|
|
||||||
|
|
||||||
this._release = new Release(range).connect(this.output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all the samples
|
|
||||||
* @param {String} baseUrl The url for the Salamander base folder
|
|
||||||
* @return {Promise}
|
|
||||||
*/
|
|
||||||
load(url){
|
|
||||||
const promises = [this._notes.load(url), this._pedal.load(url)]
|
|
||||||
if (this._harmonics){
|
|
||||||
promises.push(this._harmonics.load(url))
|
|
||||||
}
|
|
||||||
if (this._release){
|
|
||||||
promises.push(this._release.load(url))
|
|
||||||
}
|
|
||||||
return Promise.all(promises).then(() => {
|
|
||||||
this._loaded = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put the pedal down at the given time. Causes subsequent
|
|
||||||
* notes and currently held notes to sustain.
|
|
||||||
* @param {Time} time The time the pedal should go down
|
|
||||||
* @returns {Piano} this
|
|
||||||
*/
|
|
||||||
pedalDown(time){
|
|
||||||
if (this._loaded){
|
|
||||||
time = this.toSeconds(time)
|
|
||||||
if (!this._pedal.isDown(time)){
|
|
||||||
this._pedal.down(time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put the pedal up. Dampens sustained notes
|
|
||||||
* @param {Time} time The time the pedal should go up
|
|
||||||
* @returns {Piano} this
|
|
||||||
*/
|
|
||||||
pedalUp(time){
|
|
||||||
if (this._loaded){
|
|
||||||
time = this.toSeconds(time)
|
|
||||||
if (this._pedal.isDown(time)){
|
|
||||||
this._pedal.up(time)
|
|
||||||
// dampen each of the notes
|
|
||||||
this._sustainedNotes.forEach((notes) => {
|
|
||||||
notes.forEach((note) => {
|
|
||||||
note.stop(time)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
this._sustainedNotes.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play a note.
|
|
||||||
* @param {String|Number} note The note to play
|
|
||||||
* @param {Number} velocity The velocity to play the note
|
|
||||||
* @param {Time} time The time of the event
|
|
||||||
* @return {Piano} this
|
|
||||||
*/
|
|
||||||
keyDown(note, velocity=0.8, time=Tone.now()){
|
|
||||||
if (this._loaded){
|
|
||||||
time = this.toSeconds(time)
|
|
||||||
|
|
||||||
if (this.isString(note)){
|
|
||||||
note = Math.round(Frequency(note).toMidi())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._heldNotes.has(note)){
|
|
||||||
let key = this._notes.start(note, velocity, time)
|
|
||||||
if (key){
|
|
||||||
this._heldNotes.set(note, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Release a held note.
|
|
||||||
* @param {String|Number} note The note to stop
|
|
||||||
* @param {Time} time The time of the event
|
|
||||||
* @return {Piano} this
|
|
||||||
*/
|
|
||||||
keyUp(note, time=Tone.now()){
|
|
||||||
if (this._loaded){
|
|
||||||
time = this.toSeconds(time)
|
|
||||||
|
|
||||||
if (this.isString(note)){
|
|
||||||
note = Math.round(Frequency(note).toMidi())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._heldNotes.has(note)){
|
|
||||||
|
|
||||||
let key = this._heldNotes.get(note)
|
|
||||||
this._heldNotes.delete(note)
|
|
||||||
|
|
||||||
if (this._release){
|
|
||||||
this._release.start(note, time)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._pedal.isDown(time)){
|
|
||||||
let notes = []
|
|
||||||
if (this._sustainedNotes.has(note)){
|
|
||||||
notes = this._sustainedNotes.get(note)
|
|
||||||
}
|
|
||||||
notes.push(key)
|
|
||||||
this._sustainedNotes.set(note, notes)
|
|
||||||
} else {
|
|
||||||
let dampenGain = key.stop(time)
|
|
||||||
if (this._harmonics){
|
|
||||||
this._harmonics.start(note, dampenGain, time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the volumes of each of the components
|
|
||||||
* @param {String} param
|
|
||||||
* @param {Decibels} vol
|
|
||||||
* @return {Piano} this
|
|
||||||
* @example
|
|
||||||
* //either as an string
|
|
||||||
* piano.setVolume('release', -10)
|
|
||||||
*/
|
|
||||||
setVolume(param, vol){
|
|
||||||
switch(param){
|
|
||||||
case 'note':
|
|
||||||
this._notes.volume = vol
|
|
||||||
break
|
|
||||||
case 'pedal':
|
|
||||||
this._pedal.volume = vol
|
|
||||||
break
|
|
||||||
case 'release':
|
|
||||||
if (this._release){
|
|
||||||
this._release.volume = vol
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'harmonics':
|
|
||||||
if (this._harmonics){
|
|
||||||
this._harmonics.volume = vol
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
16
static/third_party/Piano/src/PianoBase.js
vendored
16
static/third_party/Piano/src/PianoBase.js
vendored
@ -1,16 +0,0 @@
|
|||||||
import Tone from 'Tone/core/Tone'
|
|
||||||
import Master from 'Tone/core/Master'
|
|
||||||
|
|
||||||
export default class PianoBase extends Tone {
|
|
||||||
constructor(vol=0){
|
|
||||||
super(0, 1)
|
|
||||||
|
|
||||||
this.volume = vol
|
|
||||||
}
|
|
||||||
get volume(){
|
|
||||||
return this.gainToDb(this.output.gain.value)
|
|
||||||
}
|
|
||||||
set volume(vol){
|
|
||||||
this.output.gain.value = this.dbToGain(vol)
|
|
||||||
}
|
|
||||||
}
|
|
29
static/third_party/Piano/src/Release.js
vendored
29
static/third_party/Piano/src/Release.js
vendored
@ -1,29 +0,0 @@
|
|||||||
import Salamander from './Salamander'
|
|
||||||
import PianoBase from './PianoBase'
|
|
||||||
import {createSource} from './Util'
|
|
||||||
import Buffers from 'Tone/core/Buffers'
|
|
||||||
|
|
||||||
export default class Release extends PianoBase {
|
|
||||||
|
|
||||||
constructor(range){
|
|
||||||
super()
|
|
||||||
|
|
||||||
this._buffers = {}
|
|
||||||
for (let i = range[0]; i <= range[1]; i++){
|
|
||||||
this._buffers[i] = Salamander.getReleasesUrl(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
load(baseUrl){
|
|
||||||
return new Promise((success) => {
|
|
||||||
this._buffers = new Buffers(this._buffers, success, baseUrl)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
start(note, time){
|
|
||||||
if (this._buffers.has(note)){
|
|
||||||
let source = createSource(this._buffers.get(note)).connect(this.output)
|
|
||||||
source.start(time, 0, undefined, 0.01, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
16
static/third_party/Piano/src/Salamander.js
vendored
16
static/third_party/Piano/src/Salamander.js
vendored
@ -1,16 +0,0 @@
|
|||||||
import {noteToMidi, midiToNote} from './Util'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
getReleasesUrl(midi){
|
|
||||||
return `rel${midi - 20}.mp3`
|
|
||||||
},
|
|
||||||
|
|
||||||
getHarmonicsUrl(midi){
|
|
||||||
return `harmL${encodeURIComponent(midiToNote(midi))}.mp3`
|
|
||||||
},
|
|
||||||
|
|
||||||
getNotesUrl(midi, vel){
|
|
||||||
return `${encodeURIComponent(midiToNote(midi))}.mp3`
|
|
||||||
}
|
|
||||||
}
|
|
28
static/third_party/Piano/src/Util.js
vendored
28
static/third_party/Piano/src/Util.js
vendored
@ -1,28 +0,0 @@
|
|||||||
import Tone from 'Tone/core/Tone'
|
|
||||||
import Frequency from 'Tone/type/Frequency'
|
|
||||||
import BufferSource from 'Tone/source/BufferSource'
|
|
||||||
|
|
||||||
function noteToMidi(note){
|
|
||||||
return Frequency(note).toMidi()
|
|
||||||
}
|
|
||||||
|
|
||||||
function midiToNote(midi){
|
|
||||||
return Frequency(midi, 'midi').toNote().replace('#', 's')
|
|
||||||
}
|
|
||||||
|
|
||||||
function midiToFrequencyRatio(midi){
|
|
||||||
let mod = midi % 3
|
|
||||||
if (mod === 1){
|
|
||||||
return [midi - 1, Tone.prototype.intervalToFrequencyRatio(1)]
|
|
||||||
} else if (mod === 2){
|
|
||||||
return [midi + 1, Tone.prototype.intervalToFrequencyRatio(-1)]
|
|
||||||
} else {
|
|
||||||
return [midi, 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSource(buffer){
|
|
||||||
return new BufferSource(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {midiToNote, noteToMidi, createSource, midiToFrequencyRatio}
|
|
Loading…
Reference in New Issue
Block a user