diff --git a/.gitignore b/.gitignore index b948985..492ee29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,10 @@ *.swp *.pyc +gongs_wav +CLIPS.json +DRONES.json +GONGS.json +MUSIC.json +PATHS.json +VOCALS.json + diff --git a/gongs_wav.py b/gongs_wav.py new file mode 100755 index 0000000..58aaa29 --- /dev/null +++ b/gongs_wav.py @@ -0,0 +1,12 @@ +#!/usr/bin/python3 +from glob import glob +import subprocess +import os + +for f in glob('gongs/*/*.mp3'): + t = f.replace('gongs/', 'gongs_wav/').replace('.mp3', '.wav') + print(f, t) + folder = os.path.dirname(t) + if not os.path.exists(folder): + os.makedirs(folder) + subprocess.call(['ffmpeg', '-i', f, t]) diff --git a/pi.py b/pi.py index 7b3ebd1..491a633 100644 --- a/pi.py +++ b/pi.py @@ -6,6 +6,7 @@ class random(object): PI = str(mp.pi).replace('.', '') def __init__(self, offset=0): + self.position = offset self.numbers = list(map(int, self.PI[offset:])) def __call__(self): @@ -14,5 +15,6 @@ class random(object): mp.dps += 1000 self.PI = str(mp.pi).replace('.', '') self.numbers = list(map(int, self.PI[offset:])) + self.position += 1 return self.numbers.pop(0) diff --git a/render.py b/render.py index 19b05e8..726c5a9 100755 --- a/render.py +++ b/render.py @@ -419,6 +419,10 @@ def sequence(seq, letter): tduration = sum([c['duration'] for c in result[track]]) if not abs(tduration - duration) < 0.000001: raise Exception('invalid duration on track: %s %s vs %s %s' % (track, tduration, duration, result[track])) + result['gongs'] = { + 'tracks': 100 + seq() * 23, + 'offset': seq.position, + } return result diff --git a/render_gongs.py b/render_gongs.py new file mode 100755 index 0000000..755e88e --- /dev/null +++ b/render_gongs.py @@ -0,0 +1,132 @@ +#!/usr/bin/python3 +import os +import sys +import json +import subprocess +from collections import defaultdict +import string +from glob import glob + +import nmt +import numpy as np + +from pi import random +from keywords import KEYWORDS +import ox +import ox.web.auth + + +if not os.path.exists('GONGS.json'): + GONGS = defaultdict(list) + prefix = 'gongs_wav' + wavs = glob('%s/*/*.wav' % prefix) + for path in sorted(wavs): + name = os.path.basename(path).split('.')[0] + GONGS[name] = { + 'path': path, + 'duration': ox.avinfo(path)['duration'] + } + with open('GONGS.json', 'w') as fd: + json.dump(GONGS, fd, indent=2, sort_keys=True) +else: + GONGS = json.load(open('GONGS.json')) + + +def random_choice(seq, items): + n = n_ = len(items) - 1 + #print('len', n) + if n == 0: + return items[0] + r = seq() + base = 10 + while n > 10: + n /= 10 + #print(r) + r += seq() + base += 10 + r = int(n_ * r / base) + #print('result', r, items) + return items[r] + + +def splitint(number, by): + div = int(number/by) + mod = number % by + return [div + 1 if i > (by - 1 - mod) else div for i in range(by)] + + +def add_blank(track, d): + if track and track[-1].get('blank'): + track[-1]['duration'] += d + else: + blank = {'blank': True, 'duration': d} + track.append(blank) + return d + + +def get_gongs(seq, result, duration, tracks=42): + for tn in range(tracks): + track = 'gongs%d' % tn + if track not in result: + result[track] = [] + + position = 0 + while position < duration: + n1 = seq() + n2 = seq() + clip = GONGS['%d_%d' % (n1, n2)] + if position + clip['duration'] < duration: + position += clip['duration'] + result[track].append(clip) + else: + c = clip.copy() + c['duration'] = duration - position + result[track].append(c) + position += c['duration'] + if position < duration: + position += add_blank(result[track], duration - position) + + for track in result: + if result[track]: + tduration = sum([c['duration'] for c in result[track]]) + if not abs(tduration - duration) < 0.000001: + raise Exception('invalid duration on track: %s %s vs %s %s' % (track, tduration, duration, result[track])) + return result + + +def mix_gongs(data, output): + mixed = None + for n, clips in enumerate(data.values()): + track = [nmt.sound2np(c['path']) for c in clips] + track = np.vstack(track) + if mixed is None: + mixed = track.astype(np.int) + else: + limit = min(len(track), len(mixed)) + mixed = mixed[:limit] + track = track[:limit] + mixed += track.astype(np.int) + print(n) + + d = np.sqrt(len(data)) + while \ + np.max((mixed/d).astype(np.int)) != np.max((mixed/d).astype(np.int16)) \ + or np.min((mixed/d).astype(np.int)) != np.min((mixed/d).astype(np.int16)): + d = d + 2 + mixed = (mixed/d).astype(np.int16) + nmt.np2sound(mixed, output) + + +if __name__ == '__main__': + n = int(sys.argv[1]) + seq = random(n) + duration = float(sys.argv[2]) + 10 + tracks = int(sys.argv[3]) + if len(sys.argv) > 1: + tracks = int(sys.argv[1]) + else: + tracks = 42 + data = get_gongs(seq, {}, duration, tracks) + mix_gongs(data, output) + with open(output + '.json', 'w') as fd: + json.dump(result, fd, indent=4, sort_keys=True) diff --git a/render_mlt.py b/render_mlt.py index 1f510a4..d182675 100755 --- a/render_mlt.py +++ b/render_mlt.py @@ -50,8 +50,7 @@ def add_clip(playlist, clip, in_, duration): tractor.plant_filter(volume) playlist.append(tractor) -def add_audio_clip(playlist, file_, duration): - in_ = 0 +def add_audio_clip(playlist, file_, duration, in_=0): if not isinstance(file_, str): file_ = file_.encode('utf-8') clip = mlt.Producer(profile, file_) @@ -109,6 +108,7 @@ target_vocals = source.replace('.json', '.vocals.xml') target_music = source.replace('.json', '.music.xml') target_drones = source.replace('.json', '.drones.xml') target_source = source.replace('.json', '.source.xml') +gongs_wav = source.replace('.json', '.gongs.wav') with open(source) as fd: data = json.load(fd) @@ -119,12 +119,14 @@ music = mlt.Playlist() vocals = mlt.Playlist() drones0 = mlt.Playlist() drones1 = mlt.Playlist() +gongs = mlt.Playlist() # hide Set to 1 to hide the video (make it an audio-only track), # 2 to hide the audio (make it a video-only track), # or 3 to hide audio and video (hidden track). drones0.set("hide", 1) drones1.set("hide", 1) +gongs.set("hide", 1) vocals.set("hide", 1) music.set("hide", 1) @@ -194,8 +196,25 @@ save_xml(drones, target_drones) save_xml(music, target_music) +# render gongs +subprocess.call([ + # offset in pi, duration, number of tracks, target + './render_gongs.py', + str(data['gongs']['offset']), + str(duration), + str(data['gongs']['tracks']), + gongs_wav +]) +# load gongs +add_audio_clip(gongs, gongs_wav, int(duration * fps), int(5*fps)) + + # mix drones + music -mtractor = mix_audio_tracks(drones, music, 0.3) +#mtractor = mix_audio_tracks(drones, music, 0.3) + +# mix gongs + music +mtractor = mix_audio_tracks(gongs, music, 0.3) + norm = mlt.Filter(profile, "volume") # lower volume norm.set("gain", "-12dB")