My Music Editor

Started by Hardcoal, May 24, 2021, 23:57:24

Previous topic - Next topic

Hardcoal

Ok Im now adding more Tracks.. so one track is kinda working ok, So progress is not bad
Code

Hardcoal

#91
Hi, Im trying to Merge two Samples..
Any Idea how?

thats the best i did so far . but it comes noisy and short

Function MixSamples:TAudioSample(Sample1:TAudioSample, Sample2:TAudioSample)
Local NewSample:TAudioSample, INT16_MIN = -32768, INT16_MAX = 32767, SampleToLoop:TAudioSample

If Sample1.length > Sample2.length Then
SampleToLoop = Sample1
NewSample = CreateAudioSample(Sample1.length, Sample1.hertz, Sample1.format)
Else
SampleToLoop = Sample2
NewSample = CreateAudioSample(Sample2.length, Sample2.hertz, Sample2.format)
End If

For Local I = 0 To SampleToLoop.length
If Sample1.samples[I] < 0 And Sample2.samples[I] < 0
    NewSample.samples[I] = (Sample1.samples[I] + Sample2.samples[I]) - ((Sample1.samples[I] * Sample2.samples[I]) / INT16_MIN)
Else If Sample1.samples[I] > 0 And Sample2.samples[I] > 0
    NewSample.samples[I] = (Sample1.samples[I] + Sample2.samples[I]) - ((Sample1.samples[I] * Sample2.samples[I]) / INT16_MAX)
Else
NewSample.samples[I] = Sample1.samples[I] + Sample2.samples[I]
End If
Next
Return NewSample
End Function
Code

Hardcoal

#92
Ok, i see helped has seized.
No worries i promise ill manage myself.

Ive also finally read all this thing between Adam and mmaster

Code

Midimaster

You cannot simply calculate Sample.samples values! It depends on the Format you used. As you often use SF_STEREO16LE this would be a possible approach:

given: 3 samples o same size, SF_STEREO16LE,

SampleA:TAudioSample
SampleB:TAudioSample
SampleNew:TAudioSample

ShortPointerA: Short Ptr = Short Ptr(SampleA.Samples)
ShortPointerB: Short Ptr = Short Ptr(SampleB.Samples)
ShortPointerN: Short Ptr = Short Ptr(SampleNew.Samples)

For local i:Int=0 to SampleN.Length*2
   Local ValueA:Int = ShortToInt(ShortPointerA[i])
   Local ValueB:Int = ShortToInt(ShortPointerB[i])
   Local Result:Int = (ValueA + Value B) /2
   ShortPointerN[i]  = Result
next


not to risk any hate post again, I tell you you can also do it in a different manner... but I would do it this way:

1.
You have to access the TAudioSamples at a SHORT mode. You can do this with my Pointer way. You need to scan SampleN.Length*2 bcause you are working with STEREO. (But also stepping with STEP2 through the TAudioSample would work, ask others for more complicate solutions)

2.
you have to transform the values to INTEGER before you do calculations. You can do this with my ShortToInt-function. (And of course you can do this also with an different way.)

3.
Now you can simply add the values. You do not need to care about, whether they are positive or negativ. Simply add them.
To care about Distortion you can simply reduce the result volume by dividing by 2. (Or you can do it in a other way of course.)

4.
Then you can store back the result simply into  the target TAudioSample.



I did not write the code for all aspects, only for learning the access. It is no runnable code nor a complete Audio-tool. F.e So additional you have to code a soultion for different length, etc...



...on the way to Egypt

Hardcoal

#94
Ok thanks, midi.

I actually copied a code that Ive found on the internet from some other language. maybe C++ dunno

13:14

Ok here is what ive made and it seems to work fine

Function MixSamples:TAudioSample(SampleA:TAudioSample, SampleB:TAudioSample)
Local SampleToLoop:TAudioSample, SampleNew:TAudioSample

If SampleA.length > SampleB.length Then
SampleToLoop = SampleA
SampleNew = CreateAudioSample(SampleA.length, SampleA.hertz, SampleA.format)
Else
SampleToLoop = SampleB
SampleNew = CreateAudioSample(SampleB.length, SampleB.hertz, SampleB.format)
End If

SampleNew:TAudioSample = CreateAudioSample(SampleA.length, SampleA.hertz, SampleA.format)

Local ShortPointerA:Short Ptr = Short Ptr(SampleA.Samples)
Local ShortPointerB:Short Ptr = Short Ptr(SampleB.Samples)
Local ShortPointerN:Short Ptr = Short Ptr(SampleNew.Samples)

For Local i:Int = 0 To SampleToLoop.Length * 2
   Local ValueA:Int = ShortToInt(ShortPointerA[i])
   Local ValueB:Int = ShortToInt(ShortPointerB[i])
   Local Result:Int = (ValueA + ValueB) / 2
   ShortPointerN[i] = Result
Next
SampleNew.samples = ShortPointerN
Return SampleNew
End Function
Code

Hardcoal

something very annoying that happened to me..
sometimes you build and sometimes you destroy..

I mean i got to a point that all worked ok than i started to try to make mutli track and ruined my own achievements.
I wasnt wise to keep an old working verion.

its very frustrating that you need to rework something you already made to work and spend days on it..
one of the things that might make you give up programming
Code

Midimaster

#96
dont know what you mean... the code in post #94 looks pretty good.

First critic:

There is no need to "copy back" the short pointer to the byte pointer:
SampleNew.samples = ShortPointerN

The Byte Pointer SampleNew.Samples always points to the same RAM as the Short Pointer ShortPointerN does.

SampleNew.Samples is a pointer that enables us access to the RAM of the data... but only byte-wise. We only establish the SHORT pointer to have a 2bytes-step-access to the same RAM. But both are existing achievements during all the time.


Second critic:
You need not a variable SampleToLoop. You only use it for the Length-property. But the target variable SampleNew has the same length! Always!


Samples of different size

If you want to combine two samples, which have different size you should think about the final size of the SampleNew. If SampleNew will get the size of the smaller one, you can do this:
For Local i:Int = 0 To SampleNew.Length * 2
   Local ValueA:Int = ShortToInt(ShortPointerA[i])
   Local ValueB:Int = ShortToInt(ShortPointerB[i])
   Local Result:Int = (ValueA + ValueB) / 2
   ShortPointerN[i] = Result
Next


But if you want to have the final TAudioSample to have the size of the bigger one you have to do two steps:

First is to copy the "together"-part of both TAudioSamples. Then add the "leftover"-part of the bigger TAudioSample:

I would think, this works too (did not test it):
Code (BlitzMax) Select
Function MixSamples:TAudioSample(SampleA:TAudioSample, SampleB:TAudioSample)
Local SampleNew:TAudioSample
Local Together:Int   ' length of the smaller sample

If SampleA.length > SampleB.length Then
Together = SampleB.Length
SampleNew = CreateAudioSample(SampleA.length, SampleA.hertz, SampleA.format)
Else
Together = SampleA.Length
SampleNew = CreateAudioSample(SampleB.length, SampleB.hertz, SampleB.format)
End If

Local ShortPointerA:Short Ptr = Short Ptr(SampleA.Samples)
Local ShortPointerB:Short Ptr = Short Ptr(SampleB.Samples)
Local ShortPointerN:Short Ptr = Short Ptr(SampleNew.Samples)

' 1st step: combine the common parts:
For Local i:Int = 0 To Together * 2
   Local ValueA:Int = ShortToInt(ShortPointerA[i])
   Local ValueB:Int = ShortToInt(ShortPointerB[i])
   Local Result:Int = (ValueA + ValueB) / 2
   ShortPointerN[i] = Result
Next

' 2nd step: add the leftover part of the bigger one
For Local i:Int = Together*2 To SampleNew.Length * 2
' (no need for the ShortToInt() function here, because we do not calculate anything)
If SampleA.length > SampleB.length Then
ShortPointerN[i] = ShortPointerA[i]
Else
ShortPointerN[i] = ShortPointerB[i]
EndIf
Next
Return SampleNew
End Function



Multi-Track on BlitzMax 1.50

Please descripe, what features you plan with "Multitrack"?  In BlitzMax 1.50 you only have STEREO, which allowes 2 tracks as a maximum. If  you want to use more than 2 tracks you should think about your own virtual multitrack system. This means you record  (capture) max 2 tracks from OpenAL and add them to a 32 track system. But when playback you need to mix down all 32 tracks to a STEREO track again to be able to send it to BlitzMax TSound (or my FreeAudioRingBuffer).

A virtual 32 track system can be build with a 2-dimensional INTEGER array or FLOAT array. Also a BANK-System would work

Each single Sample-value of a TAudioSample need to be converted to the new format and stored into the correct position.

In the Array you can calculate with the samples as you are used it with other "normal" array numbers. Move, copy, add, substract,adjust or filter the values as you like.

Before you can Playback it all, you have to add all values of the same timestamp, convert the result into a SHORT, then put it into a TAudioSample.


Real Multi-Track on BlitzMax NG

On BlitzMax NG you could use the MiniAudio-Wrapper to record upto 32 tracks at the same time, they already have a 32-bit-FLOAT-format. So you can manipulate immediately without the need of converting. When Playback you can send all 32tracks together to the MiniAudio which cares about the mixdown to the STEREO-Device of your computer or to a hardware 16channel-USB-device.




...on the way to Egypt

Hardcoal

I have no plans to record more than one or two tracks same time.
I was complaining about a lose of code that I had when I tried to move from one track to multitrack.

Im trying to reach to a certain point like i did with the notations.
Than ill decide whats next..
Im not trying to make a full music editor..
theirs no point doing stuff other did much better..

From the point that ill feel pleased.. than Ill start thinking on original Ideas..
I already got some in my mind..

thanks for all the code midimaster.. ill test it later.. after ill solve serious issues that I got atm..

weird stuff, like when i load audiosample it plays only quarter of it..
but that might be my fault.. need checkings


Code

Midimaster

You need no plan to record more than 2 tracks without having additional hardware. So do not think about it, before there is no need for it.

You still did forget to descripe, what you mean, when you talk about "Multitrack". What should the app be able to do?


Load Audio-Sample?

Are you talking about "Audio-Files", when you write "AudioSamples"?

When you load a given Audio-File you normally need not care about the length. The LoadSound() loads the Audio-File  and always plays it in correct length. If not... you may have made mistakes when saved the audio file previously. Do you already use AUDACITY? It is a good tool to check the self made audio files.


Quarter Size

Your always returning problem of quarter sizes in TAudioSamples still comes from mis-calculation of the byte-length!

Always remember:

A SF_MONO16LE format has length frames. Each frame has one sample, which has 2 bytes. So File-Size is (needs to be) double of Sample.Length

A SF_STEREO16LE format has length frames. Each frame has now 2 samples (L+R), each of them has 2 bytes. So File-Size is (needs to be) 4 times of Sample.Length

The value Sample.Length does not tell you the number of bytes nor the number of samples, but the number of FRAMES!






...on the way to Egypt

Hardcoal

So whats Exactly is a Frame? sorry if you wrote it and I missed it.

Im making atm a multi track like mixcraft (take a look at mixcraft) that suppose to contain Audio Samples and Midi Recordings..
but like i said that doest not mean im gonna continue it all trough ..
Im just learning how to make it..

bottom line. I must know what frames are.. if its not samples.. than what is it exactly..
Code

Midimaster

What is A Frame?

A frame is a bundle of all sample-values that belong to the same moment (timestamp). All sample-values belonging to the same frame need to be (fired) played at the same moment.

In a MONO-recording a frame is the same as a sample-value, because a MONO-frame contains only one sample value.

In a STEREO-recording a frame contains two sample-values. One for LEFT, one for RIGHT.

In a MULTITRACK-recording with n% tracks a frame contains a bundle of n% sample-values, starting from CHANNEL 1 upto CHANNEL-n.

Audio-Data are organized as a sequence of frames. Frame follows after frame. All data in a frame are played at the same time tick.

Example: given: A recording of 1 second length with 44.1kHz in 16bit.

MONO has 44.100 frames = 44.100 sample = 88.200 bytes

STEREO has 44.100 frames = 88.200 sample = 176.400 bytes

10-TRACK-MULTI-RECORDING has 44.100 frames = 441.000 samples = 882.000 bytes

In post #71 I sent you a explaining graphic related to frames-samples-bytes. You should again have a look on it!
...on the way to Egypt

iWasAdam

Whilst correct. Frame usually refers to visual FPS (frames per second). This is what FPS is all about.

But... Frames (with audio) are best refered as 'Audio Frames' so you don't get the 2 confused

a single sample number (8 bit, 12 bit, 16 bit, etc) is one channel of audio. An Audio frame (usually just collectively called a sample), is one or more channels being played together.

E.G.
- Stereo would have a left and right
- 5.1 audio would have 5 channels, etc

A single snapshot of the stereo or 5.1 can be refered to as an Audio Frame or better still just the 'sample'

Sample and audio frame are interchangable, but it would be much better never to use aduio frame as simply frame - as that is easily confused with FPS and visual frames.

------

Where it gets more confusing is 'usually' these stereo/5.1 groups of ausio are linked together and just work together so you don't have to worry about them... but you could have 2 mono channels that you are multiplexing together to form a stereo channel, etc

My advice (at this stage) would be to focus on mono/stereo, (Ignore all mention of frames), and get everything working at a level you are pleased with :)



Hardcoal

#102
Looking on Post 71# Thanks

And Ill also check your App, Midi.

Im trying to make something like a real time Looper..
ATM thats what on my mind..

And lots of small Apps.
Like voice recorder, Note Recognizer.. and Whatever..

Ill try to understand this Frame Concept.. Because eventually I need to know how to use it properly..

but I just woke up, so I quickly respond.. I dont like to delay responses .. IM too OCD
Code

iWasAdam

QuoteIm trying to make something like a real time Looper..
the command you are looking for is 'Loop'
This is in Sound or set in Audio Data when you bring it in.
What it will do is loop the sound back to the start and continue playing it

If you are winting to set specific loop points in a sample - then you are out of luck - you will need to find a third party solution or write your own playback system

Midimaster

Something like a Boss Loop Station? Recording a new round while playback the prior takes?

Your biggest problem will be the latency. This should be as short as possible. Because when you hear the playbacks the data left your app 120msec ago.
And when you now start (synchron with the listening) another (electronic) instrument, the data of the new instrument will have another latency during the OpenAL-process. Lets say again 40msec. This means that the new sound will appear 150msec after the proir takes at the speakers. 160msec are to much! like a 1/16 note!

There are several strategies to optimize this:

1. Play the new instrument via a hardware mixer and use its monitor output. Then you hear the new sound in the same moment when the playback was audible. Later you add the sample-values of the new sound at 160 msec prior position into the main TAudioSample. On 16bit-Mono 44100kHz this means 6630 Shorts earlier:
ShortPointerN(i-6630) = ShortPointerN(i-6630) + ShortPointerR(i)
The result is that you corrected the position of the new recoding and now as a part of the playback, it is at the same time as the prior playback.

2.Use a low latency system like my FreeAudioRingBuffer (total expected 70msec Latency) or MiniAudio (expected 30msec). Every latency below 60msec is acceptable.


The realtime adding of new tracks is not that complicate, when you use the FreeAudioRingbuffer. Incoming signal will be converted and added to an INTEGER array. For Playback this array feeds the ringbuffer. We should try some experiments...


...on the way to Egypt