Skip to content

Support for Python 3 #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ SampleScanner is a command-line tool to turn MIDI instruments (usually hardware)

## Installation

Requires a working `python` (version 2.7), `pip`, and `ffmpeg` to be installed on the system.
Requires a working `python` (version 3.10), `pip`, and `ffmpeg` to be installed on the system.

```
git clone [email protected]:psobot/SampleScanner
Expand All @@ -29,7 +29,7 @@ pip install -r requirements.txt

## How to run

Run `./samplescanner -h` for a full argument listing:
Run `./samplescanner.py -h` for a full argument listing:

```contentsof<samplescanner -h>
usage: samplescanner [-h] [--cc-before [CC_BEFORE [CC_BEFORE ...]]]
Expand Down
26 changes: 13 additions & 13 deletions lib/audio_helpers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import time
import numpy
from utils import note_name, percent_to_db
from record import record
from constants import CLIPPING_THRESHOLD, \
from .utils import note_name, percent_to_db
from .record import record
from .consts import CLIPPING_THRESHOLD, \
CLIPPING_CHECK_NOTE, \
EXIT_ON_CLIPPING, \
SAMPLE_RATE
from midi_helpers import all_notes_off, CHANNEL_OFFSET
from .midi_helpers import all_notes_off, CHANNEL_OFFSET


def generate_sample(
Expand Down Expand Up @@ -46,7 +46,7 @@ def on_time_up():

def sample_threshold_from_noise_floor(bit_depth, audio_interface_name):
time.sleep(1)
print "Sampling noise floor..."
print("Sampling noise floor...")
sample_width, data, release_time = record(
limit=2.0,
after_start=None,
Expand All @@ -60,9 +60,9 @@ def sample_threshold_from_noise_floor(bit_depth, audio_interface_name):
numpy.amax(numpy.absolute(data)) /
float(2 ** (bit_depth - 1))
)
print "Noise floor has volume %8.8f dBFS" % percent_to_db(noise_floor)
print("Noise floor has volume %8.8f dBFS" % percent_to_db(noise_floor))
threshold = noise_floor * 1.1
print "Setting threshold to %8.8f dBFS" % percent_to_db(threshold)
print("Setting threshold to %8.8f dBFS" % percent_to_db(threshold))
return threshold


Expand All @@ -74,9 +74,9 @@ def check_for_clipping(
audio_interface_name,
):
time.sleep(1)
print "Checking for clipping and balance on note %s..." % (
print("Checking for clipping and balance on note %s..." % (
note_name(CLIPPING_CHECK_NOTE)
)
))

sample_width, data, release_time = generate_sample(
limit=2.0,
Expand All @@ -99,14 +99,14 @@ def check_for_clipping(
)

# All notes off, but like, a lot, again
for _ in xrange(0, 2):
for _ in range(0, 2):
all_notes_off(midiout, midi_channel)

print "Maximum volume is around %8.8f dBFS" % percent_to_db(max_volume)
print("Maximum volume is around %8.8f dBFS" % percent_to_db(max_volume))
if max_volume >= CLIPPING_THRESHOLD:
print "Clipping detected (%2.2f dBFS >= %2.2f dBFS) at max volume!" % (
print("Clipping detected (%2.2f dBFS >= %2.2f dBFS) at max volume!" % (
percent_to_db(max_volume), percent_to_db(CLIPPING_THRESHOLD)
)
))
if EXIT_ON_CLIPPING:
raise ValueError("Clipping detected at max volume!")

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion lib/deflac.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from wavio import read_wave_file
from utils import normalized
from record import RATE, save_to_file
from constants import bit_depth
from consts import bit_depth


def full_path(sfzfile, filename):
Expand Down
17 changes: 9 additions & 8 deletions lib/flacize.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import argparse
import subprocess
from tqdm import tqdm
from sfzparser import SFZFile, Group
from wavio import read_wave_file
from utils import group_by_attr, note_name

from .sfzparser import SFZFile, Group
from .wavio import read_wave_file
from .utils import group_by_attr, note_name


def full_path(sfzfile, filename):
Expand Down Expand Up @@ -66,7 +67,7 @@ def flacize_after_sampling(
for key, key_regions in
group_by_attr(group.regions, [
'key', 'pitch_keycenter'
]).iteritems()], [])
]).items()], [])
new_groups.append(Group(group.attributes, output))

with open(sfzfile + '.flac.sfz', 'w') as file:
Expand All @@ -77,7 +78,7 @@ def flacize_after_sampling(
try:
os.unlink(path)
except OSError as e:
print "Could not unlink path: %s: %s" % (path, e)
print("Could not unlink path: %s: %s" % (path, e))


ANTI_CLICK_OFFSET = 3
Expand Down Expand Up @@ -129,7 +130,7 @@ def concat_samples(regions, path, name=None):
note_name(key)))
for key, regions in
tqdm(group_by_attr(group.regions,
'key').iteritems())], [])
print group.just_group()
'key').items())], [])
print(group.just_group())
for region in output:
print region
print(region)
4 changes: 2 additions & 2 deletions lib/group_velcurves.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ def should_group_key(key):


def group_by_pitch(regions):
for key, regions in group_by_attr(regions, 'key').iteritems():
for key, regions in group_by_attr(regions, 'key').items():
# Group together all amp_velcurve_* and key params.
yield Group(dict([
(key, value)
for region in regions
for key, value in region.attributes.iteritems()
for key, value in region.attributes.items()
if should_group_key(key)
] + DEFAULT_ATTRIBUTES.items()), [
region.without_attributes(should_group_key) for region in regions
Expand Down
39 changes: 23 additions & 16 deletions lib/loop.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import sys
import numpy
from tqdm import tqdm
from truncate import read_wave_file
from audio_helpers import fundamental_frequency

from .truncate import read_wave_file
from .audio_helpers import fundamental_frequency

QUANTIZE_FACTOR = 8

Expand All @@ -12,20 +13,20 @@ def compare_windows(window_a, window_b):


def slide_window(file, period, start_at=0, end_before=0):
for power in reversed(xrange(7, 10)):
for power in reversed(range(7, 10)):
multiple = 2 ** power
window_size = int(period * multiple)
# Uncomment this to search from the start_at value to the end_before
# rather than just through one window's length
# end_range = len(file) - (window_size * 2) - end_before
end_range = start_at + window_size
for i in xrange(start_at, end_range):
for i in range(start_at, end_range):
yield power, i, window_size


def window_match(file):
period = (1.0 / fundamental_frequency(file, 1)) * 2
print period, 'period in samples'
print(period, 'period in samples')

winner = None

Expand All @@ -48,14 +49,14 @@ def window_match(file):
i,
abs(file[i] - file[window_start])
)
print 'new winner', winner
print('new winner', winner)

lowest_difference, winning_window_size, winning_index, gap = winner

print "Best loop match:", lowest_difference
print "window size", winning_window_size
print "winning index", winning_index
print "winning gap", gap
print("Best loop match:", lowest_difference)
print("window size", winning_window_size)
print("winning index", winning_index)
print("winning gap", gap)
return winning_index, winning_window_size


Expand All @@ -71,7 +72,7 @@ def find_similar_sample_index(
):
reference_slope = slope_at_index(file, reference_index) > 0
best_match = None
search_range = xrange(
search_range = range(
search_around_index - search_size,
search_around_index + search_size
)
Expand All @@ -93,12 +94,12 @@ def find_similar_sample_index(

def zero_crossing_match(file):
period = (1.0 / fundamental_frequency(file, 1)) * 2
print period, 'period in samples'
print(period, 'period in samples')

period_multiple = 64
period = period * period_multiple

for i in reversed(xrange(2 * len(file) / 3, 5 * len(file) / 6)):
for i in reversed(range(2 * len(file) / 3, 5 * len(file) / 6)):
if file[i] >= 0 and file[i + 1] < 0 and \
file[int(i + period)] >= 0 and \
file[int(i + 1 + period)] < 0 and \
Expand Down Expand Up @@ -131,7 +132,13 @@ def fast_autocorrelate(x):
f = numpy.fft.fft(xp)
p = numpy.absolute(numpy.power(f, 2))
pi = numpy.fft.ifft(p)
result = numpy.real(pi)[:x.size / 2] / numpy.sum(numpy.power(xp, 2))

index = int(x.size / 2)
top = numpy.real(pi)[:index]
bottom = numpy.sum(numpy.power(xp, 2))
result = top / bottom


return result


Expand Down Expand Up @@ -164,7 +171,7 @@ def find_loop_from_autocorrelation(
min_loop_width_in_seconds=0.2,
sample_rate=48000
):
search_start /= 2
search_start = int(search_start/2)
max_autocorrelation_peak_width = int(
min_loop_width_in_seconds * sample_rate
)
Expand Down Expand Up @@ -234,7 +241,7 @@ def process(aif, sample_rate=48000):

file = file[0]

print 'start, end', loop_start, loop_end
print('start, end', loop_start, loop_end)

plt.plot(file[loop_start:loop_end])
plt.plot(file[loop_end:loop_start + (2 * loop_size)])
Expand Down
6 changes: 3 additions & 3 deletions lib/midi_helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import time
import rtmidi


CHANNEL_OFFSET = 0x90 - 1
CC_CHANNEL_OFFSET = 0xB0 - 1

Expand Down Expand Up @@ -37,6 +36,7 @@ def all_notes_off(midiout, midi_channel):
def open_midi_port(midi_port_name):
midiout = rtmidi.MidiOut()
ports = midiout.get_ports()

for i, port_name in enumerate(ports):
if not midi_port_name or midi_port_name.lower() in port_name.lower():
midiout.open_port(i)
Expand All @@ -49,7 +49,7 @@ def open_midi_port(midi_port_name):

def set_program_number(midiout, midi_channel, program_number):
if program_number is not None:
print "Sending program change to program %d..." % program_number
print("Sending program change to program %d..." % program_number)
# Bank change (fine) to (program_number / 128)
midiout.send_message([
CC_CHANNEL_OFFSET + midi_channel,
Expand All @@ -64,7 +64,7 @@ def set_program_number(midiout, midi_channel, program_number):
])

# All notes off, but like, a lot
for _ in xrange(0, 2):
for _ in range(0, 2):
all_notes_off(midiout, midi_channel)

time.sleep(0.5)
Expand Down
8 changes: 4 additions & 4 deletions lib/quantize.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def quantize_pitch(regions, pitch_levels=25):

# a dict of sample_pitch -> [lokey, hikey, pitch_keycenter]
pitchmapping = {}
for key in xrange(
for key in range(
lowestkey + (pitch_skip / 2),
highestkey + 1 + (pitch_skip / 2),
pitch_skip):
Expand All @@ -35,7 +35,7 @@ def quantize_pitch(regions, pitch_levels=25):
'hikey': key + (pitch_skip / 2) - (0 if evenly_divided else 1),
}

for key, regions in group_by_attr(regions, 'key').iteritems():
for key, regions in group_by_attr(regions, 'key').items():
if int(key) in pitchmapping:
for region in regions:
region.attributes.update(pitchmapping[int(key)])
Expand All @@ -55,7 +55,7 @@ def quantize_velocity(regions, velocity_levels=5):

# a dict of sample_pitch -> [lokey, hikey, pitch_keycenter]
pitchmapping = {}
for key in xrange(
for key in range(
lowestkey + (pitch_skip / 2),
highestkey + 1 + (pitch_skip / 2),
pitch_skip):
Expand All @@ -65,7 +65,7 @@ def quantize_velocity(regions, velocity_levels=5):
'hikey': key + (pitch_skip / 2) - (0 if evenly_divided else 1),
}

for key, regions in group_by_attr(regions, 'key').iteritems():
for key, regions in group_by_attr(regions, 'key').items():
if int(key) in pitchmapping:
for region in regions:
region.attributes.update(pitchmapping[int(key)])
Expand Down
16 changes: 8 additions & 8 deletions lib/record.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import sys
import numpy
from struct import pack
from constants import bit_depth, NUMPY_DTYPE, SAMPLE_RATE
from utils import percent_to_db, dbfs_as_percent

from .consts import bit_depth, NUMPY_DTYPE, SAMPLE_RATE
from .utils import percent_to_db, dbfs_as_percent

import pyaudio
import wave
Expand Down Expand Up @@ -41,7 +42,7 @@ def get_input_device_index(py_audio, audio_interface_name=None):
input_interface_names = get_input_device_names(py_audio, info)

if audio_interface_name:
for index, name in input_interface_names.iteritems():
for index, name in input_interface_names.items():
if audio_interface_name.lower() in name.lower():
return index
else:
Expand All @@ -55,8 +56,7 @@ def get_input_device_name_by_index(audio_interface_index):
py_audio = pyaudio.PyAudio()
info = py_audio.get_host_api_info_by_index(0)
input_interface_names = get_input_device_names(py_audio, info)

for index, name in input_interface_names.iteritems():
for index, name in input_interface_names.items():
if index == audio_interface_index:
return name
else:
Expand All @@ -68,7 +68,7 @@ def get_input_device_name_by_index(audio_interface_index):

def list_input_devices(device_names):
lines = []
for index, name in sorted(device_names.iteritems()):
for index, name in sorted(device_names.items()):
lines.append(u"{:3d}. {}".format(index, name))
return u"\n".join(lines).encode("ascii", "ignore")

Expand Down Expand Up @@ -256,13 +256,13 @@ def save_to_file(path, sample_width, data, sample_rate=SAMPLE_RATE):
flattened = numpy.asarray(data.flatten('F'), dtype=NUMPY_DTYPE)

write_chunk_size = 512
for chunk_start in xrange(0, len(flattened), write_chunk_size):
for chunk_start in range(0, len(flattened), write_chunk_size):
chunk = flattened[chunk_start:chunk_start + write_chunk_size]
packstring = '<' + ('h' * len(chunk))
wf.writeframes(pack(packstring, *chunk))
wf.close()


if __name__ == '__main__':
print record_to_file('./demo.wav', sys.argv[1] if sys.argv[1] else None)
print(record_to_file('./demo.wav', sys.argv[1] if sys.argv[1] else None))
print("done - result written to demo.wav")
Loading