first python ana version
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
.stack-work/
|
||||||
|
*~
|
||||||
|
*.aup
|
||||||
|
*.swp
|
||||||
|
*.wav
|
||||||
|
*.lock
|
||||||
|
*_data
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Changelog for `autocut`
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to the
|
||||||
|
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.1.0.0 - YYYY-MM-DD
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright Author name here (c) 2022
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Author name here nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+243
@@ -0,0 +1,243 @@
|
|||||||
|
import librosa
|
||||||
|
import librosa.display
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import soundfile as sf
|
||||||
|
from pydub import AudioSegment
|
||||||
|
from pydub.silence import split_on_silence, detect_nonsilent
|
||||||
|
import math
|
||||||
|
import wave
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
import webrtcvad
|
||||||
|
|
||||||
|
|
||||||
|
def calc_dtw_sim(y1, y2, sr1, sr2, plot_result=False):
|
||||||
|
hop_length = 64
|
||||||
|
|
||||||
|
l = min(len(y1), len(y2))
|
||||||
|
|
||||||
|
to_consider = min(l, max(round(0.2*l), 2048))
|
||||||
|
bound = round(0.2 * l)
|
||||||
|
|
||||||
|
y1 = y1[0:round(0.2*l)]
|
||||||
|
y2 = y2[0:round(0.2*l)]
|
||||||
|
|
||||||
|
if bound < 2048:
|
||||||
|
n_fft = 512
|
||||||
|
n_mels = 64
|
||||||
|
else:
|
||||||
|
n_fft = 2048
|
||||||
|
n_mels = 128
|
||||||
|
|
||||||
|
mfcc1 = librosa.feature.mfcc(y=y1, sr=sr1, hop_length=hop_length, n_mfcc=42, n_fft=n_fft, n_mels=n_mels)[1:,:]
|
||||||
|
mfcc2 = librosa.feature.mfcc(y=y2, sr=sr2, hop_length=hop_length, n_mfcc=42, n_fft=n_fft, n_mels=n_mels)[1:,:]
|
||||||
|
|
||||||
|
D, wp = librosa.sequence.dtw(mfcc1, mfcc2)
|
||||||
|
|
||||||
|
if plot_result:
|
||||||
|
fig, ax = plt.subplots(nrows=4)
|
||||||
|
|
||||||
|
img = librosa.display.specshow(D, x_axis='frames', y_axis='frames',
|
||||||
|
ax=ax[0])
|
||||||
|
|
||||||
|
ax[0].set(title='DTW cost', xlabel='Noisy sequence', ylabel='Target')
|
||||||
|
|
||||||
|
ax[0].plot(wp[:, 1], wp[:, 0], label='Optimal path', color='y')
|
||||||
|
|
||||||
|
ax[0].legend()
|
||||||
|
|
||||||
|
fig.colorbar(img, ax=ax[0])
|
||||||
|
|
||||||
|
ax[1].plot(D[-1, :] / wp.shape[0])
|
||||||
|
|
||||||
|
ax[1].set(xlim=[0, mfcc1.shape[1]],
|
||||||
|
title='Matching cost function')
|
||||||
|
|
||||||
|
ax[2].imshow(mfcc1)
|
||||||
|
ax[3].imshow(mfcc2)
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
total_alignment_cost = D[-1, -1] / wp.shape[0]
|
||||||
|
|
||||||
|
return total_alignment_cost
|
||||||
|
|
||||||
|
|
||||||
|
def calc_xcorr_sim(y1, y2, sr1, sr2):
|
||||||
|
hop_length = 256
|
||||||
|
|
||||||
|
y1 = y1[0:round(len(y1)*0.2)]
|
||||||
|
y2 = y2[0:round(len(y2)*0.2)]
|
||||||
|
|
||||||
|
mfcc1 = librosa.feature.mfcc(y=y1, sr=sr1, hop_length=hop_length, n_mfcc=13)[1:,:]
|
||||||
|
mfcc2 = librosa.feature.mfcc(y=y2, sr=sr2, hop_length=hop_length, n_mfcc=13)[1:,:]
|
||||||
|
|
||||||
|
xsim = librosa.segment.cross_similarity(mfcc1, mfcc2, mode='distance')
|
||||||
|
return xsim
|
||||||
|
|
||||||
|
|
||||||
|
def match_target_amplitude(aChunk, target_dBFS):
|
||||||
|
''' Normalize given audio chunk '''
|
||||||
|
change_in_dBFS = target_dBFS - aChunk.dBFS
|
||||||
|
return aChunk.apply_gain(change_in_dBFS)
|
||||||
|
|
||||||
|
|
||||||
|
def spl_on_silence():
|
||||||
|
# Import the AudioSegment class for processing audio and the
|
||||||
|
|
||||||
|
# Load your audio.
|
||||||
|
song = AudioSegment.from_wav("recording.wav")
|
||||||
|
|
||||||
|
# Split track where the silence is 2 seconds or more and get chunks using
|
||||||
|
# the imported function.
|
||||||
|
chunks = split_on_silence (
|
||||||
|
# Use the loaded audio.
|
||||||
|
song,
|
||||||
|
# Specify that a silent chunk must be at least 2 seconds or 2000 ms long.
|
||||||
|
min_silence_len = 1000,
|
||||||
|
# Consider a chunk silent if it's quieter than -16 dBFS.
|
||||||
|
# (You may want to adjust this parameter.)
|
||||||
|
silence_thresh = -50,
|
||||||
|
timestamps=True
|
||||||
|
)
|
||||||
|
|
||||||
|
## Process each chunk with your parameters
|
||||||
|
#for i, chunk in enumerate(chunks):
|
||||||
|
# # Create a silence chunk that's 0.5 seconds (or 500 ms) long for padding.
|
||||||
|
# silence_chunk = AudioSegment.silent(duration=500)
|
||||||
|
|
||||||
|
# # Add the padding chunk to beginning and end of the entire chunk.
|
||||||
|
# audio_chunk = silence_chunk + chunk + silence_chunk
|
||||||
|
|
||||||
|
# # Normalize the entire chunk.
|
||||||
|
# normalized_chunk = match_target_amplitude(audio_chunk, -20.0)
|
||||||
|
|
||||||
|
# # Export the audio chunk with new bitrate.
|
||||||
|
# print("Exporting chunk{0}.mp3.".format(i))
|
||||||
|
# normalized_chunk.export(
|
||||||
|
# ".//chunk{0}.wav".format(i),
|
||||||
|
# bitrate = "192k",
|
||||||
|
# format = "wav"
|
||||||
|
# )
|
||||||
|
return ([ audiosegment_to_librosawav(c) for c in chunks ], song.frame_rate)
|
||||||
|
|
||||||
|
|
||||||
|
def non_silent_chunks(song):
|
||||||
|
#song = AudioSegment.from_wav("recording.wav")
|
||||||
|
|
||||||
|
return detect_nonsilent(song, min_silence_len=400, silence_thresh=-50)
|
||||||
|
|
||||||
|
|
||||||
|
def audiosegment_to_librosawav(audiosegment):
|
||||||
|
channel_sounds = audiosegment.split_to_mono()
|
||||||
|
samples = [s.get_array_of_samples() for s in channel_sounds]
|
||||||
|
|
||||||
|
fp_arr = np.array(samples).T.astype(np.float32)
|
||||||
|
fp_arr /= np.iinfo(samples[0].typecode).max
|
||||||
|
fp_arr = fp_arr.reshape(-1)
|
||||||
|
|
||||||
|
return fp_arr
|
||||||
|
|
||||||
|
|
||||||
|
# sr = samples / second
|
||||||
|
def millisecond_to_samples(ms, sr):
|
||||||
|
return round((ms / 1000) * sr)
|
||||||
|
|
||||||
|
|
||||||
|
def ms_to_time(ms):
|
||||||
|
secs = ms / 1000
|
||||||
|
return "{0}:{1}".format(math.floor(secs / 60), secs % 60)
|
||||||
|
|
||||||
|
|
||||||
|
def seg_is_speech(seg):
|
||||||
|
f = lambda x: int(32768 * x)
|
||||||
|
x = np.vectorize(f)(seg)
|
||||||
|
pcm_data = x.tobytes()
|
||||||
|
|
||||||
|
speeches = 0
|
||||||
|
total = 0
|
||||||
|
offset = 0
|
||||||
|
n = int(sr * (frame_duration_ms / 1000.0) * 2)
|
||||||
|
duration = (float(n) / sr) / 2.0
|
||||||
|
|
||||||
|
while offset + n < len(pcm_data):
|
||||||
|
frame = pcm_data[offset:(offset+n)]
|
||||||
|
if vad.is_speech(frame, sr):
|
||||||
|
speeches += 1
|
||||||
|
offset = offset + n
|
||||||
|
total += 1
|
||||||
|
|
||||||
|
return speeches / total
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
vad = webrtcvad.Vad()
|
||||||
|
|
||||||
|
frame_duration_ms = 10
|
||||||
|
fp = "hard_piece_2.wav"
|
||||||
|
y, sr = librosa.load(fp, mono=True, sr=32000)
|
||||||
|
|
||||||
|
#pcm_data = y.tobytes()
|
||||||
|
|
||||||
|
#n = int(sample_rate * (frame_duration_ms / 1000.0) * 2)
|
||||||
|
#duration = (float(n) / sample_rate) / 2.0
|
||||||
|
#frame = pcm_data[0:n]
|
||||||
|
|
||||||
|
#y, sr = librosa.load("recording.wav")
|
||||||
|
song = AudioSegment.from_wav(fp)
|
||||||
|
#print("pydub load done")
|
||||||
|
|
||||||
|
#with contextlib.closing(wave.open(fp, "rb")) as wf:
|
||||||
|
# num_channels = wf.getnchannels()
|
||||||
|
# assert num_channels == 1
|
||||||
|
# sample_width = wf.getsampwidth()
|
||||||
|
# assert sample_width == 2
|
||||||
|
# sample_rate = wf.getframerate()
|
||||||
|
# assert sample_rate in (8000, 16000, 32000, 48000)
|
||||||
|
# pcm_data = wf.readframes(wf.getnframes())
|
||||||
|
|
||||||
|
# n = int(sample_rate * (frame_duration_ms / 1000.0) * 2)
|
||||||
|
# duration = (float(n) / sample_rate) / 2.0
|
||||||
|
# frame = pcm_data[0:n]
|
||||||
|
|
||||||
|
# #print(len(pcm_data))
|
||||||
|
# print(vad.is_speech(frame, sample_rate))
|
||||||
|
|
||||||
|
#y2 = audiosegment_to_librosawav(song)
|
||||||
|
#print(y)
|
||||||
|
#print(y2)
|
||||||
|
|
||||||
|
#segs = librosa.effects.split(y, top_db = 5, hop_length=512, frame_length=4096)
|
||||||
|
|
||||||
|
#segs, sr = spl_on_silence()
|
||||||
|
|
||||||
|
#print("librosa load done")
|
||||||
|
|
||||||
|
segs = []
|
||||||
|
for ts in non_silent_chunks(song):
|
||||||
|
start, end = ts[0], ts[1]
|
||||||
|
seg = y[ millisecond_to_samples(start, sr) : millisecond_to_samples(end, sr) ]
|
||||||
|
segs.append(((start, end), seg))
|
||||||
|
|
||||||
|
for i in range(len(segs)-1):
|
||||||
|
(s1, e1), y1 = segs[i]
|
||||||
|
(s2, e2), y2 = segs[i+1]
|
||||||
|
diff = calc_dtw_sim(y1, y2, sr, sr, plot_result=False)
|
||||||
|
vad_coeff = seg_is_speech(y1)
|
||||||
|
|
||||||
|
#if diff < 100:
|
||||||
|
#print("{0}\t{1}\tdiff: {2}, vad: {3}".format(s1/1000, e1/1000, diff, vad_coeff))
|
||||||
|
#print(ms_to_time(s1), ms_to_time(e1), ms_to_time(s2), ms_to_time(e2), diff)
|
||||||
|
|
||||||
|
#if vad_coeff < 0.9:
|
||||||
|
#print("{0}\t{1}\tvad {2}".format(s1/1000, e1/1000, vad_coeff))
|
||||||
|
|
||||||
|
|
||||||
|
#for n, seg in enumerate(segs):
|
||||||
|
# sf.write('part' + str(n) + '.wav', seg, sr)
|
||||||
|
#print(segs)
|
||||||
|
|
||||||
|
#y1, sr1 = librosa.load("out000.wav")
|
||||||
|
#y2, sr2 = librosa.load("out004.wav")
|
||||||
|
|
||||||
|
#print("total alignment cost:", calc_dtw_sim(y1, y2, sr1, sr2, plot_result=True))
|
||||||
|
#print("xcorr:", np.trace(calc_xcorr_sim(y1, y2, sr1, sr2)))
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
module Main (main) where
|
||||||
|
|
||||||
|
import Lib
|
||||||
|
import Parser
|
||||||
|
import Driver
|
||||||
|
import Types
|
||||||
|
|
||||||
|
runDehum :: Flags -> FilePath -> FilePath -> IO ()
|
||||||
|
runDehum flags input output =
|
||||||
|
withSound flags input $ \fmtIn params sig ->
|
||||||
|
SoxLib.withWrite
|
||||||
|
(writerInfoFromFormat fmtIn params)
|
||||||
|
output $ \fmtOut ->
|
||||||
|
SoxLib.writeStorableVectorLazy fmtOut $
|
||||||
|
SVL.interleaveFirstPattern $
|
||||||
|
map
|
||||||
|
(Causal.apply
|
||||||
|
(arr (Bin.fromCanonicalWith Real.roundSimple)
|
||||||
|
<<<
|
||||||
|
dehum params
|
||||||
|
<<<
|
||||||
|
arr Bin.toCanonical)) $
|
||||||
|
SVL.deinterleave (numChannels params) sig
|
||||||
|
|
||||||
|
runEnvelope :: Flags -> FilePath -> FilePath -> IO ()
|
||||||
|
runEnvelope flags input output =
|
||||||
|
withSound flags input $ \fmtIn params sig ->
|
||||||
|
SoxLib.withWrite
|
||||||
|
(monoInfoFromFormat fmtIn params)
|
||||||
|
output $ \fmtOut ->
|
||||||
|
SoxLib.writeStorableVectorLazy fmtOut $
|
||||||
|
Causal.apply
|
||||||
|
(arr (Bin.fromCanonicalWith Real.roundSimple)) $
|
||||||
|
trackEnvelope params $
|
||||||
|
map
|
||||||
|
(Causal.apply
|
||||||
|
(arr (^2)
|
||||||
|
<<<
|
||||||
|
dehum params
|
||||||
|
<<<
|
||||||
|
arr Bin.toCanonical)) $
|
||||||
|
SVL.deinterleave (numChannels params) sig
|
||||||
|
|
||||||
|
runSizes :: Flags -> FilePath -> IO ()
|
||||||
|
runSizes flags input =
|
||||||
|
withSound flags input $ \_fmt params sig ->
|
||||||
|
mapM_ print $ pieceDurations params sig
|
||||||
|
|
||||||
|
runLabels :: Flags -> FilePath -> IO ()
|
||||||
|
runLabels flags input =
|
||||||
|
withSound flags input $ \_fmt params sig ->
|
||||||
|
mapM_ (\(n, (from,to)) -> printf "%s\t%s\t%d\n" from to n) $
|
||||||
|
zip [(0::Int) ..] $
|
||||||
|
ListHT.mapAdjacent (,) $
|
||||||
|
map (\t ->
|
||||||
|
case divMod (Real.roundSimple (fromIntegral t * 10^6 / sampleRate params)) (10^6) of
|
||||||
|
-- FIXME: the comma is certainly only correct in German locale
|
||||||
|
(seconds,micros) ->
|
||||||
|
printf "%d,%06d" seconds (micros::Integer) :: String) $
|
||||||
|
scanl (+) 0 $
|
||||||
|
prefetch (preStart params) $
|
||||||
|
pieceDurations params sig
|
||||||
|
|
||||||
|
{- |
|
||||||
|
> runChop flags "in.wav" "%03d.wav"
|
||||||
|
-}
|
||||||
|
runChop :: Flags -> FilePath -> FilePath -> IO ()
|
||||||
|
runChop flags input output =
|
||||||
|
withSound flags input $ \fmtIn params sig ->
|
||||||
|
forM_ (zip [(0::Int)..] $ chopLazy params sig) $ \(n,piece) ->
|
||||||
|
SoxLib.withWrite
|
||||||
|
(writerInfoFromFormat fmtIn params)
|
||||||
|
(printf output n) $ \fmtOut ->
|
||||||
|
SoxLib.writeStorableVectorLazy fmtOut piece
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = SoxLib.formatWith $ do
|
||||||
|
argv <- getArgs
|
||||||
|
let (opts, files, errors) =
|
||||||
|
getOpt Opt.RequireOrder description argv
|
||||||
|
when (not $ null errors) $
|
||||||
|
exitFailureMsg (init (concat errors))
|
||||||
|
|
||||||
|
flags <- foldl (>>=) (return defltFlags) opts
|
||||||
|
|
||||||
|
if flagComputeEnvelope flags
|
||||||
|
then
|
||||||
|
case files of
|
||||||
|
[input,output] -> runEnvelope flags input output
|
||||||
|
[] -> exitFailureMsg "need input and output file envelope computation"
|
||||||
|
_ -> exitFailureMsg "more than two file names given"
|
||||||
|
else
|
||||||
|
case files of
|
||||||
|
[input,output] -> runChop flags input output
|
||||||
|
[input] -> runLabels flags input
|
||||||
|
[] -> exitFailureMsg "no input or output given"
|
||||||
|
_ -> exitFailureMsg "more than two file names given"
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
cabal-version: 1.12
|
||||||
|
|
||||||
|
-- This file has been generated from package.yaml by hpack version 0.34.4.
|
||||||
|
--
|
||||||
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
|
name: autocut
|
||||||
|
version: 0.1.0.0
|
||||||
|
description: Please see the README on GitHub at <https://github.com/githubuser/autocut#readme>
|
||||||
|
homepage: https://github.com/githubuser/autocut#readme
|
||||||
|
bug-reports: https://github.com/githubuser/autocut/issues
|
||||||
|
author: Author name here
|
||||||
|
maintainer: example@example.com
|
||||||
|
copyright: 2022 Author name here
|
||||||
|
license: BSD3
|
||||||
|
license-file: LICENSE
|
||||||
|
build-type: Simple
|
||||||
|
extra-source-files:
|
||||||
|
README.md
|
||||||
|
CHANGELOG.md
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://github.com/githubuser/autocut
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules:
|
||||||
|
Driver
|
||||||
|
Lib
|
||||||
|
Parser
|
||||||
|
Types
|
||||||
|
other-modules:
|
||||||
|
Paths_autocut
|
||||||
|
hs-source-dirs:
|
||||||
|
src
|
||||||
|
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
|
||||||
|
build-depends:
|
||||||
|
base >=4.7 && <5
|
||||||
|
, numeric-prelude
|
||||||
|
, shell-utility
|
||||||
|
, soxlib
|
||||||
|
, storablevector
|
||||||
|
, synthesizer-core
|
||||||
|
, transformers
|
||||||
|
, utility-ht
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
executable autocut-exe
|
||||||
|
main-is: Main.hs
|
||||||
|
other-modules:
|
||||||
|
Paths_autocut
|
||||||
|
hs-source-dirs:
|
||||||
|
app
|
||||||
|
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
|
||||||
|
build-depends:
|
||||||
|
autocut
|
||||||
|
, base >=4.7 && <5
|
||||||
|
, numeric-prelude
|
||||||
|
, shell-utility
|
||||||
|
, soxlib
|
||||||
|
, storablevector
|
||||||
|
, synthesizer-core
|
||||||
|
, transformers
|
||||||
|
, utility-ht
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
test-suite autocut-test
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
main-is: Spec.hs
|
||||||
|
other-modules:
|
||||||
|
Paths_autocut
|
||||||
|
hs-source-dirs:
|
||||||
|
test
|
||||||
|
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
|
||||||
|
build-depends:
|
||||||
|
autocut
|
||||||
|
, base >=4.7 && <5
|
||||||
|
, numeric-prelude
|
||||||
|
, shell-utility
|
||||||
|
, soxlib
|
||||||
|
, storablevector
|
||||||
|
, synthesizer-core
|
||||||
|
, transformers
|
||||||
|
, utility-ht
|
||||||
|
default-language: Haskell2010
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
name: autocut
|
||||||
|
version: 0.1.0.0
|
||||||
|
github: "githubuser/autocut"
|
||||||
|
license: BSD3
|
||||||
|
author: "Author name here"
|
||||||
|
maintainer: "example@example.com"
|
||||||
|
copyright: "2022 Author name here"
|
||||||
|
|
||||||
|
extra-source-files:
|
||||||
|
- README.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
|
# Metadata used when publishing your package
|
||||||
|
# synopsis: Short description of your package
|
||||||
|
# category: Web
|
||||||
|
|
||||||
|
# To avoid duplicated efforts in documentation and dealing with the
|
||||||
|
# complications of embedding Haddock markup inside cabal files, it is
|
||||||
|
# common to point users to the README.md file.
|
||||||
|
description: Please see the README on GitHub at <https://github.com/githubuser/autocut#readme>
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base >= 4.7 && < 5
|
||||||
|
- transformers
|
||||||
|
- synthesizer-core
|
||||||
|
- soxlib
|
||||||
|
- numeric-prelude
|
||||||
|
- utility-ht
|
||||||
|
- storablevector
|
||||||
|
- shell-utility
|
||||||
|
|
||||||
|
ghc-options:
|
||||||
|
- -Wall
|
||||||
|
- -Wcompat
|
||||||
|
- -Widentities
|
||||||
|
- -Wincomplete-record-updates
|
||||||
|
- -Wincomplete-uni-patterns
|
||||||
|
- -Wmissing-export-lists
|
||||||
|
- -Wmissing-home-modules
|
||||||
|
- -Wpartial-fields
|
||||||
|
- -Wredundant-constraints
|
||||||
|
|
||||||
|
library:
|
||||||
|
source-dirs: src
|
||||||
|
|
||||||
|
executables:
|
||||||
|
autocut-exe:
|
||||||
|
main: Main.hs
|
||||||
|
source-dirs: app
|
||||||
|
ghc-options:
|
||||||
|
- -threaded
|
||||||
|
- -rtsopts
|
||||||
|
- -with-rtsopts=-N
|
||||||
|
dependencies:
|
||||||
|
- autocut
|
||||||
|
|
||||||
|
tests:
|
||||||
|
autocut-test:
|
||||||
|
main: Spec.hs
|
||||||
|
source-dirs: test
|
||||||
|
ghc-options:
|
||||||
|
- -threaded
|
||||||
|
- -rtsopts
|
||||||
|
- -with-rtsopts=-N
|
||||||
|
dependencies:
|
||||||
|
- autocut
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
module Driver where
|
||||||
|
|
||||||
|
import qualified Sound.SoxLib as SoxLib
|
||||||
|
import Types
|
||||||
|
|
||||||
|
withSound ::
|
||||||
|
Flags -> FilePath ->
|
||||||
|
(SoxLib.Format SoxLib.ReadMode ->
|
||||||
|
Params -> SVL.Vector Int32 -> IO b) ->
|
||||||
|
IO b
|
||||||
|
withSound flags path act =
|
||||||
|
SoxLib.withRead SoxLib.defaultReaderInfo path $ \fmtPtr -> do
|
||||||
|
fmt <- peek fmtPtr
|
||||||
|
let numChan =
|
||||||
|
fromMaybe 1 $ SoxLib.channels $ SoxLib.signalInfo fmt
|
||||||
|
rate =
|
||||||
|
case flagSampleRate flags of
|
||||||
|
Just r -> r
|
||||||
|
Nothing ->
|
||||||
|
case SoxLib.rate $ SoxLib.signalInfo fmt of
|
||||||
|
Just r -> r
|
||||||
|
Nothing -> defaultSampleRate
|
||||||
|
params =
|
||||||
|
Params {
|
||||||
|
sampleRate = rate,
|
||||||
|
numChannels = numChan,
|
||||||
|
smooth = freq rate flagSmooth flags,
|
||||||
|
humFreq = freq rate flagHumFreq flags,
|
||||||
|
pauseVolume = flagPauseVolume flags,
|
||||||
|
minPause = time rate flagMinPause flags,
|
||||||
|
preStart = time rate flagPreStart flags
|
||||||
|
}
|
||||||
|
act fmt params =<<
|
||||||
|
SoxLib.readStorableVectorLazy fmtPtr
|
||||||
|
(case flagBlocksize flags of
|
||||||
|
SVL.ChunkSize size -> SVL.ChunkSize $ numChan * size)
|
||||||
|
|
||||||
|
monoInfoFromFormat ::
|
||||||
|
SoxLib.Format mode -> Params -> SoxLib.WriterInfo
|
||||||
|
monoInfoFromFormat fmtIn params =
|
||||||
|
SoxLib.defaultWriterInfo {
|
||||||
|
SoxLib.writerSignalInfo = Just $
|
||||||
|
(SoxLib.signalInfo fmtIn) {
|
||||||
|
SoxLib.channels = Just 1,
|
||||||
|
SoxLib.rate = Just $ sampleRate params
|
||||||
|
},
|
||||||
|
SoxLib.writerEncodingInfo = Just $ SoxLib.encodingInfo fmtIn
|
||||||
|
}
|
||||||
|
|
||||||
|
writerInfoFromFormat ::
|
||||||
|
SoxLib.Format mode -> Params -> SoxLib.WriterInfo
|
||||||
|
writerInfoFromFormat fmtIn params =
|
||||||
|
SoxLib.defaultWriterInfo {
|
||||||
|
SoxLib.writerSignalInfo = Just $
|
||||||
|
(SoxLib.signalInfo fmtIn) {
|
||||||
|
SoxLib.rate = Just $ sampleRate params
|
||||||
|
},
|
||||||
|
SoxLib.writerEncodingInfo = Just $ SoxLib.encodingInfo fmtIn
|
||||||
|
}
|
||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
module Lib (someFunc) where
|
||||||
|
|
||||||
|
import qualified Synthesizer.Storable.Signal as SigSt
|
||||||
|
import qualified Synthesizer.ChunkySize.Cut as CutCS
|
||||||
|
import qualified Synthesizer.ChunkySize as ChunkySize
|
||||||
|
import qualified Synthesizer.Plain.Filter.Recursive.FirstOrder as Filt1
|
||||||
|
import qualified Synthesizer.Causal.Process as Causal
|
||||||
|
import qualified Synthesizer.State.Cut as Cut
|
||||||
|
import qualified Synthesizer.State.Signal as Sig
|
||||||
|
import qualified Synthesizer.Basic.Binary as Bin
|
||||||
|
|
||||||
|
import qualified Sound.SoxLib as SoxLib
|
||||||
|
|
||||||
|
import qualified Data.StorableVector.Lazy as SVL
|
||||||
|
import Foreign.Storable (peek, )
|
||||||
|
|
||||||
|
import qualified Control.Monad.Trans.State as MS
|
||||||
|
import Control.Monad (when, )
|
||||||
|
import Control.Arrow (arr, (<<<), (^<<), )
|
||||||
|
|
||||||
|
import qualified Data.List.HT as ListHT
|
||||||
|
import qualified Data.List as List
|
||||||
|
import Data.Tuple.HT (swap, )
|
||||||
|
import Data.Foldable (forM_, )
|
||||||
|
import Data.Maybe (fromMaybe, )
|
||||||
|
|
||||||
|
import qualified System.Console.GetOpt as Opt
|
||||||
|
import System.Console.GetOpt
|
||||||
|
(getOpt, usageInfo, ArgDescr(NoArg, ReqArg), )
|
||||||
|
import System.Environment (getArgs, getProgName, )
|
||||||
|
import Text.Printf (printf, )
|
||||||
|
|
||||||
|
import qualified System.Exit as Exit
|
||||||
|
import Shell.Utility.Exit (exitFailureMsg)
|
||||||
|
|
||||||
|
import qualified Algebra.RealRing as Real
|
||||||
|
import NumericPrelude.Numeric
|
||||||
|
import NumericPrelude.Base
|
||||||
|
|
||||||
|
import Data.Int (Int32, )
|
||||||
|
|
||||||
|
import Types
|
||||||
|
import Driver
|
||||||
|
|
||||||
|
import Prelude ()
|
||||||
|
|
||||||
|
dehum :: Params -> Causal.T Float Float
|
||||||
|
dehum params =
|
||||||
|
Filt1.highpass_
|
||||||
|
^<<
|
||||||
|
Filt1.causal
|
||||||
|
<<<
|
||||||
|
Causal.feedConstFst (Filt1.parameter (humFreq params))
|
||||||
|
|
||||||
|
trackEnvelope :: Params -> [SVL.Vector Float] -> SVL.Vector Float
|
||||||
|
trackEnvelope params =
|
||||||
|
Causal.apply
|
||||||
|
(Filt1.lowpassCausal
|
||||||
|
<<<
|
||||||
|
Causal.feedConstFst (Filt1.parameter (smooth params))
|
||||||
|
<<<
|
||||||
|
arr sqrt)
|
||||||
|
.
|
||||||
|
foldl SigSt.mix SVL.empty
|
||||||
|
|
||||||
|
threshold :: Params -> Causal.T Float Bool
|
||||||
|
threshold params = Causal.map (< pauseVolume params)
|
||||||
|
|
||||||
|
findStarts :: Params -> Causal.T Bool Bool
|
||||||
|
findStarts params =
|
||||||
|
flip Causal.fromState 0 $ \b ->
|
||||||
|
if b
|
||||||
|
then MS.modify succ >> evalReturn False
|
||||||
|
else do n <- MS.get; MS.put 0; return (n >= minPause params)
|
||||||
|
|
||||||
|
measurePauses :: Causal.T Bool (Maybe Int)
|
||||||
|
measurePauses =
|
||||||
|
flip Causal.fromState 0 $ \b ->
|
||||||
|
if b
|
||||||
|
then do n <- MS.get; MS.put 1; return (Just n)
|
||||||
|
else MS.modify succ >> evalReturn Nothing
|
||||||
|
|
||||||
|
evalReturn :: a -> MS.State Int a
|
||||||
|
evalReturn x =
|
||||||
|
MS.gets (\n -> seq n x)
|
||||||
|
|
||||||
|
pieceDurations :: Params -> SVL.Vector Int32 -> [Int]
|
||||||
|
pieceDurations params =
|
||||||
|
-- catMaybes . Sig.toList .
|
||||||
|
Sig.foldR (maybe id (:)) [] .
|
||||||
|
Causal.apply
|
||||||
|
(measurePauses <<< findStarts params <<< threshold params) .
|
||||||
|
Sig.fromStorableSignal .
|
||||||
|
trackEnvelope params .
|
||||||
|
map (Causal.apply (arr (^2) <<< dehum params <<< arr Bin.toCanonical)) .
|
||||||
|
SVL.deinterleave (numChannels params)
|
||||||
|
|
||||||
|
pieceDurationsPrefetchLazy :: Params -> SVL.Vector Int32 -> [ChunkySize.T]
|
||||||
|
pieceDurationsPrefetchLazy params sig =
|
||||||
|
flip Cut.chopChunkySize (CutCS.length sig) .
|
||||||
|
flip Sig.append (Sig.repeat False) .
|
||||||
|
Sig.drop (preStart params) .
|
||||||
|
Causal.apply (findStarts params <<< threshold params) .
|
||||||
|
Sig.fromStorableSignal .
|
||||||
|
trackEnvelope params .
|
||||||
|
map (Causal.apply (arr (^2) <<< dehum params <<< arr Bin.toCanonical)) .
|
||||||
|
SVL.deinterleave (numChannels params) $ sig
|
||||||
|
|
||||||
|
|
||||||
|
prefetch :: Int -> [Int] -> [Int]
|
||||||
|
prefetch _ [] = []
|
||||||
|
prefetch n (s:ss) =
|
||||||
|
if s <= n
|
||||||
|
then prefetch (n-s) ss
|
||||||
|
else (s-n) : ss
|
||||||
|
|
||||||
|
chop, chopLazy ::
|
||||||
|
Params -> SVL.Vector Int32 -> [SVL.Vector Int32]
|
||||||
|
chop params sig0 =
|
||||||
|
snd $
|
||||||
|
List.mapAccumL (\sig n -> swap $ SVL.splitAt n sig) sig0 $
|
||||||
|
map (numChannels params *) .
|
||||||
|
prefetch (preStart params) $ pieceDurations params sig0
|
||||||
|
|
||||||
|
chopLazy params sig0 =
|
||||||
|
snd $
|
||||||
|
List.mapAccumL (\sig n -> swap $ CutCS.splitAt n sig) sig0 $
|
||||||
|
map (fromIntegral (numChannels params) *) .
|
||||||
|
pieceDurationsPrefetchLazy params $ sig0
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
module Parser where
|
||||||
|
|
||||||
|
import qualified Sound.SoxLib as SoxLib
|
||||||
|
|
||||||
|
import Types
|
||||||
|
import Driver
|
||||||
|
|
||||||
|
parseCard :: (Read a, Real.C a) => String -> String -> IO a
|
||||||
|
parseCard name str =
|
||||||
|
case reads str of
|
||||||
|
[(n,"")] ->
|
||||||
|
case compare n zero of
|
||||||
|
GT -> return n
|
||||||
|
EQ -> exitFailureMsg $ name ++ " must not be zero"
|
||||||
|
LT -> exitFailureMsg $ "negative " ++ name ++ ": " ++ str
|
||||||
|
_ -> exitFailureMsg $ "could not parse " ++ name ++ " " ++ show str
|
||||||
|
|
||||||
|
numberArg ::
|
||||||
|
(Read a, Real.C a) =>
|
||||||
|
String -> (a -> Flags -> IO Flags) ->
|
||||||
|
Opt.ArgDescr (Flags -> IO Flags)
|
||||||
|
numberArg name update =
|
||||||
|
flip ReqArg name $ \str flags ->
|
||||||
|
flip update flags =<< parseCard name str
|
||||||
|
|
||||||
|
description :: [ Opt.OptDescr (Flags -> IO Flags) ]
|
||||||
|
description =
|
||||||
|
Opt.Option ['h'] ["help"]
|
||||||
|
(NoArg $ \ _flags -> do
|
||||||
|
programName <- getProgName
|
||||||
|
putStrLn $
|
||||||
|
usageInfo ("Usage: " ++ programName ++ " [OPTIONS] INPUT [OUTPUT]") $
|
||||||
|
description
|
||||||
|
Exit.exitSuccess)
|
||||||
|
"show options" :
|
||||||
|
Opt.Option ['r'] ["rate"]
|
||||||
|
(numberArg "SAMPLERATE" $ \n flags ->
|
||||||
|
return $ flags{flagSampleRate = Just n})
|
||||||
|
("sample rate, default " ++ show defaultSampleRate) :
|
||||||
|
Opt.Option [] ["pause-volume"]
|
||||||
|
(numberArg "AMPLITUDE" $ \n flags ->
|
||||||
|
return $ flags{flagPauseVolume = n})
|
||||||
|
("required maximum amplitude in pauses between pieces, default " ++ show (flagPauseVolume defltFlags)) :
|
||||||
|
Opt.Option [] ["smooth"]
|
||||||
|
(numberArg "FREQUENCY" $ \n flags ->
|
||||||
|
return $ flags{flagSmooth = Freq n})
|
||||||
|
("cutoff frequency for smoothing envelope, default " ++ formatFreq (flagSmooth defltFlags)) :
|
||||||
|
Opt.Option [] ["hum-frequency"]
|
||||||
|
(numberArg "FREQUENCY" $ \n flags ->
|
||||||
|
return $ flags{flagHumFreq = Freq n})
|
||||||
|
("cutoff frequency for hum elimination, default " ++ formatFreq (flagHumFreq defltFlags)) :
|
||||||
|
Opt.Option [] ["min-pause"]
|
||||||
|
(numberArg "TIME" $ \n flags ->
|
||||||
|
return $ flags{flagMinPause = Time n})
|
||||||
|
("minimal required pause between pieces, default " ++ formatTime (flagMinPause defltFlags)) :
|
||||||
|
Opt.Option [] ["pre-start"]
|
||||||
|
(numberArg "TIME" $ \n flags ->
|
||||||
|
return $ flags{flagPreStart = Time n})
|
||||||
|
("time to start before threshold is exceeded, default " ++ formatTime (flagPreStart defltFlags)) :
|
||||||
|
Opt.Option [] ["blocksize"]
|
||||||
|
(numberArg "NUMSAMPLES" $ \n flags ->
|
||||||
|
return $ flags{flagBlocksize = SVL.chunkSize n})
|
||||||
|
("size of processing chunks, default " ++
|
||||||
|
case flagBlocksize defltFlags of SVL.ChunkSize size -> show size) :
|
||||||
|
Opt.Option [] ["compute-envelope"]
|
||||||
|
(NoArg $ \ flags -> do
|
||||||
|
return $ flags{flagComputeEnvelope = True})
|
||||||
|
"compute envelope for assistance in finding appropriate parameters" :
|
||||||
|
[]
|
||||||
|
|
||||||
|
defaultSampleRate :: SoxLib.Rate
|
||||||
|
defaultSampleRate = 44100
|
||||||
|
|
||||||
|
freq :: SoxLib.Rate -> (Flags -> Freq) -> (Flags -> Float)
|
||||||
|
freq sr acc flags =
|
||||||
|
(case acc flags of Freq f -> f) / realToFrac sr
|
||||||
|
|
||||||
|
time :: SoxLib.Rate -> (Flags -> Time) -> (Flags -> Int)
|
||||||
|
time sr acc flags =
|
||||||
|
round ((case acc flags of Time t -> t) * realToFrac sr)
|
||||||
|
|
||||||
|
formatFreq :: Freq -> String
|
||||||
|
formatFreq (Freq t) = show t -- ++ "Hz"
|
||||||
|
|
||||||
|
formatTime :: Time -> String
|
||||||
|
formatTime (Time t) = show t -- ++ "s"
|
||||||
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
module Types where
|
||||||
|
|
||||||
|
newtype Time = Time Float
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
newtype Freq = Freq Float
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
data Flags =
|
||||||
|
Flags {
|
||||||
|
flagComputeEnvelope :: Bool,
|
||||||
|
flagSampleRate :: Maybe SoxLib.Rate,
|
||||||
|
flagSmooth, flagHumFreq :: Freq,
|
||||||
|
flagPauseVolume :: Float,
|
||||||
|
flagMinPause, flagPreStart :: Time,
|
||||||
|
flagBlocksize :: SVL.ChunkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
defltFlags :: Flags
|
||||||
|
defltFlags =
|
||||||
|
Flags {
|
||||||
|
flagComputeEnvelope = False,
|
||||||
|
flagSampleRate = Nothing,
|
||||||
|
flagSmooth = Freq 1,
|
||||||
|
flagHumFreq = Freq 100,
|
||||||
|
flagPauseVolume = 0.02,
|
||||||
|
flagMinPause = Time 2,
|
||||||
|
{-
|
||||||
|
Sometimes a piece starts with breath which is almost undetectable.
|
||||||
|
Thus we start a little bit earlier than necessary.
|
||||||
|
-}
|
||||||
|
-- flagPreStart = Time 1.5,
|
||||||
|
flagPreStart = Time 0.05,
|
||||||
|
flagBlocksize = SVL.chunkSize 65536
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data Params =
|
||||||
|
Params {
|
||||||
|
sampleRate :: SoxLib.Rate,
|
||||||
|
numChannels :: Int,
|
||||||
|
smooth, humFreq :: Float,
|
||||||
|
pauseVolume :: Float,
|
||||||
|
minPause, preStart :: Int
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
# This file was automatically generated by 'stack init'
|
||||||
|
#
|
||||||
|
# Some commonly used options have been documented as comments in this file.
|
||||||
|
# For advanced use and comprehensive documentation of the format, please see:
|
||||||
|
# https://docs.haskellstack.org/en/stable/yaml_configuration/
|
||||||
|
|
||||||
|
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
|
||||||
|
# A snapshot resolver dictates the compiler version and the set of packages
|
||||||
|
# to be used for project dependencies. For example:
|
||||||
|
#
|
||||||
|
# resolver: lts-3.5
|
||||||
|
# resolver: nightly-2015-09-21
|
||||||
|
# resolver: ghc-7.10.2
|
||||||
|
#
|
||||||
|
# The location of a snapshot can be provided as a file or url. Stack assumes
|
||||||
|
# a snapshot provided as a file might change, whereas a url resource does not.
|
||||||
|
#
|
||||||
|
# resolver: ./custom-snapshot.yaml
|
||||||
|
# resolver: https://example.com/snapshots/2018-01-01.yaml
|
||||||
|
resolver:
|
||||||
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/21.yaml
|
||||||
|
|
||||||
|
# User packages to be built.
|
||||||
|
# Various formats can be used as shown in the example below.
|
||||||
|
#
|
||||||
|
# packages:
|
||||||
|
# - some-directory
|
||||||
|
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
|
||||||
|
# subdirs:
|
||||||
|
# - auto-update
|
||||||
|
# - wai
|
||||||
|
packages:
|
||||||
|
- .
|
||||||
|
# Dependency packages to be pulled from upstream that are not in the resolver.
|
||||||
|
# These entries can reference officially published versions as well as
|
||||||
|
# forks / in-progress versions pinned to a git hash. For example:
|
||||||
|
#
|
||||||
|
# extra-deps:
|
||||||
|
# - acme-missiles-0.3
|
||||||
|
# - git: https://github.com/commercialhaskell/stack.git
|
||||||
|
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
|
||||||
|
#
|
||||||
|
# extra-deps: []
|
||||||
|
|
||||||
|
# Override default flag values for local packages and extra-deps
|
||||||
|
# flags: {}
|
||||||
|
|
||||||
|
# Extra package databases containing global packages
|
||||||
|
# extra-package-dbs: []
|
||||||
|
|
||||||
|
# Control whether we use the GHC we find on the path
|
||||||
|
# system-ghc: true
|
||||||
|
#
|
||||||
|
# Require a specific version of stack, using version ranges
|
||||||
|
# require-stack-version: -any # Default
|
||||||
|
# require-stack-version: ">=2.7"
|
||||||
|
#
|
||||||
|
# Override the architecture used by stack, especially useful on Windows
|
||||||
|
# arch: i386
|
||||||
|
# arch: x86_64
|
||||||
|
#
|
||||||
|
# Extra directories used by stack for building
|
||||||
|
# extra-include-dirs: [/path/to/dir]
|
||||||
|
# extra-lib-dirs: [/path/to/dir]
|
||||||
|
#
|
||||||
|
# Allow a newer minor version of GHC than the snapshot specifies
|
||||||
|
# compiler-check: newer-minor
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
main :: IO ()
|
||||||
|
main = putStrLn "Test suite not yet implemented"
|
||||||
Reference in New Issue
Block a user