|
Post by scalion on Apr 9, 2021 13:52:14 GMT 1
Bonjour à tous, Aujourd'hui un petit synthétiseur utilisant une nouvelle fonction de ma librairie DSounLib : DS_WaveFromDblArray(44000, Channels, BPS, NSamples, w()) On remplit un array de valeur allant de -1 à 1 (correspondant aux oscillations) et le transfère dans un Wav.
Juste que je n'ai pas fini de débugger la fonction et le BPS n'est pas fonctionnel et chanels ç'est pas au point donc juste les Hertz et NSamples ca marche nickel. On a donc que des sons en mono 8 bits pour l'instant. Ce n'est pas un vrai synthé vu que ca crée un sample qui est joué à différentes fréquences, c'est plutot un hybride entre sampler/synthétiseur.
Il n'y a que 9 instrument mais c'est super facile d'en rajouter. Il suffit de créer une procedure Instru10 par exemple et de la déclarer en début de programme (vous verrez vite où). Après vous n'avez plus qu'à jouer a remplir le tableau w() avec des sinusoidales ou autre et de renseigner NSamples pour la longueur du sample généré.
Ha et j'a failli oublié : mettez des écouteurs sinon vous allez énerver tout le monde à la maison
|
|
|
Post by scalion on Apr 9, 2021 20:58:58 GMT 1
Maintenant on à la stéréo, j'ai trouvé comment faire dans la libraire. Du coup j'ai rajouté 2 instruments avec de l'écho "Bell of Beffroi" et "Magic". J'en ai profité pour rajouter un sélecteur d'octave et corriger le bug d'affichage du texte des instruments.
Edit : je m'éclate à créer des nouveaux instru, celui-là je l'adore, je l'ai appelé "Space FX".
Proc Instru13 Local Long i, j, k Channels = 2 NSamples = 50000 If NSamples + 1 > Dim?(w()) ReDim w(NSamples + 1) EndIf k = 10 For i = 0 To NSamples u = Sin(i / 10) / 300 If Mod(i, 2) = 0 w(i) = Sin(i * u / 5) w(i) += Cos(i * u / 4) Else w(i) = Sin(i * u / 3) w(i) += Cos(i * u / 2) EndIf u = i / NSamples w(i) *= (1 - u) / 2 Next i For i = 0 To NSamples u = i / NSamples w(i) *= (1 - u) Next i EndProc
|
|
|
Post by Roger Cabo on Apr 10, 2021 3:09:56 GMT 1
Wow really impressive!!
I just wondering how you build the sound.. Are there example codes sin/on for this? Can I raise the sound quality without? Sounds very noisy like 4KHz and 8 bit? :-)
|
|
|
Post by scalion on Apr 10, 2021 6:15:50 GMT 1
Wow really impressive!! I just wondering how you build the sound.. Are there example codes sin/on for this? Can I raise the sound quality without? Sounds very noisy like 4KHz and 8 bit? :-) Hi Roger, Thank's, Soon I'll be posting the full code for the library, for now I'm working on it. The 8-bit sound it's true that it's a bit rotten, even if there is an interesting retro side, like bontempi, I promise you that I will develop the BPS parameter for good 16-bit sound (maybe 32bits but it's not sure that I need to document myself). I would explain with code how it all works.
|
|
|
Post by scalion on Apr 10, 2021 10:14:36 GMT 1
Well, chose promise... Now with 16 bits sound New instruments... Octaves extends 0 to 9. I will post in lib section for the library source... please wait. Have fun !
|
|
|
Post by Roger Cabo on Apr 10, 2021 18:33:12 GMT 1
This program deserves a deep dive! Well done! It's not that easy to understand :-)
|
|
|
Post by Roger Cabo on Apr 10, 2021 21:44:41 GMT 1
I think the most important to understand the data structure. q0) Does the project play wave tables? 1) The wave table contains the wave samples. Seems like Global Dim w(NSamples) As Double 2) Proc Instru1 fills the wave table w() with data.
3) The InstruDescription_Struct Global Dim InstruDescription(100) As InstruDescription_Struct Contains the ProcAdress and name of the instrument.
q1) Why we need the ProcAddress of the Instrument function? To call a function by index instead of the name ?
q2) Is the ProcAddress required by D2Sound? (I think not)
------------
|
|
|
Post by scalion on Apr 11, 2021 12:42:40 GMT 1
Hi roger, Well, there is many data structure :
InstruDescription_Struct : Not uses by DirectSound but only to manage instruments informations
In the library DSoundLib the most important structure are : WAVEFORMATEX store the wave description
WAVEEXTRAINFO_STRUCT store informations used by DS_SHUTDOWN and DS_PLAY_MORE specific library function
DSFILEINFO store informations about Dsound Object. Note an "Object" is schematically an array of sub-program address then you can access to method with an offset. Example with SetPan DirectSound function (left-right balance) :
Function DSBuffer_SetPan(lpDSB As Long, lPan As Long) As Int Naked . push [lPan] ' <- Parameter PAN . mov eax, [lpDSB] ' <- Long Pointer to the DirectSound Buffer Object created by DSCreateSoundBuffer . push eax . mov eax, [ eax] . call [ eax + idsbSetPan] ' <- Call sub-programm at offset idsbSetPan (= 64) that mean Addres of Object + 64 . mov [DSBuffer_SetPan], eax ' <- Return Code of this function (must be DS_OK =0 if success) EndFunc
No, the table w() are converted by DSoundLib in Direct Sound object that can be played later.
I use ProcAddress just for simplify adding instrument. This is the easiest way to index an instrument list. This avoids having to use a tedious Select InstrumentIndex case 1 .... case 2 ... Endselect. Then effectively ProcAddress is not required By D2Sound, It's just a quick way to manage any number of instruments.
|
|
|
Post by Roger Cabo on Apr 11, 2021 18:52:07 GMT 1
Ah thank you.. That helps a lot!
A) Is it possible to recognize the exact playing position of a sample/time? No idea how this can be done. If I play a Stereo track with 44.1KHz. And I want to cycle a loop between 2 positions L and R (Left Right - Locators)
<----------------|
|---------------->
L Loop Cycle R (Cycle Location)
| |
track 1 +--------O----------------O--------------
How that can be done? :-)
Here is a small conversion: Sample is 8 Seconds or whatever at 44.1Khz 120bpm
Now Loop between Bar 1 and 2 L = 2000 R = 4000ms // milliseconds L = 88200 R = 176200 // samples at 44.1Khz
Dim Left_LocatorMs% = BPM_Get_NoteLenght_Ms(120, 1, 1) Dim Right_LocatorMs% = BPM_Get_NoteLenght_Ms(120, 1, 2)
MsgBox "ms: L:" & Left_LocatorMs% & " R:" & Right_LocatorMs%
// ---------------
Dim Left_LocatorSamples% = BPM_Get_NoteLenght_Samples(120, 1, 1, 44100) Dim Right_LocatorSamples% = BPM_Get_NoteLenght_Samples(120, 1, 2, 44100)
MsgBox "ms: L:" & Left_LocatorSamples% & " R:" & Right_LocatorSamples%
Function BPM_Get_NoteLenght_Ms(bpm#, note%, count%) As Int Return ((240000 / bpm#) / note%) * count%
Function BPM_Get_NoteLenght_Samples(bpm#, note%, count%, khz%) As Int Return (((khz% * 240) / bpm#) / note%) * count%
|
|
|
Post by scalion on Apr 11, 2021 21:21:28 GMT 1
Hi roger, I wrote 3 new function to reach this target. Can you try it and comment ? Thank's.
|
|
|
Post by Roger Cabo on Apr 12, 2021 2:07:41 GMT 1
Ah great! The "easy as possible" examples are much more understandable! A) DS_GetCurrentPosition(SND_TEST) returns Samples * 2 ? If I play from 0 to 44100 Samples its only 0.5 sec instead of 1.0 sec B) I see a audio problem in DS_SetCurrentPosition SND_TEST, Left_LocatorSamples When playing a SND and change the current position, it Clicks/Crackles if the samples starts with != 0. See attachment image:
Some ideas:
The function should have a 3rd parameter DS_SetCurrentPosition SND_TEST, Left_LocatorSamples, Fade_Samples It requires a smoothlerp function on fade out/in for 50-100 samples. t = t*t * (3f - 2f*t) The function: DSBuffer_SetCurrentPosition(lpDSB As Long, dwNewPosition As Long) As Int Naked I think the problem is , there is no possibility to access the samples in this fast case to fade in or out. Or? 1) Another workaround perhaps: poke-in the 50-100 samples before starting. But can we access the original play buffer of DS to manipulate? 2) Not sure if we can use DS_SetVolume so fast and for each track.
We need exactly 1ms (44 samples) before Right Locator to Fade out, and 1 ms (44 samples) before Left Locator to fade in. (then no clicks happen) Damn how that can be done?
Attachments:
|
|
|
Post by scalion on Apr 12, 2021 20:03:21 GMT 1
Hi Roger, i write something and come back... Hop, tadaaa !
I read many docs on DirectSound and i se it's a recurrent problem (clics) beacuse the notify function of directound have latency. I decided the only way to play part of wave looping without clic is to copy the part defined by the left/right locator in a new buffer. And apply the attack/decay on the 50 first and 50 last samples. This program demonstrate how that work but sorry i have too much information to write. I come back later for the next... oups, i omitted the t = t*t * (3f - 2f*t) function, but that's work (in future version i write it, sure).
I think that's begin to be seriously become urgent to document all of this (i write more than 10 new functions).
|
|
|
Post by Roger Cabo on Apr 12, 2021 21:05:14 GMT 1
>> I read many docs on DirectSound and i se it's a recurrent problem (clics) beacuse the notify function of directound have latency. >> I decided the only way to play part of wave looping without clic is to copy the part defined by the left/right locator in a new buffer. >> And apply the attack/decay on the 50 first and 50 last samples. This program demonstrate how that work but sorry i have too much information to write. >> I come back later for the next... oups, i omitted the t = t*t * (3f - 2f*t) function, but that's work (in future version i write it, sure).
Hi scalion, thank you..
Wait... don't hurry too much :-))) Let us shortly think about what the best solution.
If you update the samples in GFA memory, then real-time functions are not possible. Perhaps we can do this in real time without any updating physical samples.
1) Do you know if we can access the DSoundLib wave out play buffer directly? That would be interesting to know.
2) Perhaps we can use DS_volume to fade shortly in and out in real time?
3) If we have more than 1 track to play, can we set the volume for different tracks or channels?
____________________________________________________________________________________________________________________
I have wrote a 1ms NTTimer that continue running! Doesn't matter if updating graphics, other windows or whatever. The update happen in TimerQProc(ByVal pParameter As Long, ByVal TimerOrWaitFired As Long) Naked
So why? I think it's important to have a stable audio timing. For example: If we cycle from a Locator-R back to Locator-L, it cannot happen in the graphics update. Because if you resize the own window or switch to another application, Do : Sleep : Until XX updates about 10 times / second. This cause some ugly cycle issues or running behaviors.
Solutions: When running the TimerProc and our application crash for any reason, then the DS loops as long as the application persist in memory and crash completely if the ProcAddress not exist any longer. (Eg. deleting out programm with the Taskmanager)
With ping-ing a global variable in the ProcAddress function. so the ProcAddress function knows that our Application is still alive. If the ping does not received in the ProcAddress function after about 3 seconds, then it shut down itself. It's very easy to handle.
How2? The ProcAddress function handles all commands of DS by a global variable or array of commands. Another advantage is handling loop cycles with fadeIn/Out as well to prevent clicks and crackles. I hope. :-) Also soft Fade in when start or stop would be possible, because of the fast 1ms ProcAddress execution.
What do you think?
Attachments:NTTimer-1ms.G32 (2.53 KB)
|
|
|
Post by scalion on Apr 14, 2021 6:34:37 GMT 1
Hi Roger , Yes using a thread is a good idea. I have no time for the moment, i come back later. *
Yes it's possible but unfortunaly reading cause some stroubleshooting.
The way to access the buffer is the lock function :
Function DSBuffer_Lock(lpDSB As Long, dwWriteCursor As Long, dwWriteBytes As Long, lplpvAudioPtr1 As Long, lpdwAudioBytes1 As Long, lplpvAudioPtr2 As Long, lpdwAudioBytes2 As Long, dwFlags As Long) As Int Naked . push [dwFlags] : . push [lpdwAudioBytes2] : . push [lplpvAudioPtr2] . push [lpdwAudioBytes1] : . push [lplpvAudioPtr1] : . push [dwWriteBytes] . push [dwWriteCursor] . mov eax, [lpDSB] . push eax . mov eax, [ eax] . call [ eax + idsbLock] . mov [DSBuffer_Lock], eax EndFunc
lpDSB ithe buffer object
After call lplpvAudioPtr1 is a valid pointer to the data.
|
|
|
Post by Roger Cabo on Apr 16, 2021 15:46:28 GMT 1
The way to access the buffer is the lock function :
Okay thank you. But then, it's not the right way to do.
hmm.... I' just wondering how synth poking massive data in realtime to the wave and you hear the changes directly without any click or crackle.
Perhaps we should try. To see what happens in the loop..
Do // Save wave play position WavePosition = DS_GetCurrentPosition(SND_TEST)
// Stop Playing .. or maybe not? DS_Stop SND_TEST // perhaps without ?
// Change wave samples in w() ChangeSampeData() /
// Rebuild or what every happen in this case. SND_TEST = DS_WaveFromDblArray(44100, 1, 16, NSamples, w())
// reset the buffer position. DS_SetCurrentPosition SND_TEST, WavePosition
// and go again DS_PlayLoop SND_TEST
Sleep(1) // wait 1ms only DoEvents // make sure other events are collected
Loop Until ...
SND_TEST = DS_WaveFromDblArray(44100, 1, 16, NSamples, w()) . Could be possible conversion takes too long or/and cause an point to reset internally and crash.
It's strange that we cannot poke/peek directly in the converted single SND_TEST wave buffer while it plays... Because writing/reading don't cause problems when playing the wave. Volume, Paning is all possible in realtime... but not write and read? hmmm..
|
|
|
Post by scalion on Apr 17, 2021 9:39:11 GMT 1
Actually I don't know how to write directly sample, i don't know if it is possible. I will take a look in a VST plug in source if i found one. Maybe if you make dsound buffer with only 1 or 2 bytes... Then change data output is an equation byte to write = volume /pan/frequency?... Omg... Why not.
|
|