]> git.sesse.net Git - mlt/commitdiff
Add new audio loudness filter based on EBU R128
authorBrian Matherly <pez4brian@yahoo.com>
Wed, 5 Feb 2014 04:38:07 +0000 (22:38 -0600)
committerBrian Matherly <pez4brian@yahoo.com>
Wed, 5 Feb 2014 04:38:13 +0000 (22:38 -0600)
src/modules/plus/Makefile
src/modules/plus/ebur128/COPYING [new file with mode: 0644]
src/modules/plus/ebur128/ebur128.c [new file with mode: 0644]
src/modules/plus/ebur128/ebur128.h [new file with mode: 0644]
src/modules/plus/factory.c
src/modules/plus/filter_loudness.c [new file with mode: 0644]
src/modules/plus/filter_loudness.yml [new file with mode: 0644]

index 446466551e5cfa9b7fdf30818c67336fd3aa4abf..8102a1b7e847e4e099976e91437711ed03d70356 100644 (file)
@@ -12,10 +12,12 @@ OBJS = consumer_blipflash.o \
           filter_charcoal.o \
           filter_dynamictext.o \
           filter_invert.o \
+          filter_loudness.o \
           filter_sepia.o \
           producer_blipflash.o \
           producer_count.o \
-          transition_affine.o
+          transition_affine.o \
+          ebur128/ebur128.o
 
 SRCS := $(OBJS:.o=.c)
 
diff --git a/src/modules/plus/ebur128/COPYING b/src/modules/plus/ebur128/COPYING
new file mode 100644 (file)
index 0000000..11a4821
--- /dev/null
@@ -0,0 +1,739 @@
+
+
+
+<!DOCTYPE html>
+<html>
+  <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# githubog: http://ogp.me/ns/fb/githubog#">
+    <meta charset='utf-8'>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <title>libebur128/COPYING at master · jiixyj/libebur128</title>
+    <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub" />
+    <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub" />
+    <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-114.png" />
+    <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114.png" />
+    <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-144.png" />
+    <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144.png" />
+    <link rel="logo" type="image/svg" href="https://github-media-downloads.s3.amazonaws.com/github-logo.svg" />
+    <meta property="og:image" content="https://github.global.ssl.fastly.net/images/modules/logos_page/Octocat.png">
+    <meta name="hostname" content="github-fe130-cp1-prd.iad.github.net">
+    <meta name="ruby" content="ruby 2.1.0p0-github-tcmalloc (60139581e1) [x86_64-linux]">
+    <link rel="assets" href="https://github.global.ssl.fastly.net/">
+    <link rel="conduit-xhr" href="https://ghconduit.com:25035/">
+    <link rel="xhr-socket" href="/_sockets" />
+    
+
+
+    <meta name="msapplication-TileImage" content="/windows-tile.png" />
+    <meta name="msapplication-TileColor" content="#ffffff" />
+    <meta name="selected-link" value="repo_source" data-pjax-transient />
+    <meta content="collector.githubapp.com" name="octolytics-host" /><meta content="collector-cdn.github.com" name="octolytics-script-host" /><meta content="github" name="octolytics-app-id" /><meta content="18DCB909:7D39:2D34A1:52DCA654" name="octolytics-dimension-request_id" /><meta content="821968" name="octolytics-actor-id" /><meta content="pez4brian" name="octolytics-actor-login" /><meta content="e1c69b58cbd1b68b0e7f93c351ab47e8bf2d3cfa957bb8a2265794bf05ce747d" name="octolytics-actor-hash" />
+    
+
+    
+    
+    <link rel="icon" type="image/x-icon" href="/favicon.ico" />
+
+    <meta content="authenticity_token" name="csrf-param" />
+<meta content="bUsJKeNDBdW6sL3g5ojzZJ5XksGcrDgGRp2LGt9IVyo=" name="csrf-token" />
+
+    <link href="https://github.global.ssl.fastly.net/assets/github-afd65da2802beafd8aee40df66e8b576092b2913.css" media="all" rel="stylesheet" type="text/css" />
+    <link href="https://github.global.ssl.fastly.net/assets/github2-3a909621ac79c89e7f414c6a6babc7f449d8ee93.css" media="all" rel="stylesheet" type="text/css" />
+    
+
+
+      <script src="https://github.global.ssl.fastly.net/assets/frameworks-bf5987648bb83690ac0a5e955f74bbaf6ba44c4a.js" type="text/javascript"></script>
+      <script src="https://github.global.ssl.fastly.net/assets/github-8f0f971134413bf7449fde4428a2d0683d647ca5.js" type="text/javascript"></script>
+      
+      <meta http-equiv="x-pjax-version" content="5fe7ec139c1bfc53297d312484aaf0e3">
+
+        <link data-pjax-transient rel='permalink' href='/jiixyj/libebur128/blob/12145473831324d3b72b490f34c3d8993ba69b44/COPYING'>
+  <meta property="og:title" content="libebur128"/>
+  <meta property="og:type" content="githubog:gitrepository"/>
+  <meta property="og:url" content="https://github.com/jiixyj/libebur128"/>
+  <meta property="og:image" content="https://github.global.ssl.fastly.net/images/gravatars/gravatar-user-420.png"/>
+  <meta property="og:site_name" content="GitHub"/>
+  <meta property="og:description" content="libebur128 - A library implementing the EBU R128 loudness standard."/>
+
+  <meta name="description" content="libebur128 - A library implementing the EBU R128 loudness standard." />
+
+  <meta content="491153" name="octolytics-dimension-user_id" /><meta content="jiixyj" name="octolytics-dimension-user_login" /><meta content="1641727" name="octolytics-dimension-repository_id" /><meta content="jiixyj/libebur128" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="false" name="octolytics-dimension-repository_is_fork" /><meta content="1641727" name="octolytics-dimension-repository_network_root_id" /><meta content="jiixyj/libebur128" name="octolytics-dimension-repository_network_root_nwo" />
+  <link href="https://github.com/jiixyj/libebur128/commits/master.atom" rel="alternate" title="Recent Commits to libebur128:master" type="application/atom+xml" />
+
+  </head>
+
+
+  <body class="logged_in  env-production linux vis-public page-blob">
+    <div class="wrapper">
+      
+      
+      
+      
+
+
+      <div class="header header-logged-in true">
+  <div class="container clearfix">
+
+    <a class="header-logo-invertocat" href="https://github.com/">
+  <span class="mega-octicon octicon-mark-github"></span>
+</a>
+
+    
+    <a href="/notifications" class="notification-indicator tooltipped downwards" data-gotokey="n" title="You have unread notifications">
+        <span class="mail-status unread"></span>
+</a>
+
+      <div class="command-bar js-command-bar  in-repository">
+          <form accept-charset="UTF-8" action="/search" class="command-bar-form" id="top_search_form" method="get">
+
+<input type="text" data-hotkey=" s" name="q" id="js-command-bar-field" placeholder="Search or type a command" tabindex="1" autocapitalize="off"
+    
+    data-username="pez4brian"
+      data-repo="jiixyj/libebur128"
+      data-branch="master"
+      data-sha="2071a1c55439f7ecbcae0bf7118d52c3f9aa0b57"
+  >
+
+    <input type="hidden" name="nwo" value="jiixyj/libebur128" />
+
+    <div class="select-menu js-menu-container js-select-menu search-context-select-menu">
+      <span class="minibutton select-menu-button js-menu-target">
+        <span class="js-select-button">This repository</span>
+      </span>
+
+      <div class="select-menu-modal-holder js-menu-content js-navigation-container">
+        <div class="select-menu-modal">
+
+          <div class="select-menu-item js-navigation-item js-this-repository-navigation-item selected">
+            <span class="select-menu-item-icon octicon octicon-check"></span>
+            <input type="radio" class="js-search-this-repository" name="search_target" value="repository" checked="checked" />
+            <div class="select-menu-item-text js-select-button-text">This repository</div>
+          </div> <!-- /.select-menu-item -->
+
+          <div class="select-menu-item js-navigation-item js-all-repositories-navigation-item">
+            <span class="select-menu-item-icon octicon octicon-check"></span>
+            <input type="radio" name="search_target" value="global" />
+            <div class="select-menu-item-text js-select-button-text">All repositories</div>
+          </div> <!-- /.select-menu-item -->
+
+        </div>
+      </div>
+    </div>
+
+  <span class="octicon help tooltipped downwards" title="Show command bar help">
+    <span class="octicon octicon-question"></span>
+  </span>
+
+
+  <input type="hidden" name="ref" value="cmdform">
+
+</form>
+        <ul class="top-nav">
+          <li class="explore"><a href="/explore">Explore</a></li>
+            <li><a href="https://gist.github.com">Gist</a></li>
+            <li><a href="/blog">Blog</a></li>
+          <li><a href="https://help.github.com">Help</a></li>
+        </ul>
+      </div>
+
+    
+
+
+  <ul id="user-links">
+    <li>
+      <a href="/pez4brian" class="name">
+        <img height="20" src="https://2.gravatar.com/avatar/55f78d3c922861ef9496c88beb291499?d=https%3A%2F%2Fidenticons.github.com%2Faf7938a8dd1620e54ac18e1f164014ec.png&amp;r=x&amp;s=140" width="20" /> pez4brian
+      </a>
+    </li>
+
+      <li class="new-menu dropdown-toggle js-menu-container">
+        <a href="#" class="js-menu-target tooltipped downwards" title="Create new…">
+          <span class="octicon octicon-plus"></span>
+          <span class="dropdown-arrow"></span>
+        </a>
+
+        <div class="js-menu-content">
+        </div>
+      </li>
+
+      <li>
+        <a href="/settings/profile" id="account_settings"
+          class="tooltipped downwards"
+          aria-label="Account settings "
+          title="Account settings ">
+          <span class="octicon octicon-tools"></span>
+        </a>
+      </li>
+      <li>
+        <a class="tooltipped downwards" href="/logout" data-method="post" id="logout" title="Sign out" aria-label="Sign out">
+          <span class="octicon octicon-log-out"></span>
+        </a>
+      </li>
+
+  </ul>
+
+<div class="js-new-dropdown-contents hidden">
+  
+
+<ul class="dropdown-menu">
+  <li>
+    <a href="/new"><span class="octicon octicon-repo-create"></span> New repository</a>
+  </li>
+  <li>
+    <a href="/organizations/new"><span class="octicon octicon-organization"></span> New organization</a>
+  </li>
+
+
+
+    <li class="section-title">
+      <span title="jiixyj/libebur128">This repository</span>
+    </li>
+      <li>
+        <a href="/jiixyj/libebur128/issues/new"><span class="octicon octicon-issue-opened"></span> New issue</a>
+      </li>
+</ul>
+
+</div>
+
+
+    
+  </div>
+</div>
+
+      
+
+      
+
+
+
+
+          <div class="site" itemscope itemtype="http://schema.org/WebPage">
+    
+    <div class="pagehead repohead instapaper_ignore readability-menu">
+      <div class="container">
+        
+
+<ul class="pagehead-actions">
+
+    <li class="subscription">
+      <form accept-charset="UTF-8" action="/notifications/subscribe" class="js-social-container" data-autosubmit="true" data-remote="true" method="post"><div style="margin:0;padding:0;display:inline"><input name="authenticity_token" type="hidden" value="bUsJKeNDBdW6sL3g5ojzZJ5XksGcrDgGRp2LGt9IVyo=" /></div>  <input id="repository_id" name="repository_id" type="hidden" value="1641727" />
+
+    <div class="select-menu js-menu-container js-select-menu">
+      <a class="social-count js-social-count" href="/jiixyj/libebur128/watchers">
+        14
+      </a>
+      <span class="minibutton select-menu-button with-count js-menu-target" role="button" tabindex="0">
+        <span class="js-select-button">
+          <span class="octicon octicon-eye-watch"></span>
+          Watch
+        </span>
+      </span>
+
+      <div class="select-menu-modal-holder">
+        <div class="select-menu-modal subscription-menu-modal js-menu-content">
+          <div class="select-menu-header">
+            <span class="select-menu-title">Notification status</span>
+            <span class="octicon octicon-remove-close js-menu-close"></span>
+          </div> <!-- /.select-menu-header -->
+
+          <div class="select-menu-list js-navigation-container" role="menu">
+
+            <div class="select-menu-item js-navigation-item selected" role="menuitem" tabindex="0">
+              <span class="select-menu-item-icon octicon octicon-check"></span>
+              <div class="select-menu-item-text">
+                <input checked="checked" id="do_included" name="do" type="radio" value="included" />
+                <h4>Not watching</h4>
+                <span class="description">You only receive notifications for conversations in which you participate or are @mentioned.</span>
+                <span class="js-select-button-text hidden-select-button-text">
+                  <span class="octicon octicon-eye-watch"></span>
+                  Watch
+                </span>
+              </div>
+            </div> <!-- /.select-menu-item -->
+
+            <div class="select-menu-item js-navigation-item " role="menuitem" tabindex="0">
+              <span class="select-menu-item-icon octicon octicon octicon-check"></span>
+              <div class="select-menu-item-text">
+                <input id="do_subscribed" name="do" type="radio" value="subscribed" />
+                <h4>Watching</h4>
+                <span class="description">You receive notifications for all conversations in this repository.</span>
+                <span class="js-select-button-text hidden-select-button-text">
+                  <span class="octicon octicon-eye-unwatch"></span>
+                  Unwatch
+                </span>
+              </div>
+            </div> <!-- /.select-menu-item -->
+
+            <div class="select-menu-item js-navigation-item " role="menuitem" tabindex="0">
+              <span class="select-menu-item-icon octicon octicon-check"></span>
+              <div class="select-menu-item-text">
+                <input id="do_ignore" name="do" type="radio" value="ignore" />
+                <h4>Ignoring</h4>
+                <span class="description">You do not receive any notifications for conversations in this repository.</span>
+                <span class="js-select-button-text hidden-select-button-text">
+                  <span class="octicon octicon-mute"></span>
+                  Stop ignoring
+                </span>
+              </div>
+            </div> <!-- /.select-menu-item -->
+
+          </div> <!-- /.select-menu-list -->
+
+        </div> <!-- /.select-menu-modal -->
+      </div> <!-- /.select-menu-modal-holder -->
+    </div> <!-- /.select-menu -->
+
+</form>
+    </li>
+
+  <li>
+  
+
+  <div class="js-toggler-container js-social-container starring-container ">
+    <a href="/jiixyj/libebur128/unstar"
+      class="minibutton with-count js-toggler-target star-button starred upwards"
+      title="Unstar this repository" data-remote="true" data-method="post" rel="nofollow">
+      <span class="octicon octicon-star-delete"></span><span class="text">Unstar</span>
+    </a>
+
+    <a href="/jiixyj/libebur128/star"
+      class="minibutton with-count js-toggler-target star-button unstarred upwards"
+      title="Star this repository" data-remote="true" data-method="post" rel="nofollow">
+      <span class="octicon octicon-star"></span><span class="text">Star</span>
+    </a>
+
+      <a class="social-count js-social-count" href="/jiixyj/libebur128/stargazers">
+        36
+      </a>
+  </div>
+
+  </li>
+
+
+        <li>
+          <a href="/jiixyj/libebur128/fork" class="minibutton with-count js-toggler-target fork-button lighter upwards" title="Fork this repo" rel="facebox nofollow">
+            <span class="octicon octicon-git-branch-create"></span><span class="text">Fork</span>
+          </a>
+          <a href="/jiixyj/libebur128/network" class="social-count">5</a>
+        </li>
+
+
+</ul>
+
+        <h1 itemscope itemtype="http://data-vocabulary.org/Breadcrumb" class="entry-title public">
+          <span class="repo-label"><span>public</span></span>
+          <span class="mega-octicon octicon-repo"></span>
+          <span class="author">
+            <a href="/jiixyj" class="url fn" itemprop="url" rel="author"><span itemprop="title">jiixyj</span></a>
+          </span>
+          <span class="repohead-name-divider">/</span>
+          <strong><a href="/jiixyj/libebur128" class="js-current-repository js-repo-home-link">libebur128</a></strong>
+
+          <span class="page-context-loader">
+            <img alt="Octocat-spinner-32" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+          </span>
+
+        </h1>
+      </div><!-- /.container -->
+    </div><!-- /.repohead -->
+
+    <div class="container">
+      
+
+      <div class="repository-with-sidebar repo-container  ">
+
+        <div class="repository-sidebar">
+            
+
+<div class="sunken-menu vertical-right repo-nav js-repo-nav js-repository-container-pjax js-octicon-loaders">
+  <div class="sunken-menu-contents">
+    <ul class="sunken-menu-group">
+      <li class="tooltipped leftwards" title="Code">
+        <a href="/jiixyj/libebur128" aria-label="Code" class="selected js-selected-navigation-item sunken-menu-item" data-gotokey="c" data-pjax="true" data-selected-links="repo_source repo_downloads repo_commits repo_tags repo_branches /jiixyj/libebur128">
+          <span class="octicon octicon-code"></span> <span class="full-word">Code</span>
+          <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+
+        <li class="tooltipped leftwards" title="Issues">
+          <a href="/jiixyj/libebur128/issues" aria-label="Issues" class="js-selected-navigation-item sunken-menu-item js-disable-pjax" data-gotokey="i" data-selected-links="repo_issues /jiixyj/libebur128/issues">
+            <span class="octicon octicon-issue-opened"></span> <span class="full-word">Issues</span>
+            <span class='counter'>4</span>
+            <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>        </li>
+
+      <li class="tooltipped leftwards" title="Pull Requests">
+        <a href="/jiixyj/libebur128/pulls" aria-label="Pull Requests" class="js-selected-navigation-item sunken-menu-item js-disable-pjax" data-gotokey="p" data-selected-links="repo_pulls /jiixyj/libebur128/pulls">
+            <span class="octicon octicon-git-pull-request"></span> <span class="full-word">Pull Requests</span>
+            <span class='counter'>0</span>
+            <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+
+
+        <li class="tooltipped leftwards" title="Wiki">
+          <a href="/jiixyj/libebur128/wiki" aria-label="Wiki" class="js-selected-navigation-item sunken-menu-item" data-pjax="true" data-selected-links="repo_wiki /jiixyj/libebur128/wiki">
+            <span class="octicon octicon-book"></span> <span class="full-word">Wiki</span>
+            <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>        </li>
+    </ul>
+    <div class="sunken-menu-separator"></div>
+    <ul class="sunken-menu-group">
+
+      <li class="tooltipped leftwards" title="Pulse">
+        <a href="/jiixyj/libebur128/pulse" aria-label="Pulse" class="js-selected-navigation-item sunken-menu-item" data-pjax="true" data-selected-links="pulse /jiixyj/libebur128/pulse">
+          <span class="octicon octicon-pulse"></span> <span class="full-word">Pulse</span>
+          <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+
+      <li class="tooltipped leftwards" title="Graphs">
+        <a href="/jiixyj/libebur128/graphs" aria-label="Graphs" class="js-selected-navigation-item sunken-menu-item" data-pjax="true" data-selected-links="repo_graphs repo_contributors /jiixyj/libebur128/graphs">
+          <span class="octicon octicon-graph"></span> <span class="full-word">Graphs</span>
+          <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+
+      <li class="tooltipped leftwards" title="Network">
+        <a href="/jiixyj/libebur128/network" aria-label="Network" class="js-selected-navigation-item sunken-menu-item js-disable-pjax" data-selected-links="repo_network /jiixyj/libebur128/network">
+          <span class="octicon octicon-git-branch"></span> <span class="full-word">Network</span>
+          <img alt="Octocat-spinner-32" class="mini-loader" height="16" src="https://github.global.ssl.fastly.net/images/spinners/octocat-spinner-32.gif" width="16" />
+</a>      </li>
+    </ul>
+
+
+  </div>
+</div>
+
+            <div class="only-with-full-nav">
+              
+
+  
+
+<div class="clone-url open"
+  data-protocol-type="http"
+  data-url="/users/set_protocol?protocol_selector=http&amp;protocol_type=clone">
+  <h3><strong>HTTPS</strong> clone URL</h3>
+  <div class="clone-url-box">
+    <input type="text" class="clone js-url-field"
+           value="https://github.com/jiixyj/libebur128.git" readonly="readonly">
+
+    <span class="js-zeroclipboard url-box-clippy minibutton zeroclipboard-button" data-clipboard-text="https://github.com/jiixyj/libebur128.git" data-copied-hint="copied!" title="copy to clipboard"><span class="octicon octicon-clippy"></span></span>
+  </div>
+</div>
+
+  
+
+<div class="clone-url "
+  data-protocol-type="ssh"
+  data-url="/users/set_protocol?protocol_selector=ssh&amp;protocol_type=clone">
+  <h3><strong>SSH</strong> clone URL</h3>
+  <div class="clone-url-box">
+    <input type="text" class="clone js-url-field"
+           value="git@github.com:jiixyj/libebur128.git" readonly="readonly">
+
+    <span class="js-zeroclipboard url-box-clippy minibutton zeroclipboard-button" data-clipboard-text="git@github.com:jiixyj/libebur128.git" data-copied-hint="copied!" title="copy to clipboard"><span class="octicon octicon-clippy"></span></span>
+  </div>
+</div>
+
+  
+
+<div class="clone-url "
+  data-protocol-type="subversion"
+  data-url="/users/set_protocol?protocol_selector=subversion&amp;protocol_type=clone">
+  <h3><strong>Subversion</strong> checkout URL</h3>
+  <div class="clone-url-box">
+    <input type="text" class="clone js-url-field"
+           value="https://github.com/jiixyj/libebur128" readonly="readonly">
+
+    <span class="js-zeroclipboard url-box-clippy minibutton zeroclipboard-button" data-clipboard-text="https://github.com/jiixyj/libebur128" data-copied-hint="copied!" title="copy to clipboard"><span class="octicon octicon-clippy"></span></span>
+  </div>
+</div>
+
+
+<p class="clone-options">You can clone with
+      <a href="#" class="js-clone-selector" data-protocol="http">HTTPS</a>,
+      <a href="#" class="js-clone-selector" data-protocol="ssh">SSH</a>,
+      or <a href="#" class="js-clone-selector" data-protocol="subversion">Subversion</a>.
+  <span class="octicon help tooltipped upwards" title="Get help on which URL is right for you.">
+    <a href="https://help.github.com/articles/which-remote-url-should-i-use">
+    <span class="octicon octicon-question"></span>
+    </a>
+  </span>
+</p>
+
+
+
+              <a href="/jiixyj/libebur128/archive/master.zip"
+                 class="minibutton sidebar-button"
+                 title="Download this repository as a zip file"
+                 rel="nofollow">
+                <span class="octicon octicon-cloud-download"></span>
+                Download ZIP
+              </a>
+            </div>
+        </div><!-- /.repository-sidebar -->
+
+        <div id="js-repo-pjax-container" class="repository-content context-loader-container" data-pjax-container>
+          
+
+
+<!-- blob contrib key: blob_contributors:v21:91171c652a3664ed7857f22a8d4032fc -->
+
+<p title="This is a placeholder element" class="js-history-link-replace hidden"></p>
+
+<a href="/jiixyj/libebur128/find/master" data-pjax data-hotkey="t" class="js-show-file-finder" style="display:none">Show File Finder</a>
+
+<div class="file-navigation">
+  
+
+<div class="select-menu js-menu-container js-select-menu" >
+  <span class="minibutton select-menu-button js-menu-target" data-hotkey="w"
+    data-master-branch="master"
+    data-ref="master"
+    role="button" aria-label="Switch branches or tags" tabindex="0">
+    <span class="octicon octicon-git-branch"></span>
+    <i>branch:</i>
+    <span class="js-select-button">master</span>
+  </span>
+
+  <div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax>
+
+    <div class="select-menu-modal">
+      <div class="select-menu-header">
+        <span class="select-menu-title">Switch branches/tags</span>
+        <span class="octicon octicon-remove-close js-menu-close"></span>
+      </div> <!-- /.select-menu-header -->
+
+      <div class="select-menu-filters">
+        <div class="select-menu-text-filter">
+          <input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
+        </div>
+        <div class="select-menu-tabs">
+          <ul>
+            <li class="select-menu-tab">
+              <a href="#" data-tab-filter="branches" class="js-select-menu-tab">Branches</a>
+            </li>
+            <li class="select-menu-tab">
+              <a href="#" data-tab-filter="tags" class="js-select-menu-tab">Tags</a>
+            </li>
+          </ul>
+        </div><!-- /.select-menu-tabs -->
+      </div><!-- /.select-menu-filters -->
+
+      <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches">
+
+        <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
+
+
+            <div class="select-menu-item js-navigation-item selected">
+              <span class="select-menu-item-icon octicon octicon-check"></span>
+              <a href="/jiixyj/libebur128/blob/master/COPYING"
+                 data-name="master"
+                 data-skip-pjax="true"
+                 rel="nofollow"
+                 class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target"
+                 title="master">master</a>
+            </div> <!-- /.select-menu-item -->
+        </div>
+
+          <div class="select-menu-no-results">Nothing to show</div>
+      </div> <!-- /.select-menu-list -->
+
+      <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
+        <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
+
+
+            <div class="select-menu-item js-navigation-item ">
+              <span class="select-menu-item-icon octicon octicon-check"></span>
+              <a href="/jiixyj/libebur128/tree/v1.0.1/COPYING"
+                 data-name="v1.0.1"
+                 data-skip-pjax="true"
+                 rel="nofollow"
+                 class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target"
+                 title="v1.0.1">v1.0.1</a>
+            </div> <!-- /.select-menu-item -->
+            <div class="select-menu-item js-navigation-item ">
+              <span class="select-menu-item-icon octicon octicon-check"></span>
+              <a href="/jiixyj/libebur128/tree/v1.0.0/COPYING"
+                 data-name="v1.0.0"
+                 data-skip-pjax="true"
+                 rel="nofollow"
+                 class="js-navigation-open select-menu-item-text js-select-button-text css-truncate-target"
+                 title="v1.0.0">v1.0.0</a>
+            </div> <!-- /.select-menu-item -->
+        </div>
+
+        <div class="select-menu-no-results">Nothing to show</div>
+      </div> <!-- /.select-menu-list -->
+
+    </div> <!-- /.select-menu-modal -->
+  </div> <!-- /.select-menu-modal-holder -->
+</div> <!-- /.select-menu -->
+
+  <div class="breadcrumb">
+    <span class='repo-root js-repo-root'><span itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb"><a href="/jiixyj/libebur128" data-branch="master" data-direction="back" data-pjax="true" itemscope="url"><span itemprop="title">libebur128</span></a></span></span><span class="separator"> / </span><strong class="final-path">COPYING</strong> <span class="js-zeroclipboard minibutton zeroclipboard-button" data-clipboard-text="COPYING" data-copied-hint="copied!" title="copy to clipboard"><span class="octicon octicon-clippy"></span></span>
+  </div>
+</div>
+
+
+
+  <div class="commit file-history-tease">
+    <img class="main-avatar" height="24" src="https://1.gravatar.com/avatar/3b5bd17d0af4e7a25c76e01905abeebe?d=https%3A%2F%2Fidenticons.github.com%2F6022381812a68b1ea9816ac0417536f7.png&amp;r=x&amp;s=140" width="24" />
+    <span class="author"><a href="/jiixyj" rel="author">jiixyj</a></span>
+    <time class="js-relative-date" datetime="2011-03-14T08:06:24-07:00" title="2011-03-14 08:06:24">March 14, 2011</time>
+    <div class="commit-title">
+        <a href="/jiixyj/libebur128/commit/8d5617cc4bac6a22dbf9d1ce4af8668fbb0a866d" class="message" data-pjax="true" title="rename LICENSE to COPYING">rename LICENSE to COPYING</a>
+    </div>
+
+    <div class="participation">
+      <p class="quickstat"><a href="#blob_contributors_box" rel="facebox"><strong>1</strong> contributor</a></p>
+      
+    </div>
+    <div id="blob_contributors_box" style="display:none">
+      <h2 class="facebox-header">Users who have contributed to this file</h2>
+      <ul class="facebox-user-list">
+          <li class="facebox-user-list-item">
+            <img height="24" src="https://1.gravatar.com/avatar/3b5bd17d0af4e7a25c76e01905abeebe?d=https%3A%2F%2Fidenticons.github.com%2F6022381812a68b1ea9816ac0417536f7.png&amp;r=x&amp;s=140" width="24" />
+            <a href="/jiixyj">jiixyj</a>
+          </li>
+      </ul>
+    </div>
+  </div>
+
+<div id="files" class="bubble">
+  <div class="file">
+    <div class="meta">
+      <div class="info">
+        <span class="icon"><b class="octicon octicon-file-text"></b></span>
+        <span class="mode" title="File Mode">file</span>
+          <span>20 lines (16 sloc)</span>
+        <span>1.059 kb</span>
+      </div>
+      <div class="actions">
+        <div class="button-group">
+                <a class="minibutton tooltipped upwards"
+                   title="Clicking this button will automatically fork this project so you can edit the file"
+                   href="/jiixyj/libebur128/edit/master/COPYING"
+                   data-method="post" rel="nofollow">Edit</a>
+          <a href="/jiixyj/libebur128/raw/master/COPYING" class="button minibutton " id="raw-url">Raw</a>
+            <a href="/jiixyj/libebur128/blame/master/COPYING" class="button minibutton ">Blame</a>
+          <a href="/jiixyj/libebur128/commits/master/COPYING" class="button minibutton " rel="nofollow">History</a>
+        </div><!-- /.button-group -->
+          <a class="minibutton danger empty-icon tooltipped downwards"
+             href="/jiixyj/libebur128/delete/master/COPYING"
+             title="Fork this project and delete file"
+             data-method="post" data-test-id="delete-blob-file" rel="nofollow">
+          Delete
+        </a>
+      </div><!-- /.actions -->
+
+    </div>
+        <div class="blob-wrapper data type-text js-blob-data">
+        <table class="file-code file-diff">
+          <tr class="file-code-line">
+            <td class="blob-line-nums">
+              <span id="L1" rel="#L1">1</span>
+<span id="L2" rel="#L2">2</span>
+<span id="L3" rel="#L3">3</span>
+<span id="L4" rel="#L4">4</span>
+<span id="L5" rel="#L5">5</span>
+<span id="L6" rel="#L6">6</span>
+<span id="L7" rel="#L7">7</span>
+<span id="L8" rel="#L8">8</span>
+<span id="L9" rel="#L9">9</span>
+<span id="L10" rel="#L10">10</span>
+<span id="L11" rel="#L11">11</span>
+<span id="L12" rel="#L12">12</span>
+<span id="L13" rel="#L13">13</span>
+<span id="L14" rel="#L14">14</span>
+<span id="L15" rel="#L15">15</span>
+<span id="L16" rel="#L16">16</span>
+<span id="L17" rel="#L17">17</span>
+<span id="L18" rel="#L18">18</span>
+<span id="L19" rel="#L19">19</span>
+
+            </td>
+            <td class="blob-line-code">
+                    <div class="code-body highlight"><pre><div class='line' id='LC1'>Copyright (c) 2011 Jan Kokemüller</div><div class='line' id='LC2'><br/></div><div class='line' id='LC3'>Permission is hereby granted, free of charge, to any person obtaining a copy</div><div class='line' id='LC4'>of this software and associated documentation files (the &quot;Software&quot;), to deal</div><div class='line' id='LC5'>in the Software without restriction, including without limitation the rights</div><div class='line' id='LC6'>to use, copy, modify, merge, publish, distribute, sublicense, and/or sell</div><div class='line' id='LC7'>copies of the Software, and to permit persons to whom the Software is</div><div class='line' id='LC8'>furnished to do so, subject to the following conditions:</div><div class='line' id='LC9'><br/></div><div class='line' id='LC10'>The above copyright notice and this permission notice shall be included in</div><div class='line' id='LC11'>all copies or substantial portions of the Software.</div><div class='line' id='LC12'><br/></div><div class='line' id='LC13'>THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR</div><div class='line' id='LC14'>IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,</div><div class='line' id='LC15'>FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE</div><div class='line' id='LC16'>AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER</div><div class='line' id='LC17'>LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,</div><div class='line' id='LC18'>OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN</div><div class='line' id='LC19'>THE SOFTWARE.</div></pre></div>
+            </td>
+          </tr>
+        </table>
+  </div>
+
+  </div>
+</div>
+
+<a href="#jump-to-line" rel="facebox[.linejump]" data-hotkey="l" class="js-jump-to-line" style="display:none">Jump to Line</a>
+<div id="jump-to-line" style="display:none">
+  <form accept-charset="UTF-8" class="js-jump-to-line-form">
+    <input class="linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line&hellip;" autofocus>
+    <button type="submit" class="button">Go</button>
+  </form>
+</div>
+
+        </div>
+
+      </div><!-- /.repo-container -->
+      <div class="modal-backdrop"></div>
+    </div><!-- /.container -->
+  </div><!-- /.site -->
+
+
+    </div><!-- /.wrapper -->
+
+      <div class="container">
+  <div class="site-footer">
+    <ul class="site-footer-links right">
+      <li><a href="https://status.github.com/">Status</a></li>
+      <li><a href="http://developer.github.com">API</a></li>
+      <li><a href="http://training.github.com">Training</a></li>
+      <li><a href="http://shop.github.com">Shop</a></li>
+      <li><a href="/blog">Blog</a></li>
+      <li><a href="/about">About</a></li>
+
+    </ul>
+
+    <a href="/">
+      <span class="mega-octicon octicon-mark-github" title="GitHub"></span>
+    </a>
+
+    <ul class="site-footer-links">
+      <li>&copy; 2014 <span title="0.03197s from github-fe130-cp1-prd.iad.github.net">GitHub</span>, Inc.</li>
+        <li><a href="/site/terms">Terms</a></li>
+        <li><a href="/site/privacy">Privacy</a></li>
+        <li><a href="/security">Security</a></li>
+        <li><a href="/contact">Contact</a></li>
+    </ul>
+  </div><!-- /.site-footer -->
+</div><!-- /.container -->
+
+
+    <div class="fullscreen-overlay js-fullscreen-overlay" id="fullscreen_overlay">
+  <div class="fullscreen-container js-fullscreen-container">
+    <div class="textarea-wrap">
+      <textarea name="fullscreen-contents" id="fullscreen-contents" class="js-fullscreen-contents" placeholder="" data-suggester="fullscreen_suggester"></textarea>
+          <div class="suggester-container">
+              <div class="suggester fullscreen-suggester js-navigation-container" id="fullscreen_suggester"
+                 data-url="/jiixyj/libebur128/suggestions/commit">
+              </div>
+          </div>
+    </div>
+  </div>
+  <div class="fullscreen-sidebar">
+    <a href="#" class="exit-fullscreen js-exit-fullscreen tooltipped leftwards" title="Exit Zen Mode">
+      <span class="mega-octicon octicon-screen-normal"></span>
+    </a>
+    <a href="#" class="theme-switcher js-theme-switcher tooltipped leftwards"
+      title="Switch themes">
+      <span class="octicon octicon-color-mode"></span>
+    </a>
+  </div>
+</div>
+
+
+
+    <div id="ajax-error-message" class="flash flash-error">
+      <span class="octicon octicon-alert"></span>
+      <a href="#" class="octicon octicon-remove-close close ajax-error-dismiss"></a>
+      Something went wrong with that request. Please try again.
+    </div>
+
+  </body>
+</html>
+
diff --git a/src/modules/plus/ebur128/ebur128.c b/src/modules/plus/ebur128/ebur128.c
new file mode 100644 (file)
index 0000000..d59f8e9
--- /dev/null
@@ -0,0 +1,997 @@
+/* See COPYING file for copyright and license details. */
+
+#include "ebur128.h"
+
+#include <float.h>
+#include <limits.h>
+#include <math.h> /* You may have to define _USE_MATH_DEFINES if you use MSVC */
+#include <stdio.h>
+#include <stdlib.h>
+
+/* This can be replaced by any BSD-like queue implementation. */
+#include <sys/queue.h>
+
+#ifdef USE_SPEEX_RESAMPLER
+  #include <speex/speex_resampler.h>
+#endif
+
+#define CHECK_ERROR(condition, errorcode, goto_point)                          \
+  if ((condition)) {                                                           \
+    errcode = (errorcode);                                                     \
+    goto goto_point;                                                           \
+  }
+
+SLIST_HEAD(ebur128_double_queue, ebur128_dq_entry);
+struct ebur128_dq_entry {
+  double z;
+  SLIST_ENTRY(ebur128_dq_entry) entries;
+};
+
+struct ebur128_state_internal {
+  /** Filtered audio data (used as ring buffer). */
+  double* audio_data;
+  /** Size of audio_data array. */
+  size_t audio_data_frames;
+  /** Current index for audio_data. */
+  size_t audio_data_index;
+  /** How many frames are needed for a gating block. Will correspond to 400ms
+   *  of audio at initialization, and 100ms after the first block (75% overlap
+   *  as specified in the 2011 revision of BS1770). */
+  unsigned long needed_frames;
+  /** The channel map. Has as many elements as there are channels. */
+  int* channel_map;
+  /** How many samples fit in 100ms (rounded). */
+  unsigned long samples_in_100ms;
+  /** BS.1770 filter coefficients (nominator). */
+  double b[5];
+  /** BS.1770 filter coefficients (denominator). */
+  double a[5];
+  /** BS.1770 filter state. */
+  double v[5][5];
+  /** Linked list of block energies. */
+  struct ebur128_double_queue block_list;
+  /** Linked list of 3s-block energies, used to calculate LRA. */
+  struct ebur128_double_queue short_term_block_list;
+  int use_histogram;
+  unsigned long *block_energy_histogram;
+  unsigned long *short_term_block_energy_histogram;
+  /** Keeps track of when a new short term block is needed. */
+  size_t short_term_frame_counter;
+  /** Maximum sample peak, one per channel */
+  double* sample_peak;
+  /** Maximum true peak, one per channel */
+  double* true_peak;
+#ifdef USE_SPEEX_RESAMPLER
+  SpeexResamplerState* resampler;
+#endif
+  size_t oversample_factor;
+  float* resampler_buffer_input;
+  size_t resampler_buffer_input_frames;
+  float* resampler_buffer_output;
+  size_t resampler_buffer_output_frames;
+};
+
+static double relative_gate = -10.0;
+
+/* Those will be calculated when initializing the library */
+static double relative_gate_factor;
+static double minus_twenty_decibels;
+static double histogram_energies[1000];
+static double histogram_energy_boundaries[1001];
+
+static void ebur128_init_filter(ebur128_state* st) {
+  int i, j;
+
+  double f0 = 1681.974450955533;
+  double G  =    3.999843853973347;
+  double Q  =    0.7071752369554196;
+
+  double K  = tan(M_PI * f0 / (double) st->samplerate);
+  double Vh = pow(10.0, G / 20.0);
+  double Vb = pow(Vh, 0.4996667741545416);
+
+  double pb[3] = {0.0,  0.0, 0.0};
+  double pa[3] = {1.0,  0.0, 0.0};
+  double rb[3] = {1.0, -2.0, 1.0};
+  double ra[3] = {1.0,  0.0, 0.0};
+
+  double a0 =      1.0 + K / Q + K * K      ;
+  pb[0] =     (Vh + Vb * K / Q + K * K) / a0;
+  pb[1] =           2.0 * (K * K -  Vh) / a0;
+  pb[2] =     (Vh - Vb * K / Q + K * K) / a0;
+  pa[1] =           2.0 * (K * K - 1.0) / a0;
+  pa[2] =         (1.0 - K / Q + K * K) / a0;
+
+  /* fprintf(stderr, "%.14f %.14f %.14f %.14f %.14f\n",
+                     b1[0], b1[1], b1[2], a1[1], a1[2]); */
+
+  f0 = 38.13547087602444;
+  Q  =  0.5003270373238773;
+  K  = tan(M_PI * f0 / (double) st->samplerate);
+
+  ra[1] =   2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K);
+  ra[2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K);
+
+  /* fprintf(stderr, "%.14f %.14f\n", a2[1], a2[2]); */
+
+  st->d->b[0] = pb[0] * rb[0];
+  st->d->b[1] = pb[0] * rb[1] + pb[1] * rb[0];
+  st->d->b[2] = pb[0] * rb[2] + pb[1] * rb[1] + pb[2] * rb[0];
+  st->d->b[3] = pb[1] * rb[2] + pb[2] * rb[1];
+  st->d->b[4] = pb[2] * rb[2];
+
+  st->d->a[0] = pa[0] * ra[0];
+  st->d->a[1] = pa[0] * ra[1] + pa[1] * ra[0];
+  st->d->a[2] = pa[0] * ra[2] + pa[1] * ra[1] + pa[2] * ra[0];
+  st->d->a[3] = pa[1] * ra[2] + pa[2] * ra[1];
+  st->d->a[4] = pa[2] * ra[2];
+
+  for (i = 0; i < 5; ++i) {
+    for (j = 0; j < 5; ++j) {
+      st->d->v[i][j] = 0.0;
+    }
+  }
+}
+
+static int ebur128_init_channel_map(ebur128_state* st) {
+  size_t i;
+  st->d->channel_map = (int*) malloc(st->channels * sizeof(int));
+  if (!st->d->channel_map) return EBUR128_ERROR_NOMEM;
+  if (st->channels == 4) {
+    st->d->channel_map[0] = EBUR128_LEFT;
+    st->d->channel_map[1] = EBUR128_RIGHT;
+    st->d->channel_map[2] = EBUR128_LEFT_SURROUND;
+    st->d->channel_map[3] = EBUR128_RIGHT_SURROUND;
+  } else if (st->channels == 5) {
+    st->d->channel_map[0] = EBUR128_LEFT;
+    st->d->channel_map[1] = EBUR128_RIGHT;
+    st->d->channel_map[2] = EBUR128_CENTER;
+    st->d->channel_map[3] = EBUR128_LEFT_SURROUND;
+    st->d->channel_map[4] = EBUR128_RIGHT_SURROUND;
+  } else {
+    for (i = 0; i < st->channels; ++i) {
+      switch (i) {
+        case 0:  st->d->channel_map[i] = EBUR128_LEFT;           break;
+        case 1:  st->d->channel_map[i] = EBUR128_RIGHT;          break;
+        case 2:  st->d->channel_map[i] = EBUR128_CENTER;         break;
+        case 3:  st->d->channel_map[i] = EBUR128_UNUSED;         break;
+        case 4:  st->d->channel_map[i] = EBUR128_LEFT_SURROUND;  break;
+        case 5:  st->d->channel_map[i] = EBUR128_RIGHT_SURROUND; break;
+        default: st->d->channel_map[i] = EBUR128_UNUSED;         break;
+      }
+    }
+  }
+  return EBUR128_SUCCESS;
+}
+
+#ifdef USE_SPEEX_RESAMPLER
+static int ebur128_init_resampler(ebur128_state* st) {
+  int errcode = EBUR128_SUCCESS;
+
+  if (st->samplerate < 96000) {
+    st->d->oversample_factor = 4;
+  } else if (st->samplerate < 192000) {
+    st->d->oversample_factor = 2;
+  } else {
+    st->d->oversample_factor = 1;
+    st->d->resampler_buffer_input = NULL;
+    st->d->resampler_buffer_output = NULL;
+    st->d->resampler = NULL;
+  }
+
+  st->d->resampler_buffer_input_frames = st->d->samples_in_100ms * 4;
+  st->d->resampler_buffer_input = malloc(st->d->resampler_buffer_input_frames *
+                                      st->channels *
+                                      sizeof(float));
+  CHECK_ERROR(!st->d->resampler_buffer_input, EBUR128_ERROR_NOMEM, exit)
+
+  st->d->resampler_buffer_output_frames =
+                                    st->d->resampler_buffer_input_frames *
+                                    st->d->oversample_factor;
+  st->d->resampler_buffer_output = malloc
+                                      (st->d->resampler_buffer_output_frames *
+                                       st->channels *
+                                       sizeof(float));
+  CHECK_ERROR(!st->d->resampler_buffer_output, EBUR128_ERROR_NOMEM, free_input)
+
+  st->d->resampler = speex_resampler_init
+                 ((spx_uint32_t) st->channels,
+                  (spx_uint32_t) st->samplerate,
+                  (spx_uint32_t) (st->samplerate * st->d->oversample_factor),
+                  8, NULL);
+  CHECK_ERROR(!st->d->resampler, EBUR128_ERROR_NOMEM, free_output)
+
+  return errcode;
+
+free_output:
+  free(st->d->resampler_buffer_output);
+  st->d->resampler_buffer_output = NULL;
+free_input:
+  free(st->d->resampler_buffer_input);
+  st->d->resampler_buffer_input = NULL;
+exit:
+  return errcode;
+}
+
+static void ebur128_destroy_resampler(ebur128_state* st) {
+  free(st->d->resampler_buffer_input);
+  st->d->resampler_buffer_input = NULL;
+  free(st->d->resampler_buffer_output);
+  st->d->resampler_buffer_output = NULL;
+  speex_resampler_destroy(st->d->resampler);
+  st->d->resampler = NULL;
+}
+#endif
+
+void ebur128_get_version(int* major, int* minor, int* patch) {
+  *major = EBUR128_VERSION_MAJOR;
+  *minor = EBUR128_VERSION_MINOR;
+  *patch = EBUR128_VERSION_PATCH;
+}
+
+ebur128_state* ebur128_init(unsigned int channels,
+                            unsigned long samplerate,
+                            int mode) {
+  int errcode, result;
+  ebur128_state* st;
+  unsigned int i;
+
+  st = (ebur128_state*) malloc(sizeof(ebur128_state));
+  CHECK_ERROR(!st, 0, exit)
+  st->d = (struct ebur128_state_internal*)
+          malloc(sizeof(struct ebur128_state_internal));
+  CHECK_ERROR(!st->d, 0, free_state)
+  st->channels = channels;
+  errcode = ebur128_init_channel_map(st);
+  CHECK_ERROR(errcode, 0, free_internal)
+
+  st->d->sample_peak = (double*) malloc(channels * sizeof(double));
+  CHECK_ERROR(!st->d->sample_peak, 0, free_channel_map)
+  st->d->true_peak = (double*) malloc(channels * sizeof(double));
+  CHECK_ERROR(!st->d->true_peak, 0, free_sample_peak)
+  for (i = 0; i < channels; ++i) {
+    st->d->sample_peak[i] = 0.0;
+    st->d->true_peak[i] = 0.0;
+  }
+
+  st->d->use_histogram = mode & EBUR128_MODE_HISTOGRAM ? 1 : 0;
+
+  st->samplerate = samplerate;
+  st->d->samples_in_100ms = (st->samplerate + 5) / 10;
+  st->mode = mode;
+  if ((mode & EBUR128_MODE_S) == EBUR128_MODE_S) {
+    st->d->audio_data_frames = st->d->samples_in_100ms * 30;
+  } else if ((mode & EBUR128_MODE_M) == EBUR128_MODE_M) {
+    st->d->audio_data_frames = st->d->samples_in_100ms * 4;
+  } else {
+    return NULL;
+  }
+  st->d->audio_data = (double*) malloc(st->d->audio_data_frames *
+                                       st->channels *
+                                       sizeof(double));
+  CHECK_ERROR(!st->d->audio_data, 0, free_true_peak)
+  ebur128_init_filter(st);
+
+  if (st->d->use_histogram) {
+    st->d->block_energy_histogram = malloc(1000 * sizeof(unsigned long));
+    CHECK_ERROR(!st->d->block_energy_histogram, 0, free_audio_data)
+    for (i = 0; i < 1000; ++i) {
+      st->d->block_energy_histogram[i] = 0;
+    }
+  } else {
+    st->d->block_energy_histogram = NULL;
+  }
+  if (st->d->use_histogram) {
+    st->d->short_term_block_energy_histogram = malloc(1000 * sizeof(unsigned long));
+    CHECK_ERROR(!st->d->short_term_block_energy_histogram, 0, free_block_energy_histogram)
+    for (i = 0; i < 1000; ++i) {
+      st->d->short_term_block_energy_histogram[i] = 0;
+    }
+  } else {
+    st->d->short_term_block_energy_histogram = NULL;
+  }
+  SLIST_INIT(&st->d->block_list);
+  SLIST_INIT(&st->d->short_term_block_list);
+  st->d->short_term_frame_counter = 0;
+
+#ifdef USE_SPEEX_RESAMPLER
+  result = ebur128_init_resampler(st);
+  CHECK_ERROR(result, 0, free_short_term_block_energy_histogram)
+#endif
+
+  /* the first block needs 400ms of audio data */
+  st->d->needed_frames = st->d->samples_in_100ms * 4;
+  /* start at the beginning of the buffer */
+  st->d->audio_data_index = 0;
+
+  /* initialize static constants */
+  relative_gate_factor = pow(10.0, relative_gate / 10.0);
+  minus_twenty_decibels = pow(10.0, -20.0 / 10.0);
+  histogram_energy_boundaries[0] = pow(10.0, (-70.0 + 0.691) / 10.0);
+  if (st->d->use_histogram) {
+    for (i = 0; i < 1000; ++i) {
+      histogram_energies[i] = pow(10.0, ((double) i / 10.0 - 69.95 + 0.691) / 10.0);
+    }
+    for (i = 1; i < 1001; ++i) {
+      histogram_energy_boundaries[i] = pow(10.0, ((double) i / 10.0 - 70.0 + 0.691) / 10.0);
+    }
+  }
+
+  return st;
+
+free_short_term_block_energy_histogram:
+  free(st->d->short_term_block_energy_histogram);
+free_block_energy_histogram:
+  free(st->d->block_energy_histogram);
+free_audio_data:
+  free(st->d->audio_data);
+free_true_peak:
+  free(st->d->true_peak);
+free_sample_peak:
+  free(st->d->sample_peak);
+free_channel_map:
+  free(st->d->channel_map);
+free_internal:
+  free(st->d);
+free_state:
+  free(st);
+exit:
+  return NULL;
+}
+
+void ebur128_destroy(ebur128_state** st) {
+  struct ebur128_dq_entry* entry;
+  free((*st)->d->block_energy_histogram);
+  free((*st)->d->short_term_block_energy_histogram);
+  free((*st)->d->audio_data);
+  free((*st)->d->channel_map);
+  free((*st)->d->sample_peak);
+  free((*st)->d->true_peak);
+  while (!SLIST_EMPTY(&(*st)->d->block_list)) {
+    entry = SLIST_FIRST(&(*st)->d->block_list);
+    SLIST_REMOVE_HEAD(&(*st)->d->block_list, entries);
+    free(entry);
+  }
+  while (!SLIST_EMPTY(&(*st)->d->short_term_block_list)) {
+    entry = SLIST_FIRST(&(*st)->d->short_term_block_list);
+    SLIST_REMOVE_HEAD(&(*st)->d->short_term_block_list, entries);
+    free(entry);
+  }
+#ifdef USE_SPEEX_RESAMPLER
+  ebur128_destroy_resampler(*st);
+#endif
+
+  free((*st)->d);
+  free(*st);
+  *st = NULL;
+}
+
+static int ebur128_use_speex_resampler(ebur128_state* st) {
+#ifdef USE_SPEEX_RESAMPLER
+  return ((st->mode & EBUR128_MODE_TRUE_PEAK) == EBUR128_MODE_TRUE_PEAK);
+#else
+  (void) st;
+  return 0;
+#endif
+}
+
+static void ebur128_check_true_peak(ebur128_state* st, size_t frames) {
+#ifdef USE_SPEEX_RESAMPLER
+  size_t c, i;
+  spx_uint32_t in_len = (spx_uint32_t) frames;
+  spx_uint32_t out_len = (spx_uint32_t) st->d->resampler_buffer_output_frames;
+  speex_resampler_process_interleaved_float(
+                      st->d->resampler,
+                      st->d->resampler_buffer_input,  &in_len,
+                      st->d->resampler_buffer_output, &out_len);
+  for (c = 0; c < st->channels; ++c) {
+    for (i = 0; i < out_len; ++i) {
+      if (st->d->resampler_buffer_output[i * st->channels + c] >
+                                                         st->d->true_peak[c]) {
+        st->d->true_peak[c] =
+            st->d->resampler_buffer_output[i * st->channels + c];
+      } else if (-st->d->resampler_buffer_output[i * st->channels + c] >
+                                                         st->d->true_peak[c]) {
+        st->d->true_peak[c] =
+           -st->d->resampler_buffer_output[i * st->channels + c];
+      }
+    }
+  }
+#else
+  (void) st; (void) frames;
+#endif
+}
+
+#ifdef __SSE2_MATH__
+#include <xmmintrin.h>
+#define TURN_ON_FTZ \
+        unsigned int mxcsr = _mm_getcsr(); \
+        _mm_setcsr(mxcsr | _MM_FLUSH_ZERO_ON);
+#define TURN_OFF_FTZ _mm_setcsr(mxcsr);
+#define FLUSH_MANUALLY
+#else
+#warning "manual FTZ is being used, please enable SSE2 (-msse2 -mfpmath=sse)"
+#define TURN_ON_FTZ
+#define TURN_OFF_FTZ
+#define FLUSH_MANUALLY \
+    st->d->v[ci][4] = fabs(st->d->v[ci][4]) < DBL_MIN ? 0.0 : st->d->v[ci][4]; \
+    st->d->v[ci][3] = fabs(st->d->v[ci][3]) < DBL_MIN ? 0.0 : st->d->v[ci][3]; \
+    st->d->v[ci][2] = fabs(st->d->v[ci][2]) < DBL_MIN ? 0.0 : st->d->v[ci][2]; \
+    st->d->v[ci][1] = fabs(st->d->v[ci][1]) < DBL_MIN ? 0.0 : st->d->v[ci][1];
+#endif
+
+#define EBUR128_FILTER(type, min_scale, max_scale)                             \
+static void ebur128_filter_##type(ebur128_state* st, const type* src,          \
+                                  size_t frames) {                             \
+  static double scaling_factor = -((double) min_scale) > (double) max_scale ?  \
+                                 -((double) min_scale) : (double) max_scale;   \
+  double* audio_data = st->d->audio_data + st->d->audio_data_index;            \
+  size_t i, c;                                                                 \
+                                                                               \
+  TURN_ON_FTZ                                                                  \
+                                                                               \
+  if ((st->mode & EBUR128_MODE_SAMPLE_PEAK) == EBUR128_MODE_SAMPLE_PEAK) {     \
+    for (c = 0; c < st->channels; ++c) {                                       \
+      double max = 0.0;                                                        \
+      for (i = 0; i < frames; ++i) {                                           \
+        if (src[i * st->channels + c] > max) {                                 \
+          max =        src[i * st->channels + c];                              \
+        } else if (-src[i * st->channels + c] > max) {                         \
+          max = -1.0 * src[i * st->channels + c];                              \
+        }                                                                      \
+      }                                                                        \
+      max /= scaling_factor;                                                   \
+      if (max > st->d->sample_peak[c]) st->d->sample_peak[c] = max;            \
+    }                                                                          \
+  }                                                                            \
+  if (ebur128_use_speex_resampler(st)) {                                       \
+    for (c = 0; c < st->channels; ++c) {                                       \
+      for (i = 0; i < frames; ++i) {                                           \
+        st->d->resampler_buffer_input[i * st->channels + c] =                  \
+                      (float) (src[i * st->channels + c] / scaling_factor);    \
+      }                                                                        \
+    }                                                                          \
+    ebur128_check_true_peak(st, frames);                                       \
+  }                                                                            \
+  for (c = 0; c < st->channels; ++c) {                                         \
+    int ci = st->d->channel_map[c] - 1;                                        \
+    if (ci < 0) continue;                                                      \
+    else if (ci > 4) ci = 0; /* dual mono */                                   \
+    for (i = 0; i < frames; ++i) {                                             \
+      st->d->v[ci][0] = (double) (src[i * st->channels + c] / scaling_factor)  \
+                   - st->d->a[1] * st->d->v[ci][1]                             \
+                   - st->d->a[2] * st->d->v[ci][2]                             \
+                   - st->d->a[3] * st->d->v[ci][3]                             \
+                   - st->d->a[4] * st->d->v[ci][4];                            \
+      audio_data[i * st->channels + c] =                                       \
+                     st->d->b[0] * st->d->v[ci][0]                             \
+                   + st->d->b[1] * st->d->v[ci][1]                             \
+                   + st->d->b[2] * st->d->v[ci][2]                             \
+                   + st->d->b[3] * st->d->v[ci][3]                             \
+                   + st->d->b[4] * st->d->v[ci][4];                            \
+      st->d->v[ci][4] = st->d->v[ci][3];                                       \
+      st->d->v[ci][3] = st->d->v[ci][2];                                       \
+      st->d->v[ci][2] = st->d->v[ci][1];                                       \
+      st->d->v[ci][1] = st->d->v[ci][0];                                       \
+    }                                                                          \
+    FLUSH_MANUALLY                                                             \
+  }                                                                            \
+  TURN_OFF_FTZ                                                                 \
+}
+EBUR128_FILTER(short, SHRT_MIN, SHRT_MAX)
+EBUR128_FILTER(int, INT_MIN, INT_MAX)
+EBUR128_FILTER(float, -1.0f, 1.0f)
+EBUR128_FILTER(double, -1.0, 1.0)
+
+static double ebur128_energy_to_loudness(double energy) {
+  return 10 * (log(energy) / log(10.0)) - 0.691;
+}
+
+static size_t find_histogram_index(double energy) {
+  size_t index_min = 0;
+  size_t index_max = 1000;
+  size_t index_mid;
+
+  do {
+    index_mid = (index_min + index_max) / 2;
+    if (energy >= histogram_energy_boundaries[index_mid]) {
+      index_min = index_mid;
+    } else {
+      index_max = index_mid;
+    }
+  } while (index_max - index_min != 1);
+
+  return index_min;
+}
+
+static int ebur128_calc_gating_block(ebur128_state* st, size_t frames_per_block,
+                                     double* optional_output) {
+  size_t i, c;
+  double sum = 0.0;
+  double channel_sum;
+  for (c = 0; c < st->channels; ++c) {
+    if (st->d->channel_map[c] == EBUR128_UNUSED) continue;
+    channel_sum = 0.0;
+    if (st->d->audio_data_index < frames_per_block * st->channels) {
+      for (i = 0; i < st->d->audio_data_index / st->channels; ++i) {
+        channel_sum += st->d->audio_data[i * st->channels + c] *
+                       st->d->audio_data[i * st->channels + c];
+      }
+      for (i = st->d->audio_data_frames -
+              (frames_per_block -
+               st->d->audio_data_index / st->channels);
+           i < st->d->audio_data_frames; ++i) {
+        channel_sum += st->d->audio_data[i * st->channels + c] *
+                       st->d->audio_data[i * st->channels + c];
+      }
+    } else {
+      for (i = st->d->audio_data_index / st->channels - frames_per_block;
+           i < st->d->audio_data_index / st->channels;
+           ++i) {
+        channel_sum += st->d->audio_data[i * st->channels + c] *
+                       st->d->audio_data[i * st->channels + c];
+      }
+    }
+    if (st->d->channel_map[c] == EBUR128_LEFT_SURROUND ||
+        st->d->channel_map[c] == EBUR128_RIGHT_SURROUND) {
+      channel_sum *= 1.41;
+    } else if (st->d->channel_map[c] == EBUR128_DUAL_MONO) {
+      channel_sum *= 2.0;
+    }
+    sum += channel_sum;
+  }
+  sum /= (double) frames_per_block;
+  if (optional_output) {
+    *optional_output = sum;
+    return EBUR128_SUCCESS;
+  } else if (sum >= histogram_energy_boundaries[0]) {
+    if (st->d->use_histogram) {
+      ++st->d->block_energy_histogram[find_histogram_index(sum)];
+    } else {
+      struct ebur128_dq_entry* block;
+      block = (struct ebur128_dq_entry*) malloc(sizeof(struct ebur128_dq_entry));
+      if (!block) return EBUR128_ERROR_NOMEM;
+      block->z = sum;
+      SLIST_INSERT_HEAD(&st->d->block_list, block, entries);
+    }
+    return EBUR128_SUCCESS;
+  } else {
+    return EBUR128_SUCCESS;
+  }
+}
+
+int ebur128_set_channel(ebur128_state* st,
+                        unsigned int channel_number,
+                        int value) {
+  if (channel_number >= st->channels) {
+    return 1;
+  }
+  if (value == EBUR128_DUAL_MONO &&
+      (st->channels != 1 || channel_number != 0)) {
+    fprintf(stderr, "EBUR128_DUAL_MONO only works with mono files!\n");
+    return 1;
+  }
+  st->d->channel_map[channel_number] = value;
+  return 0;
+}
+
+int ebur128_change_parameters(ebur128_state* st,
+                              unsigned int channels,
+                              unsigned long samplerate) {
+  int errcode;
+  if (channels == st->channels &&
+      samplerate == st->samplerate) {
+    return 2;
+  }
+  free(st->d->audio_data);
+  st->d->audio_data = NULL;
+
+  if (channels != st->channels) {
+    unsigned int i;
+
+    free(st->d->channel_map); st->d->channel_map = NULL;
+    free(st->d->sample_peak); st->d->sample_peak = NULL;
+    free(st->d->true_peak);   st->d->true_peak = NULL;
+    st->channels = channels;
+
+#ifdef USE_SPEEX_RESAMPLER
+    ebur128_destroy_resampler(st);
+    ebur128_init_resampler(st);
+#endif
+
+    errcode = ebur128_init_channel_map(st);
+    CHECK_ERROR(errcode, EBUR128_ERROR_NOMEM, exit)
+
+    st->d->sample_peak = (double*) malloc(channels * sizeof(double));
+    CHECK_ERROR(!st->d->sample_peak, EBUR128_ERROR_NOMEM, exit)
+    st->d->true_peak = (double*) malloc(channels * sizeof(double));
+    CHECK_ERROR(!st->d->true_peak, EBUR128_ERROR_NOMEM, exit)
+    for (i = 0; i < channels; ++i) {
+      st->d->sample_peak[i] = 0.0;
+      st->d->true_peak[i] = 0.0;
+    }
+  }
+  if (samplerate != st->samplerate) {
+    st->samplerate = samplerate;
+    ebur128_init_filter(st);
+  }
+  if ((st->mode & EBUR128_MODE_S) == EBUR128_MODE_S) {
+    st->d->audio_data_frames = st->d->samples_in_100ms * 30;
+  } else if ((st->mode & EBUR128_MODE_M) == EBUR128_MODE_M) {
+    st->d->audio_data_frames = st->d->samples_in_100ms * 4;
+  } else {
+    return 1;
+  }
+  st->d->audio_data = (double*) malloc(st->d->audio_data_frames *
+                                       st->channels *
+                                       sizeof(double));
+  CHECK_ERROR(!st->d->audio_data, EBUR128_ERROR_NOMEM, exit)
+
+  /* the first block needs 400ms of audio data */
+  st->d->needed_frames = st->d->samples_in_100ms * 4;
+  /* start at the beginning of the buffer */
+  st->d->audio_data_index = 0;
+  /* reset short term frame counter */
+  st->d->short_term_frame_counter = 0;
+
+  return 0;
+
+exit:
+  return 1;
+}
+
+
+static int ebur128_energy_shortterm(ebur128_state* st, double* out);
+#define EBUR128_ADD_FRAMES(type)                                               \
+int ebur128_add_frames_##type(ebur128_state* st,                               \
+                              const type* src, size_t frames) {                \
+  size_t src_index = 0;                                                        \
+  while (frames > 0) {                                                         \
+    if (frames >= st->d->needed_frames) {                                      \
+      ebur128_filter_##type(st, src + src_index, st->d->needed_frames);        \
+      src_index += st->d->needed_frames * st->channels;                        \
+      frames -= st->d->needed_frames;                                          \
+      st->d->audio_data_index += st->d->needed_frames * st->channels;          \
+      /* calculate the new gating block */                                     \
+      if ((st->mode & EBUR128_MODE_I) == EBUR128_MODE_I) {                     \
+        if (ebur128_calc_gating_block(st, st->d->samples_in_100ms * 4, NULL)) {\
+          return EBUR128_ERROR_NOMEM;                                          \
+        }                                                                      \
+      }                                                                        \
+      if ((st->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA) {                 \
+        st->d->short_term_frame_counter += st->d->needed_frames;               \
+        if (st->d->short_term_frame_counter == st->d->samples_in_100ms * 30) { \
+          struct ebur128_dq_entry* block;                                      \
+          double st_energy;                                                    \
+          ebur128_energy_shortterm(st, &st_energy);                            \
+          if (st_energy >= histogram_energy_boundaries[0]) {                   \
+            if (st->d->use_histogram) {                                        \
+              ++st->d->short_term_block_energy_histogram[                      \
+                                              find_histogram_index(st_energy)];\
+            } else {                                                           \
+              block = (struct ebur128_dq_entry*)                               \
+                      malloc(sizeof(struct ebur128_dq_entry));                 \
+              if (!block) return EBUR128_ERROR_NOMEM;                          \
+              block->z = st_energy;                                            \
+              SLIST_INSERT_HEAD(&st->d->short_term_block_list, block, entries);\
+            }                                                                  \
+          }                                                                    \
+          st->d->short_term_frame_counter = st->d->samples_in_100ms * 20;      \
+        }                                                                      \
+      }                                                                        \
+      /* 100ms are needed for all blocks besides the first one */              \
+      st->d->needed_frames = st->d->samples_in_100ms;                          \
+      /* reset audio_data_index when buffer full */                            \
+      if (st->d->audio_data_index == st->d->audio_data_frames * st->channels) {\
+        st->d->audio_data_index = 0;                                           \
+      }                                                                        \
+    } else {                                                                   \
+      ebur128_filter_##type(st, src + src_index, frames);                      \
+      st->d->audio_data_index += frames * st->channels;                        \
+      if ((st->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA) {                 \
+        st->d->short_term_frame_counter += frames;                             \
+      }                                                                        \
+      st->d->needed_frames -= frames;                                          \
+      frames = 0;                                                              \
+    }                                                                          \
+  }                                                                            \
+  return EBUR128_SUCCESS;                                                      \
+}
+EBUR128_ADD_FRAMES(short)
+EBUR128_ADD_FRAMES(int)
+EBUR128_ADD_FRAMES(float)
+EBUR128_ADD_FRAMES(double)
+
+static int ebur128_gated_loudness(ebur128_state** sts, size_t size,
+                                  double* out) {
+  struct ebur128_dq_entry* it;
+  double relative_threshold = 0.0;
+  double gated_loudness = 0.0;
+  size_t above_thresh_counter = 0;
+  size_t i, j, start_index;
+
+  for (i = 0; i < size; i++) {
+    if (sts[i] && (sts[i]->mode & EBUR128_MODE_I) != EBUR128_MODE_I) {
+      return EBUR128_ERROR_INVALID_MODE;
+    }
+  }
+
+  for (i = 0; i < size; i++) {
+    if (!sts[i]) continue;
+    if (sts[i]->d->use_histogram) {
+      for (j = 0; j < 1000; ++j) {
+        relative_threshold += sts[i]->d->block_energy_histogram[j] *
+                              histogram_energies[j];
+        above_thresh_counter += sts[i]->d->block_energy_histogram[j];
+      }
+    } else {
+      SLIST_FOREACH(it, &sts[i]->d->block_list, entries) {
+        ++above_thresh_counter;
+        relative_threshold += it->z;
+      }
+    }
+  }
+  if (!above_thresh_counter) {
+    *out = -HUGE_VAL;
+    return EBUR128_SUCCESS;
+  }
+  relative_threshold /= (double) above_thresh_counter;
+  relative_threshold *= relative_gate_factor;
+  above_thresh_counter = 0;
+  if (relative_threshold < histogram_energy_boundaries[0]) {
+    start_index = 0;
+  } else {
+    start_index = find_histogram_index(relative_threshold);
+    if (relative_threshold > histogram_energies[start_index]) {
+      ++start_index;
+    }
+  }
+  for (i = 0; i < size; i++) {
+    if (!sts[i]) continue;
+    if (sts[i]->d->use_histogram) {
+      for (j = start_index; j < 1000; ++j) {
+        gated_loudness += sts[i]->d->block_energy_histogram[j] *
+                          histogram_energies[j];
+        above_thresh_counter += sts[i]->d->block_energy_histogram[j];
+      }
+    } else {
+      SLIST_FOREACH(it, &sts[i]->d->block_list, entries) {
+        if (it->z >= relative_threshold) {
+          ++above_thresh_counter;
+          gated_loudness += it->z;
+        }
+      }
+    }
+  }
+  if (!above_thresh_counter) {
+    *out = -HUGE_VAL;
+    return EBUR128_SUCCESS;
+  }
+  gated_loudness /= (double) above_thresh_counter;
+  *out = ebur128_energy_to_loudness(gated_loudness);
+  return EBUR128_SUCCESS;
+}
+
+int ebur128_loudness_global(ebur128_state* st, double* out) {
+  return ebur128_gated_loudness(&st, 1, out);
+}
+
+int ebur128_loudness_global_multiple(ebur128_state** sts, size_t size,
+                                     double* out) {
+  return ebur128_gated_loudness(sts, size, out);
+}
+
+static int ebur128_energy_in_interval(ebur128_state* st,
+                                      size_t interval_frames,
+                                      double* out) {
+  if (interval_frames > st->d->audio_data_frames) {
+    return EBUR128_ERROR_INVALID_MODE;
+  }
+  ebur128_calc_gating_block(st, interval_frames, out);
+  return EBUR128_SUCCESS;
+}
+
+static int ebur128_energy_shortterm(ebur128_state* st, double* out) {
+  return ebur128_energy_in_interval(st, st->d->samples_in_100ms * 30, out);
+}
+
+int ebur128_loudness_momentary(ebur128_state* st, double* out) {
+  double energy;
+  int error = ebur128_energy_in_interval(st, st->d->samples_in_100ms * 4,
+                                         &energy);
+  if (error) {
+    return error;
+  } else if (energy <= 0.0) {
+    *out = -HUGE_VAL;
+    return EBUR128_SUCCESS;
+  }
+  *out = ebur128_energy_to_loudness(energy);
+  return EBUR128_SUCCESS;
+}
+
+int ebur128_loudness_shortterm(ebur128_state* st, double* out) {
+  double energy;
+  int error = ebur128_energy_shortterm(st, &energy);
+  if (error) {
+    return error;
+  } else if (energy <= 0.0) {
+    *out = -HUGE_VAL;
+    return EBUR128_SUCCESS;
+  }
+  *out = ebur128_energy_to_loudness(energy);
+  return EBUR128_SUCCESS;
+}
+
+static int ebur128_double_cmp(const void *p1, const void *p2) {
+  const double* d1 = (const double*) p1;
+  const double* d2 = (const double*) p2;
+  return (*d1 > *d2) - (*d1 < *d2);
+}
+
+/* EBU - TECH 3342 */
+int ebur128_loudness_range_multiple(ebur128_state** sts, size_t size,
+                                    double* out) {
+  size_t i, j;
+  struct ebur128_dq_entry* it;
+  double* stl_vector;
+  size_t stl_size;
+  double* stl_relgated;
+  size_t stl_relgated_size;
+  double stl_power, stl_integrated;
+  /* High and low percentile energy */
+  double h_en, l_en;
+  int use_histogram = 0;
+
+  for (i = 0; i < size; ++i) {
+    if (sts[i]) {
+      if ((sts[i]->mode & EBUR128_MODE_LRA) != EBUR128_MODE_LRA) {
+        return EBUR128_ERROR_INVALID_MODE;
+      }
+      if (i == 0 && sts[i]->mode & EBUR128_MODE_HISTOGRAM) {
+        use_histogram = 1;
+      } else if (use_histogram != !!(sts[i]->mode & EBUR128_MODE_HISTOGRAM)) {
+        return EBUR128_ERROR_INVALID_MODE;
+      }
+    }
+  }
+
+  if (use_histogram) {
+    unsigned long hist[1000] = { 0 };
+    size_t percentile_low, percentile_high;
+    size_t index;
+
+    stl_size = 0;
+    stl_power = 0.0;
+    for (i = 0; i < size; ++i) {
+      if (!sts[i]) continue;
+      for (j = 0; j < 1000; ++j) {
+        hist[j]   += sts[i]->d->short_term_block_energy_histogram[j];
+        stl_size  += sts[i]->d->short_term_block_energy_histogram[j];
+        stl_power += sts[i]->d->short_term_block_energy_histogram[j]
+                     * histogram_energies[j];
+      }
+    }
+    if (!stl_size) {
+      *out = 0.0;
+      return EBUR128_SUCCESS;
+    }
+
+    stl_power /= stl_size;
+    stl_integrated = minus_twenty_decibels * stl_power;
+
+    if (stl_integrated < histogram_energy_boundaries[0]) {
+      index = 0;
+    } else {
+      index = find_histogram_index(stl_integrated);
+      if (stl_integrated > histogram_energies[index]) {
+        ++index;
+      }
+    }
+    stl_size = 0;
+    for (j = index; j < 1000; ++j) {
+      stl_size += hist[j];
+    }
+    if (!stl_size) {
+      *out = 0.0;
+      return EBUR128_SUCCESS;
+    }
+
+    percentile_low  = (size_t) ((stl_size - 1) * 0.1 + 0.5);
+    percentile_high = (size_t) ((stl_size - 1) * 0.95 + 0.5);
+
+    stl_size = 0;
+    j = index;
+    while (stl_size <= percentile_low) {
+      stl_size += hist[j++];
+    }
+    l_en = histogram_energies[j - 1];
+    while (stl_size <= percentile_high) {
+      stl_size += hist[j++];
+    }
+    h_en = histogram_energies[j - 1];
+    *out = ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en);
+    return EBUR128_SUCCESS;
+
+  } else {
+    stl_size = 0;
+    for (i = 0; i < size; ++i) {
+      if (!sts[i]) continue;
+      SLIST_FOREACH(it, &sts[i]->d->short_term_block_list, entries) {
+        ++stl_size;
+      }
+    }
+    if (!stl_size) {
+      *out = 0.0;
+      return EBUR128_SUCCESS;
+    }
+    stl_vector = (double*) malloc(stl_size * sizeof(double));
+    if (!stl_vector)
+      return EBUR128_ERROR_NOMEM;
+
+    for (j = 0, i = 0; i < size; ++i) {
+      if (!sts[i]) continue;
+      SLIST_FOREACH(it, &sts[i]->d->short_term_block_list, entries) {
+        stl_vector[j] = it->z;
+        ++j;
+      }
+    }
+    qsort(stl_vector, stl_size, sizeof(double), ebur128_double_cmp);
+    stl_power = 0.0;
+    for (i = 0; i < stl_size; ++i) {
+      stl_power += stl_vector[i];
+    }
+    stl_power /= (double) stl_size;
+    stl_integrated = minus_twenty_decibels * stl_power;
+
+    stl_relgated = stl_vector;
+    stl_relgated_size = stl_size;
+    while (stl_relgated_size > 0 && *stl_relgated < stl_integrated) {
+      ++stl_relgated;
+      --stl_relgated_size;
+    }
+
+    if (stl_relgated_size) {
+      h_en = stl_relgated[(size_t) ((stl_relgated_size - 1) * 0.95 + 0.5)];
+      l_en = stl_relgated[(size_t) ((stl_relgated_size - 1) * 0.1 + 0.5)];
+      free(stl_vector);
+      *out = ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en);
+      return EBUR128_SUCCESS;
+    } else {
+      free(stl_vector);
+      *out = 0.0;
+      return EBUR128_SUCCESS;
+    }
+  }
+}
+
+int ebur128_loudness_range(ebur128_state* st, double* out) {
+  return ebur128_loudness_range_multiple(&st, 1, out);
+}
+
+int ebur128_sample_peak(ebur128_state* st,
+                        unsigned int channel_number,
+                        double* out) {
+  if ((st->mode & EBUR128_MODE_SAMPLE_PEAK) != EBUR128_MODE_SAMPLE_PEAK) {
+    return EBUR128_ERROR_INVALID_MODE;
+  } else if (channel_number >= st->channels) {
+    return EBUR128_ERROR_INVALID_CHANNEL_INDEX;
+  }
+  *out = st->d->sample_peak[channel_number];
+  return EBUR128_SUCCESS;
+}
+
+#ifdef USE_SPEEX_RESAMPLER
+int ebur128_true_peak(ebur128_state* st,
+                      unsigned int channel_number,
+                      double* out) {
+  if ((st->mode & EBUR128_MODE_TRUE_PEAK) != EBUR128_MODE_TRUE_PEAK) {
+    return EBUR128_ERROR_INVALID_MODE;
+  } else if (channel_number >= st->channels) {
+    return EBUR128_ERROR_INVALID_CHANNEL_INDEX;
+  }
+  *out = st->d->true_peak[channel_number] > st->d->sample_peak[channel_number]
+       ? st->d->true_peak[channel_number]
+       : st->d->sample_peak[channel_number];
+  return EBUR128_SUCCESS;
+}
+#endif
diff --git a/src/modules/plus/ebur128/ebur128.h b/src/modules/plus/ebur128/ebur128.h
new file mode 100644 (file)
index 0000000..02be47f
--- /dev/null
@@ -0,0 +1,288 @@
+/* See COPYING file for copyright and license details. */
+
+#ifndef EBUR128_H_
+#define EBUR128_H_
+
+/** \file ebur128.h
+ *  \brief libebur128 - a library for loudness measurement according to
+ *         the EBU R128 standard.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EBUR128_VERSION_MAJOR 1
+#define EBUR128_VERSION_MINOR 0
+#define EBUR128_VERSION_PATCH 1
+
+#include <stddef.h>       /* for size_t */
+
+/** \enum channel
+ *  Use these values when setting the channel map with ebur128_set_channel().
+ */
+enum channel {
+  EBUR128_UNUSED = 0,     /**< unused channel (for example LFE channel) */
+  EBUR128_LEFT,           /**< left channel */
+  EBUR128_RIGHT,          /**< right channel */
+  EBUR128_CENTER,         /**< center channel */
+  EBUR128_LEFT_SURROUND,  /**< left surround channel */
+  EBUR128_RIGHT_SURROUND, /**< right surround channel */
+  EBUR128_DUAL_MONO       /**< a channel that is counted twice */
+};
+
+/** \enum error
+ *  Error return values.
+ */
+enum error {
+  EBUR128_SUCCESS = 0,
+  EBUR128_ERROR_NOMEM,
+  EBUR128_ERROR_INVALID_MODE,
+  EBUR128_ERROR_INVALID_CHANNEL_INDEX,
+  EBUR128_ERROR_NO_CHANGE
+};
+
+/** \enum mode
+ *  Use these values in ebur128_init (or'ed). Try to use the lowest possible
+ *  modes that suit your needs, as performance will be better.
+ */
+enum mode {
+  /** can call ebur128_loudness_momentary */
+  EBUR128_MODE_M           = (1 << 0),
+  /** can call ebur128_loudness_shortterm */
+  EBUR128_MODE_S           = (1 << 1) | EBUR128_MODE_M,
+  /** can call ebur128_gated_loudness_* */
+  EBUR128_MODE_I           = (1 << 2) | EBUR128_MODE_M,
+  /** can call ebur128_loudness_range */
+  EBUR128_MODE_LRA         = (1 << 3) | EBUR128_MODE_S,
+  /** can call ebur128_sample_peak */
+  EBUR128_MODE_SAMPLE_PEAK = (1 << 4) | EBUR128_MODE_M,
+  /** can call ebur128_true_peak */
+  EBUR128_MODE_TRUE_PEAK   = (1 << 5) | EBUR128_MODE_M
+                                      | EBUR128_MODE_SAMPLE_PEAK,
+  /** uses histogram algorithm to calculate loudness */
+  EBUR128_MODE_HISTOGRAM   = (1 << 6)
+};
+
+/** forward declaration of ebur128_state_internal */
+struct ebur128_state_internal;
+
+/** \brief Contains information about the state of a loudness measurement.
+ *
+ *  You should not need to modify this struct directly.
+ */
+typedef struct {
+  int mode;                           /**< The current mode. */
+  unsigned int channels;              /**< The number of channels. */
+  unsigned long samplerate;           /**< The sample rate. */
+  struct ebur128_state_internal* d;   /**< Internal state. */
+} ebur128_state;
+
+/** \brief Get library version number. Do not pass null pointers here.
+ *
+ *  @param major major version number of library
+ *  @param minor minor version number of library
+ *  @param patch patch version number of library
+ */
+void ebur128_get_version(int* major, int* minor, int* patch);
+
+/** \brief Initialize library state.
+ *
+ *  @param channels the number of channels.
+ *  @param samplerate the sample rate.
+ *  @param mode see the mode enum for possible values.
+ *  @return an initialized library state.
+ */
+ebur128_state* ebur128_init(unsigned int channels,
+                            unsigned long samplerate,
+                            int mode);
+
+/** \brief Destroy library state.
+ *
+ *  @param st pointer to a library state.
+ */
+void ebur128_destroy(ebur128_state** st);
+
+/** \brief Set channel type.
+ *
+ *  The default is:
+ *  - 0 -> EBUR128_LEFT
+ *  - 1 -> EBUR128_RIGHT
+ *  - 2 -> EBUR128_CENTER
+ *  - 3 -> EBUR128_UNUSED
+ *  - 4 -> EBUR128_LEFT_SURROUND
+ *  - 5 -> EBUR128_RIGHT_SURROUND
+ *
+ *  @param st library state.
+ *  @param channel_number zero based channel index.
+ *  @param value channel type from the "channel" enum.
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index.
+ */
+int ebur128_set_channel(ebur128_state* st,
+                        unsigned int channel_number,
+                        int value);
+
+/** \brief Change library parameters.
+ *
+ *  Note that the channel map will be reset when setting a different number of
+ *  channels. The current unfinished block will be lost.
+ *
+ *  @param st library state.
+ *  @param channels new number of channels.
+ *  @param samplerate new sample rate.
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_NOMEM on memory allocation error. The state will be
+ *      invalid and must be destroyed.
+ *    - EBUR128_ERROR_NO_CHANGE if channels and sample rate were not changed.
+ */
+int ebur128_change_parameters(ebur128_state* st,
+                              unsigned int channels,
+                              unsigned long samplerate);
+
+/** \brief Add frames to be processed.
+ *
+ *  @param st library state.
+ *  @param src array of source frames. Channels must be interleaved.
+ *  @param frames number of frames. Not number of samples!
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_NOMEM on memory allocation error.
+ */
+int ebur128_add_frames_short(ebur128_state* st,
+                             const short* src,
+                             size_t frames);
+/** \brief See \ref ebur128_add_frames_short */
+int ebur128_add_frames_int(ebur128_state* st,
+                             const int* src,
+                             size_t frames);
+/** \brief See \ref ebur128_add_frames_short */
+int ebur128_add_frames_float(ebur128_state* st,
+                             const float* src,
+                             size_t frames);
+/** \brief See \ref ebur128_add_frames_short */
+int ebur128_add_frames_double(ebur128_state* st,
+                             const double* src,
+                             size_t frames);
+
+/** \brief Get global integrated loudness in LUFS.
+ *
+ *  @param st library state.
+ *  @param out integrated loudness in LUFS. -HUGE_VAL if result is negative
+ *             infinity.
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not been set.
+ */
+int ebur128_loudness_global(ebur128_state* st, double* out);
+/** \brief Get global integrated loudness in LUFS across multiple instances.
+ *
+ *  @param sts array of library states.
+ *  @param size length of sts
+ *  @param out integrated loudness in LUFS. -HUGE_VAL if result is negative
+ *             infinity.
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not been set.
+ */
+int ebur128_loudness_global_multiple(ebur128_state** sts,
+                                     size_t size,
+                                     double* out);
+
+/** \brief Get momentary loudness (last 400ms) in LUFS.
+ *
+ *  @param st library state.
+ *  @param out momentary loudness in LUFS. -HUGE_VAL if result is negative
+ *             infinity.
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ */
+int ebur128_loudness_momentary(ebur128_state* st, double* out);
+/** \brief Get short-term loudness (last 3s) in LUFS.
+ *
+ *  @param st library state.
+ *  @param out short-term loudness in LUFS. -HUGE_VAL if result is negative
+ *             infinity.
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_S" has not been set.
+ */
+int ebur128_loudness_shortterm(ebur128_state* st, double* out);
+
+/** \brief Get loudness range (LRA) of programme in LU.
+ *
+ *  Calculates loudness range according to EBU 3342.
+ *
+ *  @param st library state.
+ *  @param out loudness range (LRA) in LU. Will not be changed in case of
+ *             error. EBUR128_ERROR_NOMEM or EBUR128_ERROR_INVALID_MODE will be
+ *             returned in this case.
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_NOMEM in case of memory allocation error.
+ *    - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_LRA" has not been set.
+ */
+int ebur128_loudness_range(ebur128_state* st, double* out);
+/** \brief Get loudness range (LRA) in LU across multiple instances.
+ *
+ *  Calculates loudness range according to EBU 3342.
+ *
+ *  @param sts array of library states.
+ *  @param size length of sts
+ *  @param out loudness range (LRA) in LU. Will not be changed in case of
+ *             error. EBUR128_ERROR_NOMEM or EBUR128_ERROR_INVALID_MODE will be
+ *             returned in this case.
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_NOMEM in case of memory allocation error.
+ *    - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_LRA" has not been set.
+ */
+int ebur128_loudness_range_multiple(ebur128_state** sts,
+                                    size_t size,
+                                    double* out);
+
+/** \brief Get maximum sample peak of selected channel in float format.
+ *
+ *  @param st library state
+ *  @param channel_number channel to analyse
+ *  @param out maximum sample peak in float format (1.0 is 0 dBFS)
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_SAMPLE_PEAK" has not
+ *      been set.
+ *    - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index.
+ */
+int ebur128_sample_peak(ebur128_state* st,
+                        unsigned int channel_number,
+                        double* out);
+
+/** \brief Get maximum true peak of selected channel in float format.
+ *
+ *  Uses an implementation defined algorithm to calculate the true peak. Do not
+ *  try to compare resulting values across different versions of the library,
+ *  as the algorithm may change.
+ *
+ *  The current implementation uses the Speex resampler with quality level 8 to
+ *  calculate true peak. Will oversample 4x for sample rates < 96000 Hz, 2x for
+ *  sample rates < 192000 Hz and leave the signal unchanged for 192000 Hz.
+ *
+ *  @param st library state
+ *  @param channel_number channel to analyse
+ *  @param out maximum true peak in float format (1.0 is 0 dBFS)
+ *  @return
+ *    - EBUR128_SUCCESS on success.
+ *    - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_TRUE_PEAK" has not
+ *      been set.
+ *    - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index.
+ */
+int ebur128_true_peak(ebur128_state* st,
+                      unsigned int channel_number,
+                      double* out);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* EBUR128_H_ */
index 949b426950c401c27e4cc212b47ae10cd2139459..a73995719725d2e6dbd3e77fda31351e8e931747 100644 (file)
@@ -26,6 +26,7 @@ extern mlt_consumer consumer_blipflash_init( mlt_profile profile, mlt_service_ty
 extern mlt_filter filter_affine_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
 extern mlt_filter filter_charcoal_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
 extern mlt_filter filter_dynamictext_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
+extern mlt_filter filter_loudness_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
 extern mlt_filter filter_invert_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
 extern mlt_filter filter_sepia_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
 extern mlt_producer producer_blipflash_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
@@ -46,6 +47,7 @@ MLT_REPOSITORY
        MLT_REGISTER( filter_type, "charcoal", filter_charcoal_init );
        MLT_REGISTER( filter_type, "dynamictext", filter_dynamictext_init );
        MLT_REGISTER( filter_type, "invert", filter_invert_init );
+       MLT_REGISTER( filter_type, "loudness", filter_loudness_init );
        MLT_REGISTER( filter_type, "sepia", filter_sepia_init );
        MLT_REGISTER( producer_type, "blipflash", producer_blipflash_init );
        MLT_REGISTER( producer_type, "count", producer_count_init );
@@ -56,6 +58,7 @@ MLT_REPOSITORY
        MLT_REGISTER_METADATA( filter_type, "charcoal", metadata, "filter_charcoal.yml" );
        MLT_REGISTER_METADATA( filter_type, "dynamictext", metadata, "filter_dynamictext.yml" );
        MLT_REGISTER_METADATA( filter_type, "invert", metadata, "filter_invert.yml" );
+       MLT_REGISTER_METADATA( filter_type, "loudness", metadata, "filter_loudness.yml" );
        MLT_REGISTER_METADATA( filter_type, "sepia", metadata, "filter_sepia.yml" );
        MLT_REGISTER_METADATA( producer_type, "blipflash", metadata, "producer_blipflash.yml" );
        MLT_REGISTER_METADATA( producer_type, "count", metadata, "producer_count.yml" );
diff --git a/src/modules/plus/filter_loudness.c b/src/modules/plus/filter_loudness.c
new file mode 100644 (file)
index 0000000..fb8683f
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ * filter_ebur128.c -- normalize audio according to EBU R128
+ * Copyright (C) 2014 Brian Matherly <pez4brian@yahoo.com>
+ * Author: Brian Matherly <pez4brian@yahoo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <framework/mlt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "ebur128/ebur128.h"
+
+#define MAX_RESULT_SIZE 512
+
+typedef struct
+{
+       ebur128_state* state;
+} analyze_data;
+
+typedef struct
+{
+       double in_loudness;
+       double in_range;
+       double in_peak;
+       double coeff;
+} apply_data;
+
+typedef struct
+{
+       analyze_data* analyze;
+       apply_data* apply;
+       mlt_position last_position;
+} private_data;
+
+static void destroy_analyze_data( mlt_filter filter )
+{
+       private_data* private = (private_data*)filter->child;
+       ebur128_destroy( &private->analyze->state );
+       free( private->analyze );
+       private->analyze = NULL;
+}
+
+static void init_analyze_data( mlt_filter filter, int channels, int samplerate )
+{
+       private_data* private = (private_data*)filter->child;
+       private->analyze = (analyze_data*)calloc( 1, sizeof(analyze_data) );
+       private->analyze->state = ebur128_init( (unsigned int)channels, (unsigned long)samplerate, EBUR128_MODE_I | EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK );
+       private->last_position = 0;
+}
+
+static void destroy_apply_data( mlt_filter filter )
+{
+       private_data* private = (private_data*)filter->child;
+       free( private->apply );
+       private->apply = NULL;
+}
+
+static void init_apply_data( mlt_filter filter )
+{
+       private_data* private = (private_data*)filter->child;
+       mlt_properties properties = MLT_FILTER_PROPERTIES( filter );
+       char* results = mlt_properties_get( properties, "results" );
+       int scan_return = 0;
+
+       private->apply = (apply_data*)calloc( 1, sizeof(apply_data) );
+
+       scan_return = sscanf( results,"L: %lf\tR: %lf\tP %lf\n", &private->apply->in_loudness, &private->apply->in_range, &private->apply->in_peak );
+       if( scan_return != 3 )
+       {
+               mlt_log_error( MLT_FILTER_SERVICE( filter ), "Unable to load results: %s\n", results );
+               destroy_apply_data( filter );
+               return;
+       }
+       else
+       {
+               double target_db = mlt_properties_get_double( properties, "program" );
+               double delta_db = target_db - private->apply->in_loudness;
+               private->apply->coeff = delta_db > -90.0f ? powf(10.0f, delta_db * 0.05f) : 0.0f;
+               mlt_log_info( MLT_FILTER_SERVICE( filter ), "Loaded Results: L: %lf\tR: %lf\tP %lf\n", private->apply->in_loudness, private->apply->in_range, private->apply->in_peak );
+               mlt_log_info( MLT_FILTER_SERVICE( filter ), "Coefficient: %lf\n", private->apply->coeff );
+       }
+}
+
+static void analyze( mlt_filter filter, mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples )
+{
+       private_data* private = (private_data*)filter->child;
+       mlt_position pos = mlt_filter_get_position( filter, frame );
+
+       // If any frames are skipped, analysis data will be incomplete.
+       if( private->analyze && pos != private->last_position + 1 )
+       {
+               mlt_log_error( MLT_FILTER_SERVICE(filter), "Analysis Failed: Bad frame sequence\n" );
+               destroy_analyze_data( filter );
+       }
+
+       // Analyze Audio
+       if( !private->analyze && pos == 0 )
+       {
+               init_analyze_data( filter, *channels, *frequency );
+       }
+
+       if( private->analyze )
+       {
+               ebur128_add_frames_float( private->analyze->state, *buffer, *samples );
+
+               if ( pos + 1 == mlt_filter_get_length2( filter, frame ) )
+               {
+                       mlt_properties properties = MLT_FILTER_PROPERTIES( filter );
+                       double loudness = 0.0;
+                       double range = 0.0;
+                       double tmpPeak = 0.0;
+                       double peak = 0.0;
+                       int i = 0;
+                       char result[MAX_RESULT_SIZE];
+                       ebur128_loudness_global( private->analyze->state, &loudness );
+                       ebur128_loudness_range( private->analyze->state, &range );
+
+                       for ( i = 0; i < *channels; i++ )
+                       {
+                               ebur128_sample_peak( private->analyze->state, i, &tmpPeak );
+                               if( tmpPeak > peak )
+                               {
+                                       peak = tmpPeak;
+                               }
+                       }
+
+                       snprintf( result, MAX_RESULT_SIZE, "L: %lf\tR: %lf\tP %lf\n", loudness, range, peak );
+                       result[ MAX_RESULT_SIZE - 1 ] = '\0';
+                       mlt_log_info( MLT_FILTER_SERVICE( filter ), "Stored results: %s", result );
+                       mlt_properties_set( properties, "results", result );
+                       destroy_analyze_data( filter );
+               }
+
+               private->last_position = pos;
+       }
+}
+
+static void apply( mlt_filter filter, mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples )
+{
+       private_data* private = (private_data*)filter->child;
+
+       // Analyze Audio
+       if( !private->apply )
+       {
+               init_apply_data( filter );
+       }
+
+       if( private->apply )
+       {
+               float* p = *buffer;
+               int count = *samples * *channels;
+               while( count-- )
+               {
+                       *p = *p * private->apply->coeff;
+                       p++;
+               }
+       }
+}
+
+/** Get the audio.
+*/
+
+static int filter_get_audio( mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples )
+{
+       mlt_filter filter = mlt_frame_pop_audio( frame );
+       mlt_properties properties = MLT_FILTER_PROPERTIES( filter );
+
+       mlt_service_lock( MLT_FILTER_SERVICE( filter ) );
+
+       // Get the producer's audio
+       *format = mlt_audio_f32le;
+       mlt_frame_get_audio( frame, buffer, format, frequency, channels, samples );
+
+       char* results = mlt_properties_get( properties, "results" );
+       if( results && strcmp( results, "" ) )
+       {
+               apply( filter, frame, buffer, format, frequency, channels, samples );
+       }
+       else
+       {
+               analyze( filter, frame, buffer, format, frequency, channels, samples );
+       }
+
+       mlt_service_unlock( MLT_FILTER_SERVICE( filter ) );
+
+       return 0;
+}
+
+/** Filter processing.
+*/
+
+static mlt_frame filter_process( mlt_filter filter, mlt_frame frame )
+{
+       mlt_frame_push_audio( frame, filter );
+       mlt_frame_push_audio( frame, filter_get_audio );
+       return frame;
+}
+
+/** Destructor for the filter.
+*/
+
+static void filter_close( mlt_filter filter )
+{
+       private_data* private = (private_data*)filter->child;
+
+       if ( private )
+       {
+               if ( private->analyze )
+               {
+                       destroy_analyze_data( filter );
+               }
+               free( private );
+       }
+       filter->child = NULL;
+       filter->close = NULL;
+       filter->parent.close = NULL;
+       mlt_service_close( &filter->parent );
+}
+
+/** Constructor for the filter.
+*/
+
+mlt_filter filter_loudness_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
+{
+       mlt_filter filter = mlt_filter_new( );
+       private_data* data = (private_data*)calloc( 1, sizeof(private_data) );
+
+       if ( filter && data )
+       {
+               mlt_properties properties = MLT_FILTER_PROPERTIES( filter );
+               mlt_properties_set( properties, "program", "-23.0" );
+
+               data->analyze = NULL;
+
+               filter->close = filter_close;
+               filter->process = filter_process;
+               filter->child = data;
+       }
+       else
+       {
+               if( filter )
+               {
+                       mlt_filter_close( filter );
+                       filter = NULL;
+               }
+
+               if( data )
+               {
+                       free( data );
+               }
+       }
+
+       return filter;
+}
diff --git a/src/modules/plus/filter_loudness.yml b/src/modules/plus/filter_loudness.yml
new file mode 100644 (file)
index 0000000..c5398e8
--- /dev/null
@@ -0,0 +1,41 @@
+schema_version: 0.1
+type: filter
+identifier: loudness
+title: Loudness
+version: 1
+copyright: Brian Matherly <pez4brian@yahoo.com>
+creator: Brian Matherly <pez4brian@yahoo.com>
+license: LGPLv2.1
+language: en
+tags:
+  - Audio
+description: Correct audio loudness as recommended by EBU R128.
+notes: >
+  This filter requires two passes. The first pass performs analysis and stores
+  the result in the "results" property. The second pass applies the results to
+  the audio in order to achieve the desired loudness over the range of the 
+  filter.
+  
+parameters:
+  - identifier: results
+    title: Analysis Results
+    type: string
+    description: >
+      Set after analysis. Used during application.
+      Loudness information about the original audio.
+      When results are not supplied, the filter computes the results and stores
+      them in this property when the last frame has been processed.
+    mutable: no
+    
+  - identifier: program
+    title: Target Program Loudness
+    type: float
+    description: >
+      Used during application.
+      The target program loudness in LUFS (Loudness Units Full Scale).
+    readonly: no
+    mutable: no
+    default: -23.0
+    minimum: -50.0
+    maximum: -10.0
+    unit: LUFS