Initial commit: ACE-Step UI - Open source music generation interface
@@ -0,0 +1,321 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>AudioMass - About</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
|
||||
<meta name="description" content="AudioMass is a free full-featured web-based audio & waveform editing tool"/>
|
||||
<meta property="og:image" content="https://audiomass.co/icon.jpg"/>
|
||||
<meta property="og:title" content="AudioMass">
|
||||
<meta property="og:url" content="https://audiomass.co/">
|
||||
<meta property="og:description" content="AudioMass is a free full-featured web-based audio & waveform editing tool">
|
||||
<meta name="keywords" content="AudioMass, WebAudio, WaveForm, audio editing, free audio editing, audio tool, waveform editor, sound editor, open source">
|
||||
<link href="/ico.ico" rel="shortcut icon">
|
||||
|
||||
<style>
|
||||
body{
|
||||
padding:0;margin:0;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
color:rgb(5, 10, 5);
|
||||
background:#fefefe
|
||||
}
|
||||
#w{
|
||||
width: 90%;
|
||||
max-width:960px;
|
||||
margin:0 auto
|
||||
}
|
||||
|
||||
#w > div{
|
||||
padding:20px 0 0 0
|
||||
}
|
||||
|
||||
p{
|
||||
line-height:21px;
|
||||
font-size:16px;
|
||||
margin-bottom:1.2em
|
||||
}
|
||||
|
||||
h1{
|
||||
font-size: 26px;
|
||||
margin-top:35px
|
||||
}
|
||||
|
||||
h5{
|
||||
font-size:1em;
|
||||
margin-bottom:0
|
||||
}
|
||||
|
||||
.top{
|
||||
max-width:100%;
|
||||
outline:0;
|
||||
display:inline-block
|
||||
}
|
||||
|
||||
.mid{
|
||||
max-width:504px;
|
||||
width: 90%;
|
||||
outline:0;
|
||||
margin-bottom:16px;
|
||||
display:inline-block
|
||||
}
|
||||
|
||||
.nomarg{
|
||||
margin-bottom:0
|
||||
}
|
||||
|
||||
pre {
|
||||
padding:12px 16px;
|
||||
display:inline-block;
|
||||
border-radius:6px;
|
||||
background:#e1f3e9;
|
||||
text-shadow:0 1px #fff;
|
||||
border-bottom:1px solid #aaa;
|
||||
max-width:90%;
|
||||
overflow:scroll
|
||||
}
|
||||
|
||||
ul {
|
||||
padding:0;
|
||||
list-style:none
|
||||
}
|
||||
|
||||
li {
|
||||
padding:0 0 3px 0;
|
||||
color:#333;
|
||||
font-size:0.92em
|
||||
}
|
||||
|
||||
video {
|
||||
max-width:660px;
|
||||
width:90%;
|
||||
}
|
||||
|
||||
::-moz-selection { background: #080808; color:#fff; text-shadow:none }
|
||||
::selection { background: #080808; color:#fff; text-shadow:none }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="w">
|
||||
|
||||
|
||||
<div>
|
||||
<h1>Introducing AudioMass (<a href="https://audiomass.co" target="_blank">https://audiomass.co</a>) an open-source web based audio and waveform editing tool.</h1>
|
||||
|
||||
|
||||
<img class="top" src="/about/audiomass_top.jpg" />
|
||||
|
||||
|
||||
<p>AudioMass lets you record, or use your existing audio tracks, and modify them by trimming, cutting, pasting or applying a plethora of effects, from compression and paragraphic equalizers to reverb, delay and distortion fx. AudioMass also supports more than 20 hotkeys combinations and a dynamic responsive interface to ensure ease of use and that your producivity remains high.
|
||||
it is written solely in plain old-school javascript, weights approximately 65kb and has no backend or framework dependencies.</p>
|
||||
|
||||
<img class="mid" src="/about/audiomass_support.jpg" />
|
||||
<p>It also has very good browser and device support.</p>
|
||||
|
||||
|
||||
<h5>:: Feature List ::</h5>
|
||||
|
||||
<ul>
|
||||
<li>Loading Audio, navigating the waveform, zoom and pan</li>
|
||||
<li>Visualization of frequency levels</li>
|
||||
<li>Peak and distortion signaling</li>
|
||||
<li>Cutting/Pasting/Trimming parts of the audio</li>
|
||||
<li>Inverting and Reversing Audio</li>
|
||||
<li>Exporting to mp3</li>
|
||||
<li>Modifying volume levels</li>
|
||||
<li>Fade In/Out</li>
|
||||
<li>Compressor</li>
|
||||
<li>Normalization</li>
|
||||
<li>Reverb</li>
|
||||
<li>Delay</li>
|
||||
<li>Distortion</li>
|
||||
<li>Pitch Shift</li>
|
||||
<li>Keeps track of states so you can undo mistakes</li>
|
||||
<li>Offline support!</li>
|
||||
</ul>
|
||||
|
||||
<p>And all this, in only 83kb of JS!</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<h3>Getting Started</h3>
|
||||
|
||||
|
||||
<p>To get started, drag and drop an audio file, or try the included sample.<br />
|
||||
Once the file is loaded and you can view the waveform, zoom in, pan around, or select a region.</p>
|
||||
|
||||
<img class="mid" src="/about/audiomass_2.jpg" />
|
||||
|
||||
|
||||
<h3>Recording Audio</h3>
|
||||
|
||||
<p>To record audio, simply press the Recording button, or the <i>[R]</i> key.</p>
|
||||
|
||||
<img class="mid" src="/about/audiomass_3.jpg" />
|
||||
|
||||
|
||||
<h3>Exporting to mp3</h3>
|
||||
|
||||
<p>In order to export back to mp3, click on 'File', then 'Export to mp3', and follow the modal's instructions.</p>
|
||||
|
||||
<img class="mid" src="/about/audiomass_4.jpg" />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
<div>
|
||||
<h3>The story behind AudioMass. And a short rant on web interfaces.</h3>
|
||||
|
||||
<p>I wrote AudioMass back in June 2018 and it stayed dormant on my hard disk until I decided to share it with the world today (July 13th, 19 -- but you might be seeing this in 2020. Hi!!).
|
||||
It started as a personal small tool for quick visualization of waveforms. Later I added the ability to cut/copy/paste as well as fade in and out. And soon after good 'ol feature creep and perfectionism took over! Soon after, it turned to a challenge to see how close to a full-featured waveform editor it can get, whilst maintaining acceptable performance and small filesize.</p>
|
||||
|
||||
<p>In general I am a big fan of the interfaces of DAWs <i>(Digital Audio Workstations)</i>, they are extremely, complex, intricate, versatile, and they manage to remain visually pleasant even through their infinite options and knobs. Many times I feel the web has taken a very wrong turn, as amazing interfaces such as...
|
||||
|
||||
<p class="nomarg">Sonar</p>
|
||||
<img class="mid" src="/about/sonar.jpg" />
|
||||
|
||||
<br />
|
||||
<p class="nomarg">Fruity loops</p>
|
||||
<img class="mid" src="/about/fruity.png" />
|
||||
|
||||
<br />
|
||||
|
||||
<p>Existed for more than 10-15 years now, while we are struggling with animating some rectangles for 60fps... So for AudioMass I wanted to try and make a fast and performant interface. Drawing inspiration from the examples I mentioned earlier rather than the tradiional web development practices. This is my unconvincing but truthfull excuse as to why the code is ugly; it is focused on being fast and getting the job done, with little regard on structure.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
|
||||
<h3>Building the interface</h3>
|
||||
|
||||
<p>Let's say we have a <i>"PLAY"</i> button and when we press it the track begins to play. I suppose we would want the button's color and state to reflect that the track is now playing. So naively we would do something like;</p>
|
||||
|
||||
|
||||
<pre><code>btn.onclick = function () {
|
||||
this.classList.add ('active');
|
||||
};</code></pre>
|
||||
|
||||
<p>But what happens if we have a hotkey that triggers the same action? Let's say we press <i>[SPACEBAR]</i> and the track begins to play. Do we modify that button's class in the spacebar's handler?</p>
|
||||
|
||||
<pre><code>document.onkeypress = function ( e ) {
|
||||
if ( e.keyCode === 32) {
|
||||
e.preventDefault ();
|
||||
|
||||
document.querySelector ('.playbtn').classList.add ('active');
|
||||
}
|
||||
};</code></pre>
|
||||
|
||||
<p>And what happens, if there are 2 buttons, or one gets dynamically removed? Do we do selectAll and iterate? Hmmm...<br />
|
||||
And if the track is playing and we hit <i>[SPACEBAR]</i> or the play button again, we need to stop playing. What do we do then? You can see how this becomes messy very quickly as everything gets very tighlty coupled together in a big dependency ball.</p>
|
||||
|
||||
|
||||
|
||||
<p>Introducing the observer pattern. Actions are represented by events. So the above logic and be expressed as;</p>
|
||||
|
||||
|
||||
<pre><code>btn.onclick = function () {
|
||||
FireEvent ('RequestTogglePlay');
|
||||
};
|
||||
|
||||
On ('WillPlay', function () {
|
||||
btn.classList.add ('active');
|
||||
});
|
||||
|
||||
On ('WillStop', function () {
|
||||
btn.classList.remove ('active');
|
||||
});
|
||||
|
||||
|
||||
|
||||
document.onkeypress = function ( e ) {
|
||||
if ( e.keyCode === 32) {
|
||||
e.preventDefault ();
|
||||
|
||||
FireEvent ('RequestTogglePlay');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
On ('RequestTogglePlay', function () {
|
||||
if (track.is_playing) {
|
||||
FireEvent ('WillStop');
|
||||
track.stop ();
|
||||
}
|
||||
else {
|
||||
FireEvent ('WillPlay');
|
||||
track.play ();
|
||||
}
|
||||
});
|
||||
|
||||
track.onPlayStart = function () {
|
||||
FireEvent ('DidPlay');
|
||||
};
|
||||
track.onPlayStop = function () {
|
||||
FireEvent ('DidStop');
|
||||
};
|
||||
</code></pre>
|
||||
|
||||
<p>Now this is completely decoupled and dependency free! The button will set its state according to the events it receives, and both the button and the spacebar key rely on the same mechanisms.
|
||||
You may notice the vocabulary we are using. <i>"Request"</i>, <i>"Will"</i> and <i>"Did"</i>. These are arbitrarilly chosen to impose some extra structure.<br /> <i>"Request"</i> denotes intent to perform an action, it is not guaranteed that the action will execute as there might be conditions preventing it (eg unitialized or still loading objects). <i>"Will"</i> means that the conditions passed and we are attempting to perform the action. And <i>"Did"</i> means that the action just got performed.<br />
|
||||
It might be a bit too verbose, but it worked very well for structuring AudioMass's interface.</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<h3>Dockable UI</h3>
|
||||
|
||||
<p>One thing I love about DAW interfaces, is that every window can be pulled out of the main host. I fondly remember having 3 screens full of VST plugins. So can we do the same in the browser?</p>
|
||||
|
||||
|
||||
<video src="/about/dock_ui.mp4" autoplay muted playsinline controls loop></video>
|
||||
|
||||
|
||||
<p>Yes! And it is using some of the oldest tricks in the book. Essentially we create a pop-up window with <i>window.open</i> and just pass buffers to its <i>documentWindow</i> object. Surprisingly it is quite performant on all browsers except IE Edge. I believe they are serializing in ascii or base64 every packet or something. Also Chrome has an interesting bug where you can't pass more than 512 byte buffers.</p>
|
||||
|
||||
<p>So for the undocked window, we call <i>window.open</i>. But how do we make it work when it is docked? It would be quite cumbersome to write the same functionality twice, once as a standalone page, and once as an in-page script. Luckily we can avoid that completely, and re-use the same page by using iframes.</p>
|
||||
|
||||
<p>The only difference is that the undocked version uses <i>window.opener</i> to refference its parent, whereas the iframe uses <i>window.parent</i>.</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<h3>Future work and performance considerations</h3>
|
||||
|
||||
<p>The next big feature that is planned, is multitrack support. The ability to mix tracks and different sounds is currently lacking and its usefulness as it stands is limited.</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>There is also a lot of room for improvement in almost all aspects.</p>
|
||||
|
||||
<p>First of all we can further reduce the filesize by around 20kb by removing the library we are using for rendering the waveform. We use only a fraction of its functionality so there is no reason to include it all.</p>
|
||||
|
||||
<p>We can also optimize a lot the rendering of the waveform. I heavily modified the library used to compute and draw only the visible range. However it is still clearing and re-drawing all of the canvas at each frame. We can take advantage of 2d Context's translate calls, and shift the canvas around instead of redrawing all of the pixels.</p>
|
||||
|
||||
<p>We can also move some operations to a background thread, such as the filters processing so that the UI does not freeze when applying a long chain of effects.</p>
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
<p>However, the biggest issue I encountered, is the Web Audio API itself. Every operation results in iterating over multiple long arrays per frame. Eventually the garbage collector fires and crackling is introduced. Only way to go around this is to use a small fftSize, but then the frequency range we have to work with is very narrow. Perhaps a pure WASM implementation would outperform trying to modify audio signals with JS. Only one way to find out I guess :) </p>
|
||||
<p>Additionally, <i>decodeAudioData</i> provides no progress callback, and no way of cancelling it. So if you attempt to load a huge audio file, you will waste resources until it gets processed. There is no way around this currently and it can get annoying if you push a big file by mistake.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<br /><br /><br />
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 757 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 135 KiB |
@@ -0,0 +1,107 @@
|
||||
(function (w, d) {
|
||||
'use strict';
|
||||
|
||||
var _v = '0.9',
|
||||
_id = -1;
|
||||
|
||||
function PKAE() {
|
||||
var q = this; // keeping track of current context
|
||||
|
||||
q.el = null; // reference of main html element
|
||||
q.id = ++_id; // auto incremental id
|
||||
q._deps = {}; // dependencies
|
||||
|
||||
w.PKAudioList[q.id] = q;
|
||||
|
||||
var events = {};
|
||||
|
||||
q.fireEvent = function (eventName, value, value2) {
|
||||
var group = events[eventName];
|
||||
if (!group) return (false);
|
||||
|
||||
var l = group.length;
|
||||
while (l-- > 0) {
|
||||
group[l] && group[l](value, value2);
|
||||
}
|
||||
};
|
||||
|
||||
q.listenFor = function (eventName, callback) {
|
||||
if (!events[eventName])
|
||||
events[eventName] = [callback];
|
||||
else
|
||||
events[eventName].unshift(callback);
|
||||
};
|
||||
|
||||
q.stopListeningFor = function (eventName, callback) {
|
||||
var group = events[eventName];
|
||||
if (!group) return (false);
|
||||
|
||||
var l = group.length;
|
||||
while (l-- > 0) {
|
||||
if (group[l] && group[l] === callback) {
|
||||
group[l] = null; break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
q.stopListeningForName = function (eventName) {
|
||||
var group = events[eventName];
|
||||
if (!group) return (false);
|
||||
events[eventName] = null;
|
||||
};
|
||||
|
||||
q.init = function (el_id) {
|
||||
var el = d.getElementById(el_id);
|
||||
if (!el) {
|
||||
console.log('invalid element');
|
||||
return;
|
||||
}
|
||||
q.el = el;
|
||||
|
||||
// init libraries
|
||||
q.ui = new q._deps.ui(q); q._deps.uifx(q);
|
||||
q.engine = new q._deps.engine(q);
|
||||
q.state = new q._deps.state(4, q);
|
||||
q.rec = new q._deps.rec(q);
|
||||
q.fls = new q._deps.fls(q);
|
||||
|
||||
if (w.location.href.split('local=')[1]) {
|
||||
var sess = w.location.href.split('local=')[1];
|
||||
|
||||
q.fls.Init(function () {
|
||||
q.fls.GetSession(sess, function (e) {
|
||||
if (e && e.id === sess) {
|
||||
q.engine.LoadDB(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Check for audioUrl parameter to load audio from URL
|
||||
if (w.location.href.split('audioUrl=')[1]) {
|
||||
var audioUrl = decodeURIComponent(w.location.href.split('audioUrl=')[1].split('&')[0]);
|
||||
|
||||
setTimeout(function () {
|
||||
if (audioUrl) {
|
||||
q.engine.LoadURL(audioUrl);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return (q);
|
||||
};
|
||||
|
||||
// check if we are mobile and hide tooltips on hover
|
||||
q.isMobile = (/iphone|ipod|ipad|android/).test
|
||||
(navigator.userAgent.toLowerCase());
|
||||
};
|
||||
|
||||
!w.PKAudioList && (w.PKAudioList = []);
|
||||
|
||||
// ideally we do not want a global singleto refferencing our audio tool
|
||||
// but since this is a limited demo we can safely do it.
|
||||
w.PKAudioEditor = new PKAE();
|
||||
|
||||
PKAudioList.push(w.PKAudioEditor); // keeping track in the audiolist array of our instance
|
||||
|
||||
})(window, document);
|
||||
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
_ "net/url"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
changeHeaderThenServe := func(h http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||
|
||||
if strings.Contains(r.URL.Path, ".wasm") {
|
||||
w.Header().Add("Content-Type", "application/wasm")
|
||||
}
|
||||
|
||||
if strings.Contains(r.URL.Path, ".js") {
|
||||
var epoch = time.Unix(0, 0).Format(time.RFC1123)
|
||||
var noCacheHeaders = map[string]string{
|
||||
"Expires": epoch,
|
||||
"Cache-Control": "no-cache, private, max-age=0",
|
||||
"Pragma": "no-cache",
|
||||
"X-Accel-Expires": "0",
|
||||
}
|
||||
|
||||
for k, v := range noCacheHeaders {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Req:", r.Host, r.URL.Path)
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
cmd := exec.Command("open", "-a", "Google Chrome", "http://localhost:5055/")
|
||||
cmd.Output()
|
||||
}()
|
||||
|
||||
fmt.Printf("\nListening on http://localhost:5055 \n\n")
|
||||
http.Handle("/", changeHeaderThenServe(http.FileServer(http.Dir("."))))
|
||||
panic(http.ListenAndServe(":5055", nil))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import SimpleHTTPServer
|
||||
import SocketServer
|
||||
PORT = 5055
|
||||
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
|
||||
httpd = SocketServer.TCPServer(("", PORT), Handler)
|
||||
print "serving at port", PORT
|
||||
httpd.serve_forever()
|
||||
@@ -0,0 +1,12 @@
|
||||
// Add this to app.js after line 78 (after the local= parameter check)
|
||||
|
||||
// Check for audioUrl parameter to load audio from URL
|
||||
if (w.location.href.split('audioUrl=')[1]) {
|
||||
var audioUrl = decodeURIComponent(w.location.href.split('audioUrl=')[1].split('&')[0]);
|
||||
|
||||
setTimeout(function () {
|
||||
if (audioUrl) {
|
||||
q.engine.LoadURL(audioUrl);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
CACHE MANIFEST
|
||||
# v1 - July 26, 2018
|
||||
# v2 - May 25, 2020
|
||||
|
||||
/index-cache.html
|
||||
/main.css
|
||||
/dist/wavesurfer.js
|
||||
/dist/plugin/wavesurfer.regions.js
|
||||
/oneup.js
|
||||
/app.js
|
||||
/keys.js
|
||||
/contextmenu.js
|
||||
/ui-fx.js
|
||||
/ui.js
|
||||
/modal.js
|
||||
/state.js
|
||||
/engine.js
|
||||
/actions.js
|
||||
/drag.js
|
||||
/recorder.js
|
||||
/welcome.js
|
||||
/fx-pg-eq.js
|
||||
/fx-auto.js
|
||||
/local.js
|
||||
/id3.js
|
||||
/lzma.js
|
||||
/lame.js
|
||||
/fonts/icomoon.ttf
|
||||
/fonts/icomoon.woff
|
||||
/test.mp3
|
||||
|
||||
|
||||
NETWORK:
|
||||
/index-cache.html
|
||||
|
||||
# Fallback content
|
||||
FALLBACK:
|
||||
. /index-cache.html
|
||||
@@ -0,0 +1,205 @@
|
||||
(function ( win, doc, PKAE ) {
|
||||
'use strict';
|
||||
|
||||
var activeMenu = [],
|
||||
namespace = win,
|
||||
contextStorage = {},
|
||||
_id = 0;
|
||||
|
||||
var closeEvent = [ 'mousedown', 'touchup' ];
|
||||
|
||||
/**
|
||||
* Goes through every single context instance and terminates
|
||||
* it.
|
||||
**/
|
||||
var closeContext = function ( e, force ) {
|
||||
if (!e) return ;
|
||||
if (activeMenu.length === 0) return ;
|
||||
|
||||
var el = e.target || e.srcElement;
|
||||
|
||||
if (!el || el.className.indexOf('_action') === -1 || force)
|
||||
{
|
||||
var l = activeMenu.length;
|
||||
while (l--) terminate (activeMenu[ l ]);
|
||||
activeMenu = [];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Go through every children element of the context el,
|
||||
* and remove all listeners and added attributes, then remove it
|
||||
* from the dom also
|
||||
**/
|
||||
terminate = function( e ) {
|
||||
var children = e.currentMenu.getElementsByTagName('*'),
|
||||
len = children.length;
|
||||
|
||||
while( len-- )
|
||||
children[ len ].parentNode.removeChild( children[ len ] );
|
||||
|
||||
e.currentMenu.removeEventListener( closeEvent, stopPropagation );
|
||||
doc.body.removeChild( e.currentMenu );
|
||||
e.currentMenu = null;
|
||||
return false;
|
||||
},
|
||||
/** stop propagation func, so that we don't have to use anonymous funcs **/
|
||||
stopPropagation = function(e){ e.stopPropagation(); },
|
||||
openContext = function( e, x, y ) {
|
||||
|
||||
closeContext(null);
|
||||
activeMenu.push( e );
|
||||
|
||||
//go through all the options and make the div
|
||||
var div = doc.createElement('div'),
|
||||
a,
|
||||
marginOffset = 4,
|
||||
opts = e.options,
|
||||
leftOffset = x - marginOffset,
|
||||
topOffset = y - marginOffset,
|
||||
width = 0, height = 0;
|
||||
|
||||
div.className = "pk_contextMenu " + e.menuClass;
|
||||
div.id = e.token;
|
||||
|
||||
for( var i = 0, len = opts.length; i < len; ++i ) {
|
||||
if( opts[ i ].isHTML )
|
||||
{
|
||||
a = doc.createElement('div');
|
||||
a.innerHTML = opts[ i ].isHTML;
|
||||
div.appendChild( a );
|
||||
}
|
||||
else {
|
||||
a = doc.createElement('a');
|
||||
a.className = 'pk_ctx_action';
|
||||
a.cnt = 1;
|
||||
a.innerHTML = opts[ i ].name;
|
||||
a.callback = opts[ i ].callback;
|
||||
|
||||
a.addEventListener( 'click', a.callback, false );
|
||||
|
||||
div.appendChild( a );
|
||||
}
|
||||
}
|
||||
|
||||
e.currentMenu = div;
|
||||
div.addEventListener( closeEvent, stopPropagation, false );
|
||||
|
||||
doc.body.appendChild( div );
|
||||
|
||||
width = div.offsetWidth;
|
||||
height = div.offsetHeight;
|
||||
|
||||
if( win.innerWidth < ( leftOffset + width ) && win.innerHeight < ( topOffset + height ) )
|
||||
div.style.cssText = "top:" + ( topOffset - height ) + "px;left:" + ( leftOffset - width ) + "px;";
|
||||
else if( win.innerWidth < ( leftOffset + width ) )
|
||||
div.style.cssText = "top:" + ( topOffset ) + "px;left:" + ( leftOffset - width ) + "px;";
|
||||
else if( win.innerHeight < ( topOffset + height ) )
|
||||
div.style.cssText = "top:" + ( topOffset - height ) + "px;left:" + ( leftOffset ) + "px;";
|
||||
else
|
||||
div.style.cssText = "top:" + ( topOffset ) + "px;left:" + ( leftOffset ) + "px;";
|
||||
|
||||
if (e.onOpen) {
|
||||
e.onOpen ( e, div );
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
openMenu = function( e )
|
||||
{
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
else {
|
||||
e = {pageX:0, pageY:0};
|
||||
}
|
||||
|
||||
// ----
|
||||
var instance = getInstance ( this );
|
||||
var pageX = e.pageX || e.clientX + doc.documentElement.scrollLeft;
|
||||
var pageY = e.pageY || e.clientY + doc.documentElement.scrollTop;
|
||||
|
||||
if (!instance) return false;
|
||||
|
||||
instance.curr_target = e.target || e.srcElement;
|
||||
|
||||
openContext ( instance, pageX, pageY );
|
||||
},
|
||||
getInstance = function( elem ) {
|
||||
return contextStorage[ elem.getAttribute( 'data-token' ) ];
|
||||
};
|
||||
|
||||
/**
|
||||
* Context Menu Constructor
|
||||
**/
|
||||
var contextMenu = namespace.contextMenu = function( elem, options ) {
|
||||
if (!(this instanceof contextMenu)) return new contextMenu( elem, options );
|
||||
if (!options) options = {};
|
||||
|
||||
var open_events = ['contextmenu', 'longpress'];
|
||||
|
||||
this.elem = elem;
|
||||
this.options = [];
|
||||
this.menuClass = options.className || 'pk_open';
|
||||
this.curr_target = null;
|
||||
|
||||
// modified context menu to open only when double click + no movement
|
||||
// if (elem) elem.addEventListener( 'contextmenu', openMenu, false );
|
||||
if (elem) elem.addEventListener( 'pk_ctxmn', openMenu, false );
|
||||
|
||||
this.token = ++_id;
|
||||
if (elem) elem.setAttribute( 'data-token', this.token );
|
||||
|
||||
contextStorage[ this.token ] = this;
|
||||
};
|
||||
/**
|
||||
* Wrapper to the private openMenu function
|
||||
**/
|
||||
contextMenu.prototype.open = function( e ) {
|
||||
openMenu.call( this.elem, e );
|
||||
};
|
||||
contextMenu.prototype.close = function( e ) {
|
||||
closeContext();
|
||||
};
|
||||
contextMenu.prototype.openWithToken = function( token, x, y ) {
|
||||
openContext( contextStorage[ token ], x||0, y||0 );
|
||||
};
|
||||
/**
|
||||
* Closes context and removes it fully
|
||||
**/
|
||||
contextMenu.prototype.destroy = function() {
|
||||
// this.elem.removeEventListener( 'contextmenu', openMenu );
|
||||
this.elem.removeEventListener( 'pk_ctxmn', openMenu );
|
||||
|
||||
closeContext();
|
||||
contextStorage[ this.token ] = null;
|
||||
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Adds option
|
||||
* @param string name of the option
|
||||
* @param function to run when its chosen
|
||||
* @param if this is set, then append the HTML instead of its name in its position
|
||||
* @param initialization code to run when the object is appended to the dom
|
||||
**/
|
||||
contextMenu.prototype.addOption = function( name, callback, isHTML ) {
|
||||
var q = this;
|
||||
this.options.push({ "name" : name, "callback" : function( e ) {
|
||||
|
||||
callback && callback( q, q._open );
|
||||
closeContext( q, true );
|
||||
},
|
||||
"isHTML" : isHTML
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// todo touch controls too? ####
|
||||
doc.addEventListener( closeEvent[0], closeContext, false );
|
||||
doc.addEventListener( 'killCTX', closeContext, false );
|
||||
|
||||
|
||||
PKAE._deps.ContextMenu = contextMenu;
|
||||
|
||||
})( window, document, PKAudioEditor );
|
||||
@@ -0,0 +1,178 @@
|
||||
(function( parent ) {
|
||||
'use strict';
|
||||
|
||||
/** parent object, set in the end of the selfcalling function **/
|
||||
var parent = parent || window,
|
||||
/** instance of File Reader **/
|
||||
reader,
|
||||
readFile,
|
||||
/**
|
||||
* Removes class from element
|
||||
* @param htmlObject target element
|
||||
* @param string "class to be removed"
|
||||
**/
|
||||
removeClass = function( el, value ) {
|
||||
if ( !el.className ) return false;
|
||||
var classes = el.className.split(' '),
|
||||
ret = [];
|
||||
for( var i = 0, l = classes.length; i < l; ++i )
|
||||
if( classes[i] != value )
|
||||
ret.push( classes[ i ] );
|
||||
el.className = ret.join(' ');
|
||||
};
|
||||
|
||||
if( !window.FileReader || !document.addEventListener ) {
|
||||
throw( "File API not supported" );
|
||||
readFile = function(){ throw( "File API not supported" ); };
|
||||
}
|
||||
else {
|
||||
reader = new FileReader();
|
||||
readFile = function ( file, callback, method ) {
|
||||
/** Error handler (throws error at the console) **/
|
||||
reader.onerror = function( e ) {
|
||||
var message,
|
||||
lut = [ "File not found.", "File coulnot be opened",
|
||||
"File couldnot be uploaded", "Couldnot read File", "File too large" ];
|
||||
// http://www.w3.org/TR/FileAPI/#ErrorDescriptions
|
||||
throw( lut[ ( e.target.error.code - 1 ) ] );
|
||||
},
|
||||
/** Success, calling the callback **/
|
||||
reader.onloadend = function( e ) {
|
||||
callback && callback( e.target.result, file.name );
|
||||
reader.onloadend = null;
|
||||
};
|
||||
|
||||
// the method is specified in the beginning of the file
|
||||
reader[ method ]( file );
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag n Drop Files module
|
||||
* @param HTMLElement, could be the Body
|
||||
* @param DOMElement/String, if a string is specified then a div will be built and appended to the body
|
||||
* with that String as its id. If a dom element is passed, that will be used instead. This object
|
||||
* acts as an overlay and the file should be droped to this object. If this object is null, then the first argument
|
||||
* will be used as the overlay.
|
||||
* @param Function, will be called with the file data, and the filename as its arguments
|
||||
* @param String, possible values "text, binarytext, arrabuffer" decides how the file will be read
|
||||
* if let null, defaults to text
|
||||
* @param String, class name to be added to the overlay (default is '__fadingIn')
|
||||
**/
|
||||
parent.dragNDrop = function( body, overlay_id, callback, method, _clss ) {
|
||||
var win = window;
|
||||
// check to see if we are using a mobile device - no need for dragNdrop in devices
|
||||
// that do not support it somehow yet
|
||||
if( ( 'ontouchstart' in window ) )
|
||||
return "mobile";
|
||||
|
||||
/** JS Object, used to define the file-reading method **/
|
||||
var method_lut = {
|
||||
'text' : 'readAsText',
|
||||
'binary' : 'readAsBinaryText',
|
||||
'arrayBuffer': 'readAsArrayBuffer'
|
||||
},
|
||||
/** class added/removed from overlay object **/
|
||||
clss = _clss ? _clss : "__fadingIn",
|
||||
method = method ? method_lut[ method ] : 'readAsText',
|
||||
/** how many events cast (dragenter/dragleave) **/
|
||||
entered = 0,
|
||||
/**
|
||||
* DOMElement sink for the drag events
|
||||
* if left unspecified then the body inherits the role
|
||||
**/
|
||||
overlay = !!overlay_id ? overlay_id : body,
|
||||
/**
|
||||
* (void) if the overlay_id specified is a string, then a div with that id is built
|
||||
* and appended to the body. Else the default is used
|
||||
**/
|
||||
_overlayBuilder = function() {
|
||||
if( typeof overlay_id === "string" )
|
||||
{
|
||||
var tmp = document.createElement( 'div' );
|
||||
overlay = document.createElement( 'div' );
|
||||
overlay.id = overlay_id;
|
||||
|
||||
tmp.innerHTML = "Drag n drop Files!";
|
||||
overlay.appendChild( tmp );
|
||||
|
||||
body.appendChild( overlay );
|
||||
tmp = null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* JS Object
|
||||
* The events Object contains various functions that control
|
||||
* the behavior of the events fired
|
||||
**/
|
||||
events = {
|
||||
/**
|
||||
* (void) Prevents default action and bubbling up
|
||||
**/
|
||||
silencer : function( e ) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation()
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows message to drop file
|
||||
**/
|
||||
onDragEnter : function( e ) {
|
||||
// overlay.className += " " + clss;
|
||||
++entered;
|
||||
|
||||
setTimeout(function() {
|
||||
if( entered > 1 )
|
||||
entered = 1;
|
||||
}, 10 )
|
||||
},
|
||||
/**
|
||||
* Hides the overlay... twist included!
|
||||
**/
|
||||
onDragLeave : function( e ) {
|
||||
--entered;
|
||||
|
||||
if( entered <= 0 )
|
||||
{
|
||||
removeClass( overlay, clss );
|
||||
entered = 0;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Files dropped
|
||||
**/
|
||||
onDrop : function( e ) {
|
||||
// prevent the event from bubbling/firing default
|
||||
events.silencer( e );
|
||||
|
||||
// Hide the overlay
|
||||
removeClass( overlay, clss );
|
||||
entered = 0;
|
||||
|
||||
/** dropped files. **/
|
||||
var files = e.dataTransfer.files,
|
||||
len;
|
||||
|
||||
// If anything is wrong with the dropped files, exit.
|
||||
if( !files || !files.length )
|
||||
return false;
|
||||
|
||||
len = files.length;
|
||||
while( len-- )
|
||||
// iterate files array and load them
|
||||
readFile( files[ len ], callback, method );
|
||||
}
|
||||
};
|
||||
|
||||
(function init() {
|
||||
//_overlayBuilder();
|
||||
// events initialization
|
||||
body.parentNode.addEventListener( "dragenter", events.onDragEnter, false );
|
||||
body.addEventListener( "dragleave", events.onDragLeave, false );
|
||||
body.addEventListener( "dragover", events.silencer, false);
|
||||
body.addEventListener( "drop", events.onDrop, false);
|
||||
return false;
|
||||
})( body );
|
||||
};
|
||||
})( window );
|
||||
@@ -0,0 +1,220 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script>window.update = function(){};</script>
|
||||
<title>AudioMass - Frequency Analysis</title>
|
||||
<style>
|
||||
html,body{height:100%}
|
||||
body{padding:0;margin:0;background:#111;position:relative;z-index:2;overflow:hidden;}
|
||||
div{position:absolute;top:50%;left:0;right:0;z-index:1;margin-top:-18px;text-align:center;user-select:none;
|
||||
font-size:28px;color:#191919;pointer-events:none;font-family:Arial}
|
||||
#e, #d, #f{
|
||||
cursor:pointer;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
display: block;
|
||||
height: 20px;
|
||||
z-index: 99999;
|
||||
background: #eaeaea;
|
||||
left:auto;
|
||||
border-radius:4px;
|
||||
font-family: sans-serif;
|
||||
font-size: 9px;
|
||||
width: 82px;
|
||||
line-height: 20px;
|
||||
padding: 0 6px;
|
||||
text-align: center;
|
||||
opacity:0.9;
|
||||
user-select:none;
|
||||
}
|
||||
|
||||
.b #e, .b #d{
|
||||
top:12px;
|
||||
}
|
||||
|
||||
.b #e, .b #f{
|
||||
display:block;
|
||||
}
|
||||
|
||||
#e{
|
||||
right: 106px;
|
||||
width: 10px;
|
||||
line-height: 20px;
|
||||
padding: 0px 6px;
|
||||
text-align: center;
|
||||
opacity: 0.9;
|
||||
display:none;
|
||||
}
|
||||
|
||||
#f{
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
line-height: 0;
|
||||
border-radius: 0;
|
||||
height: 4px;
|
||||
background: #333;
|
||||
display:none;
|
||||
user-select:none;
|
||||
-moz-user-select:none;
|
||||
}
|
||||
|
||||
.c #f, #f:hover{
|
||||
background:#3C3C3C;
|
||||
}
|
||||
|
||||
</style>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fr" width="600" height="188" style="width:100%;height:100%;display:block"></canvas>
|
||||
<div>FREQUENCY ANALYSER</div>
|
||||
|
||||
<a id="d" onclick="dock()">DOCK INTERFACE</a>
|
||||
<a id="e" onclick="remove()">X</a>
|
||||
<a id="f" onmousedown="drag(event)"></a>
|
||||
|
||||
<script>
|
||||
var d = document;
|
||||
var w = window;
|
||||
var iframe = location.href.indexOf('?iframe') === -1 ? 0 : 1;
|
||||
var canvas = d.getElementById('fr');
|
||||
var ctx = canvas.getContext('2d', {alpha:true, antialias:false});
|
||||
|
||||
w.remove = function (){};
|
||||
if (iframe) {
|
||||
d.body.className = 'b';
|
||||
w.remove = function () {
|
||||
w.parent.PKAudioEditor.ui.Dock ('RequestShowFreqAn', 'eq', [1, 1]);
|
||||
};
|
||||
};
|
||||
|
||||
w.drag = function ( e ) {
|
||||
e.preventDefault ();
|
||||
e.stopPropagation ();
|
||||
|
||||
w.parent.PKAudioEditor.ui.Dock ('RequestDragI', 'eq');
|
||||
};
|
||||
|
||||
w.dock = function () {
|
||||
if (!iframe)
|
||||
{
|
||||
if (!w.opener || !w.opener.PKAudioEditor) {
|
||||
return ;
|
||||
}
|
||||
|
||||
w.opener.PKAudioEditor.ui.Dock ('RequestShowFreqAn', 'eq', [1, 1]);
|
||||
w.close && w.close ();
|
||||
}
|
||||
else
|
||||
{
|
||||
var frm = w.parent.document.getElementById ('pk_fr' + 'eq');
|
||||
var t = 1;
|
||||
|
||||
if (frm && frm.getBoundingClientRect) {
|
||||
var rect = frm.getBoundingClientRect();
|
||||
|
||||
t = [(w.parent.screenLeft + rect.left + 100)||0, (w.parent.screenTop + rect.top + 25)||0];
|
||||
}
|
||||
|
||||
w.parent.PKAudioEditor.ui.Dock ('RequestShowFreqAn', 'eq', [t, 0]);
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(function () {
|
||||
if (!iframe)
|
||||
{
|
||||
if (!w.opener || !w.opener.PKAudioEditor) return ;
|
||||
}
|
||||
|
||||
var WIDTH = w.innerWidth, HEIGHT = w.innerHeight;
|
||||
|
||||
if (canvas.width != WIDTH)
|
||||
{
|
||||
canvas.width = WIDTH;
|
||||
canvas.height = HEIGHT;
|
||||
}
|
||||
|
||||
var bufferLength = 240;
|
||||
var value_changed = false;
|
||||
ctx.clearRect (0, 0, WIDTH, HEIGHT);
|
||||
|
||||
function draw( similarity ) {
|
||||
|
||||
var WIDTH = window.innerWidth, HEIGHT = window.innerHeight;
|
||||
|
||||
if (canvas.width != WIDTH || canvas.height != HEIGHT)
|
||||
{
|
||||
canvas.width = WIDTH;
|
||||
canvas.height = HEIGHT;
|
||||
}
|
||||
|
||||
ctx.fillStyle = 'rgb(0, 0, 0)';
|
||||
ctx.fillRect(0, 0, WIDTH, HEIGHT);
|
||||
|
||||
var barWidth = (WIDTH / bufferLength);
|
||||
var barHeight;
|
||||
var x = 0;
|
||||
|
||||
for(var i = 0; i < bufferLength; i += 1) {
|
||||
barHeight = similarity[i * 2]; // ? similarity[i] : 0;
|
||||
|
||||
// bar height normalize with 255
|
||||
var newheight = ((barHeight / 256) * HEIGHT) >> 0;
|
||||
|
||||
ctx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)';
|
||||
ctx.fillRect (x,HEIGHT - newheight, barWidth, newheight);
|
||||
x += barWidth;// + 1;
|
||||
}
|
||||
|
||||
value_changed = false;
|
||||
};
|
||||
w.draw = draw;
|
||||
|
||||
w.onunload = function () {
|
||||
w.destroy && w.destroy ( iframe );
|
||||
w.destroy = null;
|
||||
};
|
||||
|
||||
w.update = function (freq_arr) {
|
||||
if (!freq_arr)
|
||||
ctx.clearRect (0, 0, WIDTH, HEIGHT);
|
||||
else {
|
||||
if (value_changed) return ;
|
||||
value_changed = true;
|
||||
|
||||
window.requestAnimationFrame(function () {
|
||||
draw (freq_arr);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var last_press = 0;
|
||||
document.addEventListener ('keypress', function ( e ) {
|
||||
if (e.keyCode !== 32) return ;
|
||||
|
||||
e.preventDefault ();
|
||||
e.stopPropagation ();
|
||||
|
||||
if (e.timeStamp - last_press < 100) {
|
||||
return ;
|
||||
}
|
||||
|
||||
last_press = e.timeStamp;
|
||||
|
||||
if (!iframe) {
|
||||
w.opener && w.opener.PKAudioEditor.ui.Dock ('RequestKeyDown', 32);
|
||||
}
|
||||
else {
|
||||
w.parent && w.parent.PKAudioEditor.ui.Dock ('RequestKeyDown', 32);
|
||||
}
|
||||
});
|
||||
|
||||
}, 60);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,131 @@
|
||||
// FLAC worker for encoding audio using libflac.js
|
||||
|
||||
importScripts('libflac.js');
|
||||
|
||||
var flacEncoder;
|
||||
var FLAC_INITIALIZED = false;
|
||||
var sample_rate = 44100;
|
||||
var compression = 5; // Default compression (0-8)
|
||||
var channels = 1;
|
||||
var buffers = [];
|
||||
var bufIndex = 0;
|
||||
var first_buffer = true;
|
||||
var samples_left = null;
|
||||
var samples_right = null;
|
||||
|
||||
function convert(n) {
|
||||
var v = n < 0 ? n * 32768 : n * 32767;
|
||||
return Math.max(-32768, Math.min(32768, v));
|
||||
}
|
||||
|
||||
function initFLAC() {
|
||||
if (FLAC_INITIALIZED) return true;
|
||||
|
||||
// SAMPLERATE, CHANNELS, BPS, COMPRESSION, 0, VERIFY, BLOCK_SIZE);
|
||||
flacEncoder = Flac.create_libflac_encoder(sample_rate, channels, 16, compression, 0, true, 0);
|
||||
if (flacEncoder != 0) {
|
||||
var status = Flac.init_encoder_stream(flacEncoder, function(buffer, bytes) {
|
||||
buffers.push(new Uint8Array(buffer));
|
||||
bufIndex += buffer.byteLength;
|
||||
});
|
||||
|
||||
FLAC_INITIALIZED = true;
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function interleave(inputL, inputR) {
|
||||
var length = inputL.length + inputR.length;
|
||||
var result = new Int32Array(length);
|
||||
|
||||
var index = 0,
|
||||
inputIndex = 0;
|
||||
|
||||
while (index < length) {
|
||||
result[index++] = inputL[inputIndex];
|
||||
result[index++] = inputR[inputIndex];
|
||||
++inputIndex;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
onmessage = function(ev) {
|
||||
if (!ev.data) return;
|
||||
|
||||
if (ev.data.sample_rate) {
|
||||
sample_rate = ev.data.sample_rate / 1;
|
||||
compression = ev.data.flac_compression;
|
||||
channels = ev.data.channels / 1;
|
||||
|
||||
initFLAC();
|
||||
return;
|
||||
}
|
||||
|
||||
if (first_buffer) {
|
||||
samples_left = new Int16Array(ev.data, 0);
|
||||
first_buffer = false;
|
||||
|
||||
if (channels > 1) return;
|
||||
}
|
||||
|
||||
if (ev.data && channels > 1) {
|
||||
samples_right = new Int16Array(ev.data, 0);
|
||||
}
|
||||
|
||||
if (!FLAC_INITIALIZED) {
|
||||
postMessage({percentage: 0});
|
||||
return;
|
||||
}
|
||||
|
||||
// Progress update
|
||||
postMessage({percentage: 50});
|
||||
|
||||
// Process the audio data
|
||||
var interleaved = null;
|
||||
var samples = null;
|
||||
|
||||
if (channels > 1) {
|
||||
// Create interleaved buffer for stereo
|
||||
interleaved = interleave(samples_left, samples_right);
|
||||
samples = interleaved;
|
||||
} else {
|
||||
// Use mono buffer directly
|
||||
samples = samples_left;
|
||||
}
|
||||
|
||||
// Encode the audio data
|
||||
if (channels > 1) {
|
||||
Flac.FLAC__stream_encoder_process_interleaved(flacEncoder, samples, samples_left.length);
|
||||
} else {
|
||||
var tmp_samples = new Int32Array(samples_left.length);
|
||||
var tmp_index = 0;
|
||||
while (tmp_index < samples_left.length) {
|
||||
tmp_samples[tmp_index] = samples_left[tmp_index];
|
||||
++tmp_index;
|
||||
}
|
||||
Flac.FLAC__stream_encoder_process(flacEncoder, [tmp_samples], samples_left.length);
|
||||
}
|
||||
|
||||
// Finish encoding
|
||||
Flac.FLAC__stream_encoder_finish(flacEncoder);
|
||||
|
||||
// Combine all buffers into a single Uint8Array
|
||||
var outputData = new Uint8Array(bufIndex);
|
||||
var offset = 0;
|
||||
for (var i = 0; i < buffers.length; i++) {
|
||||
outputData.set(buffers[i], offset);
|
||||
offset += buffers[i].length;
|
||||
}
|
||||
|
||||
// Create blob and return
|
||||
var blob = new Blob([outputData], {type: 'audio/flac'});
|
||||
postMessage(blob);
|
||||
|
||||
// Clean up
|
||||
Flac.FLAC__stream_encoder_delete(flacEncoder);
|
||||
FLAC_INITIALIZED = false;
|
||||
buffers = [];
|
||||
bufIndex = 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="icomoon" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="files-empty" d="M917.806 602.924c-22.21 30.292-53.174 65.7-87.178 99.704s-69.412 64.964-99.704 87.178c-51.574 37.82-76.592 42.194-90.924 42.194h-368c-44.114 0-80-35.888-80-80v-736c0-44.112 35.886-80 80-80h608c44.112 0 80 35.888 80 80v496c0 14.332-4.372 39.35-42.194 90.924zM785.374 657.374c30.7-30.7 54.8-58.398 72.58-81.374h-153.954v153.946c22.982-17.78 50.678-41.878 81.374-72.572v0zM896 16c0-8.672-7.328-16-16-16h-608c-8.672 0-16 7.328-16 16v736c0 8.672 7.328 16 16 16 0 0 367.956 0.002 368 0v-224c0-17.672 14.324-32 32-32h224v-496zM602.924 917.804c-51.574 37.822-76.592 42.196-90.924 42.196h-368c-44.112 0-80-35.888-80-80v-736c0-38.632 27.528-70.958 64-78.39v814.39c0 8.672 7.328 16 16 16h486.876c-9.646 7.92-19.028 15.26-27.952 21.804z" />
|
||||
<glyph unicode="" glyph-name="file-text2" d="M917.806 730.924c-22.212 30.292-53.174 65.7-87.178 99.704s-69.412 64.964-99.704 87.178c-51.574 37.82-76.592 42.194-90.924 42.194h-496c-44.112 0-80-35.888-80-80v-864c0-44.112 35.888-80 80-80h736c44.112 0 80 35.888 80 80v624c0 14.332-4.372 39.35-42.194 90.924zM785.374 785.374c30.7-30.7 54.8-58.398 72.58-81.374h-153.954v153.946c22.984-17.78 50.678-41.878 81.374-72.572zM896 16c0-8.672-7.328-16-16-16h-736c-8.672 0-16 7.328-16 16v864c0 8.672 7.328 16 16 16 0 0 495.956 0.002 496 0v-224c0-17.672 14.326-32 32-32h224v-624zM736 128h-448c-17.672 0-32 14.326-32 32s14.328 32 32 32h448c17.674 0 32-14.326 32-32s-14.326-32-32-32zM736 256h-448c-17.672 0-32 14.326-32 32s14.328 32 32 32h448c17.674 0 32-14.326 32-32s-14.326-32-32-32zM736 384h-448c-17.672 0-32 14.326-32 32s14.328 32 32 32h448c17.674 0 32-14.326 32-32s-14.326-32-32-32z" />
|
||||
<glyph unicode="" glyph-name="zoom-in" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256zM448 768h-128v-128h-128v-128h128v-128h128v128h128v128h-128z" />
|
||||
<glyph unicode="" glyph-name="zoom-out" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256zM192 640h384v-128h-384z" />
|
||||
<glyph unicode="" glyph-name="hammer" d="M1009.996 131.024l-301.544 301.544c-18.668 18.668-49.214 18.668-67.882 0l-22.626-22.626-184 184 302.056 302.058h-320l-142.058-142.058-14.060 14.058h-67.882v-67.882l14.058-14.058-206.058-206.060 160-160 206.058 206.058 184-184-22.626-22.626c-18.668-18.668-18.668-49.214 0-67.882l301.544-301.544c18.668-18.668 49.214-18.668 67.882 0l113.136 113.136c18.67 18.666 18.67 49.214 0.002 67.882z" />
|
||||
<glyph unicode="" glyph-name="play3" d="M192 832l640-384-640-384z" />
|
||||
<glyph unicode="" glyph-name="pause2" d="M128 832h320v-768h-320zM576 832h320v-768h-320z" />
|
||||
<glyph unicode="" glyph-name="stop2" d="M128 832h768v-768h-768z" />
|
||||
<glyph unicode="" glyph-name="backward2" d="M576 800v-320l320 320v-704l-320 320v-320l-352 352z" />
|
||||
<glyph unicode="" glyph-name="forward3" d="M512 96v320l-320-320v704l320-320v320l352-352z" />
|
||||
<glyph unicode="" glyph-name="first" d="M128 64v768h128v-352l320 320v-320l320 320v-704l-320 320v-320l-320 320v-352z" />
|
||||
<glyph unicode="" glyph-name="last" d="M896 832v-768h-128v352l-320-320v320l-320-320v704l320-320v320l320-320v352z" />
|
||||
<glyph unicode="" glyph-name="previous2" d="M256 64v768h128v-352l320 320v-704l-320 320v-352z" />
|
||||
<glyph unicode="" glyph-name="next2" d="M768 832v-768h-128v352l-320-320v704l320-320v352z" />
|
||||
<glyph unicode="" glyph-name="loop" d="M128 640h640v-192l256 256-256 256v-192h-768v-384h128zM896 256h-640v192l-256-256 256-256v192h768v384h-128z" />
|
||||
<glyph unicode="" glyph-name="scissors" d="M913.826 280.306c-66.684 104.204-181.078 150.064-255.51 102.434-6.428-4.116-12.334-8.804-17.744-13.982l-79.452 124.262 183.462 287.972c15.016 27.73 20.558 60.758 13.266 93.974-6.972 31.75-24.516 58.438-48.102 77.226l-12.278 7.808-217.468-340.114-217.47 340.114-12.276-7.806c-23.586-18.79-41.13-45.476-48.1-77.226-7.292-33.216-1.75-66.244 13.264-93.974l183.464-287.972-79.454-124.262c-5.41 5.178-11.316 9.868-17.744 13.982-74.432 47.63-188.826 1.77-255.51-102.434-66.68-104.2-60.398-227.286 14.032-274.914 74.43-47.632 188.824-1.77 255.508 102.432l164.286 257.87 164.288-257.872c66.684-104.202 181.078-150.064 255.508-102.432 74.428 47.63 80.71 170.716 14.030 274.914zM234.852 159.57c-30.018-46.904-68.534-69.726-94.572-75.446-0.004 0-0.004 0-0.004 0-8.49-1.868-20.294-3.010-28.324 2.128-8.898 5.694-14.804 20.748-15.8 40.276-1.616 31.644 9.642 68.836 30.888 102.034 30.014 46.906 68.53 69.726 94.562 75.444 8.496 1.866 20.308 3.010 28.336-2.126 8.898-5.694 14.802-20.75 15.798-40.272 1.618-31.65-9.64-68.84-30.884-102.038zM480 448c-17.672 0-32 14.328-32 32s14.328 32 32 32 32-14.328 32-32-14.328-32-32-32zM863.85 126.53c-0.996-19.528-6.902-34.582-15.8-40.276-8.030-5.138-19.834-3.996-28.324-2.128 0 0 0 0-0.004 0-26.040 5.718-64.554 28.542-94.572 75.446-21.244 33.198-32.502 70.388-30.884 102.038 0.996 19.522 6.9 34.578 15.798 40.272 8.028 5.136 19.84 3.992 28.336 2.126 26.034-5.716 64.548-28.538 94.562-75.444 21.246-33.198 32.502-70.39 30.888-102.034z" />
|
||||
</font></defs></svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,457 @@
|
||||
(function ( w, d, PKAE ) {
|
||||
'use strict';
|
||||
|
||||
var _pid = 0;
|
||||
var _aid = 0;
|
||||
|
||||
function FXAutomation ( app, filter_modal, val_cb, preview_cb ) {
|
||||
var q = this;
|
||||
|
||||
q.modal = filter_modal;
|
||||
q.app = app;
|
||||
q.wv = app.engine.wavesurfer;
|
||||
q.points = {};
|
||||
q.act = null;
|
||||
q.act_point = null;
|
||||
q.in_auto = false;
|
||||
q.rbuff = null;
|
||||
|
||||
q.btn_auto = _make_btn_auto ( q );
|
||||
|
||||
q.GetValue = function () {
|
||||
var data = [];
|
||||
|
||||
var inputs = q.modal.el_body.getElementsByTagName('input');
|
||||
var plen = q.points.length;
|
||||
|
||||
for (var i = 0; i < inputs.length; ++i)
|
||||
{
|
||||
var curr = inputs[i];
|
||||
if (q.points[curr.id])
|
||||
{
|
||||
var arr = [];
|
||||
var p = q.points[curr.id];
|
||||
for (var j = 0; j < p.length; ++j)
|
||||
{
|
||||
var tmp = {
|
||||
time: p[j].time,
|
||||
val: p[j].val
|
||||
};
|
||||
arr.push(tmp);
|
||||
val_cb && val_cb (tmp, curr);
|
||||
}
|
||||
data.push (arr);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tmp = {
|
||||
val: curr.value
|
||||
};
|
||||
|
||||
data.push (tmp);
|
||||
val_cb && val_cb (tmp, curr);
|
||||
}
|
||||
}
|
||||
|
||||
return (data);
|
||||
};
|
||||
|
||||
q.cw = 500;
|
||||
q.ch = 200;
|
||||
var els = _make_canvas ( q, q.cw, q.ch );
|
||||
q.canvas = els[0];
|
||||
q.ctx = els[1];
|
||||
|
||||
|
||||
var _fillstyle = '#d9d955';
|
||||
q.Render = function () {
|
||||
var ctx = q.ctx;
|
||||
var cw = q.cw;
|
||||
var ch = q.ch;
|
||||
|
||||
if (q.rbuff)
|
||||
q.app.engine.GetWave (q.rbuff, 500, 200, null, null, q.canvas, q.ctx);
|
||||
|
||||
// ctx.clearRect (0, 0, q.cw, q.ch);
|
||||
ctx.fillStyle = _fillstyle;
|
||||
ctx.strokeStyle = '#FF0000';
|
||||
|
||||
if (!q.act) return ;
|
||||
|
||||
ctx.beginPath ();
|
||||
ctx.moveTo ( 0, ch / 2 );
|
||||
var last_y = ch / 2;
|
||||
|
||||
for (var o = 0; o < q.points[q.act.id].length; ++o)
|
||||
{
|
||||
var curr = q.points[q.act.id][ o ];
|
||||
|
||||
var center_x = curr.ax;
|
||||
var center_y = curr.ay;
|
||||
|
||||
ctx.lineTo ( center_x, center_y );
|
||||
last_y = center_y;
|
||||
}
|
||||
|
||||
ctx.lineTo ( cw, last_y );
|
||||
ctx.stroke ();
|
||||
|
||||
var radius = 6;
|
||||
for (var o = 0; o < q.points[q.act.id].length; ++o)
|
||||
{
|
||||
var curr = q.points[q.act.id][ o ];
|
||||
|
||||
var center_x = curr.ax;
|
||||
var center_y = curr.ay;
|
||||
|
||||
ctx.beginPath ();
|
||||
ctx.arc (center_x, center_y, radius, 0, 2 * Math.PI, false);
|
||||
|
||||
if (curr === q.act_point) {
|
||||
ctx.shadowBlur = 24;
|
||||
|
||||
if (curr._on)
|
||||
ctx.fillStyle = '#fff';
|
||||
else
|
||||
ctx.fillStyle = '#686868';
|
||||
|
||||
ctx.stroke ();
|
||||
ctx.fill ();
|
||||
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.fillStyle = _fillstyle;
|
||||
}
|
||||
else if (curr._hov) {
|
||||
|
||||
if (curr._on)
|
||||
ctx.fillStyle = 'blue';
|
||||
else
|
||||
ctx.fillStyle = 'darkblue';
|
||||
|
||||
ctx.stroke ();
|
||||
ctx.fill ();
|
||||
|
||||
ctx.fillStyle = _fillstyle;
|
||||
}
|
||||
else if (curr._on) {
|
||||
ctx.fill ();
|
||||
}
|
||||
else {
|
||||
ctx.fillStyle = '#555';
|
||||
ctx.fill ();
|
||||
ctx.fillStyle = _fillstyle;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
_make_controls ( q );
|
||||
|
||||
// -------
|
||||
function _make_controls ( q ) {
|
||||
var click_time = 0;
|
||||
q.canvas.addEventListener ('click', function ( e ) {
|
||||
if (!q.act) return;
|
||||
if (e.timeStamp - click_time < 260)
|
||||
{
|
||||
var bounds = q.canvas.getBoundingClientRect ();
|
||||
var cw = q.cw;
|
||||
var ch = q.ch;
|
||||
var posx = e.clientX - bounds.left;
|
||||
var posy = e.clientY - bounds.top;
|
||||
|
||||
var rel_x = posx / cw;
|
||||
var rel_y = posy / ch;
|
||||
|
||||
if (!q.points[q.act.id]) q.points[q.act.id] = [];
|
||||
|
||||
var duration;
|
||||
var region = q.wv.regions.list[0];
|
||||
if (region) {
|
||||
duration = region.end - region.start;
|
||||
} else {
|
||||
duration = q.wv.getDuration();
|
||||
}
|
||||
|
||||
q.points[q.act.id].push ({
|
||||
// el:q.act.el,
|
||||
id: ++_pid,
|
||||
x: rel_x,
|
||||
y: rel_y,
|
||||
ax: rel_x * cw,
|
||||
ay: rel_y * ch,
|
||||
time: duration * rel_x,
|
||||
val : ((1 - rel_y) * (q.act.max - q.act.min)) + q.act.min,
|
||||
_on: true,
|
||||
_hov: false,
|
||||
});
|
||||
|
||||
q.points[q.act.id].sort( _compare );
|
||||
q.act_point = q.points[q.act.id][q.points[q.act.id].length - 1];
|
||||
|
||||
//_process ( q, q.wv.backend.buffer );
|
||||
|
||||
q.Render ();
|
||||
// ----
|
||||
}
|
||||
|
||||
click_time = e.timeStamp;
|
||||
}, false);
|
||||
|
||||
var is_dragging = false;
|
||||
var skip = 3;
|
||||
q.canvas.addEventListener ('mousemove', function ( e ) {
|
||||
if (!is_dragging || !q.act_point) return ;
|
||||
|
||||
var ex = 0;
|
||||
var ey = 0;
|
||||
|
||||
if (e.touches) {
|
||||
if (e.touches.length > 1) { return ; }
|
||||
|
||||
ex = e.touches[0].clientX;
|
||||
ey = e.touches[0].clientY;
|
||||
} else {
|
||||
ex = e.clientX;
|
||||
ey = e.clientY;
|
||||
}
|
||||
|
||||
var bounds = q.canvas.getBoundingClientRect ();
|
||||
var cw = q.cw;
|
||||
var ch = q.ch;
|
||||
|
||||
var posx = ex - bounds.left;
|
||||
var posy = ey - bounds.top;
|
||||
|
||||
var rel_x = posx / cw;
|
||||
var rel_y = posy / ch;
|
||||
|
||||
q.act_point.ax = posx;
|
||||
q.act_point.ay = posy;
|
||||
|
||||
q.act_point.x = rel_x;
|
||||
q.act_point.y = rel_y;
|
||||
|
||||
var duration;
|
||||
var region = q.wv.regions.list[0];
|
||||
if (region) {
|
||||
duration = region.end - region.start;
|
||||
} else {
|
||||
duration = q.wv.getDuration();
|
||||
}
|
||||
|
||||
q.act_point.time = duration * rel_x;
|
||||
q.act_point.val = ((1 - rel_y) * q.act.max - q.act.min) + q.act.min;
|
||||
|
||||
q.Render ();
|
||||
|
||||
if (--skip === 0) {
|
||||
skip = 4;
|
||||
_process ( q, q.wv.backend.buffer );
|
||||
}
|
||||
});
|
||||
|
||||
q.canvas.addEventListener ('mousedown', function ( e ) {
|
||||
is_dragging = false;
|
||||
if (!q.act) return ;
|
||||
|
||||
var bounds = q.canvas.getBoundingClientRect ();
|
||||
var cw = q.cw;
|
||||
var ch = q.ch;
|
||||
|
||||
var posx = e.clientX - bounds.left;
|
||||
var posy = e.clientY - bounds.top;
|
||||
|
||||
var dist_x = e.is_touch ? 20 : 10;
|
||||
var dist_y = e.is_touch ? 20 : 9;
|
||||
|
||||
if (!q.points[q.act.id]) q.points[q.act.id] = [];
|
||||
|
||||
for (var o = 0; o < q.points[q.act.id].length; ++o)
|
||||
{
|
||||
var curr = q.points[q.act.id][ o ];
|
||||
if ( Math.abs (curr.ax - posx) < dist_x && Math.abs (curr.ay - posy) < dist_y)
|
||||
{
|
||||
is_dragging = true;
|
||||
q.act_point = curr;
|
||||
q.Render ();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_dragging)
|
||||
{
|
||||
q.act_point = null;
|
||||
q.Render ();
|
||||
}
|
||||
});
|
||||
|
||||
q.canvas.addEventListener ('mouseup', function ( e ) {
|
||||
is_dragging = false;
|
||||
});
|
||||
|
||||
|
||||
var act_el = null;
|
||||
q.modal.el_body.addEventListener ('mouseover', function(e) {
|
||||
if (!q.in_auto) return ;
|
||||
if (e.target.tagName === 'INPUT') {
|
||||
e.target.classList.add ('pk_aut');
|
||||
}
|
||||
});
|
||||
q.modal.el_body.addEventListener ('mouseout', function(e) {
|
||||
if (!q.in_auto) return ;
|
||||
if (e.target.tagName === 'INPUT') {
|
||||
e.target.classList.remove ('pk_aut');
|
||||
}
|
||||
});
|
||||
q.modal.el_body.addEventListener ('click', function(e) {
|
||||
if (!q.in_auto) return ;
|
||||
if (e.target.classList.contains ('pk_aut'))
|
||||
{
|
||||
if (act_el) {
|
||||
act_el.classList.remove ('pk_aut_act');
|
||||
act_el = null;
|
||||
}
|
||||
|
||||
e.target.classList.add ('pk_aut_act');
|
||||
act_el = e.target;
|
||||
|
||||
if (!e.target.id) e.target.id = 'pk' + (++_aid);
|
||||
|
||||
q.act = {
|
||||
id: e.target.id,
|
||||
el: e.target,
|
||||
type:e.target.range,
|
||||
min:e.target.min/1,
|
||||
max:e.target.max/1,
|
||||
step:e.target.step/1
|
||||
};
|
||||
|
||||
if (!q.points[q.act.id]) q.points[q.act.id] = [];
|
||||
|
||||
q.Render ();
|
||||
}
|
||||
|
||||
// console.log( 'click ', e.target );
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function _make_btn_auto ( q ) {
|
||||
var btn_automate = d.createElement ('a');
|
||||
btn_automate.className = 'pk_modal_a_bottom';
|
||||
btn_automate.innerHTML = 'AUTOMATE';
|
||||
|
||||
var in_auto = false;
|
||||
btn_automate.onclick = function () {
|
||||
q.in_auto = !q.in_auto;
|
||||
|
||||
if (q.in_auto) {
|
||||
btn_automate.classList.add ('pk_act');
|
||||
} else {
|
||||
btn_automate.classList.remove ('pk_act');
|
||||
}
|
||||
};
|
||||
|
||||
q.modal.el_body.appendChild( btn_automate );
|
||||
|
||||
return (btn_automate);
|
||||
};
|
||||
|
||||
function _make_canvas ( q ) {
|
||||
var cc = document.createElement ('canvas');
|
||||
cc.width = 500; cc.height = 200;
|
||||
cc.style.background = '#000';
|
||||
var ctx = cc.getContext('2d');
|
||||
|
||||
q.modal.el_body.appendChild( cc );
|
||||
|
||||
var buff = q.wv.backend.buffer;
|
||||
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
ctx.drawImage (img, 0, 0);
|
||||
};
|
||||
|
||||
var offset; var length;
|
||||
var region = q.wv.regions.list[0];
|
||||
if (region) {
|
||||
offset = (region.start * buff.sampleRate) >> 0;
|
||||
length = (region.end * buff.sampleRate) >> 0;
|
||||
}
|
||||
|
||||
_process ( q, buff );
|
||||
|
||||
img.src = q.app.engine.GetWave (buff, 500, 200, offset, length);
|
||||
|
||||
return ([cc, ctx]);
|
||||
};
|
||||
|
||||
function _compare ( a, b ) {
|
||||
if (a.x > b.x) return 1;
|
||||
return -1;
|
||||
};
|
||||
|
||||
function _process ( q, buffer ) {
|
||||
var getOfflineAudioContext = function (channels, sampleRate, duration) {
|
||||
return new (window.OfflineAudioContext ||
|
||||
window.webkitOfflineAudioContext)(channels, duration, sampleRate);
|
||||
};
|
||||
|
||||
var region = q.wv.regions.list[0];
|
||||
var offs = 0;
|
||||
var durr = buffer.duration;
|
||||
if (region) {
|
||||
offs = region.start;
|
||||
durr = region.end - region.start;
|
||||
}
|
||||
|
||||
var audio_ctx = getOfflineAudioContext (
|
||||
1, // orig_buffer.numberOfChannels,
|
||||
8000,
|
||||
(durr * 8000) >> 0
|
||||
);
|
||||
|
||||
var newbuffer = audio_ctx.createBuffer (1, durr * buffer.sampleRate, buffer.sampleRate);
|
||||
newbuffer.getChannelData ( 0 ).set (
|
||||
buffer.getChannelData ( 0 ).slice ( (offs * buffer.sampleRate) / 4, ((offs + durr) * buffer.sampleRate)/4 )
|
||||
);
|
||||
|
||||
var source = audio_ctx.createBufferSource ();
|
||||
source.buffer = newbuffer;
|
||||
|
||||
//var fx = q.app.engine.GetFX ('Gain', q.GetValue ());
|
||||
//console.log ( fx.filter ( audio_ctx, audio_ctx.destination, source ) );
|
||||
|
||||
source.connect (audio_ctx.destination);
|
||||
source.start (0); //, offs, durr);
|
||||
|
||||
var offline_callback = function( rendered_buffer ) {
|
||||
q.rbuff = rendered_buffer;
|
||||
|
||||
debugger;
|
||||
|
||||
// var img = new Image();
|
||||
// img.src = q.app.engine.GetWave (rendered_buffer, 500, 200);
|
||||
|
||||
q.Render ();
|
||||
|
||||
};
|
||||
var offline_renderer = audio_ctx.startRendering();
|
||||
if (offline_renderer)
|
||||
offline_renderer.then( offline_callback ).catch(function(err) {
|
||||
console.log('Rendering failed: ' + err);
|
||||
});
|
||||
else
|
||||
audio_ctx.oncomplete = function ( e ) {
|
||||
offline_callback ( e.renderedBuffer );
|
||||
};
|
||||
|
||||
// ---------
|
||||
};
|
||||
};
|
||||
|
||||
PKAudioEditor._deps.FxAUT = FXAutomation;
|
||||
|
||||
})( window, document, PKAudioEditor );
|
||||
|
After Width: | Height: | Size: 514 B |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,856 @@
|
||||
(function ( w, d, PKAE ) {
|
||||
'use strict';
|
||||
|
||||
|
||||
var StringUtils = {
|
||||
readUTF16String: function(bytes, bigEndian, maxBytes) {
|
||||
var ix = 0;
|
||||
var offset1 = 1, offset2 = 0;
|
||||
maxBytes = Math.min(maxBytes||bytes.length, bytes.length);
|
||||
|
||||
if( bytes[0] == 0xFE && bytes[1] == 0xFF ) {
|
||||
bigEndian = true;
|
||||
ix = 2;
|
||||
} else if( bytes[0] == 0xFF && bytes[1] == 0xFE ) {
|
||||
bigEndian = false;
|
||||
ix = 2;
|
||||
}
|
||||
if( bigEndian ) {
|
||||
offset1 = 0;
|
||||
offset2 = 1;
|
||||
}
|
||||
|
||||
var arr = [];
|
||||
for( var j = 0; ix < maxBytes; j++ ) {
|
||||
var byte1 = bytes[ix+offset1];
|
||||
var byte2 = bytes[ix+offset2];
|
||||
var word1 = (byte1<<8)+byte2;
|
||||
ix += 2;
|
||||
if( word1 == 0x0000 ) {
|
||||
break;
|
||||
} else if( byte1 < 0xD8 || byte1 >= 0xE0 ) {
|
||||
arr[j] = String.fromCharCode(word1);
|
||||
} else {
|
||||
var byte3 = bytes[ix+offset1];
|
||||
var byte4 = bytes[ix+offset2];
|
||||
var word2 = (byte3<<8)+byte4;
|
||||
ix += 2;
|
||||
arr[j] = String.fromCharCode(word1, word2);
|
||||
}
|
||||
}
|
||||
var string = new String(arr.join(""));
|
||||
string.bytesReadCount = ix;
|
||||
return string;
|
||||
},
|
||||
readUTF8String: function(bytes, maxBytes) {
|
||||
var ix = 0;
|
||||
maxBytes = Math.min(maxBytes||bytes.length, bytes.length);
|
||||
|
||||
if( bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF ) {
|
||||
ix = 3;
|
||||
}
|
||||
|
||||
var arr = [];
|
||||
for( var j = 0; ix < maxBytes; j++ ) {
|
||||
var byte1 = bytes[ix++];
|
||||
if( byte1 == 0x00 ) {
|
||||
break;
|
||||
} else if( byte1 < 0x80 ) {
|
||||
arr[j] = String.fromCharCode(byte1);
|
||||
} else if( byte1 >= 0xC2 && byte1 < 0xE0 ) {
|
||||
var byte2 = bytes[ix++];
|
||||
arr[j] = String.fromCharCode(((byte1&0x1F)<<6) + (byte2&0x3F));
|
||||
} else if( byte1 >= 0xE0 && byte1 < 0xF0 ) {
|
||||
var byte2 = bytes[ix++];
|
||||
var byte3 = bytes[ix++];
|
||||
arr[j] = String.fromCharCode(((byte1&0xFF)<<12) + ((byte2&0x3F)<<6) + (byte3&0x3F));
|
||||
} else if( byte1 >= 0xF0 && byte1 < 0xF5) {
|
||||
var byte2 = bytes[ix++];
|
||||
var byte3 = bytes[ix++];
|
||||
var byte4 = bytes[ix++];
|
||||
var codepoint = ((byte1&0x07)<<18) + ((byte2&0x3F)<<12)+ ((byte3&0x3F)<<6) + (byte4&0x3F) - 0x10000;
|
||||
arr[j] = String.fromCharCode(
|
||||
(codepoint>>10) + 0xD800,
|
||||
(codepoint&0x3FF) + 0xDC00
|
||||
);
|
||||
}
|
||||
}
|
||||
var string = new String(arr.join(""));
|
||||
string.bytesReadCount = ix;
|
||||
return string;
|
||||
},
|
||||
readNullTerminatedString: function(bytes, maxBytes) {
|
||||
var arr = [];
|
||||
maxBytes = maxBytes || bytes.length;
|
||||
for ( var i = 0; i < maxBytes; ) {
|
||||
var byte1 = bytes[i++];
|
||||
if( byte1 == 0x00 ) break;
|
||||
arr[i-1] = String.fromCharCode(byte1);
|
||||
}
|
||||
var string = new String(arr.join(""));
|
||||
string.bytesReadCount = i;
|
||||
return string;
|
||||
}
|
||||
};
|
||||
|
||||
var getBytesAt = function(data, iOffset, iLength) {
|
||||
var bytes = new Array(iLength);
|
||||
for( var i = 0; i < iLength; i++ ) {
|
||||
bytes[i] = data.getUint8(iOffset+i);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
var getStringWithCharsetAt = function(data, iOffset, iLength, iCharset) {
|
||||
var bytes = getBytesAt(data, iOffset, iLength);
|
||||
var sString;
|
||||
|
||||
switch( iCharset.toLowerCase() ) {
|
||||
case 'utf-16':
|
||||
case 'utf-16le':
|
||||
case 'utf-16be':
|
||||
sString = StringUtils.readUTF16String(bytes, iCharset);
|
||||
break;
|
||||
|
||||
case 'utf-8':
|
||||
sString = StringUtils.readUTF8String(bytes);
|
||||
break;
|
||||
|
||||
default:
|
||||
sString = StringUtils.readNullTerminatedString(bytes);
|
||||
break;
|
||||
}
|
||||
|
||||
return sString;
|
||||
};
|
||||
|
||||
var ID3v2 = {
|
||||
readFrameData: {}
|
||||
};
|
||||
|
||||
ID3v2.frames = {
|
||||
// v2.2
|
||||
"BUF" : "Recommended buffer size",
|
||||
"CNT" : "Play counter",
|
||||
"COM" : "Comments",
|
||||
"CRA" : "Audio encryption",
|
||||
"CRM" : "Encrypted meta frame",
|
||||
"ETC" : "Event timing codes",
|
||||
"EQU" : "Equalization",
|
||||
"GEO" : "General encapsulated object",
|
||||
"IPL" : "Involved people list",
|
||||
"LNK" : "Linked information",
|
||||
"MCI" : "Music CD Identifier",
|
||||
"MLL" : "MPEG location lookup table",
|
||||
"PIC" : "Attached picture",
|
||||
"POP" : "Popularimeter",
|
||||
"REV" : "Reverb",
|
||||
"RVA" : "Relative volume adjustment",
|
||||
"SLT" : "Synchronized lyric/text",
|
||||
"STC" : "Synced tempo codes",
|
||||
"TAL" : "Album/Movie/Show title",
|
||||
"TBP" : "BPM (Beats Per Minute)",
|
||||
"TCM" : "Composer",
|
||||
"TCO" : "Content type",
|
||||
"TCR" : "Copyright message",
|
||||
"TDA" : "Date",
|
||||
"TDY" : "Playlist delay",
|
||||
"TEN" : "Encoded by",
|
||||
"TFT" : "File type",
|
||||
"TIM" : "Time",
|
||||
"TKE" : "Initial key",
|
||||
"TLA" : "Language(s)",
|
||||
"TLE" : "Length",
|
||||
"TMT" : "Media type",
|
||||
"TOA" : "Original artist(s)/performer(s)",
|
||||
"TOF" : "Original filename",
|
||||
"TOL" : "Original Lyricist(s)/text writer(s)",
|
||||
"TOR" : "Original release year",
|
||||
"TOT" : "Original album/Movie/Show title",
|
||||
"TP1" : "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group",
|
||||
"TP2" : "Band/Orchestra/Accompaniment",
|
||||
"TP3" : "Conductor/Performer refinement",
|
||||
"TP4" : "Interpreted, remixed, or otherwise modified by",
|
||||
"TPA" : "Part of a set",
|
||||
"TPB" : "Publisher",
|
||||
"TRC" : "ISRC (International Standard Recording Code)",
|
||||
"TRD" : "Recording dates",
|
||||
"TRK" : "Track number/Position in set",
|
||||
"TSI" : "Size",
|
||||
"TSS" : "Software/hardware and settings used for encoding",
|
||||
"TT1" : "Content group description",
|
||||
"TT2" : "Title/Songname/Content description",
|
||||
"TT3" : "Subtitle/Description refinement",
|
||||
"TXT" : "Lyricist/text writer",
|
||||
"TXX" : "User defined text information frame",
|
||||
"TYE" : "Year",
|
||||
"UFI" : "Unique file identifier",
|
||||
"ULT" : "Unsychronized lyric/text transcription",
|
||||
"WAF" : "Official audio file webpage",
|
||||
"WAR" : "Official artist/performer webpage",
|
||||
"WAS" : "Official audio source webpage",
|
||||
"WCM" : "Commercial information",
|
||||
"WCP" : "Copyright/Legal information",
|
||||
"WPB" : "Publishers official webpage",
|
||||
"WXX" : "User defined URL link frame",
|
||||
// v2.3
|
||||
"AENC" : "Audio encryption",
|
||||
"APIC" : "Attached picture",
|
||||
"COMM" : "Comments",
|
||||
"COMR" : "Commercial frame",
|
||||
"ENCR" : "Encryption method registration",
|
||||
"EQUA" : "Equalization",
|
||||
"ETCO" : "Event timing codes",
|
||||
"GEOB" : "General encapsulated object",
|
||||
"GRID" : "Group identification registration",
|
||||
"IPLS" : "Involved people list",
|
||||
"LINK" : "Linked information",
|
||||
"MCDI" : "Music CD identifier",
|
||||
"MLLT" : "MPEG location lookup table",
|
||||
"OWNE" : "Ownership frame",
|
||||
"PRIV" : "Private frame",
|
||||
"PCNT" : "Play counter",
|
||||
"POPM" : "Popularimeter",
|
||||
"POSS" : "Position synchronisation frame",
|
||||
"RBUF" : "Recommended buffer size",
|
||||
"RVAD" : "Relative volume adjustment",
|
||||
"RVRB" : "Reverb",
|
||||
"SYLT" : "Synchronized lyric/text",
|
||||
"SYTC" : "Synchronized tempo codes",
|
||||
"TALB" : "Album/Movie/Show title",
|
||||
"TBPM" : "BPM (beats per minute)",
|
||||
"TCOM" : "Composer",
|
||||
"TCON" : "Content type",
|
||||
"TCOP" : "Copyright message",
|
||||
"TDAT" : "Date",
|
||||
"TDLY" : "Playlist delay",
|
||||
"TENC" : "Encoded by",
|
||||
"TEXT" : "Lyricist/Text writer",
|
||||
"TFLT" : "File type",
|
||||
"TIME" : "Time",
|
||||
"TIT1" : "Content group description",
|
||||
"TIT2" : "Title/songname/content description",
|
||||
"TIT3" : "Subtitle/Description refinement",
|
||||
"TKEY" : "Initial key",
|
||||
"TLAN" : "Language(s)",
|
||||
"TLEN" : "Length",
|
||||
"TMED" : "Media type",
|
||||
"TOAL" : "Original album/movie/show title",
|
||||
"TOFN" : "Original filename",
|
||||
"TOLY" : "Original lyricist(s)/text writer(s)",
|
||||
"TOPE" : "Original artist(s)/performer(s)",
|
||||
"TORY" : "Original release year",
|
||||
"TOWN" : "File owner/licensee",
|
||||
"TPE1" : "Lead performer(s)/Soloist(s)",
|
||||
"TPE2" : "Band/orchestra/accompaniment",
|
||||
"TPE3" : "Conductor/performer refinement",
|
||||
"TPE4" : "Interpreted, remixed, or otherwise modified by",
|
||||
"TPOS" : "Part of a set",
|
||||
"TPUB" : "Publisher",
|
||||
"TRCK" : "Track number/Position in set",
|
||||
"TRDA" : "Recording dates",
|
||||
"TRSN" : "Internet radio station name",
|
||||
"TRSO" : "Internet radio station owner",
|
||||
"TSIZ" : "Size",
|
||||
"TSRC" : "ISRC (international standard recording code)",
|
||||
"TSSE" : "Software/Hardware and settings used for encoding",
|
||||
"TYER" : "Year",
|
||||
"TXXX" : "User defined text information frame",
|
||||
"UFID" : "Unique file identifier",
|
||||
"USER" : "Terms of use",
|
||||
"USLT" : "Unsychronized lyric/text transcription",
|
||||
"WCOM" : "Commercial information",
|
||||
"WCOP" : "Copyright/Legal information",
|
||||
"WOAF" : "Official audio file webpage",
|
||||
"WOAR" : "Official artist/performer webpage",
|
||||
"WOAS" : "Official audio source webpage",
|
||||
"WORS" : "Official internet radio station homepage",
|
||||
"WPAY" : "Payment",
|
||||
"WPUB" : "Publishers official webpage",
|
||||
"WXXX" : "User defined URL link frame"
|
||||
};
|
||||
|
||||
var pictureType = [
|
||||
"32x32 pixels 'file icon' (PNG only)",
|
||||
"Other file icon",
|
||||
"Cover (front)",
|
||||
"Cover (back)",
|
||||
"Leaflet page",
|
||||
"Media (e.g. lable side of CD)",
|
||||
"Lead artist/lead performer/soloist",
|
||||
"Artist/performer",
|
||||
"Conductor",
|
||||
"Band/Orchestra",
|
||||
"Composer",
|
||||
"Lyricist/text writer",
|
||||
"Recording Location",
|
||||
"During recording",
|
||||
"During performance",
|
||||
"Movie/video screen capture",
|
||||
"A bright coloured fish",
|
||||
"Illustration",
|
||||
"Band/artist logotype",
|
||||
"Publisher/Studio logotype"
|
||||
];
|
||||
|
||||
var getStringAt = function(data, iOffset, iLength) {
|
||||
var aStr = [];
|
||||
for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) {
|
||||
aStr[j] = String.fromCharCode(data.getUint8(i));
|
||||
}
|
||||
return aStr.join("");
|
||||
};
|
||||
var getLongAt = function(data, iOffset, bBigEndian) {
|
||||
var iByte1 = data.getUint8(iOffset),
|
||||
iByte2 = data.getUint8(iOffset + 1),
|
||||
iByte3 = data.getUint8(iOffset + 2),
|
||||
iByte4 = data.getUint8(iOffset + 3);
|
||||
|
||||
var iLong = bBigEndian ?
|
||||
(((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4
|
||||
: (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
|
||||
if (iLong < 0) iLong += 4294967296;
|
||||
return iLong;
|
||||
};
|
||||
var getSLongAt = function(data, iOffset, bBigEndian) {
|
||||
var iULong = getLongAt(data, iOffset, bBigEndian);
|
||||
if (iULong > 2147483647)
|
||||
return iULong - 4294967296;
|
||||
else
|
||||
return iULong;
|
||||
};
|
||||
var getShortAt = function(data, iOffset, bBigEndian) {
|
||||
var iShort = bBigEndian ?
|
||||
(data.getUint8(iOffset) << 8) + data.getUint8(iOffset + 1)
|
||||
: (data.getUint8(iOffset + 1) << 8) + data.getUint8(iOffset);
|
||||
if (iShort < 0) iShort += 65536;
|
||||
return iShort;
|
||||
};
|
||||
var getInteger24At = function(data, iOffset, bBigEndian) {
|
||||
var iByte1 = data.getUint8(iOffset),
|
||||
iByte2 = data.getUint8(iOffset + 1),
|
||||
iByte3 = data.getUint8(iOffset + 2);
|
||||
|
||||
var iInteger = bBigEndian ?
|
||||
((((iByte1 << 8) + iByte2) << 8) + iByte3)
|
||||
: ((((iByte3 << 8) + iByte2) << 8) + iByte1);
|
||||
if (iInteger < 0) iInteger += 16777216;
|
||||
return iInteger;
|
||||
};
|
||||
var isBitSetAt = function ( dataview, offset, bit ) {
|
||||
var ibyte = dataview.getUint8(offset);
|
||||
return (ibyte & (1 << bit)) != 0;
|
||||
};
|
||||
var readSynchsafeInteger32At = function (offset, data) {
|
||||
var size1 = data.getUint8(offset);
|
||||
var size2 = data.getUint8(offset+1);
|
||||
var size3 = data.getUint8(offset+2);
|
||||
var size4 = data.getUint8(offset+3);
|
||||
// 0x7f = 0b01111111
|
||||
var size = size4 & 0x7f
|
||||
| ((size3 & 0x7f) << 7)
|
||||
| ((size2 & 0x7f) << 14)
|
||||
| ((size1 & 0x7f) << 21);
|
||||
|
||||
return size;
|
||||
};
|
||||
var readFrameFlags = function(data, offset) {
|
||||
var flags =
|
||||
{
|
||||
message:
|
||||
{
|
||||
tag_alter_preservation : isBitSetAt(data, offset, 6),
|
||||
file_alter_preservation : isBitSetAt(data, offset, 5),
|
||||
read_only : isBitSetAt(data, offset, 4)
|
||||
},
|
||||
format:
|
||||
{
|
||||
grouping_identity : isBitSetAt(data, offset+1, 7),
|
||||
compression : isBitSetAt(data, offset+1, 3),
|
||||
encription : isBitSetAt(data, offset+1, 2),
|
||||
unsynchronisation : isBitSetAt(data, offset+1, 1),
|
||||
data_length_indicator : isBitSetAt(data, offset+1, 0)
|
||||
}
|
||||
};
|
||||
|
||||
return flags;
|
||||
};
|
||||
var _shortcuts = {
|
||||
"title" : ["TIT2", "TT2"],
|
||||
"artist" : ["TPE1", "TP1"],
|
||||
"album" : ["TALB", "TAL"],
|
||||
"year" : ["TYER", "TYE"],
|
||||
"comment" : ["COMM", "COM"],
|
||||
"track" : ["TRCK", "TRK"],
|
||||
"genre" : ["TCON", "TCO"],
|
||||
"picture" : ["APIC", "PIC"],
|
||||
"lyrics" : ["USLT", "ULT"]
|
||||
};
|
||||
var _defaultShortcuts = ["title", "artist", "album", "track"];
|
||||
|
||||
var getTagsFromShortcuts = function(shortcuts) {
|
||||
var tags = [];
|
||||
for( var i = 0, shortcut; shortcut = shortcuts[i]; i++ ) {
|
||||
tags = tags.concat(_shortcuts[shortcut]||[shortcut]);
|
||||
}
|
||||
return tags;
|
||||
};
|
||||
var getFrameData = function( frames, ids ) {
|
||||
if( typeof ids == 'string' ) { ids = [ids]; }
|
||||
|
||||
for( var i = 0, id; id = ids[i]; i++ ) {
|
||||
if( id in frames ) { return frames[id].data; }
|
||||
}
|
||||
};
|
||||
var readFrames = function (offset, end, data, id3header, tags) {
|
||||
var frames = {};
|
||||
var frameDataSize;
|
||||
var major = id3header["major"];
|
||||
|
||||
tags = getTagsFromShortcuts(tags || _defaultShortcuts);
|
||||
|
||||
while( offset < end ) {
|
||||
var readFrameFunc = null;
|
||||
var frameData = data;
|
||||
var frameDataOffset = offset;
|
||||
var flags = null;
|
||||
|
||||
switch( major ) {
|
||||
case 2:
|
||||
var frameID = getStringAt(frameData, frameDataOffset, 3);
|
||||
var frameSize = getInteger24At(frameData, frameDataOffset+3, true);
|
||||
var frameHeaderSize = 6;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
var frameID = getStringAt(frameData, frameDataOffset, 4);
|
||||
var frameSize = getLongAt(frameData, frameDataOffset+4, true);
|
||||
var frameHeaderSize = 10;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
var frameID = getStringAt(frameData, frameDataOffset, 4);
|
||||
var frameSize = readSynchsafeInteger32At(frameDataOffset+4, frameData);
|
||||
var frameHeaderSize = 10;
|
||||
break;
|
||||
}
|
||||
// if last frame GTFO
|
||||
if( frameID == "" ) { break; }
|
||||
|
||||
// advance data offset to the next frame data
|
||||
offset += frameHeaderSize + frameSize;
|
||||
// skip unwanted tags
|
||||
if( tags.indexOf( frameID ) < 0 ) { continue; }
|
||||
|
||||
// read frame message and format flags
|
||||
if( major > 2 )
|
||||
{
|
||||
flags = readFrameFlags(frameData, frameDataOffset+8);
|
||||
}
|
||||
|
||||
frameDataOffset += frameHeaderSize;
|
||||
|
||||
// the first 4 bytes are the real data size
|
||||
// (after unsynchronisation && encryption)
|
||||
if( flags && flags.format.data_length_indicator )
|
||||
{
|
||||
frameDataSize = readSynchsafeInteger32At(frameDataOffset, frameData);
|
||||
frameDataOffset += 4;
|
||||
frameSize -= 4;
|
||||
}
|
||||
|
||||
// TODO: support unsynchronisation
|
||||
if( flags && flags.format.unsynchronisation )
|
||||
{
|
||||
//frameData = removeUnsynchronisation(frameData, frameSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
// find frame parsing function
|
||||
|
||||
if( frameID in ID3v2.readFrameData ) {
|
||||
readFrameFunc = ID3v2.readFrameData[frameID];
|
||||
} else if( frameID[0] == "T" ) {
|
||||
readFrameFunc = ID3v2.readFrameData["T*"];
|
||||
}
|
||||
|
||||
var parsedData = readFrameFunc ? readFrameFunc(frameDataOffset, frameSize, frameData, flags) : undefined;
|
||||
var desc = frameID in ID3v2.frames ? ID3v2.frames[frameID] : 'Unknown';
|
||||
|
||||
var frame = {
|
||||
id : frameID,
|
||||
size : frameSize,
|
||||
description : desc,
|
||||
data : parsedData
|
||||
};
|
||||
|
||||
if( frameID in frames ) {
|
||||
if( frames[frameID].id ) {
|
||||
frames[frameID] = [frames[frameID]];
|
||||
}
|
||||
frames[frameID].push(frame);
|
||||
} else {
|
||||
frames[frameID] = frame;
|
||||
}
|
||||
}
|
||||
|
||||
return frames;
|
||||
};
|
||||
|
||||
function getTextEncoding( bite ) {
|
||||
var charset;
|
||||
switch( bite )
|
||||
{
|
||||
case 0x00:
|
||||
charset = 'iso-8859-1';
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
charset = 'utf-16';
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
charset = 'utf-16be';
|
||||
break;
|
||||
|
||||
case 0x03:
|
||||
charset = 'utf-8';
|
||||
break;
|
||||
}
|
||||
|
||||
return charset;
|
||||
}
|
||||
|
||||
function getTime( duration )
|
||||
{
|
||||
var duration = duration/1000,
|
||||
seconds = Math.floor( duration ) % 60,
|
||||
minutes = Math.floor( duration/60 ) % 60,
|
||||
hours = Math.floor( duration/3600 );
|
||||
|
||||
return {
|
||||
seconds : seconds,
|
||||
minutes : minutes,
|
||||
hours : hours
|
||||
};
|
||||
}
|
||||
|
||||
function formatTime( time )
|
||||
{
|
||||
var seconds = time.seconds < 10 ? '0'+time.seconds : time.seconds;
|
||||
var minutes = (time.hours > 0 && time.minutes < 10) ? '0'+time.minutes : time.minutes;
|
||||
|
||||
return (time.hours>0?time.hours+':':'') + minutes + ':' + seconds;
|
||||
}
|
||||
|
||||
ID3v2.readFrameData['APIC'] = function readPictureFrame(offset, length, data, flags, v) {
|
||||
v = v || '3';
|
||||
|
||||
var start = offset;
|
||||
var charset = getTextEncoding( data.getUint8(offset) );
|
||||
switch( v ) {
|
||||
case '2':
|
||||
var format = getStringAt(data, offset+1, 3);
|
||||
offset += 4;
|
||||
break;
|
||||
|
||||
case '3':
|
||||
case '4':
|
||||
var format = getStringWithCharsetAt(data, offset+1, length - (offset-start), '');
|
||||
offset += 1 + format.bytesReadCount;
|
||||
break;
|
||||
}
|
||||
var bite = data.getUint8(offset, 1);
|
||||
var type = pictureType[bite];
|
||||
var desc = getStringWithCharsetAt(data, offset+1, length - (offset-start), charset);
|
||||
|
||||
offset += 1 + desc.bytesReadCount;
|
||||
|
||||
return {
|
||||
"format" : format.toString(),
|
||||
"type" : type,
|
||||
"description" : desc.toString(),
|
||||
"data" : getBytesAt(data, offset, (start+length) - offset)
|
||||
};
|
||||
};
|
||||
|
||||
ID3v2.readFrameData['COMM'] = function readCommentsFrame(offset, length, data) {
|
||||
var start = offset;
|
||||
var charset = getTextEncoding( data.getUint8(offset) );
|
||||
var language = getStringAt(data, offset+1, 3 );
|
||||
var shortdesc = getStringWithCharsetAt(data, offset+4, length-4, charset);
|
||||
|
||||
offset += 4 + shortdesc.bytesReadCount;
|
||||
var text = getStringWithCharsetAt(data, offset, (start+length) - offset, charset );
|
||||
|
||||
return {
|
||||
language : language,
|
||||
short_description : shortdesc.toString(),
|
||||
text : text.toString()
|
||||
};
|
||||
};
|
||||
|
||||
ID3v2.readFrameData['COM'] = ID3v2.readFrameData['COMM'];
|
||||
|
||||
ID3v2.readFrameData['PIC'] = function(offset, length, data, flags) {
|
||||
return ID3v2.readFrameData['APIC'](offset, length, data, flags, '2');
|
||||
};
|
||||
|
||||
ID3v2.readFrameData['PCNT'] = function readCounterFrame(offset, length, data) {
|
||||
// FIXME: implement the rest of the spec
|
||||
return data.getInteger32At(offset);
|
||||
};
|
||||
|
||||
ID3v2.readFrameData['CNT'] = ID3v2.readFrameData['PCNT'];
|
||||
|
||||
ID3v2.readFrameData['T*'] = function readTextFrame(offset, length, data) {
|
||||
var charset = getTextEncoding( data.getUint8(offset) );
|
||||
|
||||
return getStringWithCharsetAt(data, offset+1, length-1, charset).toString();
|
||||
};
|
||||
|
||||
ID3v2.readFrameData['TCON'] = function readGenreFrame(offset, length, data) {
|
||||
var text = ID3v2.readFrameData['T*'].apply( this, arguments );
|
||||
return text.replace(/^\(\d+\)/, '');
|
||||
};
|
||||
|
||||
ID3v2.readFrameData['TCO'] = ID3v2.readFrameData['TCON'];
|
||||
|
||||
//ID3v2.readFrameData['TLEN'] = function readLengthFrame(offset, length, data) {
|
||||
// var text = ID3v2.readFrameData['T*'].apply( this, arguments );
|
||||
//
|
||||
// return {
|
||||
// text : text,
|
||||
// parsed : formatTime( getTime(parseInt(text)) )
|
||||
// };
|
||||
//};
|
||||
|
||||
ID3v2.readFrameData['USLT'] = function readLyricsFrame(offset, length, data) {
|
||||
var start = offset;
|
||||
var charset = getTextEncoding( data.getUint8(offset) );
|
||||
var language = getStringAt(data, offset+1, 3 );
|
||||
var descriptor = getStringWithCharsetAt(data, offset+4, length-4, charset );
|
||||
|
||||
offset += 4 + descriptor.bytesReadCount;
|
||||
var lyrics = getStringWithCharsetAt(data, offset, (start+length) - offset, charset );
|
||||
|
||||
return {
|
||||
language : language,
|
||||
descriptor : descriptor.toString(),
|
||||
lyrics : lyrics.toString()
|
||||
};
|
||||
};
|
||||
|
||||
ID3v2.readFrameData['ULT'] = ID3v2.readFrameData['USLT'];
|
||||
|
||||
|
||||
ID3v2.ReadTags = function ( arraybuffer ) {
|
||||
var data = new DataView ( arraybuffer );
|
||||
var offset = 0;
|
||||
|
||||
|
||||
var major = data.getUint8(offset+3);
|
||||
if( major > 4 ) { return {version: '>2.4'}; }
|
||||
var revision = data.getUint8(offset+4);
|
||||
var unsynch = isBitSetAt(data, offset+5, 7);
|
||||
var xheader = isBitSetAt(data, offset+5, 6);
|
||||
var xindicator = isBitSetAt(data, offset+5, 5);
|
||||
var size = readSynchsafeInteger32At(offset+6, data);
|
||||
offset += 10;
|
||||
|
||||
if( xheader ) {
|
||||
var xheadersize = data.getInt32( offset, true ); //data.getLongAt(offset, true);
|
||||
// The 'Extended header size', currently 6 or 10 bytes, excludes itself.
|
||||
offset += xheadersize + 4;
|
||||
}
|
||||
|
||||
var id3 = {
|
||||
"version" : '2.' + major + '.' + revision,
|
||||
"major" : major,
|
||||
"revision" : revision,
|
||||
"flags" : {
|
||||
"unsynchronisation" : unsynch,
|
||||
"extended_header" : xheader,
|
||||
"experimental_indicator" : xindicator
|
||||
},
|
||||
"size" : size
|
||||
};
|
||||
|
||||
var frames = unsynch ? {} : readFrames(offset, size-10, data, id3);
|
||||
// create shortcuts for most common data
|
||||
for( var name in _shortcuts ) if(_shortcuts.hasOwnProperty(name)) {
|
||||
var data = getFrameData( frames, _shortcuts[name] );
|
||||
if( data ) id3[name] = data;
|
||||
}
|
||||
|
||||
for( var frame in frames ) {
|
||||
if( frames.hasOwnProperty(frame) ) {
|
||||
id3[frame] = frames[frame];
|
||||
}
|
||||
}
|
||||
|
||||
return id3;
|
||||
};
|
||||
|
||||
w.ID3v2 = ID3v2;
|
||||
|
||||
|
||||
/// -------
|
||||
|
||||
var ID4 = {};
|
||||
|
||||
ID4.types = {
|
||||
'0' : 'uint8',
|
||||
'1' : 'text',
|
||||
'13' : 'jpeg',
|
||||
'14' : 'png',
|
||||
'21' : 'uint8'
|
||||
};
|
||||
ID4.atom = {
|
||||
'©alb': ['album'],
|
||||
'©art': ['artist'],
|
||||
'©ART': ['artist'],
|
||||
'aART': ['artist'],
|
||||
'©day': ['year'],
|
||||
'©nam': ['title'],
|
||||
'©gen': ['genre'],
|
||||
'trkn': ['track'],
|
||||
'©wrt': ['composer'],
|
||||
'©too': ['encoder'],
|
||||
'cprt': ['copyright'],
|
||||
'covr': ['picture'],
|
||||
'©grp': ['grouping'],
|
||||
'keyw': ['keyword'],
|
||||
'©lyr': ['lyrics'],
|
||||
'©cmt': ['comment'],
|
||||
'tmpo': ['tempo'],
|
||||
'cpil': ['compilation'],
|
||||
'disk': ['disc']
|
||||
};
|
||||
|
||||
ID4.loadData = function(arraybuffer, callback) {
|
||||
var data = new DataView ( arraybuffer );
|
||||
// load the header of the first block
|
||||
loadAtom(data, 0, data.byteLength, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make sure that the [offset, offset+7] bytes (the block header) are
|
||||
* already loaded before calling this function.
|
||||
*/
|
||||
function loadAtom(data, offset, length, callback) {
|
||||
// 8 is the size of the atomSize and atomName fields.
|
||||
// When reading the current block we always read 8 more bytes in order
|
||||
// to also read the header of the next block.
|
||||
var atomSize = getLongAt(data, offset, true);
|
||||
if (atomSize == 0) return callback();
|
||||
var atomName = getStringAt(data, offset + 4, 4);
|
||||
|
||||
// Container atoms
|
||||
if (['moov', 'udta', 'meta', 'ilst'].indexOf(atomName) > -1)
|
||||
{
|
||||
if (atomName == 'meta') offset += 4; // next_item_id (uint32)
|
||||
// data.loadRange([offset+8, offset+8 + 8], function() {
|
||||
loadAtom(data, offset + 8, atomSize - 8, callback);
|
||||
// });
|
||||
} else {
|
||||
// Value atoms
|
||||
var readAtom = atomName in ID4.atom;
|
||||
// data.loadRange([offset+(readAtom?0:atomSize), offset+atomSize + 8], function() {
|
||||
loadAtom(data, offset+atomSize, length, callback);
|
||||
// });
|
||||
}
|
||||
};
|
||||
|
||||
ID4.ReadTags = function(arraybuffer) {
|
||||
var data = new DataView ( arraybuffer );
|
||||
var tag = {};
|
||||
readAtom(tag, data, 0, data.byteLength);
|
||||
return tag;
|
||||
};
|
||||
|
||||
function readAtom(tag, data, offset, length, indent)
|
||||
{
|
||||
// debugger;
|
||||
|
||||
indent = indent === undefined ? "" : indent + " ";
|
||||
var seek = offset;
|
||||
while (seek < offset + length)
|
||||
{
|
||||
var atomSize = data.getInt32(seek); // getLongAt(data, seek, true);
|
||||
if (atomSize == 0) return;
|
||||
var atomName = getStringAt(data, seek + 4, 4);
|
||||
// Container atoms
|
||||
if (atomName === 'meta')
|
||||
{
|
||||
seek += 4; // next_item_id (uint32)
|
||||
readAtom(tag, data, seek + 8, atomSize - 8, indent);
|
||||
return;
|
||||
}
|
||||
if (atomName === 'moov' || atomName === 'udta' || atomName === 'ilst' ) // ['moov', 'udta', 'meta', 'ilst'].indexOf(atomName) > -1)
|
||||
{
|
||||
readAtom(tag, data, seek + 8, atomSize - 8, indent);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
if (['moov', 'udta', 'meta', 'ilst'].indexOf(atomName) > -1)
|
||||
{
|
||||
if (atomName === 'meta') seek += 4; // next_item_id (uint32)
|
||||
readAtom(tag, data, seek + 8, atomSize - 8, indent);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
// Value atoms
|
||||
if (ID4.atom[atomName])
|
||||
{
|
||||
var klass = getInteger24At(data, seek + 16 + 1, true);
|
||||
var atom = ID4.atom[atomName];
|
||||
var type = ID4.types[klass];
|
||||
if (atomName === 'trkn')
|
||||
{
|
||||
tag[atom[0]] = data.getUint8(seek + 16 + 11);
|
||||
tag['count'] = data.getUint8(seek + 16 + 13);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 16: name + size + "data" + size (4 bytes each)
|
||||
// 4: atom version (1 byte) + atom flags (3 bytes)
|
||||
// 4: NULL (usually locale indicator)
|
||||
var dataStart = seek + 16 + 4 + 4;
|
||||
var dataEnd = atomSize - 16 - 4 - 4;
|
||||
var atomData;
|
||||
switch( type ) {
|
||||
case 'text':
|
||||
atomData = getStringWithCharsetAt(data, dataStart, dataEnd, "UTF-8");
|
||||
break;
|
||||
|
||||
case 'uint8':
|
||||
atomData = getShortAt(data, dataStart);
|
||||
break;
|
||||
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
atomData = {
|
||||
format : "image/" + type,
|
||||
data : getBytesAt(data, dataStart, dataEnd)
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (atom[0] === "comment") {
|
||||
tag[atom[0]] = {
|
||||
"text": atomData
|
||||
};
|
||||
} else {
|
||||
tag[atom[0]] = atomData;
|
||||
}
|
||||
}
|
||||
}
|
||||
seek += atomSize;
|
||||
}
|
||||
}
|
||||
|
||||
w.ID4 = ID4;
|
||||
|
||||
})( window, document, PKAudioEditor );
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" manifest="audiomass.appcache">
|
||||
<head>
|
||||
<title>AudioMass - Audio Editor</title>
|
||||
<link href="ico.png" rel="shortcut icon">
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/>
|
||||
|
||||
<meta name="description" content="AudioMass is a free full-featured web-based audio & waveform editing tool"/>
|
||||
<meta property="og:image" content="https://audiomass.co/icon.jpg"/>
|
||||
<meta property="og:title" content="AudioMass">
|
||||
<meta property="og:url" content="https://audiomass.co/">
|
||||
<meta property="og:description" content="AudioMass is a free full-featured web-based audio & waveform editing tool">
|
||||
<meta name="keywords" content="AudioMass, WebAudio, WaveForm, audio editing, free audio editing, audio tool, waveform editor, sound editor, open source">
|
||||
<link rel="apple-touch-icon" href="https://audiomass.co/icon-app.png">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-title" content="AudioMass">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:site" content="@pkalogiros">
|
||||
<meta name="twitter:creator" content="@pkalogiros">
|
||||
<meta name="twitter:title" content="AudioMass - Audio Editor">
|
||||
<meta name="twitter:description" content="AudioMass is a free full-featured web-based audio & waveform editing tool">
|
||||
<meta name="twitter:image" content="https://audiomass.co/icon.jpg">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="dist/wavesurfer.js"></script>
|
||||
<script src="dist/plugin/wavesurfer.regions.js"></script>
|
||||
<script src="oneup.js"></script>
|
||||
|
||||
<script src="app.js"></script>
|
||||
<script src="keys.js"></script>
|
||||
<script src="contextmenu.js"></script>
|
||||
<script src="ui-fx.js"></script>
|
||||
<script src="ui.js"></script>
|
||||
<script src="modal.js"></script>
|
||||
<script src="state.js"></script>
|
||||
<script src="engine.js"></script>
|
||||
<script src="actions.js"></script>
|
||||
<script src="drag.js"></script>
|
||||
<script src="recorder.js"></script>
|
||||
|
||||
<script src="welcome.js"></script>
|
||||
<script src="fx-pg-eq.js"></script>
|
||||
<script src="fx-auto.js"></script>
|
||||
<script src="local.js"></script>
|
||||
<script src="id3.js"></script>
|
||||
<script src="lzma.js"></script>
|
||||
|
||||
|
||||
<!--
|
||||
<script src="multitrack.js"></script>
|
||||
<script src="multitrack-ui.js"></script>
|
||||
-->
|
||||
<script>
|
||||
var editor = PKAudioEditor.init ('app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>AudioMass - Audio Editor</title>
|
||||
<link href="ico.png" rel="shortcut icon">
|
||||
<meta charset="utf-8" />
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
|
||||
|
||||
<meta name="description" content="AudioMass is a free full-featured web-based audio & waveform editing tool"/>
|
||||
<meta property="og:image" content="https://audiomass.co/icon.jpg"/>
|
||||
<meta property="og:title" content="AudioMass">
|
||||
<meta property="og:url" content="https://audiomass.co/">
|
||||
<meta property="og:description" content="AudioMass is a free full-featured web-based audio & waveform editing tool">
|
||||
<meta name="keywords" content="AudioMass, WebAudio, WaveForm, audio editing, free audio editing, audio tool, waveform editor, sound editor, open source">
|
||||
<link rel="apple-touch-icon" href="https://audiomass.co/icon-app.png">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-title" content="AudioMass">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:site" content="@pkalogiros">
|
||||
<meta name="twitter:creator" content="@pkalogiros">
|
||||
<meta name="twitter:title" content="AudioMass - Audio Editor">
|
||||
<meta name="twitter:description" content="AudioMass is a free full-featured web-based audio & waveform editing tool">
|
||||
<meta name="twitter:image" content="https://audiomass.co/icon.jpg">
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="dist/wavesurfer.js"></script>
|
||||
<script src="dist/plugin/wavesurfer.regions.js"></script>
|
||||
<script src="oneup.js"></script>
|
||||
|
||||
<script src="app.js"></script>
|
||||
<script src="keys.js"></script>
|
||||
<script src="contextmenu.js"></script>
|
||||
<script src="ui-fx.js"></script>
|
||||
<script src="ui.js"></script>
|
||||
<script src="modal.js"></script>
|
||||
<script src="state.js"></script>
|
||||
<script src="engine.js"></script>
|
||||
<script src="actions.js"></script>
|
||||
<script src="drag.js"></script>
|
||||
<script src="recorder.js"></script>
|
||||
|
||||
<script src="welcome.js"></script>
|
||||
<script src="fx-pg-eq.js"></script>
|
||||
<script src="fx-auto.js"></script>
|
||||
<script src="local.js"></script>
|
||||
<script src="id3.js"></script>
|
||||
<script src="lzma.js"></script>
|
||||
<!--
|
||||
<script src="multitrack.js"></script>
|
||||
<script src="multitrack-ui.js"></script>
|
||||
-->
|
||||
<script>
|
||||
var editor = PKAudioEditor.init ('app');
|
||||
if ('serviceWorker' in navigator) {
|
||||
try {
|
||||
// navigator.serviceWorker.register( 'sw.js' );
|
||||
} catch ( error ) {}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,89 @@
|
||||
(function( w, d, PKAE ) {
|
||||
'use strict';
|
||||
|
||||
function KeyHandler () {
|
||||
var q = this;
|
||||
|
||||
q.keyMap = {}; // holds a map of all the active keys
|
||||
q.callbacks = {}; // callbacks for when a key combintation becomes active
|
||||
q.singleCallbacks = {}; // callbacks to the 'keypress' event - not required
|
||||
|
||||
q.addCallback = function (callback_name, callback_function, keys) {
|
||||
q.callbacks[ callback_name ] = {
|
||||
keys : keys,
|
||||
callback : callback_function
|
||||
};
|
||||
};
|
||||
q.addSingleCallback = function (callback_name, callback_function, key) {
|
||||
q.singleCallbacks[ callback_name ] = {
|
||||
key : key,
|
||||
callback : callback_function
|
||||
};
|
||||
};
|
||||
q.removeCallback = function ( callback_name ) {
|
||||
q.callbacks[ callback_name ] = null;
|
||||
};
|
||||
|
||||
d.addEventListener ('keydown', function ( e ) {
|
||||
var keyCode = e.keyCode;
|
||||
|
||||
q.keyDown (keyCode, e);
|
||||
});
|
||||
|
||||
q.keyDown = function (keyCode, e ) {
|
||||
q.keyMap[keyCode] = 1;
|
||||
|
||||
for (var key in q.callbacks) {
|
||||
var group = q.callbacks[key];
|
||||
if (!group) continue;
|
||||
|
||||
var l = group.keys.length;
|
||||
var all_ok = true;
|
||||
while (l-- > 0) {
|
||||
if (!q.keyMap[group.keys[l]])
|
||||
{
|
||||
all_ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
all_ok && group.callback && group.callback ( keyCode, q.keyMap, e );
|
||||
}
|
||||
};
|
||||
|
||||
q.keyUp = function ( keyCode ) {
|
||||
q.keyMap[keyCode] = 0;
|
||||
};
|
||||
|
||||
q.keyPress = function ( keyCode, e ) {
|
||||
for (var key in q.singleCallbacks) {
|
||||
var group = q.singleCallbacks[key];
|
||||
if (!group) continue;
|
||||
|
||||
if (group.key === keyCode)
|
||||
group.callback && group.callback ( e );
|
||||
}
|
||||
};
|
||||
|
||||
d.addEventListener ('keyup', function ( e ) {
|
||||
var keyCode = e.keyCode;
|
||||
q.keyUp (keyCode);
|
||||
});
|
||||
|
||||
d.addEventListener ('keypress', function ( e ) {
|
||||
var keyCode = e.keyCode;
|
||||
|
||||
q.keyPress (keyCode, e);
|
||||
});
|
||||
|
||||
w.addEventListener ('blur', function ( e ) {
|
||||
q.keyMap = {};
|
||||
}, false);
|
||||
d.addEventListener ('contextmenu', function( e ) {
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
};
|
||||
|
||||
PKAE._deps.keyhandler = KeyHandler;
|
||||
|
||||
})( window, document, PKAudioEditor );
|
||||
@@ -0,0 +1,262 @@
|
||||
(function ( w, d, PKAE ) {
|
||||
'use strict';
|
||||
|
||||
var db;
|
||||
var db_name = 'audiomass';
|
||||
var db_version = 1;
|
||||
var db_ready = false;
|
||||
|
||||
var compressors = {
|
||||
'l4z' : {
|
||||
ready: false,
|
||||
loading: false,
|
||||
compress: null,
|
||||
decompress: null,
|
||||
init : function ( callback ) {
|
||||
|
||||
var q = this;
|
||||
q.loading = true;
|
||||
|
||||
var lz4BlockWASM;
|
||||
|
||||
lz4BlockCodec.createInstance('wasm').then(instance => {
|
||||
lz4BlockWASM = instance;
|
||||
|
||||
q.ready = true;
|
||||
q.loading = false;
|
||||
q.compress = function( input, offset ) {
|
||||
if (!lz4BlockWASM) {
|
||||
if (input instanceof ArrayBuffer)
|
||||
return new Uint8Array(input);
|
||||
else
|
||||
return input;
|
||||
}
|
||||
|
||||
return lz4BlockWASM.encodeBlock(input, 0);
|
||||
};
|
||||
q.decompress = function( input, offset, size ) {
|
||||
if (!lz4BlockWASM) {
|
||||
if (input instanceof ArrayBuffer)
|
||||
return new Uint8Array(input);
|
||||
else
|
||||
return input;
|
||||
}
|
||||
|
||||
return lz4BlockWASM.decodeBlock(input, 0, size);
|
||||
};
|
||||
|
||||
callback && callback ();
|
||||
});
|
||||
// ---
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var compression = 'l4z';
|
||||
|
||||
function SaveLocal ( app ) {
|
||||
var q = this;
|
||||
q.on = false;
|
||||
|
||||
this.Init = function ( callback ) {
|
||||
if (q.on) {
|
||||
callback && callback ();
|
||||
return ;
|
||||
}
|
||||
|
||||
if (!window.indexedDB) {
|
||||
callback && callback ('err');
|
||||
return ;
|
||||
}
|
||||
|
||||
var request = indexedDB.open (db_name, db_version);
|
||||
|
||||
request.onerror = function(e) {
|
||||
callback && callback ('err');
|
||||
// console.error('Unable to open database.');
|
||||
};
|
||||
|
||||
request.onupgradeneeded = function(e) {
|
||||
var db = e.target.result;
|
||||
db.createObjectStore('sessions', {keyPath:'id'});
|
||||
};
|
||||
|
||||
request.onsuccess = function(e) {
|
||||
db = e.target.result;
|
||||
|
||||
db.onerror = function( e ) {
|
||||
console.log( e );
|
||||
};
|
||||
|
||||
setTimeout(function() {
|
||||
db_ready = true;
|
||||
q.on = true;
|
||||
|
||||
callback && callback ();
|
||||
app.fireEvent ('DidOpenDB', q);
|
||||
},120);
|
||||
};
|
||||
};
|
||||
|
||||
this.SaveSession = function ( buffer, id, name ) {
|
||||
var q = this;
|
||||
|
||||
var comp = compressors[ compression ];
|
||||
if (!comp.loading && !comp.ready) {
|
||||
comp.init (function() {
|
||||
q.SaveSession (buffer, id, name);
|
||||
});
|
||||
|
||||
return ;
|
||||
}
|
||||
|
||||
var chans = buffer.numberOfChannels;
|
||||
var arr_buffs = [];
|
||||
var arr_buffs2 = [];
|
||||
var arr;
|
||||
var tmp;
|
||||
|
||||
var sample_rate = buffer.sampleRate;
|
||||
|
||||
for (var i = 0; i < chans; ++i) {
|
||||
arr = buffer.getChannelData ( i );
|
||||
|
||||
arr_buffs2.push (arr.buffer.byteLength);
|
||||
tmp = comp.compress ( arr.buffer, 0);
|
||||
arr_buffs.push ( tmp.buffer.slice (tmp.byteOffset, tmp.byteLength + tmp.byteOffset));
|
||||
}
|
||||
|
||||
tmp = null;
|
||||
|
||||
var ob = {
|
||||
id : id,
|
||||
name: name,
|
||||
created: new Date().getTime(),
|
||||
data: arr_buffs,
|
||||
data2: arr_buffs2,
|
||||
durr: buffer.duration.toFixed(3)/1,
|
||||
chans: chans,
|
||||
comp: compression,
|
||||
thumb: PKAudioEditor.engine.GetWave (buffer),
|
||||
samplerate: sample_rate
|
||||
};
|
||||
|
||||
var trans = db.transaction(['sessions'], 'readwrite');
|
||||
var addReq = trans.objectStore('sessions').add(ob);
|
||||
|
||||
addReq.onerror = function(e) {
|
||||
app.fireEvent ('ErrorDB', e);
|
||||
|
||||
console.log('error storing data');
|
||||
console.error(e);
|
||||
};
|
||||
|
||||
trans.oncomplete = function ( e ) {
|
||||
app.fireEvent ('DidStoreDB', ob, e );
|
||||
// console.log( 'data stored', id, e );
|
||||
};
|
||||
};
|
||||
|
||||
this.GetSession = function ( id, callback ) {
|
||||
var trans = db.transaction(['sessions'], 'readonly');
|
||||
//hard coded id
|
||||
var req = trans.objectStore('sessions').get(id);
|
||||
|
||||
req.onsuccess = function(e) {
|
||||
// console.log( e.target.result );
|
||||
|
||||
|
||||
var record = e.target.result;
|
||||
|
||||
if (record && record.comp)
|
||||
{
|
||||
var comp = compressors[ compression ];
|
||||
if (!comp.loading && !comp.ready) {
|
||||
comp.init (function() {
|
||||
|
||||
var data_arr = [];
|
||||
var tmp = null;
|
||||
|
||||
for (var i = 0; i < record.data.length; ++i) {
|
||||
tmp = comp.decompress (record.data[i], 0, record.data2[i]);
|
||||
data_arr.push (
|
||||
tmp.buffer.slice (tmp.byteOffset, tmp.byteLength + tmp.byteOffset)
|
||||
);
|
||||
}
|
||||
|
||||
tmp = null;
|
||||
record.data = data_arr;
|
||||
|
||||
callback && callback ( record );
|
||||
});
|
||||
|
||||
return ;
|
||||
}
|
||||
|
||||
var data_arr = [];
|
||||
var tmp = null;
|
||||
|
||||
for (var i = 0; i < record.data.length; ++i) {
|
||||
tmp = comp.decompress (record.data[i], 0, record.data2[i]);
|
||||
data_arr.push (
|
||||
tmp.buffer.slice (tmp.byteOffset, tmp.byteLength + tmp.byteOffset)
|
||||
);
|
||||
}
|
||||
|
||||
tmp = null;
|
||||
record.data = data_arr;
|
||||
}
|
||||
|
||||
callback && callback ( record );
|
||||
};
|
||||
};
|
||||
|
||||
this.DelSession = function ( id, callback ) {
|
||||
var trans = db.transaction(['sessions'], 'readwrite');
|
||||
|
||||
var req = trans.objectStore('sessions').delete (id);
|
||||
req.onsuccess = function (e) {
|
||||
callback && callback ( id );
|
||||
};
|
||||
};
|
||||
|
||||
this.ListSessions = function ( callback ) {
|
||||
var trans = db.transaction(['sessions'], 'readonly');
|
||||
var object_store = trans.objectStore('sessions');
|
||||
var req = object_store.openCursor();
|
||||
var ret = [];
|
||||
|
||||
req.onerror = function(event) {
|
||||
console.err("error fetching data");
|
||||
};
|
||||
req.onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
var key = cursor.primaryKey;
|
||||
var value = cursor.value;
|
||||
|
||||
ret.push (value);
|
||||
cursor.continue();
|
||||
}
|
||||
else {
|
||||
var rr = ret.sort(function compare( a, b ) {
|
||||
if ( a.created > b.created ){
|
||||
return -1;
|
||||
}
|
||||
if ( a.created < b.created ){
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
callback && callback (rr);
|
||||
// no more results
|
||||
}
|
||||
};
|
||||
};
|
||||
// ---
|
||||
};
|
||||
|
||||
PKAudioEditor._deps.fls = SaveLocal;
|
||||
|
||||
})( window, document, PKAudioEditor );
|
||||
@@ -0,0 +1,194 @@
|
||||
/*******************************************************************************
|
||||
|
||||
lz4-block-codec-wasm.js
|
||||
A javascript wrapper around a WebAssembly implementation of
|
||||
LZ4 block format codec.
|
||||
Copyright (C) 2018 Raymond Hill
|
||||
|
||||
BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Home: https://github.com/gorhill/lz4-wasm
|
||||
|
||||
I used the same license as the one picked by creator of LZ4 out of respect
|
||||
for his creation, see https://lz4.github.io/lz4/
|
||||
|
||||
*/
|
||||
|
||||
/* global WebAssembly */
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(function(context) { // >>>> Start of private namespace
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
let wd = (function() {
|
||||
let url = document.currentScript.src;
|
||||
let match = /[^\/]+$/.exec(url);
|
||||
return match !== null ?
|
||||
url.slice(0, match.index) :
|
||||
'';
|
||||
})();
|
||||
|
||||
let growMemoryTo = function(wasmInstance, byteLength) {
|
||||
let lz4api = wasmInstance.exports;
|
||||
let neededByteLength = lz4api.getLinearMemoryOffset() + byteLength;
|
||||
let pageCountBefore = lz4api.memory.buffer.byteLength >>> 16;
|
||||
let pageCountAfter = (neededByteLength + 65535) >>> 16;
|
||||
if ( pageCountAfter > pageCountBefore ) {
|
||||
lz4api.memory.grow(pageCountAfter - pageCountBefore);
|
||||
}
|
||||
return lz4api.memory.buffer;
|
||||
};
|
||||
|
||||
let encodeBlock = function(wasmInstance, inputArray, outputOffset) {
|
||||
let lz4api = wasmInstance.exports;
|
||||
let mem0 = lz4api.getLinearMemoryOffset();
|
||||
let hashTableSize = 65536 * 4;
|
||||
let inputSize = inputArray.byteLength;
|
||||
if ( inputSize >= 0x7E000000 ) { throw new RangeError(); }
|
||||
let memSize =
|
||||
hashTableSize +
|
||||
inputSize +
|
||||
outputOffset + lz4api.lz4BlockEncodeBound(inputSize);
|
||||
let memBuffer = growMemoryTo(wasmInstance, memSize);
|
||||
let hashTable = new Int32Array(memBuffer, mem0, 65536);
|
||||
hashTable.fill(-65536, 0, 65536);
|
||||
let inputMem = new Uint8Array(memBuffer, mem0 + hashTableSize, inputSize);
|
||||
inputMem.set(inputArray);
|
||||
let outputSize = lz4api.lz4BlockEncode(
|
||||
mem0 + hashTableSize,
|
||||
inputSize,
|
||||
mem0 + hashTableSize + inputSize + outputOffset
|
||||
);
|
||||
if ( outputSize === 0 ) { return; }
|
||||
let outputArray = new Uint8Array(
|
||||
memBuffer,
|
||||
mem0 + hashTableSize + inputSize,
|
||||
outputOffset + outputSize
|
||||
);
|
||||
return outputArray;
|
||||
};
|
||||
|
||||
let decodeBlock = function(wasmInstance, inputArray, inputOffset, outputSize) {
|
||||
let inputSize = inputArray.byteLength;
|
||||
let lz4api = wasmInstance.exports;
|
||||
let mem0 = lz4api.getLinearMemoryOffset();
|
||||
let memSize = inputSize + outputSize;
|
||||
let memBuffer = growMemoryTo(wasmInstance, memSize);
|
||||
let inputArea = new Uint8Array(memBuffer, mem0, inputSize);
|
||||
inputArea.set(inputArray);
|
||||
outputSize = lz4api.lz4BlockDecode(
|
||||
mem0 + inputOffset,
|
||||
inputSize - inputOffset,
|
||||
mem0 + inputSize
|
||||
);
|
||||
if ( outputSize === 0 ) { return; }
|
||||
return new Uint8Array(memBuffer, mem0 + inputSize, outputSize);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
context.LZ4BlockWASM = function() {
|
||||
this.lz4wasmInstance = undefined;
|
||||
};
|
||||
|
||||
context.LZ4BlockWASM.prototype = {
|
||||
flavor: 'wasm',
|
||||
|
||||
init: function() {
|
||||
if (
|
||||
typeof WebAssembly !== 'object' ||
|
||||
typeof WebAssembly.instantiateStreaming !== 'function'
|
||||
) {
|
||||
this.lz4wasmInstance = null;
|
||||
}
|
||||
if ( this.lz4wasmInstance === null ) {
|
||||
return Promise.reject();
|
||||
}
|
||||
if ( this.lz4wasmInstance instanceof WebAssembly.Instance ) {
|
||||
return Promise.resolve(this.lz4wasmInstance);
|
||||
}
|
||||
if ( this.lz4wasmInstance === undefined ) {
|
||||
this.lz4wasmInstance = WebAssembly.instantiateStreaming(
|
||||
fetch(wd + 'lz4-block-codec.wasm', { mode: 'same-origin' })
|
||||
).then(result => {
|
||||
this.lz4wasmInstance = undefined;
|
||||
this.lz4wasmInstance = result && result.instance || null;
|
||||
if ( this.lz4wasmInstance !== null ) { return this; }
|
||||
return null;
|
||||
});
|
||||
this.lz4wasmInstance.catch(( ) => {
|
||||
this.lz4wasmInstance = null;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return this.lz4wasmInstance;
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.lz4wasmInstance = undefined;
|
||||
},
|
||||
|
||||
bytesInUse: function() {
|
||||
return this.lz4wasmInstance instanceof WebAssembly.Instance ?
|
||||
this.lz4wasmInstance.exports.memory.buffer.byteLength :
|
||||
0;
|
||||
},
|
||||
|
||||
encodeBlock: function(input, outputOffset) {
|
||||
if ( this.lz4wasmInstance instanceof WebAssembly.Instance === false ) {
|
||||
throw new Error('LZ4BlockWASM: not initialized');
|
||||
}
|
||||
if ( input instanceof ArrayBuffer ) {
|
||||
input = new Uint8Array(input);
|
||||
} else if ( input instanceof Uint8Array === false ) {
|
||||
throw new TypeError();
|
||||
}
|
||||
return encodeBlock(this.lz4wasmInstance, input, outputOffset);
|
||||
},
|
||||
|
||||
decodeBlock: function(input, inputOffset, outputSize) {
|
||||
if ( this.lz4wasmInstance instanceof WebAssembly.Instance === false ) {
|
||||
throw new Error('LZ4BlockWASM: not initialized');
|
||||
}
|
||||
if ( input instanceof ArrayBuffer ) {
|
||||
input = new Uint8Array(input);
|
||||
} else if ( input instanceof Uint8Array === false ) {
|
||||
throw new TypeError();
|
||||
}
|
||||
return decodeBlock(this.lz4wasmInstance, input, inputOffset, outputSize);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})(this || self); // <<<< End of private namespace
|
||||
|
||||
/******************************************************************************/
|
||||
@@ -0,0 +1,169 @@
|
||||
/*******************************************************************************
|
||||
|
||||
lz4-block-codec-any.js
|
||||
A wrapper to instanciate a wasm- and/or js-based LZ4 block
|
||||
encoder/decoder.
|
||||
Copyright (C) 2018 Raymond Hill
|
||||
|
||||
BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Home: https://github.com/gorhill/lz4-wasm
|
||||
|
||||
I used the same license as the one picked by creator of LZ4 out of respect
|
||||
for his creation, see https://lz4.github.io/lz4/
|
||||
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(function(context) { // >>>> Start of private namespace
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
let wd = (function() {
|
||||
let url = document.currentScript.src;
|
||||
let match = /[^\/]+$/.exec(url);
|
||||
return match !== null ?
|
||||
url.slice(0, match.index) :
|
||||
'';
|
||||
})();
|
||||
|
||||
let removeScript = function(script) {
|
||||
if ( !script ) { return; }
|
||||
if ( script.parentNode === null ) { return; }
|
||||
script.parentNode.removeChild(script);
|
||||
};
|
||||
|
||||
let createInstanceWASM = function() {
|
||||
if ( context.LZ4BlockWASM instanceof Function ) {
|
||||
let instance = new context.LZ4BlockWASM();
|
||||
return instance.init().then(( ) => { return instance; });
|
||||
}
|
||||
if ( context.LZ4BlockWASM === null ) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let script = document.createElement('script');
|
||||
script.src = wd + 'lz4-block-codec-wasm.js';
|
||||
script.addEventListener('load', ( ) => {
|
||||
if ( context.LZ4BlockWASM instanceof Function === false ) {
|
||||
context.LZ4BlockWASM = null;
|
||||
context.LZ4BlockWASM = undefined;
|
||||
resolve(null);
|
||||
} else {
|
||||
let instance = new context.LZ4BlockWASM();
|
||||
instance.init()
|
||||
.then(( ) => {
|
||||
resolve(instance);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
script.addEventListener('error', ( ) => {
|
||||
context.LZ4BlockWASM = null;
|
||||
resolve(null);
|
||||
});
|
||||
document.head.appendChild(script);
|
||||
removeScript(script);
|
||||
});
|
||||
};
|
||||
|
||||
let createInstanceJS = function() {
|
||||
if ( context.LZ4BlockJS instanceof Function ) {
|
||||
let instance = new context.LZ4BlockJS();
|
||||
return instance.init().then(( ) => { return instance; });
|
||||
}
|
||||
if ( context.LZ4BlockJS === null ) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let script = document.createElement('script');
|
||||
script.src = wd + 'lz4-block-codec-js.js';
|
||||
script.addEventListener('load', ( ) => {
|
||||
if ( context.LZ4BlockJS instanceof Function === false ) {
|
||||
context.LZ4BlockJS = null;
|
||||
resolve(null);
|
||||
} else {
|
||||
let instance = new context.LZ4BlockJS();
|
||||
instance.init()
|
||||
.then(( ) => {
|
||||
resolve(instance);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
script.addEventListener('error', ( ) => {
|
||||
context.LZ4BlockJS = null;
|
||||
resolve(null);
|
||||
});
|
||||
document.head.appendChild(script);
|
||||
removeScript(script);
|
||||
});
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
context.lz4BlockCodec = {
|
||||
createInstance: function(flavor) {
|
||||
let instantiator;
|
||||
if ( flavor === 'wasm' ) {
|
||||
instantiator = createInstanceWASM;
|
||||
} else if ( flavor === 'js' ) {
|
||||
instantiator = createInstanceJS;
|
||||
} else {
|
||||
instantiator = createInstanceWASM || createInstanceJS;
|
||||
}
|
||||
return (instantiator)()
|
||||
.then(instance => {
|
||||
if ( instance ) { return instance; }
|
||||
if ( flavor === undefined ) {
|
||||
return createInstanceJS();
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.catch(( ) => {
|
||||
if ( flavor === undefined ) {
|
||||
return createInstanceJS();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
},
|
||||
reset: function() {
|
||||
context.LZ4BlockWASM = undefined;
|
||||
context.LZ4BlockJS = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
})(this || self); // <<<< End of private namespace
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Audiomass - Audio Editor",
|
||||
"short_name": "Audiomass - Audio Editor",
|
||||
"start_url": ".",
|
||||
"scope": ".",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "144x144"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
(function ( w, d ) {
|
||||
|
||||
var _id = 0;
|
||||
|
||||
function PKSimpleModal ( config ) {
|
||||
var q = this;
|
||||
|
||||
this.id = config.id ? config.id : (++_id);
|
||||
|
||||
var el = d.createElement ('div');
|
||||
this.els = {
|
||||
toolbar:[],
|
||||
bottom:[]
|
||||
};
|
||||
el.className = 'pk_modal ' + (config.clss ? config.clss : '');
|
||||
|
||||
q.el = el;
|
||||
|
||||
// backdrop
|
||||
var el_back = d.createElement ('div');
|
||||
el_back.className = 'pk_modal_back';
|
||||
this.el_back = el_back;
|
||||
|
||||
// var centerer
|
||||
var el_cont = d.createElement ('div');
|
||||
el_cont.className = 'pk_modal_cnt';
|
||||
this.el_cont = el_cont;
|
||||
|
||||
// title
|
||||
var el_title = d.createElement ('div');
|
||||
el_title.className = 'pk_noselect pk_modal_title';
|
||||
el_title.innerHTML = '<span>'+ (config.title || '') +'</span>';
|
||||
el.appendChild ( el_title );
|
||||
this.el_title = el_title;
|
||||
|
||||
// main
|
||||
var el_main = d.createElement ('div');
|
||||
el_main.className = 'pk_modal_main';
|
||||
el.appendChild ( el_main );
|
||||
this.el_body = el_main;
|
||||
|
||||
// bottom buttons
|
||||
var el_bottom = d.createElement ('div');
|
||||
el_bottom.className = 'pk_noselect pk_modal_bottom';
|
||||
// -----------
|
||||
var a_cancel = d.createElement ('a');
|
||||
a_cancel.innerHTML = 'CANCEL';
|
||||
a_cancel.className = 'pk_modal_cancel pk_modal_a_bottom';
|
||||
a_cancel.onclick = function () {
|
||||
q.Destroy ();
|
||||
};
|
||||
el_bottom.appendChild ( a_cancel );
|
||||
|
||||
// check if we need to construct more buttons from the config...
|
||||
if (config.buttons && config.buttons.length > 0)
|
||||
{
|
||||
for (var i = 0; i < config.buttons.length; ++i)
|
||||
{
|
||||
var curr = config.buttons[i];
|
||||
|
||||
if (!curr.title || !curr.callback) continue;
|
||||
var a_bottom = d.createElement ('a');
|
||||
a_bottom.innerHTML = curr.title;
|
||||
a_bottom.className = 'pk_modal_a_bottom ' + (curr.clss ? curr.clss : '');
|
||||
|
||||
if (curr.callback)
|
||||
{
|
||||
(function ( callback ) {
|
||||
a_bottom.onclick = function () {
|
||||
callback ( q );
|
||||
};
|
||||
})( curr.callback );
|
||||
}
|
||||
q.els.bottom.push (a_bottom);
|
||||
el_bottom.appendChild ( a_bottom );
|
||||
}
|
||||
}
|
||||
el.appendChild ( el_bottom );
|
||||
|
||||
// -----
|
||||
if (config.toolbar && config.toolbar.length > 0)
|
||||
{
|
||||
for (var i = 0; i < config.toolbar.length; ++i)
|
||||
{
|
||||
var curr = config.toolbar[i];
|
||||
if (!curr.title || !curr.callback) continue;
|
||||
var a_link = d.createElement ('a');
|
||||
a_link.innerHTML = curr.title;
|
||||
a_link.className = 'pk_modal_a_top ' + (curr.clss ? curr.clss : '');
|
||||
el_title.appendChild ( a_link );
|
||||
|
||||
if (curr.callback)
|
||||
{
|
||||
(function ( callback ) {
|
||||
a_link.onclick = function () {
|
||||
callback ( q, this );
|
||||
};
|
||||
})( curr.callback );
|
||||
}
|
||||
q.els.toolbar.push (a_link);
|
||||
}
|
||||
}
|
||||
|
||||
this.ondestroy = config.ondestroy;
|
||||
if (config.body) q.el_body.innerHTML = config.body;
|
||||
if (config.onpreset) this.onpreset = config.onpreset;
|
||||
if (config.setup) config.setup ( this );
|
||||
};
|
||||
|
||||
PKSimpleModal.prototype.Show = function () {
|
||||
|
||||
this.el_back.appendChild ( this.el_cont );
|
||||
this.el_cont.appendChild ( this.el );
|
||||
|
||||
d.body.appendChild ( this.el_back );
|
||||
|
||||
return (this);
|
||||
};
|
||||
|
||||
PKSimpleModal.prototype.Destroy = function () {
|
||||
|
||||
if (this.ondestroy) {
|
||||
this.ondestroy ( this );
|
||||
this.ondestroy = null;
|
||||
}
|
||||
this.els = null;
|
||||
d.body.removeChild ( this.el_back );
|
||||
};
|
||||
|
||||
|
||||
// Extended modal
|
||||
function PKAudioFXModal ( config, app ) {
|
||||
var toolbar = null;
|
||||
|
||||
if (config.preview)
|
||||
{
|
||||
toolbar = [
|
||||
{
|
||||
title:'ON',
|
||||
clss:'pk_inact',
|
||||
callback: function ( q, el ) {
|
||||
app.fireEvent ('RequestActionFX_TOGGLE');
|
||||
}
|
||||
},
|
||||
{
|
||||
title:'Preview',
|
||||
callback: function ( q ) {
|
||||
config.preview && config.preview ( q );
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
var inner_modal = new PKSimpleModal({
|
||||
id: config.id,
|
||||
title: config.title,
|
||||
clss: config.clss,
|
||||
presets: config.presets,
|
||||
updateFilter: config.updateFilter,
|
||||
ondestroy: function ( q ) {
|
||||
|
||||
app.fireEvent ('DidCloseFX_UI');
|
||||
|
||||
app.stopListeningFor ('DidStartPreview', q._evstart);
|
||||
app.stopListeningFor ('DidStopPreview', q._evstop);
|
||||
app.stopListeningFor ('DidTogglePreview', q._evtoggle);
|
||||
app.stopListeningFor ('DidSetPresets', q._updatePresets);
|
||||
app.stopListeningFor ('RequestActionFX_UPDATE_PREVIEW', q._updpreview);
|
||||
app.stopListeningFor ('RequestSetPresetActive', q._updpreset);
|
||||
|
||||
app.fireEvent ('RequestActionFX_PREVIEW_STOP');
|
||||
|
||||
// if preview remove callback
|
||||
app.ui.KeyHandler.removeCallback ('ksp' + q.id);
|
||||
|
||||
|
||||
config.ondestroy && config.ondestroy ( q );
|
||||
},
|
||||
toolbar: toolbar,
|
||||
buttons: config.buttons,
|
||||
body: config.body,
|
||||
onpreset: config.onpreset,
|
||||
setup:function( q ) {
|
||||
app.fireEvent ('RequestActionFX_TOGGLE', 1);
|
||||
|
||||
var slf = this;
|
||||
app.ui.KeyHandler.addCallback ('ksp' + q.id, function ( key, map ) {
|
||||
if (!app.ui.InteractionHandler.check ('modalfx')) return ;
|
||||
|
||||
var tb = slf.toolbar;
|
||||
if (tb && tb.length > 0)
|
||||
{
|
||||
var k = tb.length;
|
||||
while (k-- > 0) {
|
||||
if (tb[k].title === 'Preview') {
|
||||
tb[k].callback ( q );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [32]);
|
||||
|
||||
q._evstart = function () {
|
||||
q.els.toolbar[0].classList.remove ('pk_inact');
|
||||
q.els.toolbar[1].classList.add ('pk_act');
|
||||
};
|
||||
q._evstop = function () {
|
||||
q.els.toolbar[0].classList.add ('pk_inact');
|
||||
q.els.toolbar[1].classList.remove ('pk_act');
|
||||
}
|
||||
|
||||
q._evtoggle = function ( val ) {
|
||||
var el = q.els.toolbar[0];
|
||||
if (val) el.innerHTML = 'ON';
|
||||
else el.innerHTML = 'OFF';
|
||||
};
|
||||
|
||||
var stopped_listening = false;
|
||||
q._updpreview = function ( val ) {
|
||||
var sel_opt = q.el_presets.options[q.el_presets.selectedIndex];
|
||||
var btn = q.el.getElementsByClassName('pk_sel_edt')[0];
|
||||
|
||||
if (val === 't')
|
||||
{
|
||||
if (sel_opt && sel_opt.getAttribute('data-custom')) {
|
||||
btn.style.visibility = 'visible';
|
||||
btn.style.opacity = '1';
|
||||
app.stopListeningFor ('RequestActionFX_UPDATE_PREVIEW', q._updpreview);
|
||||
}
|
||||
else
|
||||
{
|
||||
btn.style.visibility = 'hidden';
|
||||
btn.style.opacity = '0';
|
||||
|
||||
app.stopListeningFor ('RequestActionFX_UPDATE_PREVIEW', q._updpreview);
|
||||
|
||||
//if (stopped_listening)
|
||||
//{
|
||||
setTimeout(function (){
|
||||
app.listenFor ('RequestActionFX_UPDATE_PREVIEW', q._updpreview);
|
||||
}, 100);
|
||||
stopped_listening = false;
|
||||
//}
|
||||
}
|
||||
return ;
|
||||
}
|
||||
|
||||
// if (sel_opt && sel_opt.getAttribute('data-custom')) {
|
||||
btn.style.visibility = 'visible';
|
||||
btn.style.opacity = '1';
|
||||
stopped_listening = true;
|
||||
app.stopListeningFor ('RequestActionFX_UPDATE_PREVIEW', q._updpreview);
|
||||
// }
|
||||
};
|
||||
|
||||
q._updpreset = function ( fx_id, preset_id ) {
|
||||
if (fx_id && fx_id !== q.id) {
|
||||
return ;
|
||||
}
|
||||
|
||||
var opts = q.el_presets.getElementsByTagName('option');
|
||||
var ll = opts.length;
|
||||
var curr = null;
|
||||
|
||||
while (ll-- > 0) {
|
||||
curr = opts[ll];
|
||||
|
||||
if (curr.getAttribute('data-custom') === preset_id) {
|
||||
curr.selected = 'selected';
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
q._updatePresets = function ( fx_id, presets ) {
|
||||
if (fx_id && fx_id !== q.id) {
|
||||
return ;
|
||||
}
|
||||
|
||||
var d = document;
|
||||
var sel_presets = q.el.getElementsByClassName ('pk_sel');
|
||||
|
||||
// if presets exist remove them
|
||||
if (sel_presets.length > 0)
|
||||
{
|
||||
sel_presets = sel_presets[0];
|
||||
var opts = sel_presets.getElementsByTagName('option');
|
||||
var ll = opts.length;
|
||||
var curr = null;
|
||||
|
||||
while (ll-- > 0) {
|
||||
curr = opts[ll];
|
||||
|
||||
if (curr.getAttribute('data-custom')) {
|
||||
sel_presets.removeChild( curr );
|
||||
}
|
||||
}
|
||||
|
||||
if (presets.length === 0) return ;
|
||||
|
||||
var opt = d.createElement ('option');
|
||||
// opt.value = '---custom----';
|
||||
opt.setAttribute ('disabled', '1');
|
||||
opt.setAttribute ('data-custom', '1');
|
||||
opt.innerHTML = '----custom-----';
|
||||
sel_presets.appendChild( opt );
|
||||
|
||||
for (var i = 0; i < presets.length; ++i)
|
||||
{
|
||||
var opt = d.createElement ('option');
|
||||
var curr = presets[ i ];
|
||||
opt.value = curr.val;
|
||||
opt.setAttribute ('data-custom', curr.id);
|
||||
opt.innerHTML = curr.name;
|
||||
sel_presets.appendChild( opt );
|
||||
}
|
||||
|
||||
return ;
|
||||
}
|
||||
else
|
||||
{
|
||||
sel_presets = d.createElement ('select');
|
||||
sel_presets.className = 'pk_sel';
|
||||
}
|
||||
|
||||
if (presets.length === 0) return ;
|
||||
|
||||
|
||||
for (var i = -1; i < presets.length; ++i)
|
||||
{
|
||||
var opt = d.createElement ('option');
|
||||
|
||||
if (i === -1)
|
||||
{
|
||||
opt.value = 'null';
|
||||
opt.innerHTML = 'Presets';
|
||||
}
|
||||
else
|
||||
{
|
||||
var curr = presets[ i ];
|
||||
opt.value = curr.val;
|
||||
opt.innerHTML = curr.name;
|
||||
}
|
||||
sel_presets.appendChild( opt );
|
||||
}
|
||||
|
||||
sel_presets.onchange = function () {
|
||||
var val_arr = this.value.split(',');
|
||||
var els = q.el.getElementsByTagName('input');
|
||||
|
||||
q._updpreview ('t');
|
||||
|
||||
if (q.onpreset)
|
||||
{
|
||||
q.onpreset (this.value);
|
||||
return ;
|
||||
}
|
||||
|
||||
var len = els.length;
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (!val_arr[ i ]) break;
|
||||
|
||||
var curr_val = val_arr[ i ].trim ();
|
||||
var curr_input = els[ i ];
|
||||
|
||||
if (curr_val === 'null') continue;
|
||||
|
||||
if (curr_input.type === 'checkbox' || curr_input.type === 'radio')
|
||||
curr_input.checked = curr_val;
|
||||
else
|
||||
{
|
||||
curr_input.value = curr_val;
|
||||
curr_input.oninput && curr_input.oninput.apply (curr_input);
|
||||
}
|
||||
}
|
||||
};
|
||||
var btm = q.el.getElementsByClassName('pk_modal_bottom')[0];
|
||||
|
||||
btm.appendChild ( sel_presets );
|
||||
q.el_presets = sel_presets;
|
||||
|
||||
// now add preset edit button
|
||||
var edit_presets = d.createElement ('a');
|
||||
edit_presets.className = 'pk_sel_edt';
|
||||
edit_presets.innerHTML = '...<span>Save or Modify preset</span>';
|
||||
edit_presets.onclick = function () {
|
||||
app.fireEvent ('RequestSavePreset');
|
||||
};
|
||||
|
||||
btm.appendChild ( edit_presets );
|
||||
app.listenFor ('RequestActionFX_UPDATE_PREVIEW', q._updpreview);
|
||||
app.listenFor ('RequestSetPresetActive', q._updpreset);
|
||||
};
|
||||
|
||||
|
||||
app.listenFor ('DidStartPreview', q._evstart);
|
||||
app.listenFor ('DidStopPreview', q._evstop);
|
||||
app.listenFor ('DidTogglePreview', q._evtoggle);
|
||||
app.fireEvent ('DidOpenFX_UI', q);
|
||||
|
||||
if (config.updateFilter) q.updateFilter = config.updateFilter;
|
||||
|
||||
if (config.presets)
|
||||
{
|
||||
q._updatePresets (null, config.presets);
|
||||
if (config.custom_pres) q._updatePresets (null, config.custom_pres);
|
||||
|
||||
app.listenFor ('DidSetPresets', q._updatePresets);
|
||||
}
|
||||
|
||||
config.setup && config.setup ( q );
|
||||
}
|
||||
});
|
||||
|
||||
return (inner_modal);
|
||||
};
|
||||
|
||||
w.PKSimpleModal = PKSimpleModal;
|
||||
w.PKAudioFXModal = PKAudioFXModal;
|
||||
})( window, document );
|
||||
@@ -0,0 +1,29 @@
|
||||
(function ( w, d ) {
|
||||
|
||||
function OneUp ( _text, _time, _clss ) {
|
||||
var el = d.createElement ('div');
|
||||
var cl = 'pk_oneup pk_noselect';
|
||||
|
||||
el.style.cssText = 'margin-top:20px;opacity:0';
|
||||
if (_clss) cl = cl + ' ' + _clss;
|
||||
|
||||
el.className = cl;
|
||||
el.innerHTML = _text || '';
|
||||
|
||||
d.body.appendChild ( el );
|
||||
setTimeout (function() {
|
||||
el.style.cssText = 'margin-top:0px;opacity:1';
|
||||
|
||||
setTimeout (function() {
|
||||
el.style.cssText = 'margin-top:-20px;opacity:0';
|
||||
|
||||
setTimeout (function() {
|
||||
el.parentNode.removeChild ( el );
|
||||
el = null;
|
||||
}, 330);
|
||||
}, _time || 720);
|
||||
}, 25);
|
||||
}
|
||||
|
||||
w.OneUp = OneUp;
|
||||
})( window, document );
|
||||
|
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1,223 @@
|
||||
(function ( w, d, PKAE ) {
|
||||
'use strict';
|
||||
|
||||
var PKREC = function ( app ) {
|
||||
var q = this;
|
||||
|
||||
var media_stream_source = null;
|
||||
var audio_stream = null;
|
||||
var audio_context = null;
|
||||
var script_processor = null;
|
||||
|
||||
var buffer_size = 2048 * 2; // * 2 ?
|
||||
var channel_num = 1;
|
||||
var channel_num_out = 1;
|
||||
|
||||
var is_active = false;
|
||||
|
||||
var starting_offset = 0;
|
||||
var ending_offset = 0;
|
||||
|
||||
var sample_rate = 0;
|
||||
|
||||
var temp_buffers = [];
|
||||
var temp_buffer_index = -1;
|
||||
var jumps = 1;
|
||||
|
||||
var end_record_func = null;
|
||||
var start_record_func = null;
|
||||
|
||||
// temp vars
|
||||
var curr_offset = 0;
|
||||
var first_skip = 8; // skip first samples to evade the button's click
|
||||
var fetchBufferFunction = function( ev ) {
|
||||
|
||||
if (first_skip > 0) {
|
||||
--first_skip;
|
||||
return ;
|
||||
}
|
||||
|
||||
curr_offset += ev.inputBuffer.duration * sample_rate;
|
||||
if (ending_offset <= curr_offset)
|
||||
{
|
||||
ending_offset > 0 && q.stop ();
|
||||
return ;
|
||||
}
|
||||
|
||||
var float_array = ev.inputBuffer.getChannelData (0).slice (0);
|
||||
temp_buffers[ ++temp_buffer_index ] = float_array;
|
||||
|
||||
if (--jumps === 0)
|
||||
{
|
||||
requestAnimationFrame(function(){
|
||||
jumps = 4;
|
||||
app.engine.wavesurfer.DrawTemp ( starting_offset, temp_buffers );
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.isActive = function () {
|
||||
return (is_active);
|
||||
};
|
||||
|
||||
this.setEndingOffset = function ( ending_offset_seconds ) {
|
||||
ending_offset = ending_offset_seconds; // #### * 100
|
||||
};
|
||||
|
||||
this.start = function ( _at_offset, _end_callback, _start_callback, _sample_rate ) {
|
||||
if (is_active) return (false);
|
||||
if (!navigator.mediaDevices)
|
||||
{
|
||||
app.fireEvent ('ErrorRec');
|
||||
app.fireEvent ('ShowError', 'No recording device found');
|
||||
return (false);
|
||||
}
|
||||
|
||||
starting_offset = _at_offset / 1;
|
||||
if (isNaN (starting_offset) || !starting_offset) starting_offset = 0;
|
||||
curr_offset = starting_offset;
|
||||
|
||||
audio_context = app.engine.wavesurfer.backend.getAudioContext ();
|
||||
if (!audio_context)
|
||||
{
|
||||
app.fireEvent ('ErrorRec');
|
||||
app.fireEvent ('ShowError', 'No recording device found');
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (audio_context.currentTime === 0) {
|
||||
app.engine.wavesurfer.backend.source.start (0);
|
||||
app.engine.wavesurfer.backend.source.stop (0);
|
||||
}
|
||||
|
||||
if (!_sample_rate)
|
||||
{
|
||||
if (app.engine.wavesurfer.backend.buffer) {
|
||||
sample_rate = app.engine.wavesurfer.backend.buffer.sampleRate;
|
||||
}
|
||||
else {
|
||||
sample_rate = audio_context.sampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
end_record_func = function (offset, buffers, _callback) {
|
||||
async function downsampleAudioBuffer(buffers, sourceSampleRate, targetSampleRate) {
|
||||
// Step 1: Concatenate the Float32Array chunks
|
||||
const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);
|
||||
const concatenated = new Float32Array(totalLength);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < buffers.length; i++) {
|
||||
concatenated.set(buffers[i], offset);
|
||||
offset += buffers[i].length;
|
||||
}
|
||||
|
||||
// Step 2: Create an AudioBuffer from the concatenated data at the source sample rate
|
||||
// Create a temporary AudioContext to build the AudioBuffer
|
||||
const tempCtx = new AudioContext({ sampleRate: sourceSampleRate });
|
||||
const audioBuffer = tempCtx.createBuffer(1, totalLength, sourceSampleRate);
|
||||
audioBuffer.copyToChannel(concatenated, 0, 0);
|
||||
|
||||
// Release the temporary context if you don't need it anymore
|
||||
tempCtx.close();
|
||||
|
||||
// Step 3: Use an OfflineAudioContext to resample the AudioBuffer to the target sample rate
|
||||
const duration = audioBuffer.duration;
|
||||
const newLength = Math.ceil(duration * targetSampleRate);
|
||||
const offlineCtx = new OfflineAudioContext(1, newLength, targetSampleRate);
|
||||
|
||||
const source = offlineCtx.createBufferSource();
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(offlineCtx.destination);
|
||||
source.start(0);
|
||||
|
||||
// Render the resampled AudioBuffer
|
||||
const renderedBuffer = await offlineCtx.startRendering();
|
||||
|
||||
// Return the downsampled Float32Array
|
||||
return renderedBuffer.getChannelData(0);
|
||||
}
|
||||
|
||||
var source_sample_rate = audio_context ? audio_context.sampleRate : 48000;
|
||||
if (source_sample_rate === sample_rate) {
|
||||
_callback ();
|
||||
_end_callback (offset, buffers);
|
||||
return ;
|
||||
}
|
||||
|
||||
downsampleAudioBuffer(buffers, source_sample_rate, sample_rate).then(newBuffer => {
|
||||
_callback ();
|
||||
_end_callback (offset, [newBuffer]);
|
||||
}).catch(error => {
|
||||
_callback ();
|
||||
console.error("Error during downsampling:", error);
|
||||
});
|
||||
|
||||
// _end_callback (offset, buffers);
|
||||
};
|
||||
start_record_func = _start_callback;
|
||||
|
||||
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function( stream ) {
|
||||
audio_stream = stream;
|
||||
media_stream_source = audio_context.createMediaStreamSource ( stream );
|
||||
|
||||
script_processor = audio_context.createScriptProcessor (
|
||||
buffer_size, channel_num, channel_num_out
|
||||
);
|
||||
|
||||
media_stream_source.connect ( script_processor );
|
||||
script_processor.connect ( audio_context.destination );
|
||||
|
||||
is_active = true;
|
||||
start_record_func && start_record_func ();
|
||||
|
||||
script_processor.onaudioprocess = fetchBufferFunction;
|
||||
}).catch(function(error) {
|
||||
app.fireEvent ('ErrorRec');
|
||||
|
||||
if (error && error.message)
|
||||
{
|
||||
app.fireEvent ('ShowError', error.message);
|
||||
}
|
||||
});
|
||||
|
||||
return (true);
|
||||
};
|
||||
|
||||
this.stop = function ( cancel_recording ) {
|
||||
if (!is_active) return ;
|
||||
|
||||
// fire one last callback to clean temp_buffers?
|
||||
audio_stream.getTracks().forEach(function (stream) {
|
||||
stream.stop ();
|
||||
});
|
||||
|
||||
script_processor.onaudioprocess = null;
|
||||
media_stream_source.disconnect ();
|
||||
script_processor.disconnect ();
|
||||
|
||||
app.engine.wavesurfer.DrawTemp ( null );
|
||||
|
||||
if (temp_buffers.length > 0 && !cancel_recording)
|
||||
end_record_func && end_record_func ( starting_offset / sample_rate, temp_buffers, function (){
|
||||
is_active = false;
|
||||
});
|
||||
else
|
||||
end_record_func && end_record_func ( null, null, function(){
|
||||
is_active = false;
|
||||
});
|
||||
|
||||
sample_rate = 0;
|
||||
first_skip = 8;
|
||||
jumps = 1;
|
||||
temp_buffer_index = -1;
|
||||
starting_offset = ending_offset = 0;
|
||||
temp_buffers = [];
|
||||
audio_stream = null; audio_context = null;
|
||||
end_record_func = start_record_func = null;
|
||||
};
|
||||
// ---
|
||||
};
|
||||
|
||||
PKAE._deps.rec = PKREC;
|
||||
|
||||
})( window, document, PKAudioEditor );
|
||||
@@ -0,0 +1,296 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script>window.update = function(){};</script>
|
||||
|
||||
<title>AudioMass - Spectrum Analyzer</title>
|
||||
<style>
|
||||
html,body{height:100%}
|
||||
body{padding:0;margin:0;background:#111;position:relative;z-index:2;overflow:hidden;}
|
||||
div{position:absolute;top:50%;left:0;right:0;z-index:1;margin-top:-18px;text-align:center;user-select:none;
|
||||
font-size:28px;color:#191919;pointer-events:none;opacity:.75;font-family:Arial}
|
||||
#e, #d, #f{
|
||||
cursor:pointer;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
display: block;
|
||||
height: 20px;
|
||||
z-index: 99999;
|
||||
background: #eaeaea;
|
||||
left:auto;
|
||||
border-radius:4px;
|
||||
font-family: sans-serif;
|
||||
font-size: 9px;
|
||||
width: 82px;
|
||||
line-height: 20px;
|
||||
padding: 0 6px;
|
||||
text-align: center;
|
||||
opacity:0.9;
|
||||
user-select:none;
|
||||
}
|
||||
|
||||
.b #e, .b #d{
|
||||
top:12px;
|
||||
}
|
||||
|
||||
.b #e, .b #f{
|
||||
display:block;
|
||||
}
|
||||
|
||||
#e{
|
||||
right: 106px;
|
||||
width: 10px;
|
||||
line-height: 20px;
|
||||
padding: 0px 6px;
|
||||
text-align: center;
|
||||
opacity: 0.9;
|
||||
display:none;
|
||||
}
|
||||
|
||||
#f{
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
line-height: 0;
|
||||
border-radius: 0;
|
||||
height: 4px;
|
||||
background: #333;
|
||||
display:none;
|
||||
user-select:none;
|
||||
-moz-user-select:none;
|
||||
}
|
||||
|
||||
.c #f, #f:hover{
|
||||
background:#3C3C3C;
|
||||
}
|
||||
|
||||
</style>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="fr" width="600" height="188" style="width:100%;height:100%;display:block"></canvas>
|
||||
<div>SPECTRUM ANALYSER</div>
|
||||
|
||||
<a id="d" onclick="dock()">DOCK INTERFACE</a>
|
||||
<a id="e" onclick="remove()">X</a>
|
||||
<a id="f" onmousedown="drag(event)"></a>
|
||||
|
||||
<script>
|
||||
var d = document;
|
||||
var w = window;
|
||||
var iframe = location.href.indexOf('?iframe') === -1 ? 0 : 1;
|
||||
var canvas = d.getElementById('fr');
|
||||
var ctx = canvas.getContext('2d', {alpha:true, antialias:false});
|
||||
|
||||
w.remove = function (){};
|
||||
if (iframe) {
|
||||
d.body.className = 'b';
|
||||
w.remove = function () {
|
||||
w.parent.PKAudioEditor.ui.Dock ('RequestShowFreqAn', 'sp', [1, 1]);
|
||||
};
|
||||
};
|
||||
|
||||
w.drag = function ( e ) {
|
||||
e.preventDefault ();
|
||||
e.stopPropagation ();
|
||||
|
||||
w.parent.PKAudioEditor.ui.Dock ('RequestDragI', 'sp');
|
||||
};
|
||||
|
||||
w.dock = function () {
|
||||
if (!iframe)
|
||||
{
|
||||
if (!w.opener || !w.opener.PKAudioEditor) {
|
||||
return ;
|
||||
}
|
||||
|
||||
w.opener.PKAudioEditor.ui.Dock ('RequestShowFreqAn', 'sp', [1, 1]);
|
||||
w.close && w.close ();
|
||||
}
|
||||
else
|
||||
{
|
||||
var frm = w.parent.document.getElementById ('pk_fr' + 'sp');
|
||||
var t = 1;
|
||||
|
||||
if (frm && frm.getBoundingClientRect) {
|
||||
var rect = frm.getBoundingClientRect ();
|
||||
t = [(w.parent.screenLeft + rect.left + 100)||0, (w.parent.screenTop + rect.top + 25)||0];
|
||||
}
|
||||
|
||||
w.parent.PKAudioEditor.ui.Dock ('RequestShowFreqAn', 'sp', [t, 0]);
|
||||
}
|
||||
};
|
||||
|
||||
w.getGrayColor = function(value) {
|
||||
return 'rgb(V, V, V)'.replace(/V/g, 255 - value);
|
||||
};
|
||||
|
||||
w.getFullColor = function(value) {
|
||||
var colorPalette = {
|
||||
0: [0,0,0],
|
||||
10: [75, 0, 159],
|
||||
20: [104,0,251],
|
||||
30: [131,0,255],
|
||||
40: [155,18,157],
|
||||
50: [175, 37, 0],
|
||||
60: [191, 59, 0],
|
||||
70: [206, 88, 0],
|
||||
80: [223, 132, 0],
|
||||
90: [240, 188, 0],
|
||||
100: [255, 252, 0]
|
||||
};
|
||||
|
||||
//floor to nearest 10:
|
||||
var decimalised = 100 * value / 255
|
||||
var percent = decimalised / 100;
|
||||
var floored = 10* Math.floor(decimalised / 10);
|
||||
var distFromFloor = decimalised - floored;
|
||||
var distFromFloorPercentage = distFromFloor/10;
|
||||
var rangeToNextColor;
|
||||
|
||||
if (decimalised < 100){
|
||||
rangeToNextColor = [
|
||||
colorPalette[floored + 10][0] - colorPalette[floored + 10][0],
|
||||
colorPalette[floored + 10][1] - colorPalette[floored + 10][1],
|
||||
colorPalette[floored + 10][2] - colorPalette[floored + 10][2]
|
||||
];
|
||||
} else {
|
||||
rangeToNextColor = [0,0,0];
|
||||
}
|
||||
|
||||
var color = [
|
||||
colorPalette[floored][0] + distFromFloorPercentage * rangeToNextColor[0],
|
||||
colorPalette[floored][1] + distFromFloorPercentage * rangeToNextColor[1],
|
||||
colorPalette[floored][2] + distFromFloorPercentage * rangeToNextColor[2]
|
||||
];
|
||||
|
||||
return "rgb(" + color[0] +", "+color[1] +"," + color[2]+")";
|
||||
};
|
||||
|
||||
setTimeout(function () {
|
||||
if (!iframe)
|
||||
{
|
||||
if (!w.opener || !w.opener.PKAudioEditor) return ;
|
||||
}
|
||||
|
||||
var WIDTH = w.innerWidth, HEIGHT = w.innerHeight;
|
||||
|
||||
if (canvas.width != WIDTH)
|
||||
{
|
||||
canvas.width = WIDTH;
|
||||
canvas.height = HEIGHT;
|
||||
}
|
||||
|
||||
var bufferLength = 240;
|
||||
var value_changed = false;
|
||||
var speed = 3;
|
||||
|
||||
var tempCanvas = document.createElement('canvas');
|
||||
tempCanvas.width = WIDTH;
|
||||
tempCanvas.height = HEIGHT;
|
||||
|
||||
// console.log(this.canvas.height, this.tempCanvas.height);
|
||||
var tempCtx = tempCanvas.getContext ('2d');
|
||||
|
||||
ctx.clearRect (0, 0, WIDTH, HEIGHT);
|
||||
|
||||
function draw( data ) {
|
||||
|
||||
var WIDTH = window.innerWidth, HEIGHT = window.innerHeight;
|
||||
|
||||
if (canvas.width != WIDTH || canvas.height != HEIGHT)
|
||||
{
|
||||
canvas.width = WIDTH;
|
||||
canvas.height = HEIGHT;
|
||||
tempCanvas.width = WIDTH;
|
||||
tempCanvas.height = HEIGHT;
|
||||
}
|
||||
|
||||
tempCtx.drawImage (canvas, 0, 0, WIDTH, HEIGHT);
|
||||
|
||||
ctx.fillStyle = 'rgb(0, 0, 0)';
|
||||
ctx.fillRect(0, 0, WIDTH, HEIGHT);
|
||||
|
||||
// Iterate over the frequencies.
|
||||
for (var i = 0; i < data.length; ++i)
|
||||
{
|
||||
var value;
|
||||
// Draw each pixel with the specific color.
|
||||
// if (this.log) {
|
||||
// logIndex = this.logScale(i, data.length);
|
||||
// value = data[logIndex];
|
||||
// } else {
|
||||
value = data[i];
|
||||
// }
|
||||
|
||||
ctx.fillStyle = window.getFullColor(value);
|
||||
|
||||
var percent = i / data.length;
|
||||
var y = Math.round (percent * HEIGHT);
|
||||
|
||||
// draw the line at the right side of the canvas
|
||||
ctx.fillRect(WIDTH - speed, HEIGHT - y,
|
||||
speed, speed);
|
||||
}
|
||||
|
||||
// Translate the canvas.
|
||||
ctx.translate(-speed, 0);
|
||||
// Draw the copied image.
|
||||
// console.log(this.width, this.height);
|
||||
ctx.drawImage (tempCanvas, 0, 0, WIDTH, HEIGHT,
|
||||
0, 0, WIDTH, HEIGHT);
|
||||
|
||||
// Reset the transformation matrix.
|
||||
ctx.setTransform (1, 0, 0, 1, 0, 0);
|
||||
|
||||
value_changed = false;
|
||||
};
|
||||
w.draw = draw;
|
||||
|
||||
w.onunload = function () {
|
||||
w.destroy && w.destroy ( iframe );
|
||||
w.destroy = null;
|
||||
};
|
||||
|
||||
w.update = function (freq_arr) {
|
||||
if (!freq_arr)
|
||||
ctx.clearRect (0, 0, WIDTH, HEIGHT);
|
||||
else {
|
||||
if (value_changed) return ;
|
||||
value_changed = true;
|
||||
|
||||
window.requestAnimationFrame(function () {
|
||||
draw (freq_arr);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var last_press = 0;
|
||||
document.addEventListener ('keypress', function ( e ) {
|
||||
if (e.keyCode !== 32) return ;
|
||||
|
||||
e.preventDefault ();
|
||||
e.stopPropagation ();
|
||||
|
||||
if (e.timeStamp - last_press < 100) {
|
||||
return ;
|
||||
}
|
||||
|
||||
last_press = e.timeStamp;
|
||||
|
||||
if (!iframe) {
|
||||
w.opener && w.opener.PKAudioEditor.ui.Dock ('RequestKeyDown', 32);
|
||||
}
|
||||
else {
|
||||
w.parent && w.parent.PKAudioEditor.ui.Dock ('RequestKeyDown', 32);
|
||||
}
|
||||
});
|
||||
|
||||
}, 60);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,117 @@
|
||||
(function ( PKAE ) {
|
||||
'use strict';
|
||||
|
||||
function PKState ( _depth, app ) {
|
||||
if (!_depth) _depth = 1;
|
||||
|
||||
var q = this;
|
||||
|
||||
var _id = 1;
|
||||
var _fireEvent = app.fireEvent;
|
||||
var _listenFor = app.listenFor;
|
||||
|
||||
var undo_state_list = [];
|
||||
var redo_state_list = [];
|
||||
|
||||
q.getLastUndoState = function () {
|
||||
return (undo_state_list [ undo_state_list.length - 1]);
|
||||
};
|
||||
|
||||
q.pushUndoState = function ( state ) {
|
||||
if (!state) return (false);
|
||||
|
||||
if (!state.id) state.id = ++_id;
|
||||
if (undo_state_list.length >= _depth) undo_state_list.shift ();
|
||||
|
||||
if (undo_state_list.length > 0)
|
||||
{
|
||||
if (undo_state_list[undo_state_list.length - 1].id !== state.id - 1)
|
||||
undo_state_list = [];
|
||||
}
|
||||
if (redo_state_list.length > 0)
|
||||
{
|
||||
if (redo_state_list[0].id !== state.id + 1)
|
||||
redo_state_list = [];
|
||||
}
|
||||
|
||||
undo_state_list.push ( state );
|
||||
|
||||
_fireEvent ( 'StatePush', undo_state_list.length );
|
||||
_fireEvent ( 'DidStateChange', undo_state_list, redo_state_list);
|
||||
|
||||
return (true);
|
||||
};
|
||||
|
||||
q.popUndoState = function () {
|
||||
var last_state =undo_state_list.pop ();
|
||||
|
||||
if (last_state) {
|
||||
if (redo_state_list.length > 0)
|
||||
{
|
||||
if (redo_state_list[0].id !== last_state.id + 1)
|
||||
redo_state_list = [];
|
||||
}
|
||||
|
||||
var temp = app.engine.wavesurfer.backend.buffer;
|
||||
_fireEvent ( 'StateDidPop', last_state, 1 );
|
||||
|
||||
last_state.data = temp;
|
||||
redo_state_list.unshift (last_state);
|
||||
|
||||
_fireEvent ( 'DidStateChange', undo_state_list, redo_state_list);
|
||||
}
|
||||
|
||||
return (last_state);
|
||||
};
|
||||
|
||||
q.shiftRedoState = function () {
|
||||
var last_state = redo_state_list.shift ();
|
||||
|
||||
if (last_state) {
|
||||
if (undo_state_list.length > 0)
|
||||
{
|
||||
if (undo_state_list[undo_state_list.length - 1].id !== last_state.id - 1)
|
||||
undo_state_list = [];
|
||||
}
|
||||
|
||||
var temp = app.engine.wavesurfer.backend.buffer;
|
||||
_fireEvent ( 'StateDidPop', last_state, 0 );
|
||||
|
||||
last_state.data = temp;
|
||||
undo_state_list.push (last_state);
|
||||
|
||||
_fireEvent ( 'DidStateChange', undo_state_list, redo_state_list);
|
||||
}
|
||||
|
||||
return (last_state);
|
||||
};
|
||||
|
||||
q.clearAllState = function () {
|
||||
undo_state_list = [];
|
||||
redo_state_list = [];
|
||||
|
||||
_fireEvent ( 'StateClearAll' );
|
||||
_fireEvent ( 'DidStateChange', [], []);
|
||||
};
|
||||
|
||||
_listenFor ('StateRequestPush', function ( _state ) {
|
||||
q.pushUndoState ( _state );
|
||||
});
|
||||
_listenFor ('StateRequestUndo', function () {
|
||||
q.popUndoState ();
|
||||
});
|
||||
_listenFor ('StateRequestRedo', function () {
|
||||
q.shiftRedoState ();
|
||||
});
|
||||
_listenFor ('StateRequestClearAll', function () {
|
||||
q.clearAllState ();
|
||||
});
|
||||
_listenFor ('StateRequestLastState', function () {
|
||||
_fireEvent ('StateDidLastState', q.getLastUndoState ());
|
||||
});
|
||||
// -
|
||||
};
|
||||
|
||||
PKAE._deps.state = PKState;
|
||||
|
||||
})( PKAudioEditor );
|
||||
@@ -0,0 +1,59 @@
|
||||
const assets = [
|
||||
'./manifest.json',
|
||||
'./ico.png',
|
||||
'./icon.png',
|
||||
'./index.html',
|
||||
'./main.css',
|
||||
'./dist/wavesurfer.js',
|
||||
'./dist/plugin/wavesurfer.regions.js',
|
||||
'./oneup.js',
|
||||
'./app.js',
|
||||
'./keys.js',
|
||||
'./contextmenu.js',
|
||||
'./ui-fx.js',
|
||||
'./ui.js',
|
||||
'./modal.js',
|
||||
'./state.js',
|
||||
'./engine.js',
|
||||
'./actions.js',
|
||||
'./drag.js',
|
||||
'./recorder.js',
|
||||
'./welcome.js',
|
||||
'./fx-pg-eq.js',
|
||||
'./fx-auto.js',
|
||||
'./local.js',
|
||||
'./id3.js',
|
||||
'./lzma.js',
|
||||
'./lame.js',
|
||||
'./fonts/icomoon.ttf',
|
||||
'./fonts/icomoon.woff',
|
||||
'./favicon.ico',
|
||||
'./eq.html',
|
||||
'./sp.html',
|
||||
'./test.mp3'
|
||||
|
||||
];
|
||||
|
||||
self.addEventListener( 'install', async function () {
|
||||
const cache = await caches.open( 'audiomass' );
|
||||
assets.forEach( function ( asset ) {
|
||||
cache.add( asset ).catch( function () {
|
||||
console.error( '[SW] Cound\'t cache:', asset );
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
self.addEventListener( 'fetch', async function ( event ) {
|
||||
const request = event.request;
|
||||
event.respondWith( cacheFirst( request ) );
|
||||
});
|
||||
|
||||
async function cacheFirst( request ) {
|
||||
const cachedResponse = await caches.match( request );
|
||||
if ( cachedResponse === undefined ) {
|
||||
console.error( '[SW] Not cached:', request.url );
|
||||
return fetch( request );
|
||||
}
|
||||
|
||||
return cachedResponse;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
function interleave(inputL, inputR) {
|
||||
var length = inputL.length + inputR.length;
|
||||
var result = new Int16Array(length);
|
||||
|
||||
var index = 0,
|
||||
inputIndex = 0;
|
||||
|
||||
while (index < length) {
|
||||
result[index++] = inputL[inputIndex];
|
||||
result[index++] = inputR[inputIndex];
|
||||
++inputIndex;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function floatTo16BitPCM(output, offset, input) {
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
output.setInt16(offset, input[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
function writeString(view, offset, string) {
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
function encodeWAV(samples, numChannels, sampleRate) {
|
||||
var buffer = new ArrayBuffer(44 + samples.length * 2);
|
||||
var view = new DataView(buffer);
|
||||
|
||||
/* RIFF identifier */
|
||||
writeString(view, 0, 'RIFF');
|
||||
/* RIFF chunk length */
|
||||
view.setUint32(4, 36 + samples.length * 2, true);
|
||||
/* RIFF type */
|
||||
writeString(view, 8, 'WAVE');
|
||||
/* format chunk identifier */
|
||||
writeString(view, 12, 'fmt ');
|
||||
/* format chunk length */
|
||||
view.setUint32(16, 16, true);
|
||||
/* sample format (raw) */
|
||||
view.setUint16(20, 1, true);
|
||||
/* channel count */
|
||||
view.setUint16(22, numChannels, true);
|
||||
/* sample rate */
|
||||
view.setUint32(24, sampleRate, true);
|
||||
/* byte rate (sample rate * block align) */
|
||||
view.setUint32(28, sampleRate * numChannels * 2, true);
|
||||
/* block align (channel count * bytes per sample) */
|
||||
view.setUint16(32, numChannels * 2, true);
|
||||
/* bits per sample */
|
||||
view.setUint16(34, 16, true);
|
||||
/* data chunk identifier */
|
||||
writeString(view, 36, 'data');
|
||||
/* data chunk length */
|
||||
view.setUint32(40, samples.length * 2, true);
|
||||
|
||||
floatTo16BitPCM(view, 44, samples);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var sample_rate = 44100;
|
||||
var kbps = 128;
|
||||
var channels = 1;
|
||||
var mp3encoder = null;
|
||||
|
||||
var samples_left = null;
|
||||
var samples_right = null;
|
||||
var first_buffer = true;
|
||||
|
||||
onmessage = function( ev ) {
|
||||
if (!ev.data) return ;
|
||||
|
||||
if (ev.data.sample_rate) {
|
||||
sample_rate = ev.data.sample_rate / 1;
|
||||
kbps = ev.data.kbps / 1;
|
||||
channels = ev.data.channels / 1;
|
||||
|
||||
return ;
|
||||
}
|
||||
|
||||
if (first_buffer) {
|
||||
samples_left = new Int16Array (ev.data, 0);
|
||||
first_buffer = false;
|
||||
|
||||
if (channels > 1) return ;
|
||||
}
|
||||
|
||||
if (ev.data && channels > 1) {
|
||||
samples_right = new Int16Array (ev.data, 0);
|
||||
}
|
||||
|
||||
///
|
||||
var interleaved = undefined;
|
||||
if (channels > 1) {
|
||||
interleaved = interleave(samples_left, samples_right);
|
||||
} else {
|
||||
interleaved = samples_left;
|
||||
}
|
||||
|
||||
var dataview = encodeWAV(interleaved, channels, sample_rate);
|
||||
var audioBlob = new Blob([dataview], { type: 'audio/wav' });
|
||||
|
||||
postMessage( audioBlob );
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
(function ( w, d, PKAE ) {
|
||||
'use strict';
|
||||
|
||||
setTimeout(function () {
|
||||
|
||||
PKAudioEditor._deps.Wlc = function () {
|
||||
var body_str = '';
|
||||
var body_str2 = '';
|
||||
|
||||
if (PKAE.isMobile) {
|
||||
change -= 15;
|
||||
body_str = 'Tips:<br/>Please make sure your device is not in silent mode. You might need to physically flip the silent switch. '+
|
||||
'<img src="phone-switch.jpg" style="max-width:224px;max-height:126px;width:40%;margin: 10px auto; display: block;"/>'+
|
||||
'<br/><br/>';
|
||||
}
|
||||
else {
|
||||
body_str = 'Tips:<br/>Please keep in mind that most key shortcuts rely on the <strong>Shift + <u>key</u></strong> combo. (eg Shift+Z for undo, Shift+C copy, Shift+X cut... etc )<br/><br/>';
|
||||
body_str2 = 'Check out the codebase on <a href="https://github.com/pkalogiros/audiomass" target="_blank">Github</a><br/><br/>'; // checkout the code on github
|
||||
}
|
||||
|
||||
// Welcome to AudioMass,
|
||||
var md = new PKSimpleModal({
|
||||
title: '<font style="font-size:15px">Welcome to AudioMass</font>',
|
||||
ondestroy: function( q ) {
|
||||
PKAE.ui.InteractionHandler.on = false;
|
||||
PKAE.ui.KeyHandler.removeCallback ('modalTemp');
|
||||
},
|
||||
body:'<div style="overflow:auto;-webkit-overflow-scrolling:touch;max-width:580px;width:calc(100vw - 40px);max-height:calc(100vh - 340px);min-height:110px;font-size:13px; color:#95c6c6;padding-top:7px;">'+
|
||||
'AudioMass is a free, open source, web-based Audio and Waveform Editor.<br />It runs entirely in the browser with no backend and no plugins required!'+
|
||||
'<br/><br/><br/>'+
|
||||
body_str+
|
||||
'You can load any type of audio your browser supports and perform operations such as fade in, cut, trim, change the volume, '+
|
||||
'and apply a plethora of audio effects.<br/><br/>'+
|
||||
body_str2+
|
||||
'</div>',
|
||||
setup:function( q ) {
|
||||
PKAE.ui.InteractionHandler.checkAndSet ('modal');
|
||||
PKAE.ui.KeyHandler.addCallback ('modalTemp', function ( e ) {
|
||||
q.Destroy ();
|
||||
}, [27]);
|
||||
|
||||
// ------
|
||||
var scroll = q.el_body.getElementsByTagName('div')[0];
|
||||
scroll.addEventListener ('touchstart', function(e){
|
||||
e.stopPropagation ();
|
||||
}, false);
|
||||
scroll.addEventListener ('touchmove', function(e){
|
||||
e.stopPropagation ();
|
||||
}, false);
|
||||
|
||||
// ------
|
||||
}
|
||||
});
|
||||
md.Show ();
|
||||
document.getElementsByClassName('pk_modal_cancel')[0].innerHTML = ' OK ';
|
||||
};
|
||||
|
||||
var change = 96;
|
||||
var exists = w.localStorage && w.localStorage.getItem ('k');
|
||||
|
||||
if (!exists) {
|
||||
change = 0;
|
||||
w.localStorage && w.localStorage.setItem ('k', 1);
|
||||
}
|
||||
|
||||
if ( ((Math.random () * 100) >> 0) < change) return ;
|
||||
PKAudioEditor._deps.Wlc ();
|
||||
|
||||
}, 320);
|
||||
|
||||
})( window, document, PKAudioEditor );
|
||||