Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Программирование звука > Создание прямоугольных импульсов разной длительности


Автор: Alexei 14.08.13, 09:28
Хочу сделать подобное, только на морально устаревающем смартфоне с винмобаил 6.1.
Надо получить циклическую последовательность импульсов 1-3мС с дискретностью 0.1мС и, с возможностью независимого их изменения.
Возможно,надо заранее создать 10 байтовых массивов и подключать нужный.Может кто подскажет, как подобное реализовать с минимальной загрузкой цпу.
Надеюсь,сама идея кого-нибудь заинтересует, т.к. телефоны имеют камеры ,вай-фай,жпс,акселерометр, и совсей этой :) можно попытаться взлететь , ну или хотябы поползти...

ПС В "железе" я разбираюсь чуть лучше и возможна кооперация.

Автор: Prince 14.08.13, 20:13
Цитата
Надо получить циклическую последовательность импульсов 1-3мС с дискретностью 0.1мС

Ничего непонятно, но не суть. Протокол обмена можно потом додумать.
А вывод звука под винмобайл возможен при помощи всё тех же Waveform Audio Functions Во всяком случае, попробуйте. Начните с waveOutGetNumDevs и waveOutGetDevCaps.
Или при помощи PlaySound попробуйте "побибикать" под винмобайл.
Если фунции отработают, то и прочими из списка можно воспользоваться.

Автор: Alexei 05.09.13, 11:56
Цитата Prince @
Ничего непонятно, но не суть. Протокол обмена можно потом додумать.

Чего тут не понять? :blink: :o Стандартный модельный протокол PPM.
Каждые 20мС передается посылка , состоящая из длинного импульса 3-4мс (маркер начала)и канальных импульсов . Длительночть последних определяет положение рулей , скорость вращения моторов...
1,5мс -нейтраль , +- до 0,5 от нее -отклонение рулей в ту или другую сторону. как-то так.
Я по эксперементировал тут (нашел исходник на басике c WaveOut, подрихтовал ) Результат плачевный. :'(
Сами импульсы разной скважности получаются хорошо, но растояние между ними 40мС. :'(
Делал я так: Создал несколько 100 байтовых массивов , забил в них 0 и 255, в нужном кол-ве и поочередно их подсовывал. Частота 44100.
Это я чего-то не так делаю,бэйсик тормозит или виндоуз с такими маленькими буферами не работает?

Автор: Pavia 05.09.13, 14:07
Скорее всего железка требует буфер кратный 4096 отсюда 4096/(2*44100)~46 мс. Можно в таймере попробовать писать изменения в буфере после того как отдали его на обработку, но не факт что взлетит(заработае ).

Автор: Prince 05.09.13, 17:05
Цитата
Делал я так: Создал несколько 100 байтовых массивов , забил в них 0 и 255, в нужном кол-ве и поочередно их подсовывал. Частота 44100.

Механизм подсовывания очень важен.
Цитата
Делал я так: Создал несколько 100 байтовых массивов , забил в них 0 и 255, в нужном кол-ве и поочередно их подсовывал. Частота 44100.

Хотя бы 100 мс(а не байт). Тем паче, что девайс маломощный. 100 байт - это ну совсем ничего.
Цитата
виндоуз с такими маленькими буферами не работает?

Винда мноозадачная, 100 байт выплюнутся за 22 мс, нужен следующий буфер, а ваше приложение в отключке, потому что процессорное время уже отдано другому процессу.
100 мс буфер(или больше) и правильный механизм подсовывания - ключ к успеху.
Цитата
Чего тут не понять? Стандартный модельный протокол PPM.

Даже если б я его знал назубок, пот такому описаниюи вряд ли бы догадался. :-?

Автор: Alexei 05.09.13, 17:50
Цитата Prince @
100 байт выплюнутся за 22 мс

Я вообще то расчитывал на 2,2мС :), и осцил показывает , что так и есть.
Цитата
Хотя бы 100 мс

Это невыносимо долго. :no-sad: Есть мысль удлинить до 20мС -заполнять буфер сразу всей посылкой.
Цитата
Механизм подсовывания очень важен.

Через сообщение главному окну, кажется, я не сильно вникал. В оригинале эта программка делала синус и потрескивала. Думаю, из-за незаконченности синуса в буфере.

Автор: Prince 05.09.13, 18:02
А, да, 2мс. :) Это даже не ничто, а ничего от ничто.
Цитата
Это невыносимо долго.

Невыносимо долго ЧТО? Невыносимо долго для чего?
Цитата
Есть мысль удлинить до 20мС -заполнять буфер сразу всей посылкой.

Интуитивно догадываюсь, что вам нужно поработать над алгоримтом формирования сигнала, он не должен в идеале зависеть от длительности буфера.
Цитата
Через сообщение главному окну, кажется, я не сильно вникал. В оригинале эта программка делала синус и потрескивала. Думаю, из-за незаконченности синуса в буфере.

Не, так вы вникните, механизм подсовывания важен вообще, его реализация т.е., а не способ(окно, поток, функция) уведомления только.
Сколько в очереди буферов крутится вообще? Сколько в очереди находятся одновременно? Как происходит заполнение буфера, кто является инициатором процесса?

Автор: Prince 06.09.13, 06:25
В идеале, картина должна бы выглядеть так:
в очереди крутятся 2 буфера, длительностью 20 мс каждый, равной длине посылки.
когда буфер освобождается, происходит опрос органов управления, заполнение пустого буфера новой посылкой и постановка его в очередь.

Тогда время реакции на действия пилота было бы в районе 40 мс. А опрос органов управления и формирование посылки были бы синхронизированы.
Но такой короткий буфер, девайс, вероятно, не потянет(но всё же попробуйте).

А если длительность буфера больше длительности посылки, пусть даже кратной 20 мс, придётся разделить опрос органов управления и процесс заполнения буфера и озадачиться синхронизацией очереди буферов и очереди управляющих команд. Как такой вариант реализовать, в деталях пока без понятия. Возможно, есть вариант более красивый, но с ходу его не вижу.

Автор: Alexei 06.09.13, 12:00
Как оказалось дело не в "бабине", то есть не совсем в ней. Я сначала сделал буфер в 5000байт , и теже ~40мС остались. После чего я посмотрел работу программы этого пограмиста , там щелкают теже 40мс.
Так что , возможно , шансы есть.
Я хотел сделать так: создать 2-3 десятка 100 байтных буферов, занести в них образы импульсов разной скважности(больше- меньше разных градаций и нейтраль) , и каждый раз подсовывать нужный буфер соответствующий значению команды текущего канала.
Второй вариант : сделать два буфера длиною в полную посылку~20мС ,заранее заполненых нейтральными значениями, и пока один "играет" второй дописывать нужным колвом "0" или "1" в нужных местах .

Автор: Pavia 06.09.13, 12:23
В виндоуме работа со зауком это шаманство. Так как не отгадаешь что и как будет работать. Вот второй вариант с 2 буферами возможен. Но я бы лучше взял 3 буфера по 4096. Пока первый ишрается, третий заполняем. Второй должен быть отдаем на исполнение, до того как 1 начнёт играть. Тройной буфер нуден для того чтобы небыло барьера. Т.е. щелчков.
А размер должен быть кратен странице иначе поведение плохо предсказуемо. Про размер буфера насколько помню это оговарено в документации.

Автор: Alexei 06.09.13, 12:58
Цитата
лучше взял 3 буфера по 4096.

3 буфера будут сильно замедлять реакцию на органы управления.А щелчки , я же не слушать их буду, главное чтобы они не мешали находить среди них правльные импульсы.

Автор: Alexei 11.09.13, 07:45
Попробовал играть меандр на Си , получилось чуть лучше, но тоже щелкает.Два буфера по 5000 байт,пробовал увеличить в двое -без изменений.Сделал через CALLBACK_WINDOW
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    case MM_WOM_DONE:
            
                if (numBuff==0)
                {
                    waveOutWrite(hWaveOut, &whdr1, sizeof(whdr1));
                    numBuff=1;
                }
                else    
                {
                    numBuff=0;
                    waveOutWrite(hWaveOut, &whdr2, sizeof(whdr2));
                }
                
                return TRUE;

Может можно что-то получше?
И что быстрее через окно или функцию?

В структуре WAVEHDR есть зарезервированное lpNext , наверно указатель на структуру следуущего буфера.Думаю, это то что мне подошло БЫ. :yes-sad:

Посмотрел осциллом- теже 40мс между переключениями. :wall: :no-sad:

Автор: Prince 11.09.13, 17:37
Имхо, что-то не то или не так делаете. Выкладывайте весь кусок от waveinopen с обработкой сообщений. Код, заполняющий буфер, не нужен. Просто камент поставьте "тут записываем в такой-то буфер такие-то данные".

Цитата
Цитата
if (numBuff==0)
{
waveOutWrite(hWaveOut, &whdr1, sizeof(whdr1));
numBuff=1;
}

lParam = (LONG) lpwvhdr. Т.е., адрес заголовка и буфера у вас и так есть. numBuff не нужен, и такой способ идентификации ненадёжен.

Цитата
Посмотрел осциллом- теже 40мс между переключениями.

В смысле, пауза в 40 мс между окончанием одного буфера и началом следущего?
Создайте 5 буферов по 0.5 с, заполните их значениями непрерывной синусоиды, и поставьте в очередь.
Если разрывов не будет, значит вы накосячили, если разрывы останутся, значит, девайс косячит, но я в этом сомневаюсь.

Автор: Alexei 12.09.13, 07:38
Цитата
Выкладывайте весь кусок

Все целиком.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #define WIN32_LEAN_AND_MEAN
     
     
    #include <windows.h>
    #include <windowsx.h>
    #include <commctrl.h>
    #include <tchar.h>
    #include <mmsystem.h>
    #include <stdio.h>
    #include "main.h"
     
    #pragma comment(lib, "winmm.lib")
     
    #define UNICODE
    #define NELEMS(a)  (sizeof(a) / sizeof((a)[0]))
    #define nRate   44100
    #define nBits   8
    #define nBytesPerSample      nBits / 8
     
     
     
     
    #define nBytes     10000 //
     
    /** Prototypes **************************************************************/
     
    static INT_PTR CALLBACK MainDlgProc(HWND, UINT, WPARAM, LPARAM);
     
    /** Global variables ********************************************************/
     
    static HANDLE ghInstance;
    HWAVEOUT hWaveOut;
     
    WAVEFORMATEX wfx; // для получения описателя звукового устройства:
    WAVEHDR  whdr1;
    WAVEHDR  whdr2;
    static UCHAR    Buffer1[nBytes];
    static UCHAR    Buffer2[nBytes];
    UCHAR   numBuff ;
    /****************************************************************************
     *                                                                          *
     * Function: WinMain                                                        *
     *                                                                          *
     * Purpose : Initialize the application.  Register a window class,          *
     *           create and display the main window and enter the               *
     *           message loop.                                                  *
     *                                                                          *
     * History : Date      Reason                                               *
     *           00/00/00  Created                                              *
     *                                                                          *
     ****************************************************************************/
     
    int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
    {
        INITCOMMONCONTROLSEX icc;
        WNDCLASSEX wcx;
     
        ghInstance = hInstance;
     
        
        icc.dwSize = sizeof(icc);
        icc.dwICC = ICC_WIN95_CLASSES /*|ICC_COOL_CLASSES|ICC_DATE_CLASSES|ICC_PAGESCROLLER_CLASS|ICC_USEREX_CLASSES|... */;
        InitCommonControlsEx(&icc);
        
        
        wcx.cbSize = sizeof(wcx);
        if (!GetClassInfoEx(NULL, MAKEINTRESOURCE(32770), &wcx))
            return 0;
     
      
        wcx.hInstance = hInstance;
        wcx.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_ICO_MAIN));
        wcx.lpszClassName = _T("ppmClass");
        if (!RegisterClassEx(&wcx))
            return 0;
     
        /* The user interface is a modal dialog box */
        return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)MainDlgProc);
    }
     
    /****************************************************************************
     *                                                                          *
     * Function: MainDlgProc                                                    *
     *                                                                          *
     * Purpose : Process messages for the Main dialog.                          *
     *                                                                          *
     * History : Date      Reason                                               *
     *           00/00/00  Created                                              *
     *                                                                          *
     ****************************************************************************/
     
    static INT_PTR CALLBACK MainDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (uMsg)
        {
            case WM_INITDIALOG:
                /*
                 * TODO: Add code to initialize the dialog.
                 */
            for (UINT i = 0; i < nBytes; i++)
              {
                 if((i/100)%2)
                        Buffer1[i] = 255;
                 else
                        Buffer1[i] = 0;
             }
            for (UINT i = 0; i < nBytes; i++)
              {
                 if((i/10)%2)
                        Buffer2[i] = 255;
                 else
                        Buffer2[i] = 0;
              }
            wfx.wFormatTag = WAVE_FORMAT_PCM;   // wav-формат
            wfx.nChannels = 1; // моно - звук
            wfx.nSamplesPerSec = nRate; // 44100 Гц
            wfx.nAvgBytesPerSec = nBytesPerSample * nRate; // байт в секунду
            wfx.nBlockAlign = nBytesPerSample;
            wfx.wBitsPerSample = nBits;
            wfx.cbSize = 0; // For only WAVE_FORMAT_PCM formats, this member is ignored
     
            numBuff=0;
            
                return TRUE;
     
            case WM_SIZE:
                /*
                 * TODO: Add code to process resizing, when needed.
                 */
                return TRUE;
        case MM_WOM_DONE:
            
                if (numBuff==0)
                {
                    waveOutWrite(hWaveOut, &whdr1, sizeof(whdr1));
                    numBuff=1;
                }
                else    
                {
                    numBuff=0;
                    waveOutWrite(hWaveOut, &whdr2, sizeof(whdr2));
                }
                
            return TRUE;
     
            case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam))
                {
                    /*
                     * TODO: Add more control ID's, when needed.
                     */
              case IDSTART:
                    
                waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, (UINT)hwndDlg, 0L, CALLBACK_WINDOW);
                whdr1.lpData = (LPSTR)Buffer1;
                whdr1.dwBufferLength = nBytes; // размер буфера в байтах.
                whdr1.dwFlags = 0; // Буфер подготовлен (зафиксирован в памяти)
                whdr1.dwLoops = 0; //  без циклов, поле должно быть нулевым.
     
                whdr2.lpData = (LPSTR)Buffer2;
                whdr2.dwBufferLength = nBytes; // размер буфера в байтах.
                whdr2.dwFlags = 0; // Буфер подготовлен (зафиксирован в памяти)
                whdr2.dwLoops = 0; //  без циклов, поле должно быть нулевым.
                    
                waveOutPrepareHeader(hWaveOut, &whdr1, sizeof(WAVEHDR));
                waveOutPrepareHeader(hWaveOut, &whdr2, sizeof(WAVEHDR));
     
                waveOutWrite(hWaveOut, &whdr1, sizeof(whdr1));
                    
            
              return TRUE;
            
     
                    case IDOK:
                        EndDialog(hwndDlg, TRUE);
                
                waveOutUnprepareHeader (hWaveOut, &whdr1, sizeof(WAVEHDR));
                waveOutUnprepareHeader (hWaveOut, &whdr2, sizeof(WAVEHDR));
                waveOutReset (hWaveOut);
                waveOutClose (hWaveOut);
                        return TRUE;
                }
                break;
     
            case WM_CLOSE:
                EndDialog(hwndDlg, 0);
                return TRUE;
     
            /*
             * TODO: Add more messages, when needed.
             */
        }
     
        return FALSE;
    }

Цитата
numBuff не нужен, и такой способ идентификации ненадёжен.

Это для того чтобы решить , какой буфер играть.
Цитата
В смысле, пауза в 40 мс между окончанием одного буфера и началом следущего?

Да.
Цитата
Создайте 5 буферов по 0.5 с

Не думаю, что в этом есть смысл , я нашел здесь ,неподалеку , программку на ВБ . Она прекрасно играет без разрывов. У нее два буфера по 44100(1 сек) . Я помучил ее , она не трещит, если уменьшать до 1800 байт. Кстати, это ~40мС. :)

Автор: Prince 12.09.13, 12:19
Очередь буферов устроена по принципу FIFO.
Сначала нужно поставить в очередь все(несколько) буферы, при помощи wavoutwrite. Когда первый в очереди буфер воспроизведен, он выталкивается из очереди; начинает воспроизводиться следующий за ним, без задержки(теперь этот буфер становится первым в очереди).
Когда буфер вытолкнут из очереди, приложению посылается месседж MM_WOM_DONE, поле lparam которого содержит адрес заголовка буфера. А заголовок, в свою очередь, хранит адрес буфера в lpdata. Его(буфер) можно заполнить новой порцией данных и снова поставить в очередь(в конец очереди) при помощи wavoutwrite.

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    case IDSTART:
     
                waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, (UINT)hwndDlg, 0L, CALLBACK_WINDOW);
                //...
                waveOutPrepareHeader(hWaveOut, &whdr1, sizeof(WAVEHDR));
                waveOutPrepareHeader(hWaveOut, &whdr2, sizeof(WAVEHDR));
     
                waveOutWrite(hWaveOut, &whdr1, sizeof(whdr1));
    //сюда добавить WaveOutWrite(hWaveOut, &whdr2, sizeof(whdr2));
     
              return TRUE;


Цитата
Это для того чтобы решить , какой буфер играть.

См. выше. Какая-то специальная нумерация буферов не нужна(когда-то я тоже такие костыли изобретал). Точные "координаты" буфера в lparam обрабатываемого месседжа MM_WOM_DONE.
MM_WOM_DONE не указывает "какой буфер играть", а сообщает о том, что "такой-то буфер уже отыграл, дальше делайте с ним, что хотите".
WaveOutWrite не "начинает играть такой-то буфер", а ставит буфер в конец очереди [буферов]. Поставленный в очередь буфер начинает воспроизводиться сразу только в том случае, если очередь была пуста, и воспроизведение не остановлено при помощи WaveOutPause.

Автор: Alexei 12.09.13, 13:37
Спасибо!
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
     waveOutWrite(hWaveOut, &whdr1, sizeof(whdr1));
    //сюда добавить WaveOutWrite(hWaveOut, &whdr2, sizeof(whdr2));

Это сильно помогло! :)
Цитата
Очередь буферов устроена по принципу FIFO.
....
WaveOutWrite не "начинает играть такой-то буфер", а ставит буфер в конец очереди

А это очень интересно.

Автор: Alexei 12.09.13, 14:48
Цитата
Точные "координаты" буфера в lparam обрабатываемого месседжа MM_WOM_DONE.

А доставать lparam как-то заморочисто надо? Что то у меня не получается.

Автор: Prince 12.09.13, 15:49
static INT_PTR CALLBACK MainDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
Как оно там в си может выглядеть, я не в курсе.
А вообще, вытягиваение координат из lparam и доступ к элементам буфера(семплам) выглядит(может выглядеть) так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    var pb:pbyte:// указатель на байт, который будем использовать для доступа к содержимому буфера;
    //устанавливаем указатель на начало буфера(допустим, буфер 8 битный, один канал):
    pb:=pbyte(pwavehdr(lparam).lpdata); // приведение типов, сначала lparam к wavehdr, затем lpdata к pbyte;
    // дальше чё-то делаем с ним:
    for i:=0 to pwavehdr(lparam).dwBufferLength-1 do
    Begin
    pb:=128;// заполняем текущий семпл "тишиной"
    inc(pb)// переходим к следующему семплу
    end;

Автор: Alexei 12.09.13, 16:49
"Семен Семеныч"(с) . Это я тормозил, надо заправлять тот же буфер который только что отыграл...
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
       case MM_WOM_DONE:
            waveOutWrite(hWaveOut,(PWAVEHDR) lParam, sizeof(WAVEHDR));

Автор: Prince 13.09.13, 07:31
И предварительно заправлять несколько буферов в очередь. И позаботиться о том, чтобы после отыгрывания буфера, он был возвращен в очередь до того, как отыграет следующий за ним. Этот пункт накладывает ограничения на минимальный размер буфера.
Цитата
я нашел здесь ,неподалеку , программку на ВБ . Она прекрасно играет без разрывов. У нее два буфера по 44100(1 сек) . Я помучил ее , она не трещит, если уменьшать до 1800 байт. Кстати, это ~40мС

Постепенно уменьшая размер буфера, всё больше и больше нагружаем проц и систему, сообщения MM_WOM_DONE сыпятся всё чаще и чаще. Уменьшаем, уменьшаем дальше, и опа, в какой-то момент, приложение не успевает обработать очередной MM_WOM_DONE, и добавить новый буфер в очередь до окончания воспроизведения текущего. Звук рвётся. Этот момент(и минимальный размер буфера, при котором разрывов не будет) зависит от кванта времени, выделяемого приложению виндой, производительности системы и загруженности её работающими параллельно другими процессами.
Почему я говорил про 100 мс. С буфером такой длительности приложение в штатной ситуации никогда не порвёт звук и не сильно нагружает систему. На "больших" компах.

Автор: Alexei 18.08.16, 09:54
Снова реанимировался вопрос про то , как узнать какой буфер только что отыграл.
Цитата
Параметр lParam содержит адрес структуры WAVEHDR, соответствующей проигранному блоку.
Это из интернета.
Действительно содержит, только есть небольшое отличие с тем что я отправляю.
Вскрытие показало, что у обоих младшие 64р-да совпадают, но у lParam старшие 64 равны 0, а у адреса отправленной структуры в младшем -1. Можно ее , конечно, придушить, но...

Автор: Prince 18.08.16, 15:08
В винмобайл? Вот прям 64 бита? Да ещё и старшие/младшие?
Цитата
младшие 64р-да совпадают, но у lParam старшие 64 равны 0, а у адреса отправленной структуры в младшем -1.

Ничего непонятно.

Автор: Alexei 18.08.16, 17:58
Цитата Prince @
В винмобайл?

Нет. 8.1 (64)

Автор: Prince 18.08.16, 19:22
Цитата
младшие 64р-да совпадают, но у lParam старшие 64 равны 0, а у адреса отправленной структуры в младшем -1.

Осталось эту странную лексическую конструкцию осилить.

Автор: Alexei 19.08.16, 10:05
Не надо ее осиливать, это какие-то проблемы с 64.
А у меня проблема я не знаю как сравнить :
lParam с &whdr(структура WAVEHDR)
Ругается, что я сравниваю не то не с тем. :)

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)