Ooops
October 27, 2021, 04:19:13

Author Topic: My Music Editor  (Read 6660 times)

Offline Hardcoal

  • Hero Member
  • *****
  • Posts: 748
  • Nothing is personal
Re: My Music Editor
« Reply #60 on: July 13, 2021, 03:36:09 »
I do use SF_STEREO16LE  44000 hz format only..
Im not going to make my life harder by messing with other formats while I cant handle one, lol

About displaying location using time, well i thought about that. but i thought theirs another way..
I can calculate time and know where the cursor should be.. and thats what im gonna do..

Some things can break me down.
Some cant

Offline Hardcoal

  • Hero Member
  • *****
  • Posts: 748
  • Nothing is personal
Re: My Music Editor
« Reply #61 on: July 15, 2021, 20:44:41 »
sorry its too complicated and confusing for me
Im trying to display the wave while recording.. and I have no idea with this all confusion of bytes, longs shorts..

anyway thats what ive done .. and its not working

Code: [Select]
Function DrawRecordedSampleWhileRecording(MySample:MySample_Type)
Local Ratio:Float, RealPointer:Short Ptr

Ratio = SEWidth / MySample.Sample.length / 2 - 1

RealPointer = Short Ptr(MySample.sample.Samples)

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)

Local Point = mo.LengthCounterInBytes

  Local Value:Int = RealPointer[Point]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight)
        xRect (Point * Ratio, SEHeight / 2, 1, Value)

xSetBuffer(xBackBuffer())

End Function

the LengthCounterInBytes is the added bugger counter while recording

line in this example while sampeling

Code: [Select]
      LengthCounterInBytes = LengthCounterInBytes + CapturedSamplesLengthVar * 4
usually i understand things rather quick.
but this thing is all confusing.. i dont understand whats the jumps in bytes when you step in RealPointer[]
I mean each time I read lets say  RealPointer[1] than RealPointer[2] how much does it jumps in bytes?
1 bytes 2 bytes or 4?

also what is this    RealPointer = Short Ptr(Audio.Samples)
why isnt is real to begin with?


anyway im going through all your posts here, trying to understand what i missed.
so you wont need to repeat yourself to me.. Thanks
« Last Edit: July 15, 2021, 20:57:45 by Hardcoal »
Some things can break me down.
Some cant

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: My Music Editor
« Reply #62 on: July 16, 2021, 02:08:09 »
...I have no idea with this all confusion of bytes, longs shorts..
BYTES are Bytes: one RAM cell with 8bit content (contains values from 0 to 255)
SHORTs are a combination of 2 Bytes, means 16bit content (BlitzMax values form 0 to 65535)
But in Audio SHORTS contain one bit for sign (positiv or negativ) and only 15bit for content (values from - 32767 to +32767)

If you use the audio format SF_STEREO16LE you work with these audio SHORTs, and because your Format it STEREO there are always 2 new SHORTs each tick. 2 SHORTs means 4Bytes.

Although the TAudioSample collects SHORTs when capturing Audio, it reports BYTEs to you (f.e. when calling Sample.Length. That is the reason why you have to calculate always with Sample.Length/2 to get the real number of samples) Also the Pointer to the samples Sample.Samples unfortunately is a Byte Ptr

RealPointer:Short Ptr stops this nonsens and opens a way to look at the samples really in SHORTS. Dealing with a SHORT PTR means 0 is the first SHORT, 1 is the second, and so on... So you can say a SHORT PTR jumps always 2 Bytes.

Quote
...also what is this    RealPointer = Short Ptr(Audio.Samples)...
This is "Casting".
Casting means change the type of a variable. Or "look on it as it would be a ..."

Example:
Code: [Select]
A:Float=1.234
B:Int = Int(A)
Int() is a casting. It casts the float value type to an integer value type

for us:
Code: [Select]
Sample.Samples ' is always a BYTE PTR
RealPointer:Short Ptr = Short Ptr(Sample.Samples)
Short Ptr() is a casting. It casts the Byte Pointer type to a Short Pointer Type


BlitzMax offers a lot of casting functions. But you have no casting function to convert BlitzMax SHORTs to Audio SHORTs. ( unsigned SHORTs to signed SHORTs). Also there is no casting function to convert BlitzMax SHORTs to INTEGERs. Thatss the reason why we need this...
Code: [Select]
ShortToInt(Value)ShortToInt() is a selfmade casting function. It casts the (for audio wrong) BlitzMax SHORT value type to an signed integer value type. Now we can read the true audio value.


Display Audio

When you try to display immediately, what you recorded, you first have to know, what of all this samples you want to display...

All Samples since start?
The last (newest) 1000 samples?
Only one (the very last) sample?

Then you have to know, how often you want to display....

Every new sample?
Every 1000 new sample?

The displaying of the samples shows lets say the newest 1000 samples. This means your display-function needs to have a FOR/NEXT-loop:
Code: [Select]
Function DrawRecordedSampleWhileRecording(MySample:MySample_Type)
Local RealPointer:Short Ptr = Short Ptr(MySample.sample.Samples)
xSetBuffer(xImageBuffer(SE_Image))
xColor(0, 255, 0)
Local Point = mo.LengthCounterInBytes/2-SEWidth
        If Point<0 then Point=0
        for local i% = 0 to SEWidth-1
Local Value:Int = RealPointer[Point+i]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight/2)
        xRect (i, SEHeight / 2, 1, Value)
Next
xSetBuffer(xBackBuffer())
End Function



If you want to show all possible samples, this means the display will be empty most of the time:
Code: [Select]
Function DrawRecordedSampleWhileRecording(MySample:MySample_Type)
Local RealPointer:Short Ptr = Short Ptr(MySample.sample.Samples)
xSetBuffer(xImageBuffer(SE_Image))
xColor(0, 255, 0)
        local StepWidth:Float = MySample.Sample.Length/2/SEWidth
Local Point:Float=0
        For local i%=0 to SEWidth
Local Value:Int = RealPointer[ int(Point)]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight/2)
        xRect (i, SEHeight / 2, 1, Value)
Point = Point + StepWidth
Next
xSetBuffer(xBackBuffer())
End Function


I dont know some of your commands. They are not regular BlitzMax commands:
 - xSetBuffer
 - xImageBuffer
 - Math.FitValue
 - DiversionAppearance
 - xRect
 - Mo.
...so I only can guess, what they are good for and how they are used...

and I dont know whether or how this SE_Image is refreshed after a FLIP? CLS? Or is it collecting all pixels?

If you keep the pixels from the last painting and you only want to draw the new ones, you have to store the mo.LengthCounterInBytes from the previous painting, so you can now paint only the additional samples:

Code: [Select]
Global LastPoint:Float

Function DrawRecordedSampleWhileRecording(MySample:MySample_Type)
Local RealPointer:Short Ptr = Short Ptr(MySample.sample.Samples)
xSetBuffer(xImageBuffer(SE_Image))
xColor(0, 255, 0)
        local StepWidth:Float = MySample.Sample.Length/2/SEWidth
Local Point:Float=0
local NowPoint:Float = mo.LengthCounterInBytes/2/SEWidth
        For local i%=0 to SEWidth
If (Point>LastPoint) and (Point<NowPoint)
Local Value:Int = RealPointer[ int(Point)]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight/2)
        xRect (i, SEHeight / 2, 1, Value)
Endif
Point = Point + StepWidth
Next
LastPoint = NowPoint
xSetBuffer(xBackBuffer())
End Function



« Last Edit: July 16, 2021, 02:29:45 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: My Music Editor
« Reply #63 on: July 16, 2021, 07:36:03 »
I have another idea why your graphic in post #51 looks like this!

Do you try to paint all sample values?

Lets say we have a display window of 440 x-pixel and a recording of 10sec. 10sec of STEREO-recordings means 880.000 samples values. If you now try to paint them all, 2000 samples will be paint on the same x-position. And this means that the samples value with the highest peak will always overpaint all others. In nearly every block of 2000 samples there is a max value. This would result in a graphic like you postet it in pos #51.

To fill a 440pixel graphic you cannot paint more than 440 samples values. So you strategie should not be to calculate
Code: BlitzMax
  1. Ratio = SEWidth / MySample.Sample.length
this gives x-postions like
Code: [Select]
14.001
14.002
14.003
14.004
...
which all result in a painting at x-position 14

but better
Code: BlitzMax
  1. local StepWidth:Float = MySample.Sample.Length/2/SEWidth
this gives Pointer-Position like
Code: [Select]
1900
3800
5700
7600
...
This results in not painting a lot of samples values to the nearly same x-postion, but making big jumps in the recording-sample-block to pick only single values (a little bit like random). But this brings a good optical impression of the total recording.


As an alternative you should think about displaying only the last 1 second of the recording during recording. Also there you need to jump to single values, because here you still have 44.000 values for your 440pixels. My first example in post #52 showed this. This "streaming" or "flowing" gives a strong impression for the user that the datas are " just coming in".
« Last Edit: July 16, 2021, 07:43:51 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Hardcoal

  • Hero Member
  • *****
  • Posts: 748
  • Nothing is personal
Re: My Music Editor
« Reply #64 on: July 16, 2021, 13:16:59 »
Ive made a progress midi master.. thats the good news

Now, my main problem is understanding.. but im prograssing at that as well

once i understand somthing I know how to execute it.

for example.. when i create a new sample.. it comes up in a certain size i define..
but when I save it i become a quarter for its defined size..
I dont understand why

* also i just realized that the sample length is like 8 bytes per sample .. and not 4 bytes..  hmm i need to reread what you posted.. i was certain till now its 4 bytes



Ive already managed to display the sample properly after the recording..
but during the recording . nope..

This works!! (displaying the sample after recording)
Code: [Select]
Function DisplayRecordedSampleLater(MySample:MySample_Type)
Local Ratio:Float, RealPointer:Short Ptr

Ratio = SEWidth / MySample.Sample.length * 2

RealPointer = Short Ptr(MySample.sample.Samples)

ClearSampleDisplay()

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)

For Local I = 0 To MySample.sample.length Step 16
  Local Value:Int = RealPointer[I]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight)
        xRect (I * Ratio, SEHeight / 2, 1, Value)
Next

xSetBuffer(xBackBuffer())

End Function

Global Test



this does not work properly .. (displaying the sample while recording)

Code: [Select]
Function DrawRecordedSampleWhileRecording(MySample:MySample_Type)
Local Ratio:Float, RealPointer:Short Ptr, SLIS:Float, Point

SLIS = MO.GetSampleLengthInSecs(MySample.Sample)

Ratio = SEWidth / (SLIS * 1000)

RealPointer = Short Ptr(MySample.sample.Samples)

Point = (MilliSecs() - KeepStartMS) * Ratio

If Point / 4 = Floor(Point / 4) Then

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)

  Local Value:Int = RealPointer[point]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight)
        xRect (Point, SEHeight / 2, 1, Value)

xSetBuffer(xBackBuffer())

End If

End Function


my problem is reading the sample while recording properly.. but the displaying it isnt that hard..

But im reading your post midi master and trying to understand what you mean..
dont think i ignore it..

you said
Quote
second: When you process SF_STEREO16LE you have to care about the frame size of 4 bytes. Each sample is 4 bytes long:
Code: [Select]
|high left | low left | high right | low right |
but when i create a sample it becomes double the size..


* anways when i save a sample it saves it lets say 345K, but when I load it back it shows that its size is only quarter.. say 86K
I calculate its size doing Sample.length/1024. it should bring me its size in KB
« Last Edit: July 16, 2021, 14:15:54 by Hardcoal »
Some things can break me down.
Some cant

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: My Music Editor
« Reply #65 on: July 16, 2021, 14:32:54 »
The sample length depend on the format you use. Do not mix up with the frame rate. "Frame" is defined as all samples at the same time. In Mono Frame and Sample are the same. In STEREO a Frame consists of 2 Samples: LEFT and RIGHT.

size in Bytes:
Code: [Select]
FORMAT        Sample-Size     Frame-Size
----------------------------------------
SF_MONO8           1             1
SF_STEREO8         1             2
SF_MONO16          2             2
SF_STEREO16        2             4

But a frame is never 8 Bytes!!! Can you explain, why you believe in 8?


Perhaps you should publish here a little bit more about how you defined the TAudioSample for Recording.

Quote
...but when i create a sample it becomes double the size..
That's normal! If you define a TAudioSample of 100.000 Samples in SF_MONO16LE the size of the Buffer is 200.000 Bytes. 100.000 SHORTs need space of 200.000 Bytes.

If you define a TAudioSample of 100.000 Samples in SF_STEREO16LE the size of the Buffer is 400.000 Bytes. 2x100.000 SHORTs need space of 400.000 Bytes.

Now the TAudioSample-Nonsens:
Code: BlitzMax
  1. SuperStrict
  2. Global Audio:TAudioSample=CreateAudioSample(1000,44000,SF_STEREO16LE)
  3. Print Audio.Length
  4. For Local i%=0 To 4000
  5.          Audio.Samples[i]=123
  6. Next
  7.  
Here Print Audio.Length would report 1000. But the following code lines show that is is possible to access the buffer until  [4000]

So Print Audio.Length does not report the size of the buffer nor the number of samples, but the number of frames.
Also CreateAudioSample(1000,... does not define 1000Bytes nor 1000Samples, but 1000Frames!

Loading back a audio file

Load an AudioFile of 352.000 Bytes (SF_16STEREOLE) into a TAudioSample. The Audio.Length will report  88.000 frames. Because it is STEREO each frame has 4 Bytes. 88.000 frames are exaclty 2sec of music. (when recorded with 44kHz)


A audio file of 88.000 frames recorded with SF_STEREO16LE at 44kHz:
Code: [Select]
     HERTZ =  44.000
    FRAMES =  88.000
      TIME =   2 sec
    LENGTH =  88.000
   SAMPLES = 176.000     (88.000 LEFTs + 88.000 RIGHTs)
     BYTES = 352.000
 FRAMESIZE = 2 Samples   (LEFT and RIGHT)
 FRAMESIZE = 4 Bytes
SAMPLESIZE = 2 Bytes
« Last Edit: July 16, 2021, 15:00:38 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Hardcoal

  • Hero Member
  • *****
  • Posts: 748
  • Nothing is personal
Re: My Music Editor
« Reply #66 on: July 16, 2021, 16:17:20 »
I thought the exact hertz are 44100 not 44000..

Here is all my code


Main App
Code: [Select]
'SetChannelVolume
'Try to add Recording Quantize..

Strict

Framework brl.blitz

Import maxgui.Drivers

Import xorsteam.xors3d
Import xorsteam.xscript
Import albalynx.albalynx
Import albalynx.connect_xors3d

Import vertex.openal
Import brl.standardio

Import brl.audiosample
Import brl.audio

Import brl.glmax2d
Import brl.reflection
Import brl.retro
Import brl.WavLoader
Import brl.directsoundaudio

Include "../../MyLibs/MyOpenal.bmx"
Include "../../MyLibs/UserInputs.bmx"
Include "../../MyLibs/AlbaSection.bmx"
Include "../../MyLibs/Math.bmx"

'------

 Global MO:MyOpenAL_Type = New MyOpenAL_Type
 Global UserInput:UserInputs_clas = New UserInputs_clas
 Global MA:MyAlbaLib_Class = New MyAlbaLib_Class
 Global Math:MyMathFuncs_class = New MyMathFuncs_class
 
MO.PrepareOpenAL()
MO.PrepareCaptureDevice()

'States
 Global CurrentState:Type_Class
 Global Actions:TList = CreateList()
 Global Act_Ideal:Type_Class = AddNewType(actions, "Ideal")
 Global Act_Recording:Type_Class = AddNewType(actions, "Recording")
 Global Act_Playing:Type_Class = AddNewType(actions, "Playing")
 CurrentState = Act_Ideal
 
'Xors

 xSetEngineSetting("Splash::TilingTime", "0")
 xSetEngineSetting("Splash::AfterTilingTime", "0")
 xSetEngineSetting("Engine::Multithreaded", "1")
 
 Const SHeight = 600
 xGraphics3D (800, SHeight)
 
 Global Camera = xCreateCamera()
 xCameraClsColor(Camera, 0, 100, 100)
 
 MA.SetMyAlba()

'Interface
 Global Record_bt = alCreateGButton(alDesktop, "Record", 0, 30, 70, 30)
 Global Play_bt = alCreateGButton(alDesktop, "Play", 70, 30, 70, 30)
 Global Stop_bt = alCreateGButton(alDesktop, "Stop", 140, 30, 70, 30) ; alhide(stop_bt)
 Global ResetSample_bt = alCreateGButton(alDesktop, "ResetSample", 210, 30, 110, 30) ; alhide(stop_bt)
 
 Global Waitsound_cb:SwitchButton_Type = MA.CreateSwitchButton(alDesktop, "On Record, Wait Sound", 550, 80)
 Global MetronomOn_cb:SwitchButton_Type = MA.CreateSwitchButton(alDesktop, "Metronom On", 550, 120) ; ma.SetSwitchButtonState(MetronomOn_cb, False)
 
'State Presentor
 Global CurrentState_TB = alcreategtextbox(alDesktop, "", 5, 550, 200, "CurrentState")
 alLock(CurrentState_TB)
 alSetValueS(CurrentState_TB, Act_Ideal.Name)
 
'Info Section
 Global Output_Ta = alcreategtextarea(alDesktop, "", 250, 500, 200, 4, "Output")
 Global ClearOutputWin_bt = alCreateGButton(alDesktop, "Clear", 450, 563, 50, 30)
 
'Sample Length Decider
 Global SampleLength_TB = alcreategtextbox(alDesktop, "2", 5, 350, 150, "SampleLengthInSeconds")
 
'Upper Menu
 Global Head_menu = alCreateGMenu()
Global SaveSample_mi = alAddMenuItem(Head_menu, "File\SaveSample")
Global LoadSample_mi = alAddMenuItem(Head_menu, "File\LoadSample")
Global Exit_mi = alAddMenuItem(Head_menu, "File\Exit")

'My Sample
 Global MySample:MySample_Type = New MySample_Type
 
 Global ReadSample_Flg = True
 
 Global MetroSnd:TSound = LoadSound("Metronome.wav")
 
 Global KeepStartMS:Float
 
 'SampleEditing
  Const SEX = 170
  Const SEY = 300
  Const SEWidth:Float = 600
  Const SEHeight:Float = 100
  Global SE_Image = xcreateimage(sewidth, seheight)
  ClearSampleDisplay()
 
  Const InfoDisplayX = 550

Repeat

xCls
xRenderWorld()
xUpdateWorld(5)

xDrawImage(SE_Image, SEX, SEY)

Select CurrentState

   'Ideal
Case Act_Ideal

xText(100, 180, "5 to Increase Volume")

   'Record Sample Start
If alTestClick(Record_bt) Then
MO.ClearAudioSample(MySample.Sample)
MySample = MO.CreateNewMySample(alGetValueI(SampleLength_TB))
    ChargeAction(Act_Recording)
alHide(Record_bt)
alHide(Play_bt)
ReadSample_Flg = False
End If

   'Play Sample
If xKeyHit(xKEY_2) Or alTestClick(Play_bt) And MySample.Sample.length > 0 Then ChargeAction(Act_Playing)

   'Clear Sample
If alTestClick(ResetSample_bt) Then MO.ClearAudioSample(MySample.Sample) ; OutputMessage("Reseting Sample")

   'Increase Volume
If xKeyHit(xKEY_5) Then MO.IncreaseSampleVolume(MySample.Sample)

   'Load Sample
If MA.menuitemclicked(LoadSample_mi) Then
Local FileAndPath:String, TS:TAudioSample
FileAndPath = RequestFile("ChooseSample",,, CurrentDir() + "\")
TS = LoadAudioSample(FileAndPath)
If TS <> Null Then MySample.Sample = TS
DisplayRecordedSampleLater(MySample)
End If

   'Save Sample As
If MA.menuitemclicked(SaveSample_mi) Then MO.SaveSample(MySample)

   'Clear output win
If alTestClick(ClearOutputWin_bt) Then alSetValueS(Output_Ta, "")

   'Show Volume On Ideal
DisplaySampleVolume(0, 500)

   'Tests
   
   'R Cut sample
If xKeyHit(xKEY_R) Then CutSample(MySample.Sample) ; PlayMetronom()

   'PlaySampleSection
    If xKeyHit(xKEY_P) Then MO.PlaySampleSection(MySample, 1, 4)    

   'Recording
Case Act_Recording

    Local Answer

   'Sampeling
Answer = MO.Sampeling_Process(5, MySample)

Select Answer

       'Capturing Audio
Case 1

'Play Metronom
If MA.GetSwitchButtonState(MetronomOn_cb) = True Then PlayMetronom()

'Rec Clock Counter
PlayClock()

DrawRecordedSampleWhileRecording(MySample)

DrawCursorWhileRunning(MySample)

   'End Sampeling  
Case 2
DisplayRecordedSampleLater(MySample)
    ActionEnder()
   'Wait For Sound 'Wait Sound
Default
KeepStartMS = MilliSecs()
EndSelect

xColor (255, 234, 0)
xText(InfoDisplayX, 540, "Sample Recording Counter: " + MO.LengthCounterInBytes)

   'Stop
If xKeyHit(xKEY_ESCAPE) Or alTestClick(Stop_bt) Then ActionEnder()

       'Play Sample
Case Act_Playing

PlayClock()

DrawCursorWhileRunning(MySample)

   'Stop
If xKeyHit(xKEY_ESCAPE) Or alTestClick(Stop_bt) Or ChannelPlaying(MySample.Channel) = False Then
If ChannelPlaying(MySample.Channel) Then StopChannel(MySample.Channel)
ActionEnder()
End If

End Select

xColor (255, 234, 0)

xText(InfoDisplayX, 500, "General Memory: " + GetMem())
  If MySample.Sample <> Null xText(InfoDisplayX, 520, "Sample Length Limit: " + MySample.Sample.length)

   'Display Sample wave
    xText (170, 280, "SampleWave")

   'Sample Details
    xText (170, 410, "Details:")
xText (170, 430, "Hertz: " + MySample.Sample.hertz)
If MySample.Sample.hertz > 0 Then
xText (170, 450, "Length In Seconds: " + MySample.Sample.length / MySample.Sample.hertz / 4)
xText (170, 470, "Length In KB: " + MySample.Sample.length / 1024)
End If

Delay(4)
MA.UpdateALEvents()
xFlip()

   'Exit
If xKeyHit(KEY_ESCAPE) Or xWinMessage("WM_CLOSE") Or MA.menuitemclicked(Exit_mi) Then QuitSection()

Forever

'-----------------------------'

Function QuitSection()
End
End Function

'Messages

Function OutputMessage(Message:String)
alSetValueS(Output_Ta, alGetValueS(Output_Ta) + "Reseting Sample" + "~n")
End Function

'Editing

Function CutSample(Sample:TAudioSample Var, From = 0, Till = 0)
Local T:TAudioSample, KeepSize, HalfLength

KeepSize = Sample.length

HalfLength = Sample.length / 2

T = CreateAudioSample(KeepSize, MO.SAMPLE_RATE, SF_STEREO16LE)

MemCopy(T.samples, Sample.samples, Sample.length)

For Local I = HalfLength To Sample.length
Sample.samples[I - HalfLength] = T.samples[I]
Next

End Function

'-------Metronom--------------'

Global MiliKeeper  'Needed
Global MetroPace

Function PrepareMetronom(ToBit = 120)
MetroPace = 60000 / ToBit
MiliKeeper = MilliSecs()
End Function

Function PlayMetronom()
If MilliSecs() >= MiliKeeper Then
PlaySound(MetroSnd)
MiliKeeper = MilliSecs() + MetroPace
End If
End Function

   'Clock

Function PlayClock()
xText (InfoDisplayX, 580, "Clock Counter: " + (MilliSecs() - KeepStartMS) / 1000)
End Function

   'Cursor

Function DrawCursorWhileRunning(MySample:MySample_Type)
Local X:Float = (SEWidth / MySample.Sample.length) * ((MilliSecs() - KeepStartMS) / 1000 * MySample.Sample.hertz * 4)
xColor(0, 255, 0)
xLine (SEX + X, SEY, SEX + X, SEY + SEHeight)
End Function

'-------Display-----------'

Function DisplaySampleVolume(Xpos:Float = 0, YPos:Float = 0, AlsoReadSample = True)

If AlsoReadSample = True Then MO.ReadBufferedSample_Int()

xText (Xpos, YPos - 20, "Audio Volume")
If MO.GetVolumeSize() > 0.1 Then
xColor(0, 255, 0)
xRect (Xpos, YPos, MO.GetVolumeSize(), 30, 1)
End If

xColor(255, 0, 0)
xRect (Xpos, YPos, 150, 30)

End Function

Global PreviousDX:Float
Const DiversionAppearance = 8192

Function DisplayRecordedSampleLater(MySample:MySample_Type)
Local Ratio:Float, RealPointer:Short Ptr

Ratio = SEWidth / MySample.Sample.length * 2

RealPointer = Short Ptr(MySample.sample.Samples)

ClearSampleDisplay()

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)

For Local I = 0 To MySample.sample.length Step 16
  Local Value:Int = RealPointer[I]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight)
        xRect (I * Ratio, SEHeight / 2, 1, Value)
Next

xSetBuffer(xBackBuffer())

End Function

Global Test

Function DrawRecordedSampleWhileRecording(MySample:MySample_Type)
Local Ratio:Float, RealPointer:Short Ptr, SLIS:Float, Point

SLIS = MO.GetSampleLengthInSecs(MySample.Sample)

Ratio = SEWidth / (SLIS * 1000)

RealPointer = Short Ptr(MySample.sample.Samples)

Point = (MilliSecs() - KeepStartMS) * Ratio

'Local Sample:Float[1]
'MO.RingBuffer.PeakLast(Sample, 0, 4)
'Print Sample[0]

If Point / 4 = Floor(Point / 4) Then

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)

  Local Value:Int = RealPointer[point]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight)
        xRect (Point, SEHeight / 2, 1, Value)

xSetBuffer(xBackBuffer())

End If

End Function

'Clear

Function ClearSampleDisplay()
xSetBuffer(xImageBuffer(SE_Image))
xCls()

xColor(0, 0, 0)
xRect(0, 0, SEWidth - 1, SEHeight - 1, 1)

   'Frame
xColor(255, 0, 0)
xRect(0, 0, SEWidth - 1, SEHeight - 1)

xSetBuffer(xBackBuffer())
End Function

'-----------Externals-----------'

Global MemFreq
Global Mem:String
Function GetMem:String()

   'Memory
    If MemFreq = 0 Then
Mem = (GCMemAlloced() / 1024) + "K"
Else
If MemFreq > 100 Then MemFreq = -1
End If

MemFreq = MemFreq + 1

Return Mem
End Function

Function ShortToInt:Int(s:Short)   'This gives the number minus if needed from -32768 to 32767
      Return (s Shl 16) Sar 16
End Function

Function ByteToInt:Int(B:Byte)
    Return B - 128
End Function

   'Print Sample
    Rem
ShortSample:Short Ptr = Short Ptr(MyRecording.Samples)
For Local i:Int = 0 To MyRecording.length / 2 - 1
   Print ShortSample[i]
Next
End rem

'----------Actions-----------'

Function ChargeAction(Action:Type_Class)

CurrentState = Action
alShow(Stop_bt)
alSetValueS(CurrentState_TB, Action.Name)

KeepStartMS = MilliSecs()

Select Action

Case Act_Recording

PrepareMetronom()
MO.PrepareBeofreCapture()
ClearSampleDisplay()

Case Act_Playing

MO.StartPlayMySample(MySample)
End Select

End Function

Function ActionEnder()
CurrentState = Act_Ideal
alSetValueS(CurrentState_TB, Act_Ideal.Name)
alShow(Play_bt)
alShow(Record_bt)
ReadSample_Flg = True
End Function

'--Types--'  add this to blitzmax my lib

Type Type_Class

Field Name:String
Field Flags
Field FileEnding:String

Field TypeParent:Type_Class
Field TypeGroup:TList

Field MediaHandle  'NEW!!

Field SubTypes_List:TList = CreateList()

   'Data
Field Value:Float     'maybe value should be string?

   'Temp Parameter
Field ALHandle
Field Remark:String 'not used yet

End Type

Function GetTypeByName:Type_Class(TypesList:TList, name:String)
Local TempElementType:Type_Class
For TempElementType = EachIn TypesList
If Lower(name) = Lower(TempElementType.name) Then Return TempElementType
Next
End Function

Function AddNewType:Type_Class(TypeGroup:TList, TypeName:String, FileEndings:String = "", Value:Float = Null)
Local NewElementType:Type_Class

NewElementType = New Type_Class

NewElementType.TypeGroup = TypeGroup

NewElementType.name = TypeName
NewElementType.FileEnding = FileEndings
NewElementType.value = value

If ListContains(TypeGroup, NewElementType) = False Then ListAddLast(TypeGroup, NewElementType)

Return NewElementType
End Function

Function AddSubType:Type_Class(ParentType:Type_Class, TypeName:String, FileEndings:String = "", Value:Float = Null)
Local TrackSection:Type_Class
TrackSection = AddNewType(ParentType.TypeGroup, TypeName, FileEndings, Value)
TrackSection.TypeParent = ParentType
ListAddLast(ParentType.SubTypes_List, TrackSection)
Return TrackSection
End Function

Function ReturnTypeFileEndingList:String[] (SonType:Type_Class) 'Returns an array of the specific Sontype possible FileName ending. exmp: sprite is "png|apng|jpg|ajpg"
Local I, TypeEnd:String[], CurrentMid:String, CurrentEnd:String

Local LenTargetFileEnding = Len(SonType.FileEnding)

   'Eliminating zero cell
TypeEnd = TypeEnd +[""]

For I = 1 To LenTargetFileEnding
CurrentMid = Mid(SonType.FileEnding, I, 1)
If CurrentMid = "|" Then
TypeEnd = TypeEnd +[CurrentEnd]
CurrentEnd = ""
Else If I = LenTargetFileEnding
TypeEnd = TypeEnd +[CurrentEnd + CurrentMid]
Exit
Else
CurrentEnd = CurrentEnd + CurrentMid
End If
Next

Return TypeEnd
End Function

The included File that i made
Code: [Select]
'PauseChannel
'ResumeChannel
'CueSound
'StopChannel

Type MyOpenAL_Type

Const SAMPLE_RATE:Int = 44100   'In Hertz [Hz] *4 Bytes

Field Data:Byte[] = New Byte[SAMPLE_RATE * 2] '16 Bit per sample
Field RingBuffer:TFloatRingBuffer = TFloatRingBuffer.Create(SAMPLE_RATE)

Const SampBuffSize = 1800
Field SamplesBuff:Float[] = New Float[SampBuffSize] 'This number is temp

Field Device:Byte Ptr, Context:Byte Ptr
Field CaptureDevice:Byte Ptr

Field NumAvailableSamples:Int
Const SampelingChunckSize:Float = 1000 'I think its the chunck of the moment sampeling
Field SampleCurrentVolume:Float

Global MySamplesList:TList = CreateList()

Method PrepareOpenAL:Byte Ptr(PrintListOfCaptureDevices = True)

TOpenAL.Open()
TOpenAL.InitAL()
TOpenAL.InitALC()

Device = alcOpenDevice(Null)
If Device = Null Then WriteStdout "Device is null when using alcOpenDevice~nplease check or approve in windows10 microphone capture"
Context = alcCreateContext(Device, Null)
alcMakeContextCurrent(Context)

   'List of Capture Devices
If PrintListOfCaptureDevices
WriteStdout("List of capture devices:~n")
For Local CaptureDevice:String = EachIn EnumCaptureDevices()
WriteStdout(CaptureDevice + "~n")
Next
WriteStdout("#Default Capture Device: " + String.FromCString(alcGetString(Null, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER)))
End If

End Method

   'Starts the capture but capture nothing
Method PrepareCaptureDevice:Byte ptr()
CaptureDevice = alcCaptureOpenDevice(Null, SAMPLE_RATE, AL_FORMAT_STEREO16, SAMPLE_RATE) 'i think buffer size is the size for the gaps of the cpu
If CaptureDevice = Null Then Notify ("CaptureDevice is null") ; Return
alcCaptureStart(CaptureDevice)
Return CaptureDevice
End Method

'Create
   
Method CreateNewMySample:mysample_type(LengthInSeconds:Float)
Local MS:MySample_Type
MS = New MySample_Type
MS.Sample = CreateAudioSample(SAMPLE_RATE * 4 * LengthInSeconds, SAMPLE_RATE, SF_STEREO16LE)
ListAddLast(MySamplesList, MS)
Return MS
End Method

Method DeleteMySample(MySample:MySample_Type Var)
If MySample = Null Then Return
ListRemove(MySamplesList, MySample)
MemFree(MySample.Sample)
MySample = Null
End Method
 
'Capture

Field GateOpen_flg  'Wait Sound Gate
Field LengthCounterInBytes

Method PrepareBeofreCapture()
LengthCounterInBytes = 0
End Method

Method Sampeling_Process(WaitSoundSensitivity:Float = 0, MySample:MySample_Type)
        Local CapturedSamplesLengthVar

   'Wait Note Block
If WaitSoundSensitivity > 0 And GateOpen_flg = False Then
ReadBufferedSample_Int()
if GetVolumeSize() > WaitSoundSensitivity Then
GateOpen_flg = True
Else
Return 0
End If
End If

       'Sample
    If LengthCounterInBytes <= MySample.Sample.length Then
 
       'Ask for number of samples
        alcGetIntegerv(Capturedevice, ALC_CAPTURE_SAMPLES, 4, Varptr(CapturedSamplesLengthVar))

       'Now copy them to Samples
        alcCaptureSamples(Capturedevice, MySample.Sample.Samples + LengthCounterInBytes, CapturedSamplesLengthVar)

       'Move pointer to the new end
        LengthCounterInBytes = LengthCounterInBytes + CapturedSamplesLengthVar * 4

Return 1
   'End Sampeling
  Else
Return 2
End If

End Method

Method ClearAudioSample(Sample:TAudioSample Var)
GateOpen_flg = False
End Method

Method SaveSample(MySample:MySample_Type, SampleFilename:String = "")
        Local WavFile:String
        Local SndBank:TBank, FileStream:TStream
        Local Channels:Int, BitRate:Int, BlockAlign:Int

       'Filename   
If SampleFilename = "" Then
WavFile:String = RequestFile:String("Save as...", "Wav files:wav", True)
Else
WavFile = SampleFilename
End If

        Channels = 2
        BitRate = 16
        BlockAlign = 4
       
       'Create a bank from the Samples
        SndBank = CreateStaticBank(MySample.Sample.samples, MySample.Sample.length)

       'Prevent
If WavFile = "" Then Notify "WaveFile is Null" ; Return

        fileStream = WriteStream(wavFile$)
       
       'write 44-byte wav header info
        fileStream.WriteString("RIFF")                          '"RIFF" file description header (4 bytes)
        FileStream.WriteInt(MySample.Sample.length + 40)        'file size - 8 (4 bytes)
        fileStream.WriteString("WAVE")                          '"WAVE" description header (4 bytes)
        fileStream.WriteString("fmt ")                          '"fmt " description header (4 bytes)
        fileStream.WriteInt(16)                                 'size of WAVE section chunk (4 bytes)
        fileStream.WriteShort(1)                                'WAVE type format (2 bytes)
        fileStream.WriteShort(Channels)                         'mono/stereo (2 bytes)
        FileStream.WriteInt(MySample.Sample.hertz)              'sample rate (4 bytes)
        FileStream.WriteInt(MySample.Sample.hertz * BlockAlign) 'avg bytes/sec (4 bytes)
        fileStream.WriteShort(BlockAlign)                       'Block alignment (2 bytes)
        fileStream.WriteShort(BitRate)                          'Bits/sample (2 bytes)
        fileStream.WriteString("data")                          '"data" description header (4 bytes)
        FileStream.WriteInt(MySample.Sample.length)             'size of data chunk (4 bytes)
             
       'Write wav sound data
        SndBank.Write(FileStream, 0, MySample.Sample.length)
         
       'close the stream
        CloseStream fileStream
End Method

   'Play
   
Method StartPlayMySample(MySample:MySample_Type)
Local Sound:TSound = LoadSound(MySample.Sample)  'load sound also load from memory
If Sound = Null Then Notify "Sound not loaded" ; Return - 1
  MySample.channel = PlaySound (Sound)
End Method

Field PTimeCounter

Method PlaySampleSection(MySample:MySample_Type, StartPointInSecs:Float = 0, LengthInSecs:Float = 0)
Local LengthInBytes, StartPointInBytes

If LengthInSecs <= 0 Then Return

LengthInBytes = LengthInSecs * SAMPLE_RATE * 4
StartPointInBytes = StartPointInSecs * SAMPLE_RATE * 4

Local TempSample:TAudioSample = CreateAudioSample(LengthInBytes, SAMPLE_RATE, SF_STEREO16LE)
Local Counter
For Local I = StartPointInBytes To StartPointInBytes + LengthInBytes
TempSample.samples[Counter] = MySample.Sample.samples[I]
Counter = Counter + 1
Next

Local TS:TSound = LoadSound(TempSample)
PlaySound(TS)
End Method

Method ReadBufferedSample_Int()  'Read sample? maybe prepare sample for recording

If RingBuffer.GetSize() >= SampelingChunckSize Then RingBuffer.PeakLast(SamplesBuff, 0, SampelingChunckSize)

alcGetIntegerv(CaptureDevice, ALC_CAPTURE_SAMPLES, 4, Varptr(NumAvailableSamples))

If NumAvailableSamples > 0 Then
alcCaptureSamples(CaptureDevice, Data, NumAvailableSamples)
For Local I:Int = 0 Until NumAvailableSamples
Local Offset:Int = I*2 ' 16 Bit per sample
Local Sample:Float = DecodeSigned16BitLittleEndian(Data, Offset)
RingBuffer.Put(Sample)
Next
EndIf

End Method

 'Get
 
Method GetPositionByTime(MySample:MySample_Type, TimeInSecs:Float)
Return MySample.Sample.hertz * 4 * TimeInSecs
End Method

Method GetSampleLengthInSecs:Float(Sample:TAudioSample)
Return Sample.length / Sample.hertz / 4
End Method

Method GetVolumeSize:Float()
Local SizeInt:Int = SampelingChunckSize
SampleCurrentVolume = 0
For Local I:Int = 0 Until SizeInt
SampleCurrentVolume = SampleCurrentVolume + (SamplesBuff[I] * SamplesBuff[I])
Next
SampleCurrentVolume = Sqr(SampleCurrentVolume / SizeInt)
Assert 0 <= SampleCurrentVolume And SampleCurrentVolume <= 1
Return SampleCurrentVolume * SizeInt
End Method

 'Functions
 
Method IncreaseSampleV34olume(Sample:TAudioSample)
Local I

If Sample = Null Then Return
If Sample.length = 0 Then Return

For I = 0 To Sample.length Step 2
Sample.samples[I] = Sample.samples[I] * 2
Sample.samples[I + 1] = Sample.samples[I + 1] * 2
Next

End Method

Method IncreaseSampleVolume(Sample:TAudioSample, Increment:Float = 1.1)
Local Left:Short, Right:Short, I

For I = 0 To Sample.length Step 4

Left = Sample.samples[I] + Sample.samples[I + 1] * 256
Right = Sample.samples[I + 2] + Sample.samples[I + 3] * 256

   'Left
Local RealLeft# = Left
If RealLeft > 32768 Then RealLeft = RealLeft - 65535

   'now volume:
RealLeft = RealLeft * Increment
   'Now back to SHORT
    If RealLeft < 0 Then RealLeft = RealLeft + 65535

Left=RealLeft
Sample.samples[I + 2] = Left Mod 256
Sample.samples[I + 3] = Left / 256

   'Right
Local RealRight:Float = Right
If RealRight > 32768 Then RealRight = RealRight - 65535

   'now volume:
RealRight = RealRight * Increment
   'now back to SHORT
    If RealRight < 0 Then RealRight = RealRight + 65535

Right = RealRight
Sample.samples[I + 2] = Right Mod 256
Sample.samples[I + 3] = Right / 256

Next

End Method

'--End And Reset--'

Method EndOpenAL()
alcMakeContextCurrent(Null)
If Context <> Null Then alcDestroyContext(Context)
If Device <> Null Then alcCloseDevice(Device)
TOpenAL.Close()
End Method

Method EndCaptureSound()
If CaptureDevice = Null Then Return
alcCaptureStop(CaptureDevice)
CaptureDevice = Null
End Method

' Method ResetCapturing_Int()
' SamplesBuff = Null
' SamplesBuff = New Float[SampBuffSize]
' GateOpen_flg = False
' MySample.LengthCounterInBytes = 0
' End Method

'--Mine--'

Method WaitSound_Gate(GateSensitivity:Float = 1)
  Return SampleCurrentVolume * SampelingChunckSize >= GateSensitivity
End Method

'----------------------Externals---------------------'

Method EnumCaptureDevices:String[]()
Local List       : Byte Ptr, ..
      Specifiers:String[]

   'Null-terminated specifier list
List = alcGetString(Null, ALC_CAPTURE_DEVICE_SPECIFIER)

   'Separate specifier by null character
While List[0]
Specifiers = Specifiers[..Specifiers.Length + 1]
Specifiers[Specifiers.Length - 1] = String.FromCString(List)
List :+ Specifiers[Specifiers.Length - 1].Length + 1
Wend

Return Specifiers
End Method

Method DecodeSigned16BitLittleEndian:Float(Buffer:Byte Ptr, Offset:Int)
Local LowerB:Byte, HigherB:Byte
HigherB = Buffer[offset + 1]
LowerB = Buffer[offset]
Local SampleInt:Int = (HigherB Shl 8) | (LowerB & $ff)
If SampleInt & $8000 Then SampleInt = $FFFF8000 | (SampleInt & $7FFF)
Local Sample:Float = Float(SampleInt) / Float($7fff)
Return Sample
End Method

End Type

Type TFloatRingBuffer
Field capacity:Int
Field size:Int
Field count:Long
Field elements:Float[]
Field nextIdx:Int = 0

Function Create:TFloatRingBuffer(capacity:Int)
If capacity <= 0 Then Throw "capacity " + capacity + " <= 0"
Local ringBuffer:TFloatRingBuffer = New TFloatRingBuffer
ringBuffer.capacity = capacity
ringBuffer.elements = New Float[capacity]
Return ringBuffer
End Function

Method put(e:Float)
elements[nextIdx] = e
nextIdx = (nextIdx + 1) Mod capacity
If size < capacity Then size :+ 1
count :+ 1
End Method

Method peak(start:Long, buffer:Float Ptr, offset:Int, length:Int)
If length = 0 Then
Return
ElseIf start >= count Then
Throw "try To read at " + start + ", but count is " + count
ElseIf start < count - size Then
Throw "try to read at " + start + ", but oldest available is at " + (count - size)
Else If start + length > count Then
Throw "try to read " + length + " at " + start + ", but count is " + count
EndIf

Local startIdx:Int = (nextIdx - (count - start)) Mod capacity;
If startIdx < 0 Then startIdx :+ capacity;
For Local i:Int = 0 Until length
Local j:Int = (startIdx + i) Mod capacity;
Local element:Float = elements[j]
buffer[i + offset] = element
Next
End Method

Method PeakLast(buffer:Float Ptr, offset:Int, length:Int)
Local start:Long = count - length
peak(start, buffer, offset, length)
End Method

Method PeakLastElement:Float()
If size = 0 Then Throw "size is 0"

Local index:Int = (nextIdx - 1) Mod Capacity
If index < 0 Then index :+ capacity
Return elements[index]
End Method

Method get:Float(start:Long)

If start >= count Then
Throw "try to read at " + start + ", but count is " + count
Else If (start < count - size) Then
Throw "try to read at " + start + ", but oldest available is at " + (count - size)
EndIf

Local startIdx:Int = (nextIdx - (count - start)) Mod capacity;
If startIdx < 0 Then startIdx:+capacity;

Return elements[startIdx]
End Method

Method GetCapacity:Int()
Return capacity
End Method

Method getSize:Int()
Return size
End Method

Method IsEmpty:Int()
Return getSize() = 0
End Method

Method getCount:Long()
Return count
End Method

Method clear()
nextIdx = 0
size = 0
count = 0
End Method

End Type

Type MySample_Type

Field Sample:TAudioSample
Field Channel:TChannel

End Type


Function MakeSinus(Duration:Int, PulseA:Int, PulseB:Int)  'makes an sinus audio sample
    Local SAMPLERATE = 44000
    Local SampleLen:Int= SAMPLERATE*Duration/1000
    Local FaktorA:Float = 360.0/PulseA
    Local FaktorB:Float = 360.0/PulseB

    Local Sample:TAudioSample = CreateAudioSample( SampleLen, SAMPLERATE, SF_MONO8 )

    For Local n:Int=0 Until SampleLen
            Local ValueA:Int, ValueB:Int

            If PulseA=0
                    ValueA=0
            Else
                    ValueA=60* Sin(FaktorA*(n Mod PulseA))
            EndIf
            If PulseB=0
                    ValueB=0
            Else
                    ValueB=60* Sin(FaktorB*(n Mod PulseB))
            EndIf

            Sample.samples[n] = 127+ValueA+ValueB
    Next

    Local Audio:TSound = LoadSound(Sample)

    PlaySound (Audio)

End Function




« Last Edit: July 16, 2021, 18:54:23 by Hardcoal »
Some things can break me down.
Some cant

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: My Music Editor
« Reply #67 on: July 16, 2021, 16:46:47 »
Why 2 Display functions?

I do not understand why you are using two completely different function to display the Audio later or during the recording.

I would guess that the function DisplayRecordedSampleLater() would deliver good result when you already call it during the recording process. did you test this already?

Code: BlitzMax
  1. Select Answer
  2.         Case 1
  3.                 If MA.GetSwitchButtonState(MetronomOn_cb) = True Then PlayMetronom()
  4.                 PlayClock()
  5.                 DisplayRecordedSampleLater(MySample)
  6.                 DrawCursorWhileRunning(MySample)
  7.         Case 2
  8.  

I guess the reason why the DisplayRecordedSampleLater() is working and the DrawRecordedSampleWhileRecording() is not working is related to the fact, that the DisplayRecordedSampleLater() uses a FOR/NEXT-loop and the Stepwidth=16

Please tell me (for my better understanding) the reason why you use two different functions.


OpenAl delivers not single values but bulks

When the capture in ON each second 88200 samples are coming in. Means each msec 88.2 samples are coming in. Your Main loop may cycle each 16msec. This means that each time when the DrawRecordedSampleWhileRecording() is called again new 1411 Samples reached the TAudioSample.

So why do you not use a FOR/NEXT-loop, but only calculate the value of one (the last) sample value? This may be your mistake in thinking.


To many rect paintings?

Another thing I cannot understand is the variable Ratio:Float This means that you try to paint each single sample value to a much to small window. Hundreds of values are drawn to the same x-ccordinate and overpaint each other. This also costs performance. This would draw 176400 rects (for a TAudioSample of 2sec) on a space of only 600 pixels

You should really change the system: Check the avaiable number of X-coordinates (600?) and then calculate for each of them a corresponding sample position in the TAudioSample. This would pick only 600 Sample-Positions out of the 176400 and draws only 600 rects.

Floats? Integers? Ringbuffer?

You are using different functions to convert the Audio-Shorts:
Code: [Select]
Method DecodeSigned16BitLittleEndian:Float(Buffer:Byte Ptr, Offset:Int)
Local LowerB:Byte, HigherB:Byte
HigherB = Buffer[offset + 1]
LowerB = Buffer[offset]
Local SampleInt:Int = (HigherB Shl 8) | (LowerB & $ff)
If SampleInt & $8000 Then SampleInt = $FFFF8000 | (SampleInt & $7FFF)
Local Sample:Float = Float(SampleInt) / Float($7fff)
Return Sample
End Method

....

Function ShortToInt:Int(s:Short) 
      Return (s Shl 16) Sar 16
End Function

.....


Left = Sample.samples[I] + Sample.samples[I + 1] * 256
Right = Sample.samples[I + 2] + Sample.samples[I + 3] * 256

Why?



Can you please descripe what exactly are the steps the samples values are doing from the moment when they reach OpenAL. Why do you use this RingBuffer? What is it good for? Where do you store the samples into the TAudioSample?


Volume function with use of ShortPointer:

I revised your IncreaseSampleVolume() function to show you how the use of Short Ptr and ShortToInt() can simplify the calculation:

easy to understand version:
Code: BlitzMax
  1. Method IncreaseSampleVolume(Sample:TAudioSample, Increment:Float = 1.1)
  2.                 Local ShortPointer:Short Ptr = Short Ptr(Sample.Samples)
  3.  
  4.                 For local i% = 0 To Sample.Length-1
  5.                         Local LeftValue:Int  = ShortPointer[2*i]
  6.                         Local RightValue:Int = ShortPointer[2*i+1]
  7.                        
  8.                         RightValue = ShortToInt(RightValue)
  9.                         LeftValue  = ShortToInt(LeftValue)
  10.                        
  11.                         RightValue = RightValue*Increment
  12.                         LeftValue  = LeftValue*Increment
  13.                        
  14.                         ShortPointer[2*i]   = LeftValue
  15.                         ShortPointer[2*i+1] = RightValue
  16.                 Next   
  17. End Method
  18.  


minimalized code version:
Code: BlitzMax
  1. Method IncreaseSampleVolume(Sample:TAudioSample, Increment:Float = 1.1)
  2.                 Local ShortPointer:Short Ptr = Short Ptr(Sample.Samples)
  3.                 For I = 0 To 2*Sample.length-1
  4.                         ShortPointer[i]   = ShortToInt( ShortPointer[i]   ) *Increment
  5.                 Next   
  6. End Method
« Last Edit: July 16, 2021, 18:12:07 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2484
Re: My Music Editor
« Reply #68 on: July 16, 2021, 18:46:10 »
Quote
I thought the exact hertz are 44100 not 44000..

yup 44100 is the exact number - using different variations will result in pitch changes.

Just my thoughts here:
I am one of the only other people doing this sort of low level stuff. I find the explanations for how you (midimaster) are approaching things highly confusing (and I know exactly what I am looking at). I can fully understand why Hardcoal is having issues.

Let's just for example take the usual approach:
1. open the connection to the hardware and pick the amount of data channels to send/receive data. THIS WILL NEVER CHANGE. once you open it, you leave it alone until you close it when you are finished.
2. it is then YOUR apps responsibility to keep the audio buffer full - how you do that is up to you. but it is usually by a ring buffer or threaded callback (or a mixture of both)
3. the pitch of any playing note is calculated and you feed the data to the audio buffer in whatever way works. you NEVER shut down a buffer create a new buffer with a different frequency
4. Audio buffers once created stay that way
5. The audio buffers themselves are usually small 512 to 2048 is a usual length - the bigger the length the greater potential for latancy. the length is in the base format of your sample size when you opened the buffer. you usually measure the buffer in bytes. so for 2 channel 16bit at 1024 samples. it would be 2x2x1024 = 4096. data is simply fed in a left/right stream
6. the storage method you use for your apps sample data is your responsibility. but picking something like floats make life very simple. the only thing you need to do feed the buffer doing a convert from your float to the sample format you opened the buffer with.
7. if your app sample data is simple then, displaying it will also be simple, as will modifing it

TBH I can't see a reason why Hardcoal is not using the built in freesound driver and base sound controls - unless realtime control over waveforms is really needed - which I can't say it is...





Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: My Music Editor
« Reply #69 on: July 16, 2021, 23:49:28 »
yup 44100 is the exact number - using different variations will result in pitch changes.

Just my thoughts here:
I am one of the only other people doing this sort of low level stuff. I find the explanations for how you (midimaster) are approaching things highly confusing (and I know exactly what I am looking at). I can fully understand why Hardcoal is having issues.

Let's just for example take the usual approach:
1. open the connection to the hardware and pick the amount of data channels to send/receive data. THIS WILL NEVER CHANGE. once you open it, you leave it alone until you close it when you are finished.
2. it is then YOUR apps responsibility to keep the audio buffer full - how you do that is up to you. but it is usually by a ring buffer or threaded callback (or a mixture of both)
3. the pitch of any playing note is calculated and you feed the data to the audio buffer in whatever way works. you NEVER shut down a buffer create a new buffer with a different frequency
4. Audio buffers once created stay that way
5. The audio buffers themselves are usually small 512 to 2048 is a usual length - the bigger the length the greater potential for latancy. the length is in the base format of your sample size when you opened the buffer. you usually measure the buffer in bytes. so for 2 channel 16bit at 1024 samples. it would be 2x2x1024 = 4096. data is simply fed in a left/right stream
6. the storage method you use for your apps sample data is your responsibility. but picking something like floats make life very simple. the only thing you need to do feed the buffer doing a convert from your float to the sample format you opened the buffer with.
7. if your app sample data is simple then, displaying it will also be simple, as will modifing it

TBH I can't see a reason why Hardcoal is not using the built in freesound driver and base sound controls - unless realtime control over waveforms is really needed - which I can't say it is...

I do not know what you are talking about... but I'm 100% sure it has nothing to do with things I wrote ....
yup 44100 is the exact number - using different variations will result in pitch changes.
I wrote the 44000 in my example because it was much easier to calcultate/show the relations between 44000 88000 176000 and 352000. It is only any number!!! hä?

Caution: sarkasm:
44100 is the exact number? for what? Isn't 44000 not a exact number? or 38645? not a exact number? 44100 is the exact number for pressing CDs, but this was yesterday. 48000 is the new 44100. If you capture audio... it doesnt give a shit which value you are using. Yes, if you open a file recorded with 44100 you should play it with 44100, but if you record new things you can select HERTZ whatever you want.



I can fully understand why Hardcoal is having issues.
Hardcoal wrote this code without me. I saw the code today for the first time. He did not write it this way, because I suggest him to do so... hä?

1. open the connection to the hardware and pick the amount...
I did not write any word about using the hardware. I did not wrote anything about he should "close it".. hä?

2. it is then YOUR apps responsibility to keep the audio buffer full....
I only asked him, what the ringbuffer is good for, because I do not understand. Can you explain why he converts the incoming OAL data to FLOAT, then he sends it to a ringbuffer, then he does not use the ringbuffer, but converts again the OAL-Data to INTEGER, which he saves now to the TAudioSample?

3. the pitch of any playing note is calculated and you feed the data to the audio buffer in whatever way works. you NEVER shut down a buffer create a new buffer with a different frequency....
There are no pitch calculation i his code example? Who wants "to shut down a buffer and create a new buffer with a different frequency"? I did not write a single word about something like this.  What are you talking about?

I would say your 7 points are not related to any problem he has at the moment with his code. He tries to understand how the datas returned by the OAL are structurized. He tries to display them in a rectangle box.

I think his biggest problem is that he collected code from various sources and now is not able to find the reason, why all this is not working together.

He decided to work with format SF_16StereoLE. So I help him to understand what this means and how to handle it.


See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Hardcoal

  • Hero Member
  • *****
  • Posts: 748
  • Nothing is personal
Re: My Music Editor
« Reply #70 on: July 16, 2021, 23:54:39 »
Hi...
the increase volume thing i did is before i understood how exactly the structure of a sample is..
so ignore this..

I used two functions.. to display the wave. because The DisplayLater one works because its not real time..
and the other one which im struggling with is not working.

Since im only experimenting my functions are not optimized ..yet..
Im now reading your posts and trying to correct things..

so thanks for all the effort you guys are putting and ill go now and test my doings.

Im making slow progress in understanding all this .. and soon i believe ill get it.

I had to face many things i had no idea and still dont know 100% what they are..
Like I never used Byte ptr .. and such..

i have no idea what ringbuffer is.. so i experimented with it..
so excuse me if i did wrong..

-yea.. in my real time attepmt to draw the sample.. I did tried to read the last sample inserted. but its all experimental and i thought it will be good enough.

I dont understand how can i use the For next loop when the loop displays the whole  sample at once .. when its not even sampled yet..
thats i why i have the real time function attempt separately

- I dont understand why in your For,Next loops of the samples you dont do Step 4
I mean if each sample is 4 bytes, you should step 4 bytes each read.
Yet you dont..

- Im facing many gaps in my general Programming knowledge that needs to be filled lol
so thats why all this appear to be so hard.
while its not that hard for someone who understand things i realized I didnt


- I dont use this function DecodeSigned16BitLittleEndian. its only on my code and its not mine

-I know that hundreds of values are drawn to the same x Coord.. but as ive said.. its only experimental.. and i wont do it in the Final version..
Quote
Another thing I cannot understand is the variable Ratio:Float This means that you try to paint each single sample value to a much to small window. Hundreds of values are drawn to the same x-ccordinate and overpaint each other. This also costs performance. This would draw 176400 rects (for a TAudioSample of 2sec) on a space of only 600 pixels
« Last Edit: July 17, 2021, 00:59:39 by Hardcoal »
Some things can break me down.
Some cant

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: My Music Editor
« Reply #71 on: July 17, 2021, 00:59:18 »
My IncreaseSampleVolume() was only one review to show you how you could tiden your code and reduce the danger of bugs and mistakes. You should do the same with the calculation of "Audio-Shorts to Real value". At the moment you use 3 different approaches. Decide for one to do that job, then use it in all situations. 

"Display later" will have no performance problems if you code it perfect. This is your already working way to display the samples. So optimize it and you can use it also in the realtime situation of recording. And it will work also when the TAudioSample is still empty. It does not matter! Use it in the realtime recording situation too and you will see, how the buffer windows slowly gets filled with datas.

I had a very deep look into your source code and for me it looks like this "RingBuffer code parts" are all without function in your code.

... I dont understand why in your For,Next loops of the samples you dont do Step 4
I mean if each sample is 4 bytes, you should step 4 bytes each read.
Show me, which code snipplet you mean. The stepping depends on the needs we have.

The stepping depend on whether you calculating in Bytes or in Shorts.
Again! A SF_MonoLE Sample has only the size of 2Byte or 1Short.
Again! A SF_StereoLE Frame has 2 Samples (R and L) and has a size of 4Byte or 2Short.


If you paint AudioSamples you can step 16 or 1000 ahead. There are too much data anyway!

If you do different actions for RIGHT and LEFT channel you should step 2 SHORTS ahead. This is the correct stepping

If you plan to do the same action for LEFT and RIGHT you can step 1 SHORT ahead. After each LEFT follows a RIGHT, and after this RIGHT again a LEFT and so on....

Image shows RAM use of a SF_StereoLE:
1st line: Bytes
2nd line: Samples (SHORTs)
3rd line: Frames ( 2 SHORTS = 4 BYTES)


« Last Edit: July 17, 2021, 01:11:12 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Hardcoal

  • Hero Member
  • *****
  • Posts: 748
  • Nothing is personal
Re: My Music Editor
« Reply #72 on: July 17, 2021, 01:26:04 »
Good news For me.. I finally understood this Short Ptr and Byte Ptr.. purpose.. (at least I assume I have)

I finally Got it.. why you did this RealPointer thing..

It makes the reading jump in shorts instead in bytes..

the FitValue Function Ive made is just to adjust any wave height to any sample Square size Im intending to use..

Quote
TBH I can't see a reason why Hardcoal is not using the built in freesound driver and base sound controls - unless realtime control over waveforms is really needed - which I can't say it is...
Iwasadam.  eventually Im driving towards real time waveform manipulation..
but also i dont know what you mean by Freesound Driver? I broke my head over whole this sound thing.
took long time to get anything to work.
Im really clueless on this field, so i just follow what you guys suggest me..

Quote
So optimize it and you can use it also in the realtime situation of recording
what do you mean optimize my code? this Realtime function display the whole wave at once..
so it takes time to draw.. how can i use it in real time?
I need something that get a section of the new Sample.. not the whole sample..
so I dont know how to use it.. but maybe i can think of something.. will see..



-One things is driving me crazy more than anything at this point..
 when I create a sample its samples.length is for example is 128bytes..
 when I save it and reload it, it suddenly turns into a quarter in samples.length  (32bytes)
 You tried to explain me.. but I dont get it sorry

-Also When I record a sample and than replay it . it becomes twice longer to play..
 while after it plays the sound it plays total silent for the same length for no reason.

Anyway Ive managed to show live sampling display while recording.. and here is the code
Code: [Select]
Global PreviousLocation

Function DisplayRecordedSamplerRealTime(MySample:MySample_Type)
Local Ratio:Float, ShortPointerReading:Short Ptr

Ratio = (SampleDisplayWidth / MySample.Sample.length) * 2

ShortPointerReading = Short Ptr(MySample.sample.Samples)

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)
                                'Resolution
For Local I = PreviousLocation To MO.LengthCounterInBytes / 2 - 1 Step 16
  Local Value:Int = ShortPointerReading[I]
        Value = ShortToInt(Value)
Value = Math.FitValue(Value, DiversionAppearance, SEHeight / 2)
        xRect (I * Ratio, SEHeight / 2, 1, Value)
Next

xSetBuffer(xBackBuffer())

PreviousLocation = MO.LengthCounterInBytes / 2

End Function
so at least, this i managed to do..

-I fixed the increase volume thing and now it works fine

19:16
I create a 2 Seconds Sample using CreateAudioSample(SAMPLE_RATE * 4 * 2, SAMPLE_RATE, SF_STEREO16LE)
but when i play the sample.. it keeps playing for 8 seconds..


19:36
Ok Ive managed almost making everything works.. but with weird manipulations..

SavingSample:
Before I save a Sample I divide its length by 4.
Than After save I multiply the length again by 4 to restore it


After I end Recording a sample I Divide its Length by 4..
Only than it plays and stop on the right time.. if not, it will go for 8 seconds..

also in order for the cursor to move at the correct Pace
I need to use Different speeds

Code: [Select]

Function DrawCursorWhileRunning(MySample:MySample_Type, Recording = False)
Local Divide, X:Float
If Recording Then
Divide = 1000
Else
Divide = 4000
EndIf
X = (SampleDisplayWidth / MySample.Sample.length) * ((MilliSecs() - KeepStartMS) / Divide * MySample.Sample.hertz * 4)
xColor(0, 255, 0)
xLine (SEX + X, SEY, SEX + X, SEY + SEHeight)
End Function

*----------So everything is almost working somehow-----------*

but the only thing remains for now is that when i upload a sample
the wave looks different ..

maybe something is wrong with the save function
Code: [Select]
Method SaveSample(MySample:MySample_Type, SampleFilename:String = "")
        Local WavFile:String
        Local SndBank:TBank, FileStream:TStream
        Local Channels:Int, BitRate:Int, BlockAlign:Int

       'Filename   
If SampleFilename = "" Then
WavFile:String = RequestFile:String("Save as...", "Wav files:wav", True)
Else
WavFile = SampleFilename
End If

        Channels = 2
        BitRate = 16
        BlockAlign = 4
       
       'Create a bank from the Samples
        SndBank = CreateStaticBank(MySample.Sample.samples, MySample.Sample.length)

       'Prevent
If WavFile = "" Then Notify "WaveFile is Null" ; Return

        fileStream = WriteStream(wavFile$)
       
       'write 44-byte wav header info
        fileStream.WriteString("RIFF")                          '"RIFF" file description header (4 bytes)
        FileStream.WriteInt(MySample.Sample.length + 40)        'file size - 8 (4 bytes)
        fileStream.WriteString("WAVE")                          '"WAVE" description header (4 bytes)
        fileStream.WriteString("fmt ")                          '"fmt " description header (4 bytes)
        fileStream.WriteInt(16)                                 'size of WAVE section chunk (4 bytes)
        fileStream.WriteShort(1)                                'WAVE type format (2 bytes)
        fileStream.WriteShort(Channels)                         'mono/stereo (2 bytes)
        FileStream.WriteInt(MySample.Sample.hertz)              'sample rate (4 bytes)
        FileStream.WriteInt(MySample.Sample.hertz * BlockAlign) 'avg bytes/sec (4 bytes)
        fileStream.WriteShort(BlockAlign)                       'Block alignment (2 bytes)
        fileStream.WriteShort(BitRate)                          'Bits/sample (2 bytes)
        fileStream.WriteString("data")                          '"data" description header (4 bytes)
        FileStream.WriteInt(MySample.Sample.length)             'size of data chunk (4 bytes)
             
       'Write wav sound data
        SndBank.Write(FileStream, 0, MySample.Sample.length)
         
       'close the stream
        CloseStream fileStream
End Method






« Last Edit: July 17, 2021, 20:33:04 by Hardcoal »
Some things can break me down.
Some cant

Offline Hardcoal

  • Hero Member
  • *****
  • Posts: 748
  • Nothing is personal
Re: My Music Editor
« Reply #73 on: July 18, 2021, 06:37:19 »
Ignore the previous post.. its one big mess..

Ok, Final Results..  all working fine.. 
the only weird thing I have to do is to divide the length by 4 before Playing the sample..
and than when it stops playing to multiply it back by 4

Or else the ChannelPlaying Command will go on 4 times longer than it should..

So now i can move on to actually Editing the Sample, like copy past and so on..

8:40
OK I just realized not all is fine yet.. when i save the loop its 4 times longer than it should

8:47
Solved..
After loading a sample i multiply its length by 4..
seems to do the job
« Last Edit: July 18, 2021, 06:47:51 by Hardcoal »
Some things can break me down.
Some cant

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: My Music Editor
« Reply #74 on: July 18, 2021, 07:07:03 »
Quote
what do you mean optimize my code? this Realtime function display the whole wave at once..
so it takes time to draw.. how can i use it in real time?

Please count for testing purposes the number of xRECTS your are drawing here:
Code: BlitzMax
  1. Function DisplayRecordedSampleLater(MySample:MySample_Type)
  2.                 ....
  3.                 local RectCounter:Int=0
  4.                 For Local I = 0 To MySample.sample.length Step 16
  5.                         Local Value:Int = RealPointer[I]
  6.                         Value = ShortToInt(Value)
  7.                         Value = Math.FitValue(Value, DiversionAppearance, SEHeight)
  8.                         RectCouter = RectCounter+1
  9.                         xRect (I * Ratio, SEHeight / 2, 1, Value)
  10.                 Next
  11.                 Print "Number of RECTS: " + rectcounter
  12.                 ...
  13.         End Function
  14.  
...and report to me the number you can see in the DEBUG window.


Quote
-One things is driving me crazy more than anything at this point..
 when I create a sample its samples.length is for example is 128bytes..
 when I save it and reload it, it suddenly turns into a quarter in samples.length  (32bytes)
 You tried to explain me.. but I dont get it sorry
Please report where you can read the 32. Which function is reporting that number to you?

Again: When Samples.Length reports "32", this means 32 frames, not 32 bytes! 32 Frames are 64 Shorts are 128 Bytes.

If you want to define a 44.1kHz SF_StereoLE TAudioSample for 1 second of music... How big would you define it? Please report it to me:
Code: BlitzMax
  1. local Audio:TAudioSample = Create AudioSample( ???, 44100, SF_STEREOLE)
Do you know the answer?

Quote
19:16
I create a 2 Seconds Sample using CreateAudioSample(SAMPLE_RATE * 4 * 2, SAMPLE_RATE, SF_STEREO16LE)
but when i play the sample.. it keeps playing for 8 seconds..
That is exactly the same wrong thinking like the point before: To define 2sec of Audio the answer is:
Code: BlitzMax
  1. CreateAudioSample(SAMPLE_RATE * 2, SAMPLE_RATE, SF_STEREO16LE)
The first parameter of the CreateAudioSample() does not expect Bytes, but Frames! Frames are HERTZ*TIME !!!


See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal