generowanie dźwięku i jego nagrywanie

0

Witam chciałem wygenerować dźwięk a dokładniej mam tablice z pojedynczymi próbkami, które poddaje różnego typu algorytmom następnie chce je odtworzyć do tego celu używałem bardzo fajnej procedury, która znajduje się na tej stronie Pod adresem http://4programmers.net/faq.php?id=260 jej zaletom jest to ze bardzo szybko mogę przepisać daną tablice i ja odtworzyć, jednak wadą jest to ze chodzi na 8 bitach a chciałem to zrobić na 16 a kiedy zmienię wpis

wBitsPerSample := 8

na 16 to zmienia mi się próbkowanie na dwa razy większe niż jest podane w wpisie nSamplesPerSec :=

  czego efektem jest dwukrotnie mniejszy czas <ort>odważania </ort>sygnału i co za tym idzie odtwarzana częstotliwość jest dwa razy większa. 

No i teraz pytanie mam takie:
Dlaczego tak się dzieje mogę to niby łatwo skorygować przez wydłużenie dwukrotne tablicy, ale przypuszczam jednak ze cos jest nie tak w procedurze (czy ona nie pasuje pod 16bit?)??

Następna sprawa jest taka chciałbym w momencie, gdy karta muzyczna odtwarza sygnał od razu go nagrywać przez wejście karty szukam czegoś podobnego do tej procedury odtwarzania, czyli prostego i żeby moc od razu to szybciutko przepisać na tablice będę bardzo wdzięczny za podsuniecie jakiegoś kodu i jakąkolwiek pomoc.
0
Pieli napisał(a)

No i teraz pytanie mam takie:
Dlaczego tak się dzieje mogę to niby łatwo skorygować przez wydłużenie dwukrotne tablicy, ale przypuszczam jednak ze cos jest nie tak w procedurze (czy ona nie pasuje pod 16bit?)??

Jak zmieniasz wBitsPerSample musisz także zmienić nBlockAlign i nAvgBytesPerSec. Tu masz przykład w C/C++ ale myśle, że zrozumiesz o co chodzi ;)

wfx.nBlockAlign=wfx.nChannels*(wfx.wBitsPerSample/8);
wfx.nAvgBytesPerSec=wfx.nBlockAlign*wfx.nSamplesPerSec;

//EDIT

W tym artykule też to jest:

nBlockAlign := (nChannels * wBitsPerSample) div 8;
nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
0

No właśnie tez to jest i tez to wpisuje do siebie, czyli tak jak poniżej jest skonfigurowane powinno być niby poprawnie, jednak niestety nie jest, bo sygnał jest odtwarzany dwa razy szybciej niż powinien jeżeli zmienię na 16 bit a wiec jak niby to ma być.

 with WaveFormatEx do
  begin
    wFormatTag := WAVE_FORMAT_PCM;
    nChannels := 1;
    nSamplesPerSec := 44100;
    wBitsPerSample := 16;
    nBlockAlign := (nChannels * wBitsPerSample) div 8;
    nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
    cbSize := 0;
  end;
0

No jeżeli struktura WAVEFORMATEX jest poprawnie wypełniona to szukałbym przyczyny gdzie indziej. Czym wypełniasz buffory??? Generujesz dźwięk czy sample są z pliku audio???

0

Podaje program z problemem wystarczy wkleić cały tekst i wstawić na formę dwa Przyciski
Po przyciśnięciu pierwszego powinien niby zostać odtworzony sinus o częstotliwości 1000Hz i trwający 5 sekund A tak naprawdę przez ten błąd trwa 2,5 sekundy i odtwarzana częstotliwość to 2000Hz

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs,Mmsystem, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure init;
    Procedure DoPamieci;
    Procedure GenerujDoTablicySinus;
    Procedure GenerujDoTablicySzum;
    Procedure TablicaDoPamieciPlay;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  WaveFormatEx: TWaveFormatEx;
  ZapisDoPamieci: TMemoryStream;

  i, TempInt, IleProbekWsygnale, RiffCount: integer;
  CzasSygnalu:longword;    //calkowity czas trwania sygnalu w ms
  Sygnal:array of word;    //tablica z wygenerowanym sygnalem
  Volume:integer = 50;     //Głosnosc
const
  RiffId: string = 'RIFF';
  WaveId: string = 'WAVE';
  FmtId: string = 'fmt ';
  DataId: string = 'data';

  implementation

{$R *.dfm}
Procedure tform1.GenerujDoTablicySinus;
begin
 setlength(sygnal,IleProbekWsygnale);
 for i:=0 to IleProbekWsygnale -1 do
  sygnal[i]:=32767 + trunc(Volume * sin(i * 2 * pi * 1000{Hz} / 48000{SamplePerSec}));
end;

Procedure tform1.GenerujDoTablicySzum;
begin
 setlength(sygnal,IleProbekWsygnale);
 randomize;
 for i:=0 to IleProbekWsygnale -1 do
  sygnal[i]:=32767 + trunc(Volume * random(10));
end;

///Procedurka inicjująca Waveformatex///
procedure tform1.init;
begin
  with WaveFormatEx do
  begin
    wFormatTag := WAVE_FORMAT_PCM;
    nChannels := 1;
    nSamplesPerSec := 48000;
    wBitsPerSample := 16;
    nBlockAlign := (nChannels * wBitsPerSample) div 8;
    nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
    cbSize := 0;
  end;
end;



Procedure tform1.DoPamieci;
begin

 ZapisDoPamieci := TMemoryStream.Create;

  with ZapisDoPamieci do
  begin
    IleProbekWsygnale := (CzasSygnalu * 48000) div 1000;
    RiffCount := Length(WaveId) + Length(FmtId) + SizeOf(DWORD) +
                 SizeOf(TWaveFormatEx) + Length(DataId) + SizeOf(DWORD) + IleProbekWsygnale; //


    Write(RiffId[1], 4); // 'RIFF'
    Write(RiffCount, SizeOf(DWORD));
    Write(WaveId[1], Length(WaveId)); // 'WAVE'
    Write(FmtId[1], Length(FmtId)); // 'fmt '
    TempInt := SizeOf(TWaveFormatEx);
    Write(TempInt, SizeOf(DWORD));
    Write(WaveFormatEx, SizeOf(TWaveFormatEx));
    Write(DataId[1], Length(DataId)); // 'data'
    Write(IleProbekWsygnale, SizeOf(DWORD));
  end;
end;



Procedure tform1.TablicaDoPamieciPlay;
begin
    ///Przepisanie tablicy do pamieci
  with ZapisDoPamieci do
   begin
    for i := 0 to IleProbekWsygnale - 1 do
      Write(sygnal[i], SizeOf(Byte));

    //////Odtworzenie sygnalu przez karte muzyczną/////
    sndPlaySound(ZapisDoPamieci.Memory, SND_MEMORY or SND_SYNC);
    ZapisDoPamieci.Free;
  end;
end;

////////////////////////////////////////////
////NO I OD TĄD DWA PRZYKLADY WYKOZYSTANIA
////////////////////////////////////////////
procedure TForm1.Button1Click(Sender: TObject);
begin
init;
CzasSygnalu:=5000{ms} ;  //czas sygnalu ustawiony na 5 sekund
DoPamieci;
GenerujDoTablicySinus;
TablicaDoPamieciPlay;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
init;
CzasSygnalu:=5000{ms} ;  //czas sygnalu ustawiony na 5 sekund
DoPamieci;
GenerujDoTablicySzum;
TablicaDoPamieciPlay;
end;

end.
0

Tu masz błąd ;) Zakres 16 bitowych sampli to ? 32768(min) do 32767 (max). W tym artykule jest 127 (a powinno być 128 ;P) ale dlatego, że to jest mid-point wartości 8 bitowego sampla (inne kodowanie). Dla 16 bitów mid-pointem jest 0 - czyli typowe kodowanie U2 ;)

sygnal[i]:=32767 + trunc(Volume * sin(i * 2 * pi * 1000{Hz} / 48000{SamplePerSec}));

i to:

  write(FmtId[1], Length(FmtId)); // 'fmt '
  TempInt := SizeOf(TWaveFormatEx)-4; // bez WAVEFORMATEX::cbSize
  write(TempInt, SizeOf(DWORD));
  write(WaveFormatEx,TempInt);

PS. nie programuję w Delphi więc mogłem coś schrzanić ;)

0

A wiec, jeżeli chodzi o pierwszy błąd to poprawiłem to i nic to samo jednak pomyślałem ze według tego, co mówisz to przecież wcześniej powinienem mieć niezłe przesterowanie praktycznie obcięta jedna połówka sinusoidy a tego nie było i wtedy zajrzałem do procedury przepisywania tablicy do pamięci była tam następująca linijka w pętli

Write(sygnal[i], SizeOf(byte));

Zakres ort! 0..255 a przecież dla 16 bit musi być -32768..32765 A wiec zmieniłem na <ort>smallint</ort>32768..32765)
No i jeszcze zmienną od głośności ort! musiałem zwiększyć o kilkaset razy, bo nie było nic słychać
Po odpaleniu programu częstotliwość zrobiła się taka jak należy jednak ze czas pozostał bez zmian, czyli o połowę krótszy i dalej nie wiem gdzie go obcina.

Dla pewności podłączyłem oscyloskop pod wyjście karty, aby sprawdzić próbkowanie i generowałem nią sygnał z impulsami, czyli 1próbka=1, 2próbka =0, 3próbka =1, 4próbka =0,itd. No i na 100% jest takie jak skonfigurowałem, czyli 48000 bo na oscyloskopie w jednej mili sekundzie mieściło się 48 próbek

Także wniosek z tego taki ze odgrywa sygnał tylko do połowy a reszta gdzieś ginie

Jeżeli chodzi o drugą Twoją wskazówkę to po zastosowaniu na wyjściu karty robi się cisza, brak sygnału.

Tak w ogóle to dzięki za cierpliwość i pomoc, bo pewnie bez pierwszej podpowiedzi chyba miałbym problem z wychwyceniem tego błędu no i mam nadzieje ze pomożesz mi jeszcze sprawę doprowadzić do końca :-)

0

To też miałem wypisać ale....

...
RiffCount := Length(WaveId) + Length(FmtId) + SizeOf(DWORD) +
                 SizeOf(TWaveFormatEx) + Length(DataId) + SizeOf(DWORD) + IleProbekWsygnale; 
...
...
write(DataId[1], Length(DataId)); // 'data'
write(IleProbekWsygnale, SizeOf(DWORD));
...

IleProbekWsygnale musi być w bajtach (czyli x2) ale tylko w tych przypadkach.

0

Fajnie, działa teraz bez zarzutu dzięki wielkie:-)
A na temat nagrywania dźwięku możesz mi coś powiedzieć chciałbym to zrobić w podobnym stylu jak odtwarzanie tylko ze odwrotnie, czyli jak najszybciej zgrać nagrany sygnał bezpośrednio z pamięci na tablice. A następnie połączyć żeby uruchomiło się jednocześnie nagrywanie i odtwarzanie. Może masz jakiś ciekawy przykładzik takiego nagrywania.

0

Nie rozumiem za bardzo o co ci chodzi??? ;) Mówisz o nagrywaniu do pliku przy jednoczesnym odtwarzaniu z innego, czy o nagrywaniu sampli do pamięci (bufforów), przetwarzaniu ich i puszczeniu z powrotem na wyjście karty??? Tak czy siak zainteresuj się tymi funkcjami - dają większą kontrolę nad odtwarzaniem/nagrywaniem:

waveInAddBuffer waveInClose waveInOpen waveInStart waveInStop waveInUnprepareHeader waveInPrepareHeader

waveOutClose
waveOutOpen
waveOutPrepareHeader
waveOutUnprepareHeader
waveOutWrite

0

A więc dokładniej musze tak, mniej więcej po kolei opisze jak to ma iść

1.Generuje różnego rodzaju sygnały kartą muzyczną.
2.Sygnał z karty muzycznej wychodzi a wchodzi do różnego typu urządzeń audio.
3.Nastepnie sygnał przechodzi przez urządzenie audio i wraca do wejścia karty, czyli jest nagrywany i przepisany do tablicy.
4.Analizuje, jakie zmiany zaszły w sygnale po przejściu przez urządzenie z tym już nie będę miał kłopotów.
5.Sygnał nagrany poddaje różnego typu algorytmom i wyciągam z niego np. faze czy odp. impulsową z tym tez już nie będę miał problemu.

Tak, więc jedyny problem to punk 3. No i musze to zrobić w taki sposób, aby odstęp czasowy między rozpoczęciem nagrywania a rozpoczęciem generowania sygnału był jak najmniejszy, czyli nie może być tak ze nagrywam a sygnał nie jest generowany przez jakieś pół sekundy a także nie może być tak ze sygnał jest już generowany, ale jeszcze karta nie nagrywa.
A więc musze te dwie czynności odpalić równocześnie a maksymalna różnica czasu startu między nimi nie może być większa niż 10ms oczywiście bardzo bym się cieszył gdyby było mniejsza np. 1ms

Aha no i jeszcze taki bajer musi być ze nagrywać już muszę w stereo.
Pomiar trwa jakieś 5s i cały ten 5 sekundowy sygnał musi być zapisana do jednej tablicy, także nie kreci to się cały czas w koło tylko jeden raz i koniec, czyli zatrzymuje nagrywanie i odtwarzanie.

PS. Pierwszy raz robię program z obsługą karty muzycznej, dlatego mam takie chamskie problemy, może i nawet prostackie.

0

Aha, czyli wszystko odbywa się w pamięci, a to znacznie upraszcza sprawę ;) Problem punktu 3-go wbrew pozorom nie jest taki prosty. No ale tutaj nie ma operacji dyskowych więc jakoś powinno pójść. Sprawę upraszcza także to, że nie ma potrzeby korzystania z multi-buffering'u - wystarczą dwa 5sek buffory. Jeden na nagranie, drugi - odtwarzanie. We wcześniejszym poście podałem ci funkcje WinAPI, których powinieneś użyć - sndPlaySound do tego się nie nadaje. Poszukaj w MSDNie informacji o nich... z resztą masz tu link.

0

No powoli zaczyna cos pisać pod to nagrywanie jak skończę to się odezwę a przypuszczam ze to troszkę potrwa.

Możesz mi dokładniej wytłumaczyć, czemu się nie nadaje

sndplaysound

??
Czyli cały generator trzeba pisać od nowa, no nie. :(

0
Pieli napisał(a)

Możesz mi dokładniej wytłumaczyć, czemu się nie nadaje

sndplaysound

??

Nie nadaje się ponieważ ta funkcja służy do prostego "udźwiękowienia" aplikacji. Zero kontroli nad tym jak ma być odtwarzana sampla. Dodatkowo musiałeś tworzyć plik *.wav żeby odtwarzać z pamięci - bezsens :/ Z resztą, zależy ci na synchronizacji zapis/odczyt. W przypadku tej funkcji jedyne co możesz zrobić to ją wywołać, a ona wewnętrznie otworzy urządzenie audio, stworzy buffory i zacznie je wysyłać. Wszystko cacy tylko jeszcze zostanie ci wywołać nagrywanie - procedura jak wyżej. Opóźnienia mogą być znaczne ;) W przypadku funkcji waveXXXX otwarcie urządzeń, alokację bufforów możesz sobie zrobić wcześniej, a całość odpalic dwoma funkcjami, które dadzą sygnał driverowi karty do parsowania bufforów - czyli pominiesz to, co może znacznie wpłynąć na czas między aktywacją odtwarzania i nagrywania.

0

No hej nawet jakoś mi to powoli idzie z kompanem translatorem, bo z Anglika kozak nie jestem i nawet mi się to podoba :)
Mam pytanie, chyba proste, ale nie mogę dojść jak wywołać procedurę po wypełnieniu bufora.
Jak to zrobić?

0
Pieli napisał(a)

Mam pytanie, chyba proste, ale nie mogę dojść jak wywołać procedurę po wypełnieniu bufora.
Jak to zrobić?

Rozwiń nieco bo <ort>niebardzo </ort>wiem o co chodzi :P Jaką procedurę???

0

Dokładniej to tak na razie tylko nagrywanie, czyli konfiguruje wszystko następnie zaczynam nagrywać do przykładowo 5-cio sekundowego bufora no i gdy bufor się już wypełni, czyli cały zapisze próbkami to może się to wydać śmieszne, ale nie przychodzi mi nic do głowy, w jaki sposób wychwycić ze bufor jest już pełny i wtedy uruchomić procedurę przepisującą bufor do tablicy.

0

[...] ale nie przychodzi mi nic do głowy, w jaki sposób wychwycić ze bufor jest już pełny i wtedy uruchomić procedurę przepisującą bufor do tablicy.

To zależy jaką metodę przyjąłeś - chodzi o parametr fdwOpen funkcji waveInOpen. Jeżeli dałeś CALLBACK_NULL to po rozpoczęciu nagrywania musisz sprawdzać stan pola dwFlags struktury WAVEHDR. Jeżeli będzie ustawiona flaga WHDR_DONE to znaczy, że driver skończył z bufforem i "zwrócił" go aplikacji. Pamiętaj, że dwFlags może zawierać inne flagi więc nie rób zwykłego porównania. Całość dajesz do pętli plus Sleep(100) co by proca nie obciążać ;) Minus tego rozwiązania jest taki, że zamrozi aplikacje na czas nagrywania. No ale to jedna z możliwości...

0

No hej nagrywanie jakoś poszło mam jeszcze parę problemów, ale dam sobie z nimi chyba rade.
Teraz robię generator i mam problem mianowicie na razie skonfigurowałem pootwierałem urządzenie itd. Ale nie wiem w jaki sposób mam przepisywać tablice do bufora?

0

Może lepiej będzie jak wkleję kod przy okazji się upewnię czy wszystko dobrze robię

unit Unit2;

interface

Uses forms,Windows, MMSystem, SysUtils,Dialogs,Messages;

type


  TWaveout = class(TObject)
      Constructor Create(newFormHandle:HWnd; BfSize: Integer);
      Destructor  Destroy;      Override;

      Private
      RozmiarBufora   : Integer;
      pWaveHeader     : PWAVEHDR;
      UchwytAplikacji : HWND;

      Public
      WaveOut:hwaveout;
      WaveBufSize   : integer;
      ptrWaveFmtEx  : pWaveFormatEx;
      pWaveBufor   : lpstr;            //wskaznik


      Procedure setupout;
      end;

implementation


Constructor TWaveout.Create(NewFormHandle:HWnd;
               BFSize: Integer);
begin
UchwytAplikacji:=NewFormHandle;
Rozmiarbufora:=bfsize*(16 div 8)*2;

new(ptrWavefmtEx);
  with ptrwavefmtex^ do
  begin
    wFormatTag      := WAVE_FORMAT_PCM;
    nChannels       := 2;
    nSamplesPerSec  := 48000;
    nBlockAlign     := 1;
    wBitsPerSample  := 16;
    nAvgBytesPerSec := nSamplesPerSec*(wBitsPerSample div 8)*nChannels;
    cbSize          := 0;
  end;

new(pwaveheader);

  getmem(pWaveBufor,RozmiarBufora);
  pWaveHeader.lpData := pWaveBufor;

end;



Procedure TWaveout.SetupOut;

begin
    With ptrWavefmtEx^ Do
    Begin
      wFormatTag := WAVE_FORMAT_PCM;
      nChannels:=2;
      nSamplesPerSec:=48000;
      wBitsPerSample:=16;
      nAvgBytesPerSec:=nSamplesPerSec*(wBitsPerSample div 8)*nChannels;
    end;

waveoutOpen(@Waveout, WAVE_MAPPER, ptrWaveFmtEx,
                UchwytAplikacji, 0, CALLBACK_window);

  WaveBufSize := RozmiarBufora - (RozmiarBufora mod ptrwavefmtex.nBlockAlign);

  with pWaveHeader^ Do
  begin
    lpData := pWaveBufor;           //Adres bufora w pamieci address of the waveform buffer
    dwBufferLength := WaveBufSize;  //Długosc w bajtach bufora
    dwBytesRecorded := 0;
    dwUser := 0;
    dwFlags := 0;
    dwLoops := 0;
    lpNext := Nil;
    reserved := 0;
  end;

  waveoutPrepareHeader( Waveout, pWaveHeader, sizeof(TWAVEHDR));

  ///no i co tu dalej jak zapisac tablice do bufora???
end;



Destructor  twaveout.Destroy;

begin
//
end;

end. 
0

///no i co tu dalej jak zapisac tablice do bufora???

Nie rozumiem w czym problem??? Normalnie wpisujesz wartości do buffora wskazywanego przez lpData struktury WAVEHDR. W przypadku sampli stereo wpisujesz odpowiednio LPLPLPLP...

0

No to już wszystko chyba mi dobrze chodzi jeszcze dzisiaj sprawdzę, jakie opóźnienia są między startem nagrywania a startem generowania.

Jeszcze jedno małe pytanie mam, tak dla pewności, czyli polecenia

WaveInStart
WaveOutWrite

Są już ostatecznymi i wywołując je jedno po drugi będą najmniejsze opóźnienia między startem nagrywania a generowania??

No i oczywiście na sam koniec pozostaje mi tylko Tobie bardzo podziękować za pomoc i cierpliwość.

0

Jeszcze jedno małe pytanie mam, tak dla pewności, czyli polecenia

WaveInStart
WaveOutWrite

Są już ostatecznymi i wywołując je jedno po drugi będą najmniejsze opóźnienia między startem nagrywania a generowania??

W przypadku WaveInStart to już chyba wszystko. Jeżeli chodzi o waveOutWrite można jeszcze spróbować tak: zapauzować funkcją waveOutPause driver, dodać buffor (WaveOutWrite) i zacząć odtwarzanie funkcją waveOutRestart. No ale nie jestem pewnien czy driver w stanie pauzy przyjmie buffor. Co do opóźnień. One będą ale na pewno mniejsze niż w przypadku funkcji sndPlaySound i tym podobnych.

1 użytkowników online, w tym zalogowanych: 0, gości: 1