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

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
о, чего словил:
Код:
---------------------------
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. ацессор удалял при каждой итерации, а добавлял в конструкторе...
 
Последнее редактирование:

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
так, все, кастую в тему @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

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
@Slick, да чет нихрена не выходит...
увеличиваю точность попадания по стыкам (и это еще без учета формы волны), и перестает детектироваь вообще.
А с точностью до +- километр и смысла нет...
 

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
@@Michael, а я разобрался. Кой-чего не так обозвал, и индекс сэмпла неправильно вычислялся.
Ща все норм :)
Добавлю окошко для проверки сходимости, rms-окно для вычленения паттернов и покажу.
Просто это предполагается как пакет (чтоб настройки хранить и экспортировать сразу для нескольких скриптов), поэтому и выложу позже, и написать все надо самому
 
  • Like
Реакции: Aleksandr Oleynik

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
пока без rms-окна, но поскольку уже и так время поиска получается достаточно приличное, думаю, сделать выбор: по "точной подгонке", или по rms + кроссфейд перехода.
Ну и это... тут только алгоритм поиска пока, автоматиацию нарезки позже сделаю.
Ищет точки с начала и с конца, где все каналы на одинаковом значении,
проверяет, достаточно ли коррелируют 100 сэмплов назад от этих точек (грубо говоря, похожая волна или нет), нет - ищет новую точку
 
  • Like
Реакции: CerberPic

EUGEN27771

Well-Known Member
23 Апр 2010
2.289
1.969
113
@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]
но поскольку уже и так время поиска получается достаточно приличное
Ага... Значит не зря я долго писал.
 

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
@EUGEN27771, спасибо! очень полезное дело!
В питоне, думаю, надо будет в перспективе все расчеты на numpy перевести (он все преобразует в immutable и хреначит на голом С).
Чет никак не могу побороть свою лень и начать писать на lua... Просто в библиотеке питона уже боль-менее разобрался, а в lua опять ночами читать... Здесь лень списываю на достаточно специфическую задачу, ради которой юзер и может привязать к риперу интерпретатор ;)
 

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
вроде, работает)
Правда, в конце хотел похвастаться, что rms отлично находит щипки у домры, хоть и может не попать по форме волны. но что-то пошло не так... Видно, надо окошко для усреднения уменьшить, сейчас 250 сэмплов
Надо забацать гибрид, с очень щадящим режимом по форме волны + настраиваемым rms

 
  • Like
Реакции: Slick и Aleksandr Oleynik

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
чет не могу найти, как экшн запустить... Раньше же где-то откапывал..
[DOUBLEPOST=1527439774][/DOUBLEPOST]шит. OnCommand же. А насколько опасно использовать экшны SWS по id?
[DOUBLEPOST=1527439862][/DOUBLEPOST]и это нашел: RPR_NamedCommandLookup( command_name )
 
  • Like
Реакции: Aleksandr Oleynik

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
А объясните, пожалуйста:
Код:
( 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. Просто. Работает.
 
Последнее редактирование:

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
Едем дальше: как получить 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
 
Последнее редактирование:

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
короче, либо неправильно считаю децибелы, либо время.
Коррекция, вроде уже адекватна значениям и времени полученным...
2018-05-28_17-16-06.png
где ж подстава...
 

EUGEN27771

Well-Known Member
23 Апр 2010
2.289
1.969
113
С лупами вроде бы другая задача(или я невнимательно посмотрел)...
По поводу компрессора - это действительно компрессор в буквальном смысле.
Алгоритм очень прост, один в один соответствует ReaComp. Eel-модули можно использовать в скриптах и jsfx одновременно. Если модуль подключить к JS - при одинаковых настройках результаты в противофазе складываются в минус бесконечность. При условии, что в ReaComp установлен режим classic, не используется фильтр и колено уст. в ноль. Сделать точно такой же фильтр, как в ReaComp сложно и в общем-то нет смысла, ибо неизвестно, какой фильтр "лучше". Soft knee по-нормальному не доделал, потому что надоело, ф-я поджирала ресурсы, для скрипта это ощутимо.
В скрипте, в основном, все сохранено, если установить интервал между точками равным одному-нескольким семплам и использовать моно-файлы - будет практически точно так же. Возможность задавать такой маленький интервал я намеренно убрал, как и анализ каждого канала по отдельности, в скрипте используется сумма, усредненное значение. Тут приходится искать компромиссы. Логика такая - в 90% случаев исходный файл либо моно, либо стерео, в котором оба канала практически одинаковы.
Пусть даже многоканальное аудио. Во-первых - вероятность кардинального разброса мала, во-вторых - компрессия по среднему значению ничем не хуже компрессии по максимальному - она просто другая. Пример - в варианте по макс. значению - резкий скачок в одном из каналов может угробить звук во всех остальных. Есть предположение, что это вообще оптимальный вариант.
То же самое относится и к интервалу между точками. Тут тоже сложно сказать. Стандартный компрессор строит огибающую учитывая каждый семпл, это правило полностью соблюдается, огибающая внутри скрипта строится точно также, учитываются все семплы. Стандартный компрессор устанавливает выходной уровень каждого семпла согласно текущему уровню огибающей, в зависимости от настроек - сама огибающая может изменяться в сторону роста/спада с разной скоростью, но в любом случае моментально влияет на выходной уровень. В скрипте ситуация немного другая - каждая точка на кривой громкости может быть установлена не ранее, чем через заданный промежуток времени, если ставить точки на каждый семпл - будет абсолютно такая же огибающая, как внутри компрессора. Но это полная крайность и крайняя глупость, возможно, но только для тестов, на практике это дико нерационально и бессмысленно.
В случае компрессора с "нормальными" значениями атаки/релиза(больше, чем несколько семплов) - скорость изменения огибающей(внутри прибора) не может быть очень высокой даже при резких перепадах входного сигнала. При этом, не забываем, что сигнал реальный(думаю, искусственно можно создать сигнал, который через фирмовые компрессоры пройдет вопреки настройкам), и значения атаки/релиза свойственные компрессору, а не лимитеру и тп. Наибольшая скорость, вероятно, нарастание на резких атаках у высоких звуков, что-то такое... Поскольку внутри кода я имею такую же огибающую, как в обычном компрессоре - как только она выходит за порог - откатываю назад и ставлю "открывающую" точку на кривой, то есть ловлю атаку и считаю от нее. После этого точки идут согласно указанному в настройках интервалу, через некоторое кол-во семплов, но значение равно значению реальной огибающей на тек. момент. Это значит, что на коротком интервале между точками кривой семплы реальной огибающей могут гулять в плюс/минус, но итоговая кривая на айтеме никогда не уйдет от реальной огибающей, и чем меньше интервал между точками кривой - тем меньше отклонения. Грубо говоря - кривая на айтеме полностью соблюдает общую тенденцию, но меньше уходит в детали, интервал меду точками кривой можно расценивать как смягчение огибающей. Когда огибающая падает ниже порога - ставлю "закрывающую" точку. Короче, точки не плодятся бессмысленно.
Почему ориентир на ReaComp - в тот момент не было аргументов на вопрос "что это такое? почему это компрессор?..."
 
Последнее редактирование:
  • Like
Реакции: PianoIst

Aleksandr Oleynik

Well-Known Member
16 Янв 2007
18.493
10.547
113
57
Киев
forum.videoediting.ru
@Archchie, логика данной функции очень проста - какое бы кол-во треков вы не выделили и в каком порядке, она их всех перенесёт ПОД трек указанный в функции в первой переменной один за другим, вторая переменная может делать этот указанный трек папкой для перенесенных. Вот и всё. Ни как иначе её заставить работать не получится.
 
Последнее редактирование:

CerberPic

Member
17 Фев 2017
69
22
8
30
@Archchie, ну так подсчёт же производится до перемещения. Следовательно, если трек 1 мы куда-нибудь переместим, то трек 2 уже не будет треком 2, а станет треком 1.
 

Archchie

Well-Known Member
24 Окт 2017
795
440
63
она их всех перенесёт ПОД трек указанный в функции
Все разобрался ! Она просто работает как готовый экшен 'то есть сначала ловит трек который мы указали , а потом под него перемещает ',(На ваше "ПОД" большими буквами не обратил внимания),а я думал- что какой номер мы указали под тот номер и должно переместится(что было бы удобнее). Трек у меня был подписан и покрашен только один выделенный ,а остальные просто новые и не понятно было что происходит.
 
Последнее редактирование:
  • Like
Реакции: Aleksandr Oleynik

Aleksandr Oleynik

Well-Known Member
16 Янв 2007
18.493
10.547
113
57
Киев
forum.videoediting.ru
@Archchie, ну так подсчёт же производится до перемещения. Следовательно, если трек 1 мы куда-нибудь переместим, то трек 2 уже не будет треком 2, а станет треком 1.
Это какраз значения не имеет. Функция ВСЕ выделенные треки переносит СРАЗУ, а не в лупе.
 
  • Like
Реакции: CerberPic

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
а можно скриптом добавлять скрипты в экшны?
Я просто думаю в комбайн встроить немного модифицированные скрипты сообщества, которые бы сохраняли настройки в проект.
Естессно со всеми упоминаниями на главной странице.
Просто, взять тот же шикарный енвелоп-компрессор Жени, который позволяет делать неразрывные лупы с компрессией. Но даже для двух разных итемов приходится дважды щелкать: по скрипту и по пресету. А когда их, скажем, 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 тогда не вытащить. Или вытащить?
 

Archchie

Well-Known Member
24 Окт 2017
795
440
63
а можно скриптом добавлять скрипты в экшны?
Так же интересует этот вопрос ,
с недели две назад спрашивал(просил) у @Michael-a в личку сделать такой скрипт, если возможно,
но к сожалению ответа так и не дождался.
 

@Michael

Well-Known Member
14 Дек 2010
899
1.359
93
Орёл / Москва
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
Реакции: Archchie и PianoIst

Archchie

Well-Known Member
24 Окт 2017
795
440
63
@@Michael, эта функция добавляет по имени, а подскажи пожалуйста как просканировать папку что бы автоматически получить имя каждого скрипта в папке
 

PianoIst

Well-Known Member
19 Май 2010
3.207
2.688
113
25
Новосибирск
soundcloud.com
@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
Реакции: Archchie

@Michael

Well-Known Member
14 Дек 2010
899
1.359
93
Орёл / Москва
  • Like
Реакции: Archchie и PianoIst

Aleksandr Oleynik

Well-Known Member
16 Янв 2007
18.493
10.547
113
57
Киев
forum.videoediting.ru
Не могу найти, а как будто было -
Как запомнить размер и положение GUI, чтоб при следующем открытии оно откравалось с этим размером и положением?
 

Сейчас онлайн (Пользователей: 0, Гостей: 1)