Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Tuesday, December 25, 2018

Got latency issues with Samplerbox?

If you are wondering what is wrong with the latency issues in you Samplerbox build, follow this easy 1-step tutorial:

1) Stop using the "1GrandPiano" sample set.

Just look at this:

Sound of silence

The samples in the "1GrandPiano" set have varying lengths of silence before the sample starts. The silence is often just 10ms but on some samples it is up to 50ms.

If you have fought with latency issues before, you know that 50ms is a lot. It will make playing really hard. But what's even worse than a stable 50ms latency is that these samples have different lengths of silence before them. That makes the latency seem random and is really awful to play.

This sample set was one of the first introduced on the Samplerbox site. I think it was there from the beginning. So when I read complaints about latency with Samplerbox, I get the feeling that the cause is actually this sample set in most cases.

If you really want to use this sample set, you could trim the silence out of the samples. Some years ago I made a python script to do just this.

Disclaimer: I was just learning python at the time, so the code is probably not very pythonic. But it worked for me.

Note that it will cut silence and also a part of the attack! The higher the threshold value, the more you will cut from the beginning of the sample.

It will also cut any silence after the sample. That will make the sample set much smaller and thus faster to load.

Have fun.


# Sample trimmer for Python 3.x
# by Juho-Eric
# 24.10.2016
#
# This script trims silence out from the beginning of wave files.
# It makes them respond better when played live (reduces latency) and
# makes the file size smaller by removing the unnecessary silence.
#
# The script looks for .wav files in this directory and creates
# a subdirectory "trimmed" that contains the trimmed version of each
# sample.
#
import glob
import os
import wave
import struct
import timeit

def getframe(frames, i):
    framelong = frames[4*i]+(frames[4*i+1]<<8)+(frames[4*i+2]<<16)+(frames[4*i+3]<<24)
    framebs = struct.pack('L', framelong)
    return struct.unpack("<hh",framebs)

def setframe(frames, f, l, r):
    framebs = struct.pack('<hh', int(l), int(r))
    a,b,c,d = struct.unpack('BBBB',framebs)
    frames[f*4] = a
    frames[f*4+1] = b
    frames[f*4+2] = c
    frames[f*4+3] = d
    return 

# Just for checking how much time it takes to run
scriptstart = timeit.default_timer()

startthreshold = 100
endthreshold = 10
fadeinlength = 4
fadeoutlength = 4
wavefiles = glob.glob("*.wav")

totalcutframes = 0
totalcutsecs = 0

# Make output directory if not made yet
try:
    os.stat("trimmed")
except:
    os.mkdir("trimmed")
    
# Go through all wave files in this directory
for file in wavefiles:
    win = wave.open(file, 'r')
    params = win.getparams()
    #nbits = win.getsampwidth()*8
    #nchannels = win.getnchannels()
    nframes = win.getnframes()
    framerate = win.getframerate()

    # File is opened for reading.
    #print("Opening "+file+": "+str(nchannels)+" channels "+str(nbits)+"-bit "+str(framerate)+" Hz")
    
    # Get all frames as bytearray.
    frames = bytearray(win.readframes(nframes))

    # Find the first point where threshold is broken (trim starting silence)
    framewas = 0
    for i in range(nframes-1):
        l,r = getframe(frames, i)
        if (abs(l) > startthreshold or abs(r) > startthreshold):
            framewas = i
            break
    
    startframe = framewas
    startsecs = startframe / framerate
    totalcutsecs += startsecs
    totalcutframes += startframe
    print(file +" cut from start "+str(startframe)+" frames / "+str(startsecs)+" s")
    
    # Find the frame where the silence starts again (trim ending silence)
    framewas = nframes
    for i in range(nframes-1, 0, -1):
        l,r = getframe(frames, i)
        if (abs(l) > endthreshold or abs(r) > endthreshold):
            framewas = i
            break
    
    endframe = framewas
    cutendframes = nframes - endframe
    cutendsecs = cutendframes / framerate
    totalcutsecs += cutendsecs
    totalcutframes += cutendframes
    print(file+" cut from end "+str(cutendframes)+" frames / "+str(cutendsecs)+" s")
    
    # Create fadein
    for i,f in enumerate(range(startframe,startframe+fadeinlength)):
        l,r = getframe(frames,f)
        multiplier = ((i+1)/(fadeinlength+1))
        setframe(frames, f, l*multiplier, r*multiplier)
    
    # Create fadeout
    for i,f in enumerate(range(endframe-fadeoutlength,endframe)):
        l,r = getframe(frames,f)
        multiplier = ((fadeoutlength-i)/(fadeoutlength+1))
        setframe(frames, f, l*multiplier, r*multiplier)
    
    # write output file
    wout = wave.open("trimmed/"+file, 'w')
    wout.setparams(params)
    wout.writeframes(frames[startframe*4:endframe*4])
    wout.close()
    win.close()
    
print("Total: cut "+str(totalcutframes)+" frames / "+str(totalcutsecs)+" s")

scriptstop = timeit.default_timer()
print("Operation took "+str(scriptstop-scriptstart)+" seconds")

Wednesday, December 10, 2014

Python3 TCP server for Windows 7 remote control

I did a quick and dirty volume remote control for my Win7 PC and decided to share it.

The script opens a TCP server on the machine, and listens for connections. When a client connects, it will react to commands 'U' (volume up), 'D' (volume down) and 'M' (toggle mute).

It works at least with Python 3.4 and it uses NirCmd to set the Windows master volume. I first looked at pywin32 library for the volume control, but I couldn't find any good documentation, so I went the easier route and decided to use NirCmd instead.

It might not be the prettiest solution, but it works for me!

Place the below code into a .py file, for example 'rcserver.py' and run it with Python 3.4. Also place nircmd.exe in the same directory.

You can try it out by using PuTTy and connecting to 127.0.0.1 port 5005 with 'Raw' connection type. Then enter M, D, U and your computer's volume will change.

I'm using a Raspberry Pi to control it with netcat. Here's how:

Install netcat if you don't have it:
apt-get update
apt-get install netcat 

Send mute command:
echo -n "M" | nc -q1 ip.address.of.computer 5005 
This echoes a M character to the IP address on port 5005, and quits after one second.
Replace the IP address here and in the Python code with your Windows computer's LAN address.

Note that you can remote control almost anything by adding commands to the script.

Here is the code:


 # Python 3 server for Windows 7 remote control   
 # Juho-Eric 11. Dec 2014  
 #  
 # You need the following to run this:  
 #  
 # Python 3.4  
 # https://www.python.org/  
 #  
 # NirCmd  
 # http://www.nirsoft.net/utils/nircmd.html  
 #  
 # Place nircmd.exe in the same directory as this script.  
   
 import socket  
 import subprocess  
   
 #Parameters for TCP server.   
 TCP_IP = '127.0.0.1'  
 TCP_PORT = 5005  
 BUFFER_SIZE = 20  
   
 #This code is to hide the command prompt window when running nircmd.exe  
 si = subprocess.STARTUPINFO()  
 si.dwFlags |= subprocess.STARTF_USESHOWWINDOW  
 #si.wShowWindow = subprocess.SW_HIDE # default  
   
 #Create the TCP server socket  
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
 s.bind((TCP_IP, TCP_PORT))  
 s.listen(1)  
   
 #Main loop, this will loop on each connection  
 while 1:  
   conn, addr = s.accept() #get new connection  
   print('Connection address:', addr)  
   while 1: #This will loop on each command received  
     data = conn.recv(BUFFER_SIZE) #receive data  
     if not data: break #check if connection closed  
     print("received data:", data)  
     if data == b'U':   
       print('volume up')  
       subprocess.call('nircmd.exe changesysvolume 1000', startupinfo=si) #increase volume 
       subprocess.call('nircmd.exe mutesysvolume 0', startupinfo=si) #remove mute  
     elif data == b'D':  
       print('volume down')  
       subprocess.call('nircmd.exe changesysvolume -1000', startupinfo=si) #decrease volume
       subprocess.call('nircmd.exe mutesysvolume 0', startupinfo=si) #remove mute  
     elif data == b'M':  
       print('mute toggle')  
       subprocess.call('nircmd.exe mutesysvolume 2', startupinfo=si) #toggle mute  
   conn.close() #close the connection  
   print("connection closed")