ReaScripts (скрипты для Reaper) - Учимся создавать!!!

о, чего словил:
Код:
---------------------------
Microsoft Visual C++ Runtime Library
---------------------------
Runtime Error!

Program: C:\Program Files\REAPER (x64)\reaper.exe

R6025

- pure virtual function call


---------------------------
ОК 
---------------------------

Делал объектный вариант Жениного скрипта для работы с сэмплами:

Код:
def find_loop(item, search_samples, precision):
    item = Item(item, block_size=search_samples)
    samples = item.samples.get_all()


class Item:

    def __init__(self, item, use_ts=True, block_size=1024):
        self.item = item
        self.take = RPR_GetActiveTake(self.item)
        self.playrate = RPR_GetMediaItemTakeInfo_Value(self.take, "D_PLAYRATE")
        self.PCM_source = RPR_GetMediaItemTake_Source(self.take)
        self.samplerate = RPR_GetMediaSourceSampleRate(self.PCM_source)

        self._start = RPR_GetMediaItemInfo_Value(self.item, "D_POSITION")
        self._len = RPR_GetMediaItemInfo_Value(self.item, "D_LENGTH")
        self._get_range(use_ts)

        self.samples = ItemSamples(self, block_size)

    def _get_range(self, use_ts):
        sel_start = sel_end = None
        if use_ts:
            loop_tr = RPR_GetSet_LoopTimeRange(0, 0, 0, 0, 0)
            sel_start, sel_end = loop_tr[2], loop_tr[3]
        if not sel_start or sel_end == sel_start:
            sel_start = self._start
            sel_end = self._start + self._len

        sel_start = max(sel_start, self._start)
        sel_end = min(sel_end, self._start + self._len)
        if sel_end - sel_start < 0:
            RPR_ShowMessageBox(
                "Time selection out of item range!", "Note", 0)

        self.open()

        self.start = (sel_start - self._start) * self.playrate
        self.len = (sel_end - sel_start) * self.playrate
        self.end = self._start + self._len
        self.len_spls = int(self._len * self.samplerate)

        self.close()

    def open(self):
        if self.playrate != 1:
            RPR_SetMediaItemTakeInfo_Value(self.take, "D_PLAYRATE", 1)
            RPR_SetMediaItemInfo_Value(
                self.item, "D_LENGTH", self._len * self.playrate)

    def close(self):
        if self.playrate != 1:
            RPR_SetMediaItemTakeInfo_Value(
                self.take, "D_PLAYRATE", self.playrate)
            RPR_SetMediaItemInfo_Value(self.item, "D_LENGTH", self._len)

        RPR_UpdateTimeline()


class ItemSamples:

    def __init__(self, item, block_size=1024):
        self.channels_amount = RPR_GetMediaSourceNumChannels(item.PCM_source)
        self.audio = RPR_CreateTakeAudioAccessor(item.take)

        self._block_size = block_size
        self._n_blocks = int(item.len_spls / block_size)
        self._extra_spls = item.len_spls - block_size * self._n_blocks
        self._buf_preset = list([0] * block_size * self.channels_amount)
        self.item = item

    def get_start_time(self, sample_offset):
        self.item.open()
        if sample_offset < 0:
            retval = self.item.end - (
                (sample_offset * self.channels_amount) / self.item.samplerate)
            self.item.close()
            return retval
        retval = self.item.start + (
            (sample_offset * self.channels_amount) / self.item.samplerate)
        self.item.close()
        return retval

    def get_block(self, block=False, sample=False):
        if block is False and sample is False:
            raise Exception('block or sample offset has to be specified.\
                block = %s, sample = %s' % (block, sample))
        if block is not False:
            block_size = self._block_size
            start = self.get_start_time(block * block_size)
            start_in_spls = block * block_size
            if block == self._n_blocks:
                block_size = self._extra_spls
        if sample is not False:
            if block:
                raise Exception(
                    'only block, or sample can be assigned at time')
            if self.item.end - sample < block_size:
                block_size = self.item.end - sample
            start = self.get_start_time(sample)
            start_in_spls = sample

        self.item.open()
        samplebuffer = self._buf_preset

        RPR_GetAudioAccessorSamples(self.audio, self.item.samplerate,
                                    self.channels_amount, start,
                                    block_size, samplebuffer)

        samples = list()
        for sample in range(block_size):
            channels = list()
            for channel in range(self.channels_amount):
                pos = sample * self.channels_amount + channel
                channels.append(samplebuffer[pos])
            idx = start_in_spls + sample
            samples.append(Sample(idx, channels))

        RPR_DestroyAudioAccessor(self.audio)
        self.item.close()
        return samples

    def get_all(self):
        samples = list()
        for block in range(self._n_blocks):
            samples.append(self.get_block(block=block))
        return samples


class Sample:

    def __init__(self, idx, channels):
        self.idx = idx
        self.channels_amount = len(channels)
        self.channels = tuple(channels)

причем, простой копипаст с "переводом" на питон работает отлично. Что я тут могу не оттуда запускать?

P.S. ацессор удалял при каждой итерации, а добавлял в конструкторе...
 
Последнее редактирование:
так, все, кастую в тему @EUGEN27771 !

я стопудово нахожу правильные индексы одинаковых сэмплов, во:
2018-05-26_02-55-23.png

если сдвинуть на ~10 сэмплов, получается вот:
2018-05-26_02-57-34.png


Я просто время как-то не так конвертирую (вырезка из кода):
Код:
def find_loop(item, search_samples, precision):
    item = Item(item, block_size=search_samples)
    pr('length in samples: %s' % item.len_spls)
    samples = item.samples

    start_buf = samples.get_block(block=0)
    end_buf = samples.get_block(sample=-search_samples)

    for sample in range(search_samples):
        start_spl = start_buf[sample]
        end_spl = end_buf[sample]
        loop = CheckLoop(start_spl, end_spl, precision)
        if loop.check is True:
            time1 = item.get_time_from_sample_idx(loop.idx[0])
            time2 = item.get_time_from_sample_idx(loop.idx[1])
            pr('values %s, %s' % (loop.start_spl_val, loop.end_spl_val))
            pr('time:')
            return time1, time2
class Item:

    def __init__(self, item, use_ts=True, block_size=1024):
        self.item = item
        self.take = RPR_GetActiveTake(self.item)
        self.playrate = RPR_GetMediaItemTakeInfo_Value(self.take, "D_PLAYRATE")
        self.PCM_source = RPR_GetMediaItemTake_Source(self.take)
        self.samplerate = RPR_GetMediaSourceSampleRate(self.PCM_source)

        self._start = RPR_GetMediaItemInfo_Value(self.item, "D_POSITION")
        self._len = RPR_GetMediaItemInfo_Value(self.item, "D_LENGTH")
        self._get_range(use_ts)

        self.samples = ItemSamples(self, block_size)

def get_time_from_sample_idx(self, idx):
        return self.start + idx / self.samplerate * self.playrate
[DOUBLEPOST=1527278426][/DOUBLEPOST]но вообще меня беспокоит, что на таком длинном фрагменте он начинает находить лупы только при растягивании окна поиска почти на весь отрезок...
В прошлом варианте находил больше одинаковых сэмплов.

P.S. не, время, вроде правильно находит. Значит заматчить не может с нужной точностью...
Вообще, сэмплы эти орагнные, оказывается, очень противные. Ни zero-ceossing не работает, ни endlesswav...
Надо какой-то убер-способ придумывать.
Попробую ща поискать на чем-то еще
 
Последнее редактирование:
  • Like
Реакции: Slick
@Slick, да чет нихрена не выходит...
увеличиваю точность попадания по стыкам (и это еще без учета формы волны), и перестает детектироваь вообще.
А с точностью до +- километр и смысла нет...
 
@@Michael, а я разобрался. Кой-чего не так обозвал, и индекс сэмпла неправильно вычислялся.
Ща все норм :)
Добавлю окошко для проверки сходимости, rms-окно для вычленения паттернов и покажу.
Просто это предполагается как пакет (чтоб настройки хранить и экспортировать сразу для нескольких скриптов), поэтому и выложу позже, и написать все надо самому
 
  • Like
Реакции: Aleksandr Oleynik
пока без rms-окна, но поскольку уже и так время поиска получается достаточно приличное, думаю, сделать выбор: по "точной подгонке", или по rms + кроссфейд перехода.
Ну и это... тут только алгоритм поиска пока, автоматиацию нарезки позже сделаю.
Ищет точки с начала и с конца, где все каналы на одинаковом значении,
проверяет, достаточно ли коррелируют 100 сэмплов назад от этих точек (грубо говоря, похожая волна или нет), нет - ищет новую точку
 
  • Like
Реакции: CerberPic
@PianoIst,
---------------------
Этот код изначально был на EEL, кто-то переложил на Lua.
В EEL можно за раз получить не более 65536 семплов, поэтому время делилось на блоки по 65536. То есть, получается какое-то кол-во полных блоков по 65536.
И один неполный блок - остаток.
Если playrate = 1 - акцессор работает в разы быстрее, и в Lua, и в EEL. Поэтому в начале такая манипуляция с playrate, сброс, а в конце - восстановление.
Семплы, конечно же, можно получить начиная с любой точки
По поводу акцессора, без привязки к конкретной задаче.
Если делать на Lua - можно учесть некоторые моменты - про Питон не скажу, наверное, в чем-то похоже.
В Lua можно за один раз взять очень много семплов, хотя кол-во все равно ограничено - не помню точно, 2^21 - 1 или 2^22 - 1 .
Если аудио длинное - тоже нужно делать частями. Однако блоки можно сделать большими.
Есть еще несколько нюансов по производительности - это важно, когда нужно перебрать миллионы или десятки миллионов семплов.
В Lua итерация сама по себе медленная, а итерация по reaper.array еще намного медленнее, чем по родной таблице.
Из reaper.array нужно сделать таблицу(есть ф-я в API), и идти уже по таблице, это ускорит процесс.
Локальные ф-и в Lua работают на 30-50% быстрее глобальных.
Если некая ф-я вызывается пару миллионов раз - есть смысл сделать ее локальной - относится не только к этой теме, а вообще.
То есть, тупо написав, например, в начале кода или прямо перед циклом на 100500 итераций:
local abs = math.abs; local sin = math.sin; -- получаем + 30% - практически на шару ("Lua Performance Tips." - тут же есть и другие рекомендации).
Можно подкрутить сборщик мусора, тоже немного помогает, что конкретно - не помню.
Сделав все эти манипуляции можно получить прирост производительности в 2-3 раза, хотя eel в любом случае будет на порядок быстрее.
К некоторым задачам можно подобрать альтернативные решения. В одном скрипте нужно было находить участки тишины, для каждого трека в проекте.
То есть, для всех всех айтемов на каждом треке, все аудио, с учетом пересечений, перекрытий и тп, а потом еще и миди добавилось. Что-то типа глобального гейта.
Единственное - особая точность по времени не требовалась, что и помогло. Через getpeaks этот номер прокатил, даже точность регулируется, проекты с кучей аудио анализируются практически незаметно(кто использовали ничего не сказали - значит, не заметили;)).
[DOUBLEPOST=1527358924][/DOUBLEPOST]
но поскольку уже и так время поиска получается достаточно приличное
Ага... Значит не зря я долго писал.
 
@EUGEN27771, спасибо! очень полезное дело!
В питоне, думаю, надо будет в перспективе все расчеты на numpy перевести (он все преобразует в immutable и хреначит на голом С).
Чет никак не могу побороть свою лень и начать писать на lua... Просто в библиотеке питона уже боль-менее разобрался, а в lua опять ночами читать... Здесь лень списываю на достаточно специфическую задачу, ради которой юзер и может привязать к риперу интерпретатор ;)
 
вроде, работает)
Правда, в конце хотел похвастаться, что rms отлично находит щипки у домры, хоть и может не попать по форме волны. но что-то пошло не так... Видно, надо окошко для усреднения уменьшить, сейчас 250 сэмплов
Надо забацать гибрид, с очень щадящим режимом по форме волны + настраиваемым rms

 
  • Like
Реакции: Slick и Aleksandr Oleynik
чет не могу найти, как экшн запустить... Раньше же где-то откапывал..
[DOUBLEPOST=1527439774][/DOUBLEPOST]шит. OnCommand же. А насколько опасно использовать экшны SWS по id?
[DOUBLEPOST=1527439862][/DOUBLEPOST]и это нашел: RPR_NamedCommandLookup( command_name )
 
  • Like
Реакции: Aleksandr Oleynik
А объясните, пожалуйста:
Код:
( retval, proj, extname, key, valOutNeedBig, valOutNeedBig_sz ) = RPR_GetProjExtState(proj, extname, key, valOutNeedBig, valOutNeedBig_sz )
что такое valOutNeedBig, valOutNeedBig_sz
насколько я понимаю, первое - это собственно дата, которую сохраняли
retval, это, видимо, успешность операции
а valOutNeedBig_sz?
Или я совсем неправильно думаю?
[DOUBLEPOST=1527442748][/DOUBLEPOST]чет не въезжаю я...
Код:
from reaper_python import *
from misc import pr
import pickle

obj = {"one": 123, "two": [1, 2, 3]}
output = pickle.dumps(obj, 2)
pr(output)

RPR_SetProjExtState(0, 'Levitanus_sample_editing_pack', 'test', output)

valOutNeedBig = 0
valOutNeedBig_sz = 0
retval = RPR_GetProjExtState(0, 'Levitanus_sample_editing_pack', 'test',
                             valOutNeedBig, valOutNeedBig_sz)
pr(retval)
pr(valOutNeedBig)
pr(valOutNeedBig_sz)

вывод:
Код:
b'\x80\x02}q\x00(X\x03\x00\x00\x00oneq\x01K{X\x03\x00\x00\x00twoq\x02]q\x03(K\x01K\x02K\x03eu.'
(1, 0, 'Levitanus_sample_editing_pack', 'test', '0', 0)
0
0
вопрос 1: можно ли так байты паковать?
вопрос2: почему не могу распаковать?

Ответ:
потому что пикл - плохой вариант из-за кодировки стрингов рипера.
Хороший вариант - Json в ASCII. Просто. Работает.
 
Последнее редактирование:
Едем дальше: как получить dB из значения сэмпла?
Или это только через get peaks?

UPD: немного неточно, но думаю, хватит:

Код:
item = Item(item, block_size=1)
sample = item.samples.get_block(block=0)
peak = sample[0].channels[0]
peak = abs(peak)
pr(peak)
peak = 20 * math.log10(peak)
pr(peak)

сэмпл:
2018-05-28_14-29-44.png
2018-05-28_14-31-20.png


вывод:
Код:
0.035736083984375
-28.937860797747096
 
Последнее редактирование:
короче, либо неправильно считаю децибелы, либо время.
Коррекция, вроде уже адекватна значениям и времени полученным...
2018-05-28_17-16-06.png
где ж подстава...
 
С лупами вроде бы другая задача(или я невнимательно посмотрел)...
По поводу компрессора - это действительно компрессор в буквальном смысле.
Алгоритм очень прост, один в один соответствует ReaComp. Eel-модули можно использовать в скриптах и jsfx одновременно. Если модуль подключить к JS - при одинаковых настройках результаты в противофазе складываются в минус бесконечность. При условии, что в ReaComp установлен режим classic, не используется фильтр и колено уст. в ноль. Сделать точно такой же фильтр, как в ReaComp сложно и в общем-то нет смысла, ибо неизвестно, какой фильтр "лучше". Soft knee по-нормальному не доделал, потому что надоело, ф-я поджирала ресурсы, для скрипта это ощутимо.
В скрипте, в основном, все сохранено, если установить интервал между точками равным одному-нескольким семплам и использовать моно-файлы - будет практически точно так же. Возможность задавать такой маленький интервал я намеренно убрал, как и анализ каждого канала по отдельности, в скрипте используется сумма, усредненное значение. Тут приходится искать компромиссы. Логика такая - в 90% случаев исходный файл либо моно, либо стерео, в котором оба канала практически одинаковы.
Пусть даже многоканальное аудио. Во-первых - вероятность кардинального разброса мала, во-вторых - компрессия по среднему значению ничем не хуже компрессии по максимальному - она просто другая. Пример - в варианте по макс. значению - резкий скачок в одном из каналов может угробить звук во всех остальных. Есть предположение, что это вообще оптимальный вариант.
То же самое относится и к интервалу между точками. Тут тоже сложно сказать. Стандартный компрессор строит огибающую учитывая каждый семпл, это правило полностью соблюдается, огибающая внутри скрипта строится точно также, учитываются все семплы. Стандартный компрессор устанавливает выходной уровень каждого семпла согласно текущему уровню огибающей, в зависимости от настроек - сама огибающая может изменяться в сторону роста/спада с разной скоростью, но в любом случае моментально влияет на выходной уровень. В скрипте ситуация немного другая - каждая точка на кривой громкости может быть установлена не ранее, чем через заданный промежуток времени, если ставить точки на каждый семпл - будет абсолютно такая же огибающая, как внутри компрессора. Но это полная крайность и крайняя глупость, возможно, но только для тестов, на практике это дико нерационально и бессмысленно.
В случае компрессора с "нормальными" значениями атаки/релиза(больше, чем несколько семплов) - скорость изменения огибающей(внутри прибора) не может быть очень высокой даже при резких перепадах входного сигнала. При этом, не забываем, что сигнал реальный(думаю, искусственно можно создать сигнал, который через фирмовые компрессоры пройдет вопреки настройкам), и значения атаки/релиза свойственные компрессору, а не лимитеру и тп. Наибольшая скорость, вероятно, нарастание на резких атаках у высоких звуков, что-то такое... Поскольку внутри кода я имею такую же огибающую, как в обычном компрессоре - как только она выходит за порог - откатываю назад и ставлю "открывающую" точку на кривой, то есть ловлю атаку и считаю от нее. После этого точки идут согласно указанному в настройках интервалу, через некоторое кол-во семплов, но значение равно значению реальной огибающей на тек. момент. Это значит, что на коротком интервале между точками кривой семплы реальной огибающей могут гулять в плюс/минус, но итоговая кривая на айтеме никогда не уйдет от реальной огибающей, и чем меньше интервал между точками кривой - тем меньше отклонения. Грубо говоря - кривая на айтеме полностью соблюдает общую тенденцию, но меньше уходит в детали, интервал меду точками кривой можно расценивать как смягчение огибающей. Когда огибающая падает ниже порога - ставлю "закрывающую" точку. Короче, точки не плодятся бессмысленно.
Почему ориентир на ReaComp - в тот момент не было аргументов на вопрос "что это такое? почему это компрессор?..."
 
Последнее редактирование:
  • Like
Реакции: PianoIst
@Archchie, логика данной функции очень проста - какое бы кол-во треков вы не выделили и в каком порядке, она их всех перенесёт ПОД трек указанный в функции в первой переменной один за другим, вторая переменная может делать этот указанный трек папкой для перенесенных. Вот и всё. Ни как иначе её заставить работать не получится.
 
Последнее редактирование:
@Archchie, ну так подсчёт же производится до перемещения. Следовательно, если трек 1 мы куда-нибудь переместим, то трек 2 уже не будет треком 2, а станет треком 1.
 
она их всех перенесёт ПОД трек указанный в функции
Все разобрался ! Она просто работает как готовый экшен 'то есть сначала ловит трек который мы указали , а потом под него перемещает ',(На ваше "ПОД" большими буквами не обратил внимания),а я думал- что какой номер мы указали под тот номер и должно переместится(что было бы удобнее). Трек у меня был подписан и покрашен только один выделенный ,а остальные просто новые и не понятно было что происходит.
 
Последнее редактирование:
  • Like
Реакции: Aleksandr Oleynik
@Archchie, ну так подсчёт же производится до перемещения. Следовательно, если трек 1 мы куда-нибудь переместим, то трек 2 уже не будет треком 2, а станет треком 1.
Это какраз значения не имеет. Функция ВСЕ выделенные треки переносит СРАЗУ, а не в лупе.
 
  • Like
Реакции: CerberPic
а можно скриптом добавлять скрипты в экшны?
Я просто думаю в комбайн встроить немного модифицированные скрипты сообщества, которые бы сохраняли настройки в проект.
Естессно со всеми упоминаниями на главной странице.
Просто, взять тот же шикарный енвелоп-компрессор Жени, который позволяет делать неразрывные лупы с компрессией. Но даже для двух разных итемов приходится дважды щелкать: по скрипту и по пресету. А когда их, скажем, 10 000 :D
Идея в том, чтобы делать "разметку" маркерами, а потом в один проход все резать скриптом. Короче, объяснился, чтоб вопросов не было.

Теперь к задаче: В папке с пакетом лежат чуть модифицированные скрипты, типа:
Код:
//-- mainloop -----------Женин компрессор---------------
function mainloop()
local(preset_data)
(
  GetMouseState(); // Get mouse
  Draw_Controls();
  Presets();
  preset_data = GetPreset();
  RPR_SetProjExtState(0, 'Levitanus_sample_editing_pack', "gen_ebc", preset_data)

  RunMain ? (
    MAIN();
    RunMain = 0;
    RunMain_cnt+=1; // Change cnt
  );

  SetMouseLastState(); // Update mouse last state
  gfx_update(); // Update gfx window
  gfx_char = gfx_getchar();
  //gfx_char==68 || gfx_char== 194 ? DevMode = !DevMode; // -- Shift + D - don't use!!!
  gfx_char==32 ? Main_OnCommand(40044, 0); //-- play
  gfx_char >= 0 ? defer("mainloop();");    //-- defer
);
Как бы их так запускать. Есть луа, есть eel. Не заставлять же добавлять руками все это добро в экшны. Да и Id тогда не вытащить. Или вытащить?
 
а можно скриптом добавлять скрипты в экшны?
Так же интересует этот вопрос ,
с недели две назад спрашивал(просил) у @Michael-a в личку сделать такой скрипт, если возможно,
но к сожалению ответа так и не дождался.
 
reaper.AddRemoveReaScript( add, sectionID, scriptfn, commit )
Add a ReaScript (return the new command ID, or 0 if failed) or remove a ReaScript (return >0 on success). Use commit==true when adding/removing a single script. When bulk adding/removing n scripts, you can optimize the n-1 first calls with commit==false and commit==true for the last call.
 
  • Like
Реакции: Archie's и PianoIst
@@Michael, эта функция добавляет по имени, а подскажи пожалуйста как просканировать папку что бы автоматически получить имя каждого скрипта в папке
 
@Archchie, на луа пишете?
https://stackoverflow.com/questions/5303174/how-to-get-list-of-directories-in-lua
На eel искать не в один поисковый апрос, но тоже можно постараться.
На питоне тоже один поисковый запрос
[DOUBLEPOST=1527775651][/DOUBLEPOST]фрагмент из Lokasenna_GUI 2
Код:
-- I hate working with 'requires', so I've opted to do it this way.
-- This also works much more easily with my Script Compiler.
local function req(file)
  
    if missing_lib then return function () end end
  
    local sep = string.match(reaper.GetOS(), "Win") and "\\" or "/"

    local ret, err = loadfile(script_path .."Lokasenna_GUI"..sep.. file)
    if not ret then
        reaper.ShowMessageBox("Couldn't load "..file.."\n\nError: "..tostring(err), "Library error", 0)
        missing_lib = true        
        return function () end

    else
        return ret
    end   

end

-- The Core library must be loaded prior to any classes, or the classes will throw up errors
-- when they look for functions that aren't there.
local sep = string.match(reaper.GetOS(), "Win") and "\\" or "/"
[DOUBLEPOST=1527775684][/DOUBLEPOST]упс, я его там уже немного нафаршировал. Но думаю, разберешься
 
Последнее редактирование:
  • Like
Реакции: Archie's
  • Like
Реакции: Archie's и PianoIst
Не могу найти, а как будто было -
Как запомнить размер и положение GUI, чтоб при следующем открытии оно откравалось с этим размером и положением?
 

Сейчас просматривают