SyntaxBomb - Indie Coders

Languages & Coding => BlitzMax / BlitzMax NG => Topic started by: Hardcoal on May 24, 2021, 23:57:24

Title: My Music Editor
Post by: Hardcoal on May 24, 2021, 23:57:24
Im not sure i made a topic about it so if i have i apologize for making double topics..
but i think i only mentioned it on an already existing topic..
(this time im marking this post)

anyway, im doing multiple projects as usual. each time jumping from one to another just not to get bored, and from other reasons, i wont get into now.

Ok, MY question is.. how do I control a VST synthesizer from my midi input.

I already know how to read midi input into blitzmax.. but how do i transmit it to the VST?.

I may have asked this question about the VST before, but i dont remember getting a clear answer.
(I apologize if i missed)

Second question.
how can  i control the latency of my Driver..
how to i change its interface from blitzmax like 192,000hz and so on.

thanks
Title: Re: My Music Editor
Post by: Midimaster on May 25, 2021, 10:18:16
MIDI control

This depends on the syntax each Synthi uses. A lot of them are controlled by Control Changes Messages. And some can only be controled by SYSTEM EXCLUSIV Messages, Both message types are standard members of the MIDI protocol. CC are short messages and often used to turn knobs or switch effects on/off . SYSEX have a individual length and individual syntax. Only the header and the last message byte are part of the MIDI protocol. Between this two points SYSEX do what the want .This is often used for transfer big blocks of information (soundbank, update, etc..)

Each instruments should have a MIDI IMPLEMENTATION TABLE or MANUAL, where you hopefully find the detailed syntax.

Latency
Latency is not a "feature" but a "problem". If we could "control" it, we always would set it to zero! The only chance you have is to use devices which have a low latency. For example my PRESONUS mixer has 5msec latency when recording 24 tracks to a computer. Expensive!
Or ASIO is a technology made by steinberg, which offers drivers below 30msec for all instrument, that can handle ASIO. WASAPI is the answer from microsoft to ASIO. not so good but better than old MME or DIRECTX ever was. On MAC you have better latency by default, on LINUX I dont know.

BlitzMax cannot handle ASIO or WASAPI by default. The build in FreeAudio-Driver does not know modern Backends like WASAPI and comes along with latency <130msec.

That is one of the reasons why I write this MiniAudio-Wrapper. This open the low latency-world for BlitzMax. Or enables your wish to use 192kHz (what is not really helpful, if other parameters stay low quality).

You should always find optimized set of parameters. So "48kHz with 32bit-float and 20msec latency when using 8 tracks" is a more useful set.


Title: Re: My Music Editor
Post by: Hardcoal on May 25, 2021, 10:56:34
I see ok thanks for now midi master.
I actually wanted one vst synth to workp for getting sane sounds.
Im not planning on a full music editing software.
Ill find a solution, that's for sure.

With my external audio card i get to 1. 3ms latency.

Im not looking for a complete midi control.
Only keyboard keys and maybe volume and modulation.

I already encountered the diff between one tool that doesn't properly work in midi.
But i will fix it.
It's fun anyway when things work
So im not suffering

(all in good time), Cheers
Title: Re: My Music Editor
Post by: Hardcoal on May 25, 2021, 16:00:26
I was trying to compile miniaudio and i got this error

►mima.miniaudio was scheduled to next iteration
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c: In function 'MM_GetContext':
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c:100:5: error: 'for' loop initial declarations are only allowed in C99 mode
     for (ma_uint32 iDevice = 0; iDevice < playbackCount; iDevice += 1) {
     ^
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c:100:5: note: use option -std=c99 or -std=gnu99 to compile your code
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c:107:20: error: redefinition of 'iDevice'
     for (ma_uint32 iDevice = 0; iDevice < captureCount; iDevice += 1) {
                    ^
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c:100:20: note: previous definition of 'iDevice' was here
     for (ma_uint32 iDevice = 0; iDevice < playbackCount; iDevice += 1) {
                    ^
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c:107:5: error: 'for' loop initial declarations are only allowed in C99 mode
     for (ma_uint32 iDevice = 0; iDevice < captureCount; iDevice += 1) {
     ^
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c: In function 'MM_DecodeMP3':
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c:158:2: error: 'for' loop initial declarations are only allowed in C99 mode
  for (int i=0; i<frameCount; i++){
  ^
Build Error: failed to compile D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c
►►►►mima.miniaudio was not built. See error information on latest attempt.


the other mod was compiled successfully
Title: Re: My Music Editor
Post by: Midimaster on May 25, 2021, 17:11:34
did you really use BlitzMax NG?
Title: Re: My Music Editor
Post by: Hardcoal on May 25, 2021, 17:55:33
ohhh its for blitzmax ng :S no i havnt..
I cant use my project in blitzmaxng

Title: Re: My Music Editor
Post by: Midimaster on May 26, 2021, 00:44:14
They code line you reported is written by me and not part of the mainfile miniaudio.h.

I had a look into the main code miniaudio.h and it looks like  the author kept all his code compatible to C89 standard.
There is a difference in using FOR/NEXT loop in C-versions of 1989 compared to 1999. In C99 it is allowed to define a variable i inside the FOR-loop:
C99
for (int i=0;i<10;i++) {
}


In C89 it had to be written like this:
C89
int i;
for (i=0;i<10;i++) {
}


So if the 2 code lines you reported are the only one, which made problems I can change it to a C89 standard too. Then maybe miniaudio runs also under BlitzMax 1.51.

but you have to switch on Multi-Threading.

There are only 3 changes necessary in midiaudiowrapper.c. This are the modifications:

before:
//line 99
    printf("list of playback devices: \n");
    for (ma_uint32 iDevice = 0; iDevice < playbackCount; iDevice += 1) {
....
//line 106
    printf("list of capture devices: \n");
    for (ma_uint32 iDevice = 0; iDevice < captureCount; iDevice += 1) {
...
//line 127
    int result = ma_decode_file(FileName, &audioConfig, &frameCount, &pAudioData);
    for (int i=0; i<frameCount; i++){
...


must become....

after:
//line 99
    printf("list of playback devices: \n");
    ma_uint32 iDevice;
    for (iDevice = 0; iDevice < playbackCount; iDevice += 1) {
....
//line 106
    printf("list of capture devices: \n");
    for (iDevice = 0; iDevice < captureCount; iDevice += 1) {
...
//line 127
    int result = ma_decode_file(FileName, &audioConfig, &frameCount, &pAudioData);
    int i;
    for (i=0; i<frameCount; i++){
...



The attachment is the replacement for miniaudiowrapper.c:
Title: Re: My Music Editor
Post by: Hardcoal on May 26, 2021, 03:07:18
thanks midi master. I owe you, but now i get this error

►mima.miniaudio was scheduled to next iteration
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s: Assembler messages:
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:3166: Error: junk at end of line, first unrecognized character is `,'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:84145: Error: junk at end of line, first unrecognized character is `,'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:84146: Error: junk at end of line, first unrecognized character is `,'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:84147: Error: junk at end of line, first unrecognized character is `,'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:87107: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:87747: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:87961: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:88601: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:88697: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:89337: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:89433: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:90073: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:90169: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:90809: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:98310: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:98950: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:99046: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:99686: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:99782: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:100422: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:100518: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:101158: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:102784: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:103424: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:105729: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:106338: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:112628: Error: no such instruction: `lzcntl %eax,%eax'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:139777: Error: junk at end of line, first unrecognized character is `,'
C:\Users\Hardc\AppData\Local\Temp\ccrFlFND.s:139924: Error: junk at end of line, first unrecognized character is `,'
Build Error: failed to compile D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c
►►►►mima.miniaudio was not built. See error information on latest attempt.
Title: Re: My Music Editor
Post by: Midimaster on May 26, 2021, 08:08:02
this does not look as nice as it did last time. My C-skills are not very good, so now I have to ask one of the experts Derron, Col or IWasAdam. I will forward your report to my thread "how to write a wrapper".

by the way... what is the reason you need to use BlitzMax 1.51 still?
Title: Re: My Music Editor
Post by: Hardcoal on May 26, 2021, 08:29:42
I use blitzmax 1.51 because im using xors3d..

I tried to move to other platforms and each time I got upset over something else..
I really tried..

Ill try again soon...












Title: Re: My Music Editor
Post by: col on May 27, 2021, 06:22:46
I built and tested this with a standard BMax build that uses MingW9.3 - using the later versions have bug fixes and compatibility issues resolved 8)
however this code should work with all versions.

You can get this to work with the original BlitzMax by modifying the BRL thread sources in a similar way that NG modified them. I did have a quick go at doing it without modifying them but some of the required functions are 'local' (static) functions in the BRL source so it looks like you'd have to alter them anyway so why not do it properly.

The examples seem to work ok with threaded and single-thread builds - requires some thorough testing though so if you get spurious crashes then think of this first.
All I did was add in these functions:

bbThreadRegister
bbThreadUnregister




brl.mod -> blitz.mod ->blitz_thread.h


#ifndef BLITZ_THREAD_H
#define BLITZ_THREAD_H

#ifdef _WIN32

#include <windows.h>
typedef CRITICAL_SECTION bb_mutex_t;
#define bb_mutex_init(MUTPTR) (InitializeCriticalSection(MUTPTR),1)
#define bb_mutex_destroy(MUTPTR) DeleteCriticalSection(MUTPTR)
#define bb_mutex_lock(MUTPTR) EnterCriticalSection(MUTPTR)
#define bb_mutex_unlock(MUTPTR) LeaveCriticalSection(MUTPTR)
#define bb_mutex_trylock(MUTPTR) (TryEnterCriticalSection(MUTPTR)!=0)

/*
typedef HANDLE bb_mutex_t;
#define bb_mutex_init(MUTPTR) ((*(MUTPTR)=CreateMutex(0,0,0))!=0)
#define bb_mutex_destroy(MUTPTR) CloseHandle(*(MUTPTR))
#define bb_mutex_lock(MUTPTR) WaitForSingleObject(*(MUTPTR),INFINITE)
#define bb_mutex_unlock(MUTPTR) ReleaseMutex(*(MUTPTR))
#define bb_mutex_trylock(MUTPTR) (WaitForSingleObject(*(MUTPTR),0 )==WAIT_OBJECT_0)
*/

typedef HANDLE bb_sem_t;
#define bb_sem_init(SEMPTR,COUNT) ((*(SEMPTR)=CreateSemaphore(0,(COUNT),0x7fffffff,0))!=0)
#define bb_sem_destroy(SEMPTR) CloseHandle(*(SEMPTR))
#define bb_sem_wait(SEMPTR) WaitForSingleObject(*(SEMPTR),INFINITE)
#define bb_sem_post(SEMPTR) ReleaseSemaphore(*(SEMPTR),1,0)

#else

#include <pthread.h>
typedef pthread_mutex_t bb_mutex_t;
extern pthread_mutexattr_t _bb_mutexattr;
#define bb_mutex_init(MUTPTR) (pthread_mutex_init((MUTPTR),&_bb_mutexattr)>=0)
#define bb_mutex_destroy(MUTPTR) pthread_mutex_destroy(MUTPTR)
#define bb_mutex_lock(MUTPTR) pthread_mutex_lock(MUTPTR)
#define bb_mutex_unlock(MUTPTR) pthread_mutex_unlock(MUTPTR)
#define bb_mutex_trylock(MUTPTR) (pthread_mutex_trylock(MUTPTR)==0)

#endif

#ifdef __APPLE__

#include <mach/semaphore.h>
#include <mach/task.h>
typedef semaphore_t bb_sem_t;
#define bb_sem_init(SEMPTR,COUNT) (semaphore_create( mach_task_self(),(SEMPTR),SYNC_POLICY_FIFO,(COUNT) )>=0)
#define bb_sem_destroy(SEMPTR) semaphore_destroy( mach_task_self(),*(SEMPTR) )
#define bb_sem_wait(SEMPTR) semaphore_wait( *(SEMPTR) )
#define bb_sem_post(SEMPTR) semaphore_signal( *(SEMPTR) )

#endif

#ifdef __linux

#include <semaphore.h>
typedef sem_t bb_sem_t;
#define bb_sem_init(SEMPTR,COUNT) (sem_init((SEMPTR),0,(COUNT))>=0)
#define bb_sem_destroy sem_destroy
#define bb_sem_wait sem_wait
#define bb_sem_post sem_post

#endif

#ifdef _WIN32
#define BB_THREADREGS 7 //via GetThreadContext()
#elif __ppc__
#define BB_THREADREGS 19 //via bbGCRootRegs()
#else
#define BB_THREADREGS 4 //vid bbGCRootRegs()
#endif

#include "blitz_types.h"

typedef BBObject *(*BBThreadProc)( BBObject* );

typedef struct BBThread BBThread;

struct BBThread{
BBThread *succ;
BBThreadProc proc;
void *data[32];
int detached;
void *stackTop;
void *locked_sp;
int locked_regs[BB_THREADREGS];
#ifdef _WIN32
HANDLE handle;
DWORD id;
#else
pthread_t handle;
int suspended;
bb_sem_t runsema;
bb_sem_t acksema;
#endif
};

void bbThreadStartup();

BBThread* bbThreadCreate( BBThreadProc entry,BBObject *data );
void bbThreadDetach( BBThread *thread );
BBObject* bbThreadWait( BBThread *thread );

BBThread* bbThreadGetMain();
BBThread* bbThreadGetCurrent();

int bbThreadSuspend( BBThread *thread );
int bbThreadResume( BBThread *thread );

int bbThreadAllocData();
void bbThreadSetData( int index,BBObject *data );
BBObject* bbThreadGetData( int index );

//These MUST be inside a BB_LOCK/BB_UNLOCK
BBThread* _bbThreadLockThreads();
void _bbThreadUnlockThreads();

int bbAtomicCAS( volatile int *target,int oldVal,int newVal );
int bbAtomicAdd( volatile int *target,int incr );

// Expose bbThreadRegister and bbThreadUnregister
#ifdef WIN32
BBThread* bbThreadRegister(DWORD id);
#else
BBThread* bbThreadRegister(void* handle);
#endif
void bbThreadUnregister(BBThread* thread);

//Internal locks...
extern int _bbNeedsLock;
extern bb_mutex_t _bbLock;

#define BB_LOCK if( _bbNeedsLock ){ bb_mutex_lock( &_bbLock ); }
#define BB_UNLOCK if( _bbNeedsLock ){ bb_mutex_unlock( &_bbLock ); }

#endif




brl.mod -> blitz.mod ->blitz_thread.c


#include "blitz.h"

//#define DEBUG_THREADS

//***** Common *****

int _bbNeedsLock;
bb_mutex_t _bbLock;

static int threadDataId;

static BBThread *threads;
static BBThread *deadThreads;

static BBThread *mainThread;

static void flushDeadThreads(){
BBThread **p=&deadThreads,*t;
while( t=*p ){
if( t->detached ){
*p=t->succ;
#ifdef _WIN32
CloseHandle( t->handle );
#endif
free( t );
}else{
p=&t->succ;
}
}
}

static void addThread( BBThread *thread ){
flushDeadThreads();
thread->succ=threads;
threads=thread;
}

static void removeThread( BBThread *thread ){
BBThread **p=&threads,*t;
while( t=*p ){
if( t==thread ){
*p=t->succ;
if( t->detached ){
#ifdef _WIN32
CloseHandle( t->handle );
#endif
free( t );
}else{
t->succ=deadThreads;
deadThreads=t;
}
break;
}else{
p=&t->succ;
}
}
}

int bbThreadAllocData(){
if( threadDataId<31 ) return ++threadDataId;
return 0;
}

void bbThreadSetData( int index,BBObject *data ){
bbThreadGetCurrent()->data[index]=data;
}

BBObject *bbThreadGetData( int index ){
BBObject *data=bbThreadGetCurrent()->data[index];
return data ? data : &bbNullObject;
}

void bbThreadUnregister(BBThread* thread)
{
BB_LOCK
removeThread( thread );
BB_UNLOCK
}

//***** Windows threads *****
#ifdef _WIN32

static DWORD curThreadTls;

static DWORD WINAPI threadProc( void *p ){
BBThread *thread=p;

TlsSetValue( curThreadTls,thread );

DWORD ret=(DWORD)thread->proc( thread->data[0] );

BB_LOCK
removeThread( thread );
BB_UNLOCK

return ret;
}

void bbThreadStartup(){

if( bb_mutex_init( &_bbLock )<0 ) exit(-1);

curThreadTls=TlsAlloc();

BBThread *thread=malloc( sizeof( BBThread ) );

thread->proc=0;
memset( thread->data,0,sizeof(thread->data) );
thread->detached=0;
thread->stackTop=bbGCStackTop;
thread->id=GetCurrentThreadId();
if( !DuplicateHandle( GetCurrentProcess(),GetCurrentThread(),GetCurrentProcess(),&thread->handle,0,FALSE,DUPLICATE_SAME_ACCESS ) ){
exit( -1 );
}

TlsSetValue( curThreadTls,thread );

thread->succ=threads;
threads=thread;
mainThread=thread;
}

BBThread *bbThreadCreate( BBThreadProc proc,BBObject *data ){
BBThread *thread=malloc( sizeof( BBThread ) );

thread->proc=proc;
memset( thread->data,0,sizeof(thread->data) );
thread->data[0]=data;
thread->detached=0;
thread->handle=CreateThread( 0,0,threadProc,thread,CREATE_SUSPENDED,&thread->id );

CONTEXT ctx={CONTEXT_CONTROL};
GetThreadContext( thread->handle,&ctx );
thread->stackTop=ctx.Esp;

BB_LOCK
addThread( thread );
BB_UNLOCK

_bbNeedsLock=1;

return thread;
}

void bbThreadDetach( BBThread *thread ){
thread->detached=1;
}

BBObject *bbThreadWait( BBThread *thread ){
if( WaitForSingleObject( thread->handle,INFINITE )==WAIT_OBJECT_0 ){
BBObject *p;
if( GetExitCodeThread( thread->handle,(DWORD*)&p ) ){
thread->detached=1;
return p;
}else{
printf( "ERROR! bbThreadWait: GetExitCodeThread failed!\n" );
}
}else{
printf( "ERROR! bbThreadWait: WaitForSingleObject failed!\n" );
}
printf( "LastError=%i\n",GetLastError() );

return &bbNullObject;
}

BBThread *bbThreadGetMain(){
return mainThread;
}

BBThread *bbThreadGetCurrent(){
return TlsGetValue( curThreadTls );
}

int bbThreadSuspend( BBThread *thread ){
return SuspendThread( thread->handle );
}

int bbThreadResume( BBThread *thread ){
return ResumeThread( thread->handle );
}

BBThread *_bbThreadLockThreads(){
BBThread *curThread=bbThreadGetCurrent();
BBThread *t;
for( t=threads;t;t=t->succ ){
if( t!=curThread ){
SuspendThread( t->handle );
CONTEXT ctx={CONTEXT_INTEGER|CONTEXT_CONTROL};
GetThreadContext( t->handle,&ctx );
t->locked_regs[0]=ctx.Edi;
t->locked_regs[1]=ctx.Esi;
t->locked_regs[2]=ctx.Ebx;
t->locked_regs[3]=ctx.Edx;
t->locked_regs[4]=ctx.Ecx;
t->locked_regs[5]=ctx.Eax;
t->locked_regs[6]=ctx.Ebp;
t->locked_sp=ctx.Esp;
}
}
return threads;
}

void _bbThreadUnlockThreads(){
BBThread *curThread=bbThreadGetCurrent();
BBThread *t;
for( t=threads;t;t=t->succ ){
if( t!=curThread ){
ResumeThread( t->handle );
}
}
}

BBThread* bbThreadRegister(DWORD id)
{
BBThread* thread = 0;
HANDLE handle = OpenThread(THREAD_GET_CONTEXT , FALSE, id);
if(handle)
{
thread = malloc(sizeof(BBThread));
thread->proc = 0;
thread->data[0] = 0;
thread->detached = 0;
thread->id = id;

CONTEXT ctx={CONTEXT_CONTROL};
GetThreadContext( thread->handle,&ctx );
thread->stackTop=ctx.Esp;
CloseHandle(handle);

BB_LOCK
addThread( thread );
BB_UNLOCK

_bbNeedsLock = 1;
}
else
{
printf("WARNING: Thread with id: %d was not registered with the Blitz system.\n", id);
printf("         This will result in an unstable program - especially for Debug builds.\n");
fflush(0);
}

return thread;
}

//***** POSIX threads *****
#else

#include <unistd.h>
#include <signal.h>

#if __linux
#define MUTEX_RECURSIVE 1
#elif __APPLE__
#define MUTEX_RECURSIVE 2
#endif

pthread_mutexattr_t _bb_mutexattr;

static BBThread *threads;
static pthread_key_t curThreadTls;

static void suspendSigHandler( int sig ){//,siginfo_t *info,ucontext_t *ctx ){
BBThread *thread=pthread_getspecific( curThreadTls );

thread->locked_sp=bbGCRootRegs( thread->locked_regs );

#ifdef DEBUG_THREADS
printf( "In suspendSigHandler! thread=%p locked_sp=%p\n",thread,thread->locked_sp );fflush( stdout );
#endif

bb_sem_post( &thread->acksema );

//wait for resume - apparently very naughty!
bb_sem_wait( &thread->runsema );

#ifdef DEBUG_THREADS
printf( "Got resume!\n" );fflush( stdout );
#endif
}

void bbThreadStartup(){

if( pthread_mutexattr_init( &_bb_mutexattr )<0 ) exit(-1);
if( pthread_mutexattr_settype( &_bb_mutexattr,MUTEX_RECURSIVE )<0 ) exit(-1);

if( pthread_key_create( &curThreadTls,0 )<0 ) exit(-1);

if( bb_mutex_init( &_bbLock )<0 ) exit(-1);

struct sigaction act;
memset( &act,0,sizeof(act) );
act.sa_handler=suspendSigHandler;
act.sa_flags=SA_RESTART;

if( sigaction( SIGUSR2,&act,0 )<0 ) exit(-1);

BBThread *thread=malloc( sizeof( BBThread ) );
memset( thread->data,0,sizeof(thread->data) );

thread->proc=0;
thread->detached=0;
thread->suspended=0;
thread->handle=pthread_self();
if( !bb_sem_init( &thread->runsema,0 ) ) exit(-1);
if( !bb_sem_init( &thread->acksema,0 ) ) exit(-1);

thread->stackTop=bbGCStackTop;
pthread_setspecific( curThreadTls,thread );

thread->succ=threads;
threads=thread;
mainThread=thread;
}

static void *threadProc( void *p ){
BBThread *thread=p;

thread->stackTop=bbGCRootRegs( thread->locked_regs );
pthread_setspecific( curThreadTls,thread );

BB_LOCK
addThread( thread );
BB_UNLOCK

bb_sem_post( &thread->acksema );
bb_sem_wait( &thread->runsema );

#ifdef DEBUG_THREADS
printf( "Thread %p added, stackTop=%p\n",thread,thread->stackTop );fflush( stdout );
#endif

void *ret=thread->proc( thread->data[0] );

BB_LOCK
removeThread( thread );
BB_UNLOCK

bb_sem_destroy( &thread->runsema );
bb_sem_destroy( &thread->acksema );

#ifdef DEBUG_THREADS
printf( "Thread %p removed\n",thread );fflush( stdout );
#endif

return ret;
}

BBThread *bbThreadCreate( BBThreadProc proc,BBObject *data ){
BBThread *thread=malloc( sizeof( BBThread ) );
memset( thread->data,0,sizeof(thread->data) );

thread->proc=proc;
thread->data[0]=data;
thread->detached=0;
thread->suspended=1;
if( bb_sem_init( &thread->runsema,0 ) ){
if( bb_sem_init( &thread->acksema,0 ) ){
if( pthread_create( &thread->handle,0,threadProc,thread )>=0 ){
bb_sem_wait( &thread->acksema );
_bbNeedsLock=1;
return thread;
}
bb_sem_destroy( &thread->acksema );
}
bb_sem_destroy( &thread->runsema );
}
free( thread );
return 0;
}

void bbThreadDetach( BBThread *thread ){
thread->detached=1;
pthread_detach( thread->handle );
}

BBObject *bbThreadWait( BBThread *thread ){
BBObject *p=0;
thread->detached=1;
pthread_join( thread->handle,&p );
return p;
}

BBThread *bbThreadGetMain(){
return mainThread;
}

BBThread *bbThreadGetCurrent(){
return pthread_getspecific( curThreadTls );
}

int bbThreadSuspend( BBThread *thread ){
BB_LOCK

int n=thread->suspended++;

if( n==0 ){
pthread_kill( thread->handle,SIGUSR2 );
bb_sem_wait( &thread->acksema );
}

BB_UNLOCK

return n;
}

int bbThreadResume( BBThread *thread ){
BB_LOCK

int n=thread->suspended--;

if( n==1 ){
bb_sem_post( &thread->runsema );
}

BB_UNLOCK

return n;
}

BBThread *_bbThreadLockThreads(){
BBThread *curThread=bbThreadGetCurrent();
BBThread *t;
for( t=threads;t;t=t->succ ){
if( t!=curThread ){
if( !t->suspended++ ){
pthread_kill( t->handle,SIGUSR2 );
bb_sem_wait( &t->acksema );
}
}
}
return threads;
}

void _bbThreadUnlockThreads(){
BBThread *curThread=bbThreadGetCurrent();
BBThread *t;
for( t=threads;t;t=t->succ ){
if( t!=curThread ){
if( !--t->suspended ){
bb_sem_post( &t->runsema );
}
}
}
}}

BBThread* bbThreadRegister(void* handle)
{
BBThread* thread = malloc(sizeof(BBThread);
memset(thread->data, 0, sizeof(thread->data));

thread->handle = handle;
thread->proc = 0;
thread->data[0] = 0;
thread->detached = 0;
pthread_setspecific(curThreadTls, thread);

BB_LOCK
addThread(thread);
BB_UBLOCK;

_bbNeedsLock = 1;

return thread;
}

#endif

//***** Atomic ops *****
#if __ppc__

int bbAtomicCAS( volatile int *addr,int old,int new_val ){
int oldval;
int result=0;

__asm__ __volatile__(
"1:lwarx %0,0,%2\n"   /* load and reserve              */
"cmpw %0, %4\n"      /* if load is not equal to */
"bne 2f\n"            /*   old, fail */
"stwcx. %3,0,%2\n"    /* else store conditional         */
"bne- 1b\n"           /* retry if lost reservation      */
"li %1,1\n"      /* result = 1; */
"2:\n"
: "=&r"(oldval), "=&r"(result)
: "r"(addr), "r"(new_val), "r"(old), "1"(result)
: "memory", "cc");

return result;
}

int bbAtomicAdd( volatile int *p,int incr ){
int old;
for(;;){
old=*p;
if( bbAtomicCAS( p,old,old+incr ) ) return old;
}
}

#else

int bbAtomicCAS( volatile int *addr,int old,int new_val ){
char result;

__asm__ __volatile__(
"lock; cmpxchgl %3, %0; setz %1"
: "=m"(*addr), "=q"(result)
: "m"(*addr), "r" (new_val), "a"(old) : "memory");

return (int)result;
}

int bbAtomicAdd( volatile int *p,int incr ){
int result;

__asm__ __volatile__ ("lock; xaddl %0, %1" :
"=r" (result), "=m" (*p) : "0" (incr), "m" (*p)
: "memory");

return result;
}

#endif


I'll keep an eye on the other 'wrapper' posts to what transpires there.
Title: Re: My Music Editor
Post by: Hardcoal on May 27, 2021, 19:27:16
Last Error trying to compile miniaudio is

►mima.miniaudio was scheduled to next iteration
D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c:2:19: fatal error: blitz.h: No such file or directory
#include "blitz.h"
                   ^
compilation terminated.
Build Error: failed to compile D:/PORTABLE/BlitzMax/mod/mima.mod/miniaudio.mod/miniaudiowrapper.c
►►►►mima.miniaudio was not built. See error information on latest attempt.
Title: Re: My Music Editor
Post by: Hardcoal on May 31, 2021, 09:19:27
Ok. I was busy doing other stuff related to this app.. so this MOD issue is being postponed.

I encountered a new silly problem, is there a way to read if mouse is down besides the usual mousedown(1) command?

I need a general way to read whether mouse button is down..

thanks
Title: Re: My Music Editor
Post by: Midimaster on May 31, 2021, 10:13:13
What do you mean? Inside MaxGui objects? or inside a simple Graphics(80,600)-window?
Can you descripe a situation where you need a new function, because the MouseDown() is not working as you would need?
Title: Re: My Music Editor
Post by: Derron on May 31, 2021, 10:50:31
check how mouse input is read in brl.system or sdl.mod/system.mod - they register for system events. You could do that too.


bye
Ron
Title: Re: My Music Editor
Post by: Hardcoal on May 31, 2021, 12:03:51
im working with AlbaLynx under Xors3d
when i try to read xmousedown() when im not Alba Canvas it wont get a read.. although altestclick() works but not altestdown()
and it wont read xmousedown() either no mousedown()
so i have no way to read the mouse down state.

ill try what derron recommended

Title: Re: My Music Editor
Post by: Hardcoal on May 31, 2021, 12:21:36
Ok Ive found a solution.. it was because I set the buffer into the canvas buffer ..
when i do it when buffer is on back buffer it works.. so i guess i can manage now..


Thanks for the attempt to help
Title: Re: My Music Editor
Post by: Hardcoal on June 02, 2021, 21:53:26
(https://i.imgur.com/JSkQXSF.jpg)

After few days of trying to figure out an annoying error, Im back on track again
anyway Im concentrating on my Music app on notation..

I finally succeeded to do it properly..
Im not sure im aiming towards full notation capabilities.. but Ive passed the basic level. or notation entering using Midi.
and im not talking about real time playing.
Im not their yet.

First version release. coming soon

For those who understand about this type of project
I cant begin to explain how complex all this note editing is,
if you really want to get it to a reasonable level of a user interface
Title: Re: My Music Editor
Post by: Midimaster on June 03, 2021, 00:37:46
it is still much more complex than you think. A note can not be set equal to its midi number.

there are 21 notes, not 11!


midi   59  60  61  61  62  63  63  64  65  64  65  66  66  67  68  68  69  70  70  71  72
name   Cb  C   C#  Db  D   D#  Eb  E   E#  Fb  F   F#  Gb  G   G#  Ab  A   A#  Bb  B   B#
note   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19  20  21


with a model of 12 notes you will never reach a serious level of displaying notes!

Depending on the scale, which is valid in a music document only 7 of them are "activ".

So you need to write 12 "translation tables", one for each scale to find the correct note display.


f.e. a C-min-Chord is never C-D#-G but always C-Eb-G

why?
because it is used in scales with 2b (and more) like Bb-Major

Translation table of Scale Bb-Major:
Scale Bb-Major:
midi   58  60  62  63   65   67   69  70
name   Bb  C   D   Eb   F    G    A   Bb
note   19  2   5   7    11   14   17  19 

How to code this algo for all and every chords?

First you write 12 tables for the 12 scales. Each table contains 7 entries.


Now lets say you have the midi-chord 60-63-67 and try to find the correct way to display it. you have to search through all 12 tables until you find a table which contain all 3 midi numbers.

But even the search order(which table you have to search first)  is important:

Scan the twelve scale tables in this order:
1. C
2. G
3. F
4. D
5. Bb
6. A
7. Eb
8. E
9. Ab
10. H
11. Db
12. Gb
13. F#

in the case of our 60-63-67-chord we would get a hit in scale 5. Bb-Major. Now you can translate the chord into 2-7-14 (= C-Eb-G)
Title: Re: My Music Editor
Post by: Hardcoal on June 03, 2021, 03:48:09
Hi MidiMaster thanks for the help.

Yet, You forgot its in the middle of development..

There are 12 notes.. but their are 21 notes names/signs, and that's good enough for me for the moment.
Let me face that first.
there are so many things to face before what you've mentioned.
It can wait.

And I don't know why you got upset, I still working on it.

And also I don't know music notation perfectly.. (This wasn't my main goal when I started this app)

when ill release first version, and If Ill get good feedbacks than ill take it to the next level.

If you read what I've wrote in the previous response, I said "I'm not sure I'm aiming towards full notation capabilities..

Anyway Thanks for the Tables..
Not everything is clear to me, but ill figure it out.

Anyway. I need to work on Undo
And Other Stuff, Load, Save and So on.

Thanks..

Title: Re: My Music Editor
Post by: iWasAdam on June 03, 2021, 05:10:46
with midi
you are correct there are 12 note per octave and they start at 0 to 11 with 0 usually being the C
so 127 possible notes gives 10 octaves
simples

Here's a nice simple chart showing that C is always 0 aligned and that 72 is middle C
http://computermusicresource.com/midikeys.html (http://computermusicresource.com/midikeys.html)

with notation - much more complex
Title: Re: My Music Editor
Post by: Midimaster on June 03, 2021, 08:50:57
Quote from: Hardcoal on June 03, 2021, 03:48:09
...It can wait.
And I don't know why you got upset, I still working on it.
And also I don't know music notation perfectly.. (This wasn't my main goal when I started this app)
when ill release first version, and If Ill get good feedbacks than ill take it to the next level.
If you read what I've wrote in the previous response, I said "I'm not sure I'm aiming towards full notation capabilities..
...

Yes you are in the middle of development and I want to point you to the right way how to write notes. Because I can see, that your way to notate goes to the wrong direction, I offer you an algo that make very good results.

Quote...And I don't know why you got upset, I still working on it...
Why should I go upset? I only want to share you  my experience of 20 years music education software.

This 21-notes approach is not the "full notation capability" but a very easy to code minimum way with really good looking results. Users will have the feeling that you know, what you are doing. If you publish a release with the look you now have you will not get "good feedbacks". Writing a C-Min-Chord as "C-D#-G" is not a suboptimal variation of displaying a C-Min-Chord, but a absolut No-Go!

It is like having massiv typos in a "how to spell right"-app!

The "middle C" 261Hz is MIDI=60 and written as lower case c'. Notation: in Treble Key system below system or in Bass Key system above system.

MIDI=72 is already c''

by the way....
Don't you show your intermediate result, because you want to get our feedback?



Title: Re: My Music Editor
Post by: Hardcoal on June 03, 2021, 11:59:16
MidiMaster..

Youre doing great.. sure I want feedback..
But it looked like you thought thats my final product or something.

I will surely get good feedback.. because I know what Im doing.
I will got lots of criticism, but that's different..  that is if I wont bring it to the level you are hinting.

but when I say release .. it means for itch.io and I declare that its not a finished product..

If I was to really release it as a finished product for sale. I would not dare having a 50% half job done when releasing something.

but I'm an armature programmer not professional.
no one is paying me nothing

Plus I didnt understand half of what you said..

and I didnt ask, because its not the time.
it will only confuse me at this point..

Let me first finish the usual stuff like Undo.. Load Save and so on..
And if ill get their.. than will see..

Thanks :)

Feedback is always welcomed , and everything is noted.. I assure you. its not in vain
Title: Re: My Music Editor
Post by: Hardcoal on June 03, 2021, 20:47:13
I want to add that making a perfect notation app was never my intention..
I do this tool for my own use, and Ill improve it on my own pace.
No one waits for my app..

I'm just sharing what I'm doing to show my progress. that's the main reason for my posting at this point.



Title: Re: My Music Editor
Post by: Hardcoal on June 05, 2021, 18:00:56
About the notation Editing.. Im making it as close as possible to Editing a text document..
So everything you are used to do with a common note paper will be applied on the music notes (Give or Take)
Title: Re: My Music Editor
Post by: Midimaster on June 05, 2021, 18:22:50
I do not understand your target in notation. Your last post now sound completely different compared to your post from yesterday.
Do you plan to display notes in a correct way or is this not interesting for you?
Do you actually continue with the notation code? Or will this part have a pause for some weeks?

If you already continue, I tell you again... you are on a wrong way. And I offer again to help you and point you to the right direction.

I dont want to offend you.. this is no critic, this is an offer.
Title: Re: My Music Editor
Post by: Hardcoal on June 05, 2021, 18:46:44
Atm yes, my target is notation.. but up to a certain level.. than ill leave it.
I told you MidiMaster, my way of working is different than yours probably ..
I'm not on the wrong path because I'm not finished yet.
When Ill get to the point that I'm aiming for, than Ill remodify my code according to your suggestions..

You must understand.. I don't know notations as good as you do,
But I know what I want.

I'm going to music School.. And I want to play a melody .. But I don't need all the fine details of Notations ATM

And you're right.. I'm a bit uncertain In various ways.. 

You have to understand, I highly value the opportunity of your help offer.
and everything can be fixed..
Just I need to get to the point I desire.

I don't even know how to display the things you are implying about.
I didn't ask you any questions, only because I'm waiting for the right moment, that's all.
But Its on my memory buffer rest assure

Btw if you can post an example of how my notation should look like compared to the last note picture I've posted it will really help

when i began this project.. notation was the last thing on my mind lol..
it was more about recording sound tracks..
but than i found mixcraft and i was pleased













Title: Re: My Music Editor
Post by: Hardcoal on June 05, 2021, 20:32:24
I just encountered an annoying issue with the Spacebar Key!
thats exactly what im saying when I say I must first solve the technical issues

Later..
[Solved, Ok . It was my mistake eventually]
Title: Re: My Music Editor
Post by: Midimaster on June 06, 2021, 02:25:18
(https://www.syntaxbomb.com/index.php?action=dlattach;topic=8467.0;attach=4631)

See Cm Bb and Db. Your notation is not correct.
Title: Re: My Music Editor
Post by: Hardcoal on June 06, 2021, 03:36:42
Awesome MidiMaster.. That's exactly what I thought you meant.
And that's exactly what I'm postponing for later..

It wont be that hard to Implement it..
I bet their or more things that will need special treatment..
For example ,when you Play C and C# they cant be on top of each other..

This are the fine details.. That I will deal with eventually.

Title: Re: My Music Editor
Post by: Hardcoal on June 06, 2021, 21:58:14
So midi master.. according to what I see.. all the notes that are overlapping each other turn into a Bemol Sign
On the BB Chord Case they are not overlapping each other , so why do i need to use Bemol.

anyway.. dont think i understood the note tables you posted..

that's why i postponed it for later.

anyway.. Now im starting to implement some things.. than ill post a picture and see what you think
Title: Re: My Music Editor
Post by: Midimaster on June 06, 2021, 23:59:57
I may look like you found the rule for b or #. But the example of the Bb-chord already shows you, that this cannot be the rule.

As I said, there are 12 tables which define, when or whether a midi note is displayed as with a # or a b sign.

This what I call "translation table". At the moment you try to use the midi-number to find the notation position. But the correct way is to first "translate" the midi-number into a notation-number. then you will find the correct position.

For chords this way is a 100% working solution.

1.step: Calculate
MidiMod%=(midinumber mod 12)
Octave%=(midinumber/12)


2.step: Now use one of the 12 tables to get the NotationMod:
NotationMod%= Table[TableNumber%, MidiMod]
NotationPos%=NotationMod*Octave

now you now the vertical pos of the note in the score


3.step
the system can give you additional informations now:

Select (NotationMod mod 3)
    Case 0
        ' note needs a b sign
    Case 1
        ' note needs no sign
    Case 2
        ' note needs a # sign
end select


The only question that remains is: "Which table do I use?"

The answer is: "depends on...!". In the case of chord this question is easy, because you only have to find the table which contains all three midinumbers of your chord.

In 12 hours I will send you the 12 tables and a demo code. (now it is round midnight)
Title: Re: My Music Editor
Post by: Hardcoal on June 07, 2021, 03:43:26
Awesome.. Ill wait for the demo..

I told you im gonna listen to you eventually lol :)
Title: Re: My Music Editor
Post by: Hardcoal on June 07, 2021, 05:03:49
Progress Report..

Now I got 4 Lines Notes Ladder Editing.
Up till now it was only one Line Note Editing

(https://i.imgur.com/90hU5km.jpg)

Making this a true professional any note sheet notation Editor.. well thats too much for me for the moment..
Ill just make this work right and ill be pleased
Title: Re: My Music Editor
Post by: Midimaster on June 07, 2021, 15:36:33
Ok, here is the demo for the 12->21 converter. This enables to write the chords automatically correct.
(https://www.syntaxbomb.com/index.php?action=dlattach;topic=8467.0;attach=4637)
You can use some of the fundamental  functions:
Code (BlitzMax) Select
Function EasyTranslate:Int[] ( MidiChord:Int[] )
' converts a midi noted chord into a notation noted chord
' the array can contain 1 to 4 notes


Function Name$(Note%)
' returns the name of a notation note
' notemod    0   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21
' --> name  Cb   C  C#  Db   D  D#  Eb   E  E#  Fb   F  F#  Gb   G  G#  Ab   A  A#  Bb   B  B#


Function DrawNotes (Chord:Int[])
'converts a notation chord into dispayed notes
Local ZeroY%=300, Distance%=10, BassDistance%=25
' flexible in scaling:
' ZeroY defines the absolute Y-position of  both systems
' Distance defines the absolute y-diff from one note to the next higher note
' BassDistance defines the relative distance of bass system to treble system


Function PaintHelpingLines(X%, Note%, Clef%=TREBLE, ZeroY%, Distance%, BassDistance%)
'paints all necessary helplines of a notation note


Function NoteToMidi:Int(Note%)
' converts a notation note back to a midi note
' notemod      0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20
        ' --> to midi -1   0   1   1   2   3   3   4   5   4   5   6   6   7   8   8   9  10  10  11  12



You can add your chords for testing here:
Code (BlitzMax) Select

Function NewChord()
number=number +1
Select Number
Case 1
chord=[58,62,65]    'Bb
Case 2
...
Case 9
chord=[57,60,64]    'Am
Case 10
chord=....






The whole runnable example


Start the app and click with the mouse to check some chords....

Code (BlitzMax) Select

Graphics 600,600
SetBlend alphablend
Notation.StartUp


Global MidiChord%[]=[60,63,67]    'Am

Global  ShowChord:Int[]=Notation.EasyTranslate(MidiChord)
Global Number
Notation.ChordInfo(MidiChord, ShowChord)

SetClsColor 255,255,255
Repeat
Cls
Notation.DrawNotes Showchord
If MouseHit(1)
NewChord
EndIf
Flip 1
Until AppTerminate()



Function NewChord()
number=number +1
Midichord=[58+number,59+number,60+number]    'Bb

ShowChord = Notation.EasyTranslate(MidiChord)
Notation.ChordInfo(MidiChord, ShowChord)
End Function













Type Notation
Global MidiTable:Int[]= [-1,0,1, 1,2,3, 3,4,5 ,4,5,6, 6,7,8,  8,9,10,  10,11,0]
Global Names:String[]= "Cb,C,C#,Db,D,D#,Eb,E,E#,Fb,F,F#,Gb,G,G#,Ab,A,A#,Bb,B,B#".split(",")
    Global NoteImages:TImage
Global TableMidi:Int[15,7], TableNote:Int[15,7]



Function StartUp()
'builds the 13 tables from this 2 "exotic" strings
Local t1$="0|2|4|5|7|9|11|0|2|4|6|7|9|11|0|2|4|5|7|9|10|1|2|4|6|7|9|11|0|2|3|5|7|9|10|1|2|4|6|8|9|11|0|2|3|5|7|8|10|1|3|4|6|8|9|11|0|1|3|5|7|8|10|1|3|4|6|8|10|11|0|1|3|5|6|8|10|11|1|3|5|6|8|10|1|3|5|6|8|10|11|"
Local t2$="1|4|7|10|13|16|19|1|4|7|11|13|16|19|1|4|7|10|13|16|18|2|4|7|11|13|16|19|1|4|6|10|13|16|18|2|4|7|11|14|16|19|1|4|6|10|13|15|18|2|5|7|11|14|16|19|1|3|6|10|13|15|18|2|5|7|11|14|17|19|1|3|6|10|12|15|18|21|3|6|10|12|15|18|2|5|8|11|14|17|19|"
t1=t1 + "1|3|5|6|8|10|0|1|3|4|6|8|10|11|"
t2=t2 + "2|5|8|11|14|17|-1|3|6|9|12|15|18|21|"

'cis h#    1| 3| 5| 6| 8|10| 0|
'14.       2| 5| 8|11|14|17|-1|

'Cb  fb    1| 3| 4| 6| 8|10|11|
'13.       3| 6| 9|12|15|18|21|

Local v1$[]=t1.split("|")
Local v2$[]=t2.split("|")
For Local I%=0 To 14
For Local j%=0 To 6
TableMidi[i,j]=v1[i*7+j].toint()
TableNote[i,j]=v2[i*7+j].toint()
Next
Next
'ShowTable
' load some note images for testing:
NoteImages=LoadAnimImage("noten.png",60, 140,0,15)
End Function



Function ShowTable()
' shows all 13 tables for debugging purposes only:
For Local I%=0 To 14
Print "------------Table Nr " + (i+1) + "--------------------"
Local a$= "", b$= ""
For Local j%=0 To 6
a=a+ " " + TableMidi[i,j]
b=b+ " " + TableNote[i,j]
Next
Print a
Print b
Next
End Function



Function NoteToMidi:Int(Note%)
' converts a notation note back to a midi note
' notemod      0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20
        ' --> to midi -1   0   1   1   2   3   3   4   5   4   5   6   6   7   8   8   9  10  10  11  12
Local Octave:Int=note/21
Local NoteMod:Int=Note Mod 21
Local Midi:Int=MidiTable[NoteMod] + 12*Octave
Print Note + " " + Names[NoteMod] + " => midi " + (Midi)
Return midi
End Function




Function EasyTranslate:Int[] (MidiChord:Int[])
' converts a midi noted chord into a notation noted chord
' the array can contain 1 to 4 notes
Local TakeTable%=Notation.WhichTable(MidiChord)
Print "Chord found in table " + TakeTable
Return Translate(TakeTable,MidiChord)
End Function





Function WhichTable:Int(MidiChord:Int[])
'trys to find out which of the 13 tables is the best
'returns a number between 0 and 12
Local Result%
For Local i:Int=0 To 12
Result= CheckTable(i , MidiChord)
If Result=True Then Return i
Next
Return -1
End Function




Function CheckTable:Int(Nr%, MidiChord:Int[])
'checks whether all notes of a chord are member of this table
For Local i:Int=0 To MidiChord.Length-1
Local SearchNote% = MidiChord[i] Mod 12
Local Result%=False
For Local j%=0 To 6
If TableMidi[Nr,j] = SearchNote
Result=True
Exit
EndIf
Next
If Result=False Return False
Next
Return True
End Function


Function Translate:Int[] (Nr%, MidiChord:Int[])
' converts a midi noted chord into a notation noted chord
' the array can contain 1 to 4 notes
Local ReturnChord:Int[MidiChord.Length]
If Nr=-1
' no real chord! use as minimum same sign
Return UseAllSameSign(MidiChord)


EndIf
For Local i:Int=0 To MidiChord.Length-1
Local SearchNote% = MidiChord[i] Mod 12
Local Octave%=MidiChord[i] / 12
For Local j%=0 To 6
If TableMidi[Nr,j] = SearchNote
'Print "found at " + j
ReturnChord[i]= TableNote[Nr,j] +21*Octave
Exit
EndIf
Next
Next
Return ReturnChord
End Function




Const FLAT:Int=0, SHARP:Int=1

Function UseAllSameSign:Int[] (MidiChord:Int[], Iteration:Int=0)
' tricky new function to process also "strange chords"
' if all 14 tables fail this algo trys to prevent notes on the same line
Local SameTableNote[][]= New Int[][4]
'                  [C   D   E  F     G     A     B]
SameTableNote[0]=  [1,3,4,6,7,10,12,13,15,16,18,19]
SameTableNote[1]=  [1,2,4,5,7,10,11,13,14,16,17,19]
SameTableNote[2]=  [1,3,4,6,9,10,12,13,15,16,18,21]
SameTableNote[3]=  [-1,2,4,5,7,8,11,13,14,16,17,19]

Local ReturnChord:Int[MidiChord.Length]

Print "STRANGE CHORD! now Using UseAllSameSign() function with Iteration-Round=" + Iteration
' first use one of the tables:
For Local i:Int=0 To MidiChord.Length-1
Local SearchNote% = MidiChord[i] Mod 12
Local Octave%=MidiChord[i] / 12
ReturnChord[i]= SameTableNote[Iteration][SearchNote] +21*Octave
Next

'now test, whether this table also produce notes on the same line:
For Local i:Int=0 To MidiChord.Length-1
For Local j:Int=i+1 To MidiChord.Length-1
If YPos(ReturnChord[i]) = YPos(ReturnChord[j])
'If yes... try the other table
If Iteration < 3 'for not producing a endless loop
Return UseAllSameSign(MidiChord,Iteration+1)
Else
Print "Needs Mix Methode"
Return UseMixedSigns (MidiChord, ReturnChord)
EndIf
EndIf
Next
Next
Return ReturnChord
End Function


Function UseMixedSigns:Int[] (MidiChord:Int[], BadChord:Int[])
' ultimate chance for chords: Try to mix # with b or send BadChord
Local list:TList=New TList
Local SearchNote% = MidiChord[0] Mod 12
Local Octave%=MidiChord[0] / 12
Local locChord:Int[]
AddChordsToListe(List, locChord, SearchNote, Octave)

For Local i:Int=1 To MidiChord.Length-1
Local SearchNote% = MidiChord[i] Mod 12
Local Octave%=MidiChord[i] / 12

For Local Chord:Int[]=EachIn list
If chord.length=i
AddChordsToListe(List, Chord, SearchNote, Octave)
List.Remove Chord
EndIf
Next
Next
Print "EXTREM STRANGE CHORD! now Using UseMixedSigns() function"
For Local Chord:Int[]=EachIn list
Return chord
Next
Print "!!!!! BAD CHORD! no solution found !!!!"
Return BadChord
End Function


Function AddChordsToListe(List:TList, Chord[], SearchNote, Octave)
Print "Adding " + Searchnote
Local last%=Chord.length
Local NewChord_I:Int[last+1]
Local NewChord_II:Int[last+1]
For Local i%=0 To last-1
NewChord_I [i] = Chord[i]
NewChord_II[i] = Chord[i]
Next
Select SearchNote
Case 0
NewChord_I[last]=-1+21*Octave
NewChord_II[last]=1+21*Octave
Case 1
NewChord_I[last]=2+21*Octave
NewChord_II[last]=3+21*Octave
Case 2
NewChord_I[last]=4+21*Octave
Case 3
NewChord_I[last]=5+21*Octave
NewChord_II[last]=6+21*Octave
Case 4
NewChord_I[last]=7+21*Octave
NewChord_II[last]=9+21*Octave
Case 5
NewChord_I[last]=8+21*Octave
NewChord_II[last]=10+21*Octave
Case 6
NewChord_I[last]=11+21*Octave
NewChord_II[last]=12+21*Octave
Case 7
NewChord_I[last]=13+21*Octave
Case 8
NewChord_I[last]=14+21*Octave
NewChord_II[last]=15+21*Octave
Case 9
NewChord_I[last]=16+21*Octave
Case 10
NewChord_I[last]=17+21*Octave
NewChord_II[last]=18+21*Octave
Case 11
NewChord_I[last]=19+21*Octave
NewChord_II[last]=21+21*Octave
EndSelect
If ContainsSameLine(NewChord_I)=False
Print "ADD **** Chord_I to list" + StringFromChord(newchord_I)
List.Addlast NewChord_I
EndIf

If NewChord_II[Last]>0
If ContainsSameLine(NewChord_II)=False
List.AddLast NewChord_II
Print "ADD **** Chord_II to list" + StringFromChord(newchord_II)
EndIf
EndIf
End Function


Function ContainsSameLine:Int(Chord:Int[])
For Local i:Int=0 To Chord.Length-1
For Local j:Int=i+1 To Chord.Length-1
If YPos(Chord[i]) = YPos(Chord[j])
Return True
EndIf
Next
Next
Return False
End Function

Function StringFromChord$(Chord:Int[])
Local s$
For Local i%=0 To chord.length-1
s=s + chord[i] + "-"
Next
Return s
EndFunction










Function Name$(Note%)
' returns the name of a notation note
' notemod    0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20
' --> name  Cb   C  C#  Db   D  D#  Eb   E  E#  Fb   F  F#  Gb   G  G#  Ab   A  A#  Bb   B  B#
Return Names[note Mod 21]
End Function


Const TREBLE:Int=0, BASS:Int=1

Function YPos:Int(note%, Clef%=TREBLE)
'returns the relative screen-y position of a notation note
Local pos:Int
If clef=TREBLE
Return Int(note/3) -28
EndIf
Return Int(note/3) -16
End Function


Function NeedsHelpingLines:Int (Note%, Clef%=TREBLE)
' finds out whether this note needs helplines
Local pos:Int=Ypos(note,Clef)
If pos<8 Or pos>18
Return True
EndIf
Return False
End Function


Function PaintHelpingLines(X%, Note%, Clef%=TREBLE, ZeroY%, Distance%, BassDistance%)
'paints helplines of a note
If NeedsHelpingLines(Note, Clef)=False Return
Local pos:Int=Ypos(note,Clef)
BassDistance=BassDistance*Clef
SetColor 1,1,1
If pos<8
For Local i%=7 To pos Step -2
DrawRect X-8,ZeroY-((i-Bassdistance)*distance),40,1
Next 
Else
For Local i%=19 To pos Step 2
DrawRect x-8,ZeroY-((i-Bassdistance)*distance),40,1
Next 
EndIf
SetColor 255,255,255
End Function 

Function DrawNotes (Chord:Int[])
'converts a notation chord into dispayed notes
Local ZeroY%=300, Distance%=10, BassDistance%=25, Imagedistance%=70
' flexible in scaling:
' ZeroY defines the absolute Y-position of  both systems
' Distance defines the absolute y-diff from one note to the next higher note
' BassDistance defines the relative distance of bass system to treble system
' ImageDistance is related to the "noten.png" (fine adjusting)
' this "noten.png" is best for distances from 8 to 11

'5 score-lines in bass and treble
SetColor 1,1,1
DrawText "Midi : " + MidiChord[0]+ "-" +  + MidiChord[1]+ "-" + MidiChord[2],100,100
For Local i%=0 To 4
DrawRect 100,ZeroY-((i*2+9)*distance),400,1
DrawRect 100,ZeroY-((i*2+9-BassDistance)*distance),400,1
Next
SetColor 255,255,255

'the keys:
DrawImage NoteImages,120,ZeroY-12*Distance-ImageDistance,0
DrawImage NoteImages,120,ZeroY-(12-Bassdistance)*Distance-ImageDistance,1

' paint the chord
Local signCounter%=0
For Local i:Int=0 To Chord.Length-1
'Print "found chord note " + Chord[i] + " " + ypos(chord[i])
PaintHelpingLines(250+i*25, Chord[i], 0, ZeroY, Distance, BassDistance)
Select Chord[i] Mod 3
Case 0
DrawImage NoteImages,220+signCounter,ZeroY- ypos(Chord[i])*Distance-ImageDistance,10
signCounter=signCounter+15
Case 2
DrawImage NoteImages,220+signCounter,ZeroY- ypos(Chord[i])*Distance-ImageDistance,9
signCounter=signCounter+15
End Select
DrawImage NoteImages,250+i*25,ZeroY- ypos(Chord[i])*Distance-ImageDistance,5
Next


'paint it in bass system too
Local AsBass%=1
signCounter=0
For Local i:Int=0 To Chord.Length-1
'Print "found chor note " + Chord[i] + " " + ypos(chord[i])
PaintHelpingLines(350+i*25, Chord[i], AsBass, ZeroY, Distance, BassDistance)
Select Chord[i] Mod 3
Case 0
DrawImage NoteImages,320+signCounter,ZeroY- (ypos(Chord[i],AsBass)-BassDistance)*Distance-ImageDistance,10
signCounter=signCounter+15
Case 2
DrawImage NoteImages,320+signCounter,ZeroY- (ypos(Chord[i],AsBass)-BassDistance)*Distance-ImageDistance,9
signCounter=signCounter+15
End Select
DrawImage NoteImages,350+i*25,ZeroY- (ypos(Chord[i],AsBass)-BassDistance)*Distance-ImageDistance,6
Next
End Function


Function ChordInfo(Chord:Int[],ShowChord:Int[])
Print "************************************"
Print "S C A N   R E S U L T S:"
Local ChordInfo$ = "      Midi-Chord was: "
Local NoteInfo$  = "       Note-Chord is: "
Local NameInfo$  = "           Names are: "
Local TrebleInfo$= "Draw at treble lines: "
Local BassInfo$  = " Draw at bass lines : "
For Local I%=0 To Chord.length-1
ChordInfo= ChordInfo + + Chord[i]  + " "
NoteInfo= NoteInfo + ShowChord[i] + " "
NameInfo= NameInfo + Notation.name(ShowChord[i]) + " "
TrebleInfo= TrebleInfo + Notation.Ypos(ShowChord[i]) + " "
BassInfo= BassInfo + Notation.Ypos(ShowChord[i],1) + " "
Next
Print ChordInfo
Print NoteInfo
Print NameInfo
Print TrebleInfo
Print BassInfo
End Function

End Type

Rem
Table-Explanation:
-----------------------------------
Scale      New       Midi-Notes
Order      sign      Notation-notes
-----------------------------------

-----------------------------------
no sign
Table     C  D  E  F  G  A  B
-----------------------------------
C         0| 2| 4| 5| 7| 9|11|
0.        1| 4| 7|10|13|16|19|
-----------------------------------
all #
Tables    C  D  E  F  G  A  B
-----------------------------------
G   f#    0| 2| 4| 6| 7| 9|11|
1.        1| 4| 7|11|13|16|19|
-----------------------------------
D   c#    1| 2| 4| 6| 7| 9|11|
3.        2| 4| 7|11|13|16|19|
-----------------------------------
A   g#    1| 2| 4| 6| 8| 9|11|
5.        2| 4| 7|11|14|16|19|
-----------------------------------
E   d#    1| 3| 4| 6| 8| 9|11|
7.        2| 5| 7|11|14|16|19|
-----------------------------------
H   a#    1| 3| 4| 6| 8|10|11|
9.        2| 5| 7|11|14|17|19|
-----------------------------------
fis e#    1| 3| 5| 6| 8|10|11|
12.       2| 5| 8|11|14|17|19|
-----------------------------------
cis h#    1| 3| 5| 6| 8|10| 0|
14.       2| 5| 8|11|14|17|-1|
-----------------------------------
-----------------------------------
all b
Tables    C  D  E  F  G  A  B
-----------------------------------
F   hb    0| 2| 4| 5| 7| 9|10|
2.        1| 4| 7|10|13|16|18|
-----------------------------------
Bb  eb    0| 2| 3| 5| 7| 9|10|
4.        1| 4| 6|10|13|16|18|
-----------------------------------
Eb  ab    0| 2| 3| 5| 7| 8|10|
6.        1| 4| 6|10|13|15|18|
-----------------------------------
Ab  db    0| 1| 3| 5| 7| 8|10|
8.        1| 3| 6|10|13|15|18|
-----------------------------------
Db  gb    0| 1| 3| 5| 6| 8|10|
10.       1| 3| 6|10|12|15|18|
-----------------------------------
Gb  cb    1| 3| 5| 6| 8|10|11|
11.       3| 6|10|12|15|18|21|
-----------------------------------
Cb  fb    1| 3| 4| 6| 8|10|11|
13.       3| 6| 9|12|15|18|21|
-----------------------------------

end rem


you need the Noten.png from the attachment to run the example.

Title: Re: My Music Editor
Post by: Hardcoal on June 07, 2021, 21:48:37
Thanks midimaster.. I've already experimented with it.. and i will implement it in my code.
I had to fix some Errors of Local I, but other than that it seems to work fine.

I'm not in a mood for programming atm, but ill go back to that soon.
or maybe ill implement it later.. and continue developing other things.
the only way i survive in coding.. is doing what i feel and not doing what i need
Title: Re: My Music Editor
Post by: Hardcoal on June 10, 2021, 21:03:52
MM=MidiMaster..

I tried to implement you method ..into my code.. I didnt succeed too well..
so I went with my system for now..
but I kept you code in Rem State.. in my project..
So I can continue my work..
Ill Contact you again.. soon when Ill solve one more thing and will see what to do
:)

The good news.. Is that Im serious about this App

Title: Re: My Music Editor
Post by: Hardcoal on June 13, 2021, 22:36:17
(https://i.imgur.com/h4141Of.jpg)

Im trying to understand the logic of the notation..
and ill try again to implement your code midimaster..

Im looking at muse score app to see how it preforms notations

and ill try again to implement your code..

does your code also support this 3 half tone chords arrangement?

Ive managed to make progress with MidiMaster Code, but im not their yet.

I also Started to like Muse Score..
Finally it makes sense to me

Title: Re: My Music Editor
Post by: Midimaster on June 13, 2021, 23:54:42
Also in MuseScore you can see, that the base is not a 12-objects-per-octave-system, but a 21-objects-per-octave-system.

If you want to create a "note object " the property "altitude" of the note needs to be in this 21-system. Re-calculating the midinote from the 21-systems is easy.

but using a 12-system means not beeing able to calculate the correct score position. So there is no way without 21-system.

This is the base, But this is not the final solution for all the problems you will get with notation. So drawing the notes is again a challenge with a lot of rules and still more exeptions.

Using the 21-system means you are able to write all the examples you showed in your screenshot. But these screenshots are no typical notations.

In a perfect notation it would look like this:
(https://www.syntaxbomb.com/index.php?action=dlattach;topic=8467.0;attach=4649)

Lets talk about the 5 chord in your screenshot:

1. This is allowed. You can combine D,F and G# in one chord. But if it is possible you would always use D-F-Ab. (See my example)

2. perfect

3. This is not allowed what should it be? two C# together with one D? I would write is as one C# with one D.

4. This is not allowed. You never see a Eb and a E written in this way. The solution if you need 3 notes closed together is always to notate it as: D-Eb-Fb (See my example). That is a typical solution where you would use Fb as a replacement for E.

5. Are there 3 note in your example? Not allowed. If this are 2 notes my example would be the solution. If you need again three notes: F-F# and G you would write them always as E#-F#and G

The property, whether a note is notated "normal" or "a little bit to the right" is not a fixed porperty of the note, but a decision of the drawing algorithm in the last moment before displaying it. Often adding one single new note to a chord changes to behavior of all the other members of this chord.

But one rule is: "never use the same altitude position twice". So C-C#-D would always become H#-C#-D.

Or a more complex example: G-G#-A would become a elegant F##-G#-A. The fact that a notation app like muse-score allowes your 5th example still does not mean that it is allowed or elegant.  It is a little bit like writing a typo in WORD: "God Luck" will be printed, but is still a typo.


Title: Re: My Music Editor
Post by: Midimaster on June 14, 2021, 00:48:31
To your question:

Quotedoes your code also support this 3 half tone chords arrangement?
Yes every 21-system does support this! This does not means that the result is alllowed or elegant.

My example code demostrate five things:

1.
How a 21-system works.

2.
How a midi-note is re-calculated from a 21-system-note.

3.
How to calculte a "best match" 21-system-note from a midi note or a midi-chord.

4.
How to display 21-system-notes

5.
How to calcuate the help lines



What is not implemented:

1. It has no algorithm to decide, when placing a note "a little bit to the right". But his can easyly be done by comparing all note of the chord with the given function YPos() (=note/3). If then two results have only a difference of 1 this means you will need this "right displacement" anywhere.

2. It has no a double ## or double bb algorithm. So some of the "3 half tone chords" would look similar unelegant like they do in musescore. (example: G-G#-A)

3. It has no "is it bass or treble"-flag. So this needs to be done.



Write questions if you need more explanation of the example code.


   



Title: Re: My Music Editor
Post by: Hardcoal on June 16, 2021, 05:04:14
I tried putting midi notes 60,61,62 which is C C# and D and your system didnt display it properly..

(https://i.imgur.com/oLBcttF.jpg)

anyway what I'm gonna do is to post my last version of progress and maybe slowly improve it.. by your advices..

I was busy fixing other stuff in the little time I put on this.
but now, after fixing other errors, I'm free again to concentrate on proper notes displaying.
I promise you ill improve it until it get to what you think its proper, just need patient.

I started making this type of notes Display

(https://i.imgur.com/zgLGTHP.jpg)

And ye .. I know its not correct key. Ill fix.

Title: Re: My Music Editor
Post by: Midimaster on June 16, 2021, 09:25:35
The helping line problem does not come from my code.

In the case of 60-61-62 my app crashs, because it is no "senseful chord". The function Notation.WhichTable(MidiChord) returns a "-1", what means: "No table fits!"
In this cases we have to add  "last resort" to pruduce anything but no crash.

I added a new function, which finds those senseless chords and tries to display them in a "all b" or "all #" style. As this can produce notes on the same line, it tries to test both approaches and select the "best". If nothing helps it return a ugly chord with notes onthe same line.

I updated the "Whole Runnable Code" example in my post #34:
https://www.syntaxbomb.com/index.php/topic,8467.msg347050592.html#msg347050592

There the  "Whole Runnable Code" example has now a new function...

Function UseAllSameSign:Int[] (MidiChord:Int[], FlatOrSharp:Int=FLAT)

...which tries to display anything in each situation.

This is the algo:
Code (BlitzMax) Select
Function UseAllSameSign:Int[] (MidiChord:Int[], FlatOrSharp:Int=FLAT)
Global Iteration%
' tricky new function to process also "strange chords"
' if all 14 tables fail this algo trys to prevent notes on the same line
Local FlatTableNote[]  =  [1,3,4,6,9,10,12,13,15,16,18,21]
Local SharpTableNote[] = [-1,2,4,5,7,8,11,13,14,16,17,19]
Local ReturnChord:Int[MidiChord.Length]

Print "STRANGE CHORD! now Using UseAllSameSign() function with FlatOrSharp=" + FlatOrSharp + " Iteration-Round=" + Iteration
' first use one of the tables:
For Local i:Int=0 To MidiChord.Length-1
Local SearchNote% = MidiChord[i] Mod 12
Local Octave%=MidiChord[i] / 12
If FlatOrSharp=SHARP
ReturnChord[i]= SharpTableNote[SearchNote] +21*Octave
Else
ReturnChord[i]= FlatTableNote[SearchNote] +21*Octave
EndIf
Next

'now test, whether this table also produce notes on the same line:
For Local i:Int=0 To MidiChord.Length-1
For Local j:Int=i+1 To MidiChord.Length-1
If YPos(ReturnChord[i]) = YPos(ReturnChord[j])
'If yes... try the other table
If Iteration=0
Iteration=1  'for not producing a endless loop
Return UseAllSameSign(MidiChord,1-FlatOrSharp)
EndIf
EndIf
Next
Next
Iteration=0
Return ReturnChord
End Function


Additional I added 2 more tables for the very seldom used scales "C#-Major" with 6#, and "Cb-Major" with 6b. This serves in cases where a F#-Major-Song use the B#-Chord. Or a Gb-Major-Song use the Cb-Chord.


Because my example still has no algo for the "a little bit to the right"problem it now displays the chords in a "arpeggio" style. This makes it easier to check the algo is workuing correct.

Scrennshot shows a 60-61-62-chord
(https://www.syntaxbomb.com/index.php?action=dlattach;topic=8467.0;attach=4653)
Title: Re: My Music Editor
Post by: Hardcoal on June 16, 2021, 14:08:34
now thats awesome.. im gonna try to use it.. thanks
Title: Re: My Music Editor
Post by: Midimaster on June 18, 2021, 13:50:24
Ok I again could refine the algo for chords with two improvements.

If all 14 scale-tables fail, now there are 4 additional tables for methods "all with #" and "all with b". both methods now have two steps "less signs" or "a lot of signs":

Code (BlitzMax) Select

Function UseAllSameSign:Int[] (MidiChord:Int[], Iteration:Int=0)
' tricky new function to process also "strange chords"
' if all 14 tables fail this algo trys to prevent notes on the same line
Local SameTableNote[][]= New Int[][4]
'                  [C   D   E  F     G     A     B]
SameTableNote[0]=  [1,3,4,6,7,10,12,13,15,16,18,19]
SameTableNote[1]=  [1,2,4,5,7,10,11,13,14,16,17,19]
SameTableNote[2]=  [1,3,4,6,9,10,12,13,15,16,18,21]
SameTableNote[3]=  [-1,2,4,5,7,8,11,13,14,16,17,19]


The app iterates through al 4 variants and decides for the best.

In the case where all 4 still deliver notes on the same line a new algo UseMixedSigns() starts. It tries to build the chord without any scale-rules only with using any combination of # and b that is theorectical possible:

Code (BlitzMax) Select

Function UseMixedSigns:Int[] (MidiChord:Int[], BadChord:Int[])
' ultimate chance for chords: Try to mix # with b or send BadChord
Local list:TList=New TList
Local SearchNote% = MidiChord[0] Mod 12
Local Octave%=MidiChord[0] / 12
Local locChord:Int[]
AddChordsToListe(List, locChord, SearchNote, Octave)

For Local i:Int=1 To MidiChord.Length-1
Local SearchNote% = MidiChord[i] Mod 12
Local Octave%=MidiChord[i] / 12

For Local Chord:Int[]=EachIn list
If chord.length=i
AddChordsToListe(List, Chord, SearchNote, Octave)
List.Remove Chord
EndIf
Next
Next
Print "EXTREM STRANGE CHORD! now Using UseMixedSigns() function"
For Local Chord:Int[]=EachIn list
Return chord
Next
Print "!!!!! BAD CHORD! no solution found !!!!"
Return BadChord
End Function


Function AddChordsToListe(List:TList, Chord[], SearchNote, Octave)
Print "Adding " + Searchnote
Local last%=Chord.length
Local NewChord_I:Int[last+1]
Local NewChord_II:Int[last+1]
For Local i%=0 To last-1
NewChord_I [i] = Chord[i]
NewChord_II[i] = Chord[i]
Next
Select SearchNote
Case 0
NewChord_I[last]=-1+21*Octave
NewChord_II[last]=1+21*Octave
Case 1
NewChord_I[last]=2+21*Octave
NewChord_II[last]=3+21*Octave
Case 2
NewChord_I[last]=4+21*Octave
Case 3
NewChord_I[last]=5+21*Octave
NewChord_II[last]=6+21*Octave
Case 4
NewChord_I[last]=7+21*Octave
NewChord_II[last]=9+21*Octave
Case 5
NewChord_I[last]=8+21*Octave
NewChord_II[last]=10+21*Octave
Case 6
NewChord_I[last]=11+21*Octave
NewChord_II[last]=12+21*Octave
Case 7
NewChord_I[last]=13+21*Octave
Case 8
NewChord_I[last]=14+21*Octave
NewChord_II[last]=15+21*Octave
Case 9
NewChord_I[last]=16+21*Octave
Case 10
NewChord_I[last]=17+21*Octave
NewChord_II[last]=18+21*Octave
Case 11
NewChord_I[last]=19+21*Octave
NewChord_II[last]=21+21*Octave
EndSelect
If ContainsSameLine(NewChord_I)=False
Print "ADD **** Chord_I to list" + StringFromChord(newchord_I)
List.Addlast NewChord_I
EndIf

If NewChord_II[Last]>0
If ContainsSameLine(NewChord_II)=False
List.AddLast NewChord_II
Print "ADD **** Chord_II to list" + StringFromChord(newchord_II)
EndIf
EndIf
End Function


In the case where also this algo looses, it decides to take the "Bad-Chord", what means a design of the chord with notes on the same line. This happens f.e. on midichord=67-68-69

I updated for you the The whole runnable example in my post #34. So you have a full running example. It starts with a harmless C-min chord but if you click you can step through all ugly chromatic chord like 59-60-61 then 60-61-62 and so on....

Title: Re: My Music Editor
Post by: Hardcoal on June 18, 2021, 16:40:41
Thanks MidiMaster.. have a nice weekend
Thanks for all the effort youre putting..

From time to time I leave the notation punctuality and concentrating on other aspect of this app.. (when i feel good)
so I wont give it all up.

Now Im working on live notation recording for a while

Title: Re: My Music Editor
Post by: Hardcoal on July 08, 2021, 18:39:21

I took a rest from programming returned back not long ago..

OK now Im trying to learn Audio recording..

I did a small app for recording and testing..

Ive managed to record audio just fine..
but now that I have the Sample:Byte ptr
I have no Idea how to read from it.
how do I access a Byte ptr? Like Peek and Poke on commodore 64?


-------------------------------------

OK after experimenting with Byte ptr ive managed to start understanding how to use it..

Seems you access it like Arrays..  ByePtr[AddressInBytes]


Question 2:

When i do playsound and save the channel it played on
and when i ask is channelplaying.. it wont give a false reply when the sample is suppose to have ended..
how can that be ?




Title: Re: My Music Editor
Post by: Midimaster on July 09, 2021, 07:27:40
Are you using OpenAL or MiniAudio for recording?

Depending on the format of the recording you would better use Byte Ptr or Short Ptr for receiving the true values.

simply add a new pointer if needed:
Code (BlitzMax) Select

ShortSample: Short Ptr = Short Ptr(MyRecording.Samples)
For local i:Int=0 to MyRecording.length/2-1
   Print ShortSample[i]
Next

If you recorded in 16bit the MyRecording.length still shows you the length in bytes. So you have to divide by 2.

If you only want to save the recorded samples you need not to care about the correct pointers. But if you want to process or check the recording signal you need the true values:

If you recorded 8bit Byte Ptr is the best, but you have to substract 128 to get the real values. The reason is that the Zero-Value is defined as 128 in 8bit-Streams.
You can use this function:
Code (BlitzMax) Select

Function ByteToInt:Int( s:Int )
    Return s-128
End Function



If you recorded 16bit Short Ptr is the best, but you have to transform the values from Unsigned SHORT to Signed INT to get the real values. The reason is that  BlitzMax has no Signed SHORT variable type.

You can use this function:
Code (BlitzMax) Select

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



After you transfered the samples values to true values you could do things like adjusting the volume of a recording, etc...



If you use MiniAudio and record with 32bit you could directly work with the values if you use the correct pointer Int Ptr or Float Ptr.
Title: Re: My Music Editor
Post by: Hardcoal on July 09, 2021, 12:26:15
Im using Open AL

Im trying to read the volume while recording but it wont let..

now ill try to cut half of the sample and see if it works.

ill put my little app here soon for investigation


OK i managed to cut a sample to half.. I mean clone half section to its own other half section

this is my code.. feel free to tell me whats wrong


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



once again.. i have problem telling when the playing has ended.. of the audio sample.. since the channelplaying keeps showing as if its still playing for some reason

im trying to display the sample and thats what i did. and it aint working properly

Function DisplayRecordedSample(MySample:MySample_Type, Xpos:Float = 0, Ypos:Float = 0)
Local Ratio:Float, SampleHeight:Float

Ratio = SEWidth / MySample.Sample.length

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)

SampleHeight = Math.FitValue(MySample.Sample.samples[MySample.LengthInBytes - 2], 256, SEHeight)

xRect(MySample.LengthInBytes * Ratio, SEHeight - SampleHeight, 1, SampleHeight, True)

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

xSetBuffer(xBackBuffer())

End Function
Title: Re: My Music Editor
Post by: Hardcoal on July 10, 2021, 20:35:27
Anyway when I record a sample.. and than replay.. it keeps thinking its being played for at least double the time for some reason..
but when i save the sample and reload it.. it stops exactly at the end..
I dont know why..

I also have no Idea how to display the Wave of the sample.. :( annoying
Title: Re: My Music Editor
Post by: Midimaster on July 11, 2021, 08:36:05
It looks like you are working with a library I dont know.

some of your functions are not native BlitzMax functions.

If you use regular TAudioSample and SF_STEREO16LE the values are UNSIGNED SHORT in BlitzMax, but SIGNED SHORT in reality. To get a value which you can process you have to transform it to INTEGER

second: When you process SF_STEREO16LE you have to care about the frame size of 4 bytes. Each sample is 4 bytes long:
|high left | low left | high right | low right |
This means your cuts or moves are only allowed on positions which are multiples of 4. If you not care about this may cause noise sound or mix-ups of left and right channel.

HalfLength = (Sample.length / 2) Mod 4 '!!!!!


What is the type MySample_Type. Thats a non BlitzMax standard audio type? Perhaps already converted to INT? We must know this to give you the best advise.

To get a graphic you have to calculate with the real samples values. With original TAudioSample you need to convert each from UNSIGNED SHORT to INTEGER:
    Function ShortToInt:Int( s:Int )
        Return (s Shl 16) Sar 16
    End Function


Now you can shrink the values by dividing /256 and you will get values from -256 to 256 which fit to the screen. To manage the horizontal size of the TAudioSample you could use only every hundredth sample. This would mean that only 441 x-values remain per second.




Title: Re: My Music Editor
Post by: Hardcoal on July 11, 2021, 10:06:30
OK midi master im now on it and testing what youre saying .. Thanks

MySample_Type is just something I made ..
it holds TaudioSample.

midimaster im using the code you adviced for me..

nothing else

here are my includes

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



btw midimaster i saw you on a blitzmax german site.. i tried to register but it didnt work



Title: Re: My Music Editor
Post by: Hardcoal on July 11, 2021, 20:38:04
I cant make it work.. This is the function the suppose to display then sample while recording..

Function DisplayRecordedSample(MySample:MySample_Type, Xpos:Float = 0, Ypos:Float = 0)
Local Ratio:Float, SampleHeight

Ratio = SEWidth / MySample.Sample.length

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)

    If MO.LengthCounterInBytes / 4 = Floor(MO.LengthCounterInBytes / 4) Then
Local TI =MySample.Sample.samples[MO.LengthCounterInBytes - 4]
TI = (TI Shl 8) + MySample.Sample.samples[MO.LengthCounterInBytes - 3]
SampleHeight = Math.FitValue(TI, 65536, SEHeight)
End If

Local CurrentX:Float = MO.LengthCounterInBytes * Ratio

xRect(PreviousDX, SEHeight - SampleHeight, CurrentX - PreviousDX, SampleHeight, True)

PreviousDX = CurrentX

xSetBuffer(xBackBuffer())

End Function


Function FitValue:Float(Value:Float, PeekValue:Float, NewFrame:Float)
Return NewFrame / PeekValue * Value
End Function



it suppose just to show the volume of the sample inside an Image sized 600*100
but instead it only shows lines even when its not making sound

the command FitValue is just mine.. to set the value into a 256 pixels Frame Top height limit. so dont mind it..

I understood what you said about the structure of the Sample.. but i still cant read it correctly..
ill keep working on it

Im trying to display only one side of the sample . i dont try to display stereo atm.
I wanted to pass faze one before I try Stereo . but I fail at this too.. and i dont know why

(https://i.imgur.com/ZodfL3f.jpg)

i also tried this for display not in real time while sampling, but later..

Function DisplayRecordedSampleLater(MySample:MySample_Type)
Local Ratio:Float

Ratio = SEWidth / MySample.Sample.length

xSetBuffer(xImageBuffer(SE_Image))

xColor(0, 255, 0)

For Local I = 0 To MySample.sample.length Step 4
Local High = MySample.Sample.samples[I]
Local Low = MySample.Sample.samples[I + 1]
Local Combined = high Shl 8 + Low
Local SampleHeight = Math.FitValue(Combined, 65536, SEHeight)
xRect(I * Ratio, SEHeight - SampleHeight, 1, SampleHeight, True)
Next

xSetBuffer(xBackBuffer())

End Function


Title: Re: My Music Editor
Post by: Midimaster on July 12, 2021, 01:38:03
Again! If you use SF_STEREO16LE you should use a SHORT pointer instead of a BYTE Pointer

It is a good idea first to start testing your code not in real time but with a given TAudioSample afterwards.
The capture-device will return values from -32768 to +32768, but BlitzMax Short Ptr will show them as value from 0 to 65536 which is wrong. After using the function ShortToInt() the Value are correct but need to be INTEGER.

Only with this INTEGER you can start your calculations for display.
 
BlitzMax 1.50:
Code (BlitzMax) Select
Local Audio:TAudioSample
RealPointer: Short Ptr = Short Ptr(Audio.Sample)

' for read MONO or read all of STEREO use this:
For local i%=0 to Audio.Length-1
     local Value% = RealPointer[i]
     Value =  ShortToInt( Value )   
Next

' for listen to one channel of STEREO use this:
For local i%=0 to Audio.Length-1 Step 2
     local LeftValue% = RealPointer[i]
     LeftValue =  ShortToInt( LeftValue )

     local RightValue% = RealPointer[i+1]
     RightValue =  ShortToInt( RightValue )   
Next

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



Here a runnable Example for you:
(uses the audio-file TestABC.OGG from attachment here)
Code (BlitzMax) Select
SuperStrict
Graphics 1200,600
Global Audio:TAudioSample=LoadAudioSample("testabc.ogg")

Print " HERTZ = " + Audio.Hertz
Print "FORMAT = " + Audio.Format + " (SF_MONO16LE)"
Print "LENGTH = "  +Audio.Length
'Print Audio.Samples
Global RealPointer:Short Ptr = Short Ptr(Audio.Samples)
Global Start%=0
Repeat
Cls
For Local I%= 0 To 1200
Local Value% = RealPointer[(i+Start)*5]
value = ShortToInt( Value)
DrawRect i, 300,1,Value/100
Next
Flip 1
Start=Start + 5
Until AppTerminate()


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

Title: Re: My Music Editor
Post by: Hardcoal on July 12, 2021, 03:50:13
I thought Short is a Byte.. :S

A) Why do you need a minus value in a sound wave? I didnt even expect this So minus means Left side?

B) why do you Step 5 when you said that each sample is 4 Bytes?
Title: Re: My Music Editor
Post by: Midimaster on July 12, 2021, 07:48:55
Quote from: Hardcoal on July 12, 2021, 03:50:13
I thought Short is a Byte.. :S
signed SHORTs are signed 16bit integer with 1sign-bit and 15 value bits (values from -32768 to +32768)
BYTES are 8bit integer always unsigned, so all 8bit are value bits (values from 0 to 255)


Quote
A) Why do you need a minus value in a sound wave?
The speakers are driven by AC voltage: With V=+1 the speaker is push ahead, with V=-1 the speaker is pushed back. Think of a sinus-curve which also runs from 0 (0°) to 1 (90°) back to 0 (180°) then to -1 (270°) and back to the beginning.

QuoteI didnt even expect this So minus means Left side?
No, the channels are independent, each channel has its own SHORT. In a TAudioSample they are sorted in Frames. Means all SHORTS belonging to the same time stamp are close together:
| Left 1 | Right 1 || Left 2 | Right 2 || Left 3 | Right 3 || ....|

Would the recording have 4 channels:
| ChA 1 | ChB 1 |ChC 1 | ChD 1 || ChA 2 | ChB 2 |ChC 2 | ChD 2 || ChA 3 | ChB 3 |....|



QuoteB) why do you Step 5 when you said that each sample is 4 Bytes?
In a MONO recording the correct step would be 1 to reach all samples values, because a SHORT PTR already jumps 16bit ahead each step:

SHORT PTR    RAM ADRESS
--------------------------
[0]          12345678
[1]          12345680
[2]          12345682
[3]          12345684
[4]          12345686
....


The 5 is related to the fact that in a recording the values are not changing that fast and we need not to to display them all. Its something like a "zoom factor" for the display not to show every sample but only every 5th. You can change the 5 to a 1 to get a more detailed view into the audio. Or change the 5 to a 500 to get a rough overview over the whole recording.

In a 12kHz-Recording:..
  1 means each 1/12000sec is a pixel, 1200 pixels show a time of 0.1sec
  5 means each  1/2400sec is a pixel, 1200 pixels show a time of 0.5sec
500 means each    1/24sec is a pixel, 1200 pixels show a time of 50 sec
Title: Re: My Music Editor
Post by: Midimaster on July 12, 2021, 07:50:55
sorry double post
Title: Re: My Music Editor
Post by: Hardcoal on July 12, 2021, 17:05:40
I never knew a speaker vibrate on both directions .. ive always thought it only either produce magnetic field or not..
but now i do..
Title: Re: My Music Editor
Post by: Hardcoal on July 12, 2021, 19:51:17
Although I still didnt quite understand how you read the data..
I did fix your example..
Because it got stuck

blitzmax

Strict

Const SWidth:Int = 1200
Const SHeight:Int = 600

Graphics (SWidth, SHeight)

Global AudioSample:TAudioSample = LoadAudioSample("Mine2.wav")

Print "HERTZ = " + AudioSample.Hertz
Print "FORMAT = " + AudioSample.Format + " (SF_MONO16LE)"
Print "LENGTH = "  +AudioSample.Length

Global RealPointer:Short Ptr = Short Ptr(AudioSample.Samples)

Const StepSize:Int = 5

Global Counter:Int = 0

Repeat
     Cls
    'Draws all wave
         For Local I:Int = 0 To SWidth  'This will Display from Edge To Edge what it can
            Local Value:Int = RealPointer[(I + Counter) * StepSize]
            Value = ShortToInt(Value)
            DrawRect (I, SHeight / 2, 1, Value / 100)
         Next
     Flip 1

If KeyDown(KEY_SPACE) = False Then Counter = Counter + StepSize

Until SWidth + Counter * StepSize >= AudioSample.length

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


Ive managed to display the Wave properly

But now I want when I play the Sample to see a line the represent the location in the Sample.
But when you use PlaySound you dont get any information of the location of the playing..

So i can count time manually .. or is it a way to read to current location of the PlaySound, somehow?

Anyway i still didnt get the wave thing correctly, since what I display after recording is not the same like what i display when I load what I recorded..
Title: Re: My Music Editor
Post by: iWasAdam on July 12, 2021, 19:57:08
Bytes can actually be signed or unsigned

The minus values are actually irrelevant they nearly help things. Speakers operate on a +- voltage system, but can be 0 aligned.

The computer storage can be anything - it's the os and the hardware that does the actuall transform to something an amp would understand - usually some form of optical isolated digital to analogue conversion.

So. The computer storage and manipulation can be any format it wants - it's the low level stuff that does the actual conversion.

Now.... Different audio systems (open all, etc) can use similar or completely different mechanisms.

Remember also that signed values are actually just the numbers, and cen be dealt directly as such. Using minus values is just a way to make it simple for humans to use...

The best way to think about audio is to understand what the driver wants. What you are happy with, and work out how to do the conversion when you require.

My personal suggestion is to use a signed float as the internal system storage- it makes everything simple and quick
Title: Re: My Music Editor
Post by: Midimaster on July 13, 2021, 00:59:40
Quote from: Hardcoal on July 12, 2021, 19:51:17...But now I want when I play the Sample to see a line the represent the location in the Sample.
But when you use PlaySound you dont get any information of the location of the playing..

But you can guess on which SamplePosition the PlaySound is. When we set aside, that we have latency the position in the audio is relative to the time that passed since we started the audio with PlaySound.

PlaySound plays HERTZ frames per seconds. Means in MONO:

Local Audio:TAudioSample
RealPointer: Short Ptr = Short Ptr(Audio.Sample)
Music:TSound = LoadSound(Audio)
PlaySound Music
StartTime:Int=Millisecs()
Repeat
     Position =  (Millisecs()-StartTime)*Audio.Hertz/1000
     ActSample = ShortToInt( RealPointer[Position] )
Until AppTerminate()



QuoteAnyway i still didnt get the wave thing correctly, since what I display after recording is not the same like what i display when I load what I recorded..
You need to explane more precise what the problems are or how you notice them. There should not be any difference.

Or send us the actual code snipplet for both.


Quote from: iWasAdam on July 12, 2021, 19:57:08
Bytes can actually be signed or unsigned
Of course they can, what I want to say is that in AUDIO-CAPTURE the SF_MONO8 is defined UNSIGNED with 128=ZERO. That is completely different from the other formats which are defined SIGNED and use a SIGN-BIT. Without this knowledge (how to transform) he cannot calculate (or paint) with the values.

And it matters that the SF_MONO16 is SIGNED but misinterpreted by BlitzMax. Without this knowledge he cannot calculate (or paint) with the values.

Hardcoal has to consider this in both of his functions: DisplayRecordedSample() and DisplayRecordedSampleLater()

At the moment I guess that Hardcoal is using SF_STEREO16LE to capture the audio.
Title: Re: My Music Editor
Post by: Hardcoal 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..

Title: Re: My Music Editor
Post by: Hardcoal 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

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

      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
Title: Re: My Music Editor
Post by: Midimaster on July 16, 2021, 02:08:09
Quote from: Hardcoal on July 15, 2021, 20:44:41
...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:
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:
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...
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:
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:
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:

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



Title: Re: My Music Editor
Post by: Midimaster 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) Select
Ratio = SEWidth / MySample.Sample.length
this gives x-postions like
14.001
14.002
14.003
14.004
...

which all result in a painting at x-position 14

but better
Code (BlitzMax) Select
local StepWidth:Float = MySample.Sample.Length/2/SEWidth
this gives Pointer-Position like
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".
Title: Re: My Music Editor
Post by: Hardcoal 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)
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)

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
Quotesecond: 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
Title: Re: My Music Editor
Post by: Midimaster 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:
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) Select
SuperStrict
Global Audio:TAudioSample=CreateAudioSample(1000,44000,SF_STEREO16LE)
Print Audio.Length
For Local i%=0 To 4000
Audio.Samples[i]=123
Next

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:
     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

Title: Re: My Music Editor
Post by: Hardcoal on July 16, 2021, 16:17:20
I thought the exact hertz are 44100 not 44000..

Here is all my code


Main App
'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
'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




Title: Re: My Music Editor
Post by: Midimaster 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) Select
Select Answer
Case 1
If MA.GetSwitchButtonState(MetronomOn_cb) = True Then PlayMetronom()
PlayClock()
DisplayRecordedSampleLater(MySample)
DrawCursorWhileRunning(MySample)
Case 2


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:
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) Select
Method IncreaseSampleVolume(Sample:TAudioSample, Increment:Float = 1.1)
Local ShortPointer:Short Ptr = Short Ptr(Sample.Samples)

For local i% = 0 To Sample.Length-1
Local LeftValue:Int  = ShortPointer[2*i]
Local RightValue:Int = ShortPointer[2*i+1]

RightValue = ShortToInt(RightValue)
LeftValue  = ShortToInt(LeftValue)

RightValue = RightValue*Increment
LeftValue  = LeftValue*Increment

ShortPointer[2*i]   = LeftValue
ShortPointer[2*i+1] = RightValue
Next
End Method



minimalized code version:
Code (BlitzMax) Select
Method IncreaseSampleVolume(Sample:TAudioSample, Increment:Float = 1.1)
Local ShortPointer:Short Ptr = Short Ptr(Sample.Samples)
For I = 0 To 2*Sample.length-1
ShortPointer[i]   = ShortToInt( ShortPointer[i]   ) *Increment
Next
End Method

Title: Re: My Music Editor
Post by: iWasAdam on July 16, 2021, 18:46:10
QuoteI 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...




Title: Re: My Music Editor
Post by: Midimaster on July 16, 2021, 23:49:28
Quote from: iWasAdam on July 16, 2021, 18:46:10
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 ....
Quote from: iWasAdam on July 16, 2021, 18:46:10
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.



Quote from: iWasAdam on July 16, 2021, 18:46:10
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ä?

Quote from: iWasAdam on July 16, 2021, 18:46:10
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ä?

Quote from: iWasAdam on July 16, 2021, 18:46:10
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?

Quote from: iWasAdam on July 16, 2021, 18:46:10
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.


Title: Re: My Music Editor
Post by: Hardcoal 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..
QuoteAnother 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
Title: Re: My Music Editor
Post by: Midimaster 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.

Quote from: Hardcoal on July 16, 2021, 23:54:39
... 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)
(https://www.syntaxbomb.com/blitzmax-blitzmax-ng/my-music-editor/?action=dlattach;attach=4718)

Title: Re: My Music Editor
Post by: Hardcoal 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..

QuoteTBH 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..

QuoteSo 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
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


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 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







Title: Re: My Music Editor
Post by: Hardcoal 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
Title: Re: My Music Editor
Post by: Midimaster on July 18, 2021, 07:07:03
Quotewhat 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) Select
Function DisplayRecordedSampleLater(MySample:MySample_Type)
....
local RectCounter:Int=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)
                        RectCouter = RectCounter+1
        xRect (I * Ratio, SEHeight / 2, 1, Value)
Next
Print "Number of RECTS: " + rectcounter
...
End Function

...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) Select
local Audio:TAudioSample = Create AudioSample( ???, 44100, SF_STEREOLE)
Do you know the answer?

Quote19: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) Select
CreateAudioSample(SAMPLE_RATE * 2, SAMPLE_RATE, SF_STEREO16LE)
The first parameter of the CreateAudioSample() does not expect Bytes, but Frames! Frames are HERTZ*TIME !!!


Title: Re: My Music Editor
Post by: Midimaster on July 18, 2021, 07:37:02
Quote...maybe something is wrong with the save function...
Yes, someting is wrong

Because you always define the TAudioSample 4times too big, it looks like the defining of the CreateStaticBank() would be correct:
wrong:
Method SaveSample(MySample:MySample_Type, SampleFilename:String = "")
....
'Create a bank from the Samples
SndBank = CreateStaticBank(MySample.Sample.samples, MySample.Sample.length)
...
'Write wav sound data
SndBank.Write(FileStream, 0, MySample.Sample.length)



correct:
Code (BlitzMax) Select
Method SaveSample(MySample:MySample_Type, SampleFilename:String = "")
....
BlockAlign = CHANNELS * BITRATE /8
....
'Create a bank from the Samples
SndBank = CreateStaticBank(MySample.Sample.samples, MySample.Sample.length*BlockAlign)
...
'Write wav sound data
SndBank.Write(FileStream, 0, MySample.Sample.length*BlockAlign)
Title: Re: My Music Editor
Post by: iWasAdam on July 18, 2021, 11:26:06
@Midimaster
Now if I recall...  ;D
wasn't it mentioned that banks were not the best way to store things?

MySample.Sample.samples, MySample.Sample.length

surely it would have been much simpler for you to program the interface to be:
MySample.samples, MySample.length

and of the sake of clarity:
BlockAlign = CHANNELS * BITRATE /8
        ....
        'Create a bank from the Samples
        SndBank = CreateStaticBank(MySample.Sample.samples, MySample.Sample.length*BlockAlign)


BlockAlign is something you have conjoured up to fix the save issue you created.

SndBank = CreateStaticBank(MySample.Sample.samples, MySample.Sample.length)
is a very logical was to think about the data and deal with it...

But of course I know absolutely NOTHING about audio and am talking out of my bottom...
;D
Title: Re: My Music Editor
Post by: Midimaster on July 18, 2021, 12:58:33
Quote from: iWasAdam on July 18, 2021, 11:26:06
@Midimaster
Now if I recall...  ;D
wasn't it mentioned that banks were not the best way to store things?

No! It wasn't mentioned! not by me, not by anybody else! And again: I did not write this code! Why asking me?
btw... the Banking thing is one of the thing which is working in his code!

Quotesurely it would have been much simpler for you to program the interface to be:
Code:
MySample.samples, MySample.length

Again, dear IWasAdam. I cases of SF_STEREO16LE and SF_MONO16LE the 1.50 BlitzMax function Samples.Length() does NOT report the size of the TAudioSample in Bytes! Call it a feature, I would call it a bug! And also a 2sec CreateAudioSample() does not allocate 88.200 Byte when you write ...
CreateAudioSample(44100*2, 44100, SF_STEREO16LE
...., but 352.800 !!!
And this...
CreateAudioSample(88200, 44100, SF_MONO16LE
not 88000, but 176.400 !!!

So you cannot use Sample.Length()  to calculate the saving size, but it needs to be
 
SizeInBytes = Sample.Length()*CHANNELS*BITRATE /8


If you of course define a 2sec. SampleBuffer wrongly to big:
CreateAudioSample (44100*8, 44100, SF_STEREO16LE
...you can now use Sample.Length (352.000) to save the Audio. Because the wrongly calculated Sample.Length is now exactly the same result a correct defined Sample.Length()*CHANNELS*BITRATE /8 would deliver too.
Only dis-advantage in this calculation: The TAudioSample will mysteriously play longer than 2 seconds: exactly 8 seconds.

QuoteBut of course I know absolutely NOTHING about audio and am talking out of my bottom...
I don't know which part of your body you use to produce thinking and talking... but it is "not the best" writing style coming out!

Title: Re: My Music Editor
Post by: Hardcoal on July 18, 2021, 13:59:21
This is a quick response, Ill detail later..

Perhaps you missed what I've said..
Everything is working Perfect!

I mean Everything!

My method worked along with all your advices dear people.

Ill optimize the resolution of displaying the Sample later.. now that it works it should be easy..
Before I play a Sample I divide the length by 4
and the restore it back by multiplying by 4

For some reason that works, and the ChannelPlaying reports exactly when the sample ends..


Title: Re: My Music Editor
Post by: iWasAdam on July 18, 2021, 15:16:57
QuoteI don't know which part of your body you use to produce thinking and talking... but it is "not the best" writing style coming out!
It's the part that rewrote the entire blitzMax audio code to support full audio synthesis in c++ with no help and no complaints.
It's the part that wrote the entire audio sub system to support up to 2048 simultaneous voices with no latency and no playback slowdown
It's the part that created filters, lfo and envelope system with a cross matrix on individual voices
It's the part that created the entire UI, sequencer, timing systems, programmable effects core, 3d audio display...

And it's the part that knows NOTHING about what I am talking about - think on that
Title: Re: My Music Editor
Post by: Qube on July 18, 2021, 20:35:13
Quote from: iWasAdam on July 18, 2021, 15:16:57
It's the part that...

It's the part that wrote QasarBeach (https://www.syntaxbomb.com/showcase/qasarbeach-1-29g-released/) ( iWasAdam not me as I'm dumb like that )
Title: Re: My Music Editor
Post by: Hardcoal on July 18, 2021, 20:43:40
I think this site is gone forever.. But its not :)
I was willing to pay for it to remain..

Anyway.. back to business.
Im working now on Moving the cursor.. and playing the samples from the middle and so on

Guys please forgive me if I dont replay to all the posts you posted..
Its too much of an overload for me.
I prefer to spend time going forward when things are working..

So, for now Ill move on. But I will reread everything Later
I would be glad to clean all this post because it was one big mess, but thats not in my hands.

I want to know if its possible to Make sound Effects in Real time Like Echo..
and so on..

Question: Why in the SaveSample Function you have this 40 Addings? FileStream.WriteInt(MySample.Sample.length + 40)


Title: Re: My Music Editor
Post by: Steve Elliott on July 18, 2021, 21:53:23
It's very disappointing when new people come to a site and immediately start making assumptions about people, start making demands.

They've been here 5 minutes and so should try and understand some people leave because, well they have 'issues' and could have an argument in an empty room, rather than guessing why they left, and so tarnishing the good peeps here.

The arrogance here is off the scale.  We have been here sometime and know Adam's credentials in the sound arena and coding prowess.
Title: Re: My Music Editor
Post by: Hardcoal on July 18, 2021, 22:30:45
Steve Elliott
I totally Agree with you..
and to criticize someone of the scale of IWasAdam is even more ridicules

I expect from such a site of this level to display really polite behavior towards one other..

I totally love and value the people here..
everything that Is done here comes from pure will to help, one other.


Title: Re: My Music Editor
Post by: Hardcoal on July 22, 2021, 00:04:30
Ok Im back after some break..  (I did some other programming missions)

Hope no one is Mad over me from whatever reason..

Anyway I did PlaySampleFromMIddle function and here it is..
Please tell me if i did it right

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  'should be short

Local TempSample:TAudioSample = CreateAudioSample(LengthInBytes, mysample.Sample.hertz, mysample.Sample.format)
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



I added a Demo For what I reached so far..
This Crap is Rather confusing I must Admit

https://drive.google.com/file/d/1jCpl70KQ9FNX3mn4olT4ePTHQJAbv5EL/view?usp=sharing
Title: Re: My Music Editor
Post by: iWasAdam on July 22, 2021, 14:00:43
@Hardcoal
My advice is to take things at your own pace with audio - it's very low level stuff, pointers and bytes and all sorts of weird little things that make something work one day and then not work the next.

What you have done (so far) deserves respect - particularly as you are getting conflicting and sometimes unusual advice as to what to do and what to not do.

You also deserve kudos for sticking with your editor concepts, making them work and getting results - all of this is tedius and most people give up - so you are doing things right for you \o/


I know that I can come across 'snarky' at times. but time has taught me some of the 'tricks' that make programming simpler.

Some things are easy (showing a waveform)
Some things are more difficult (having variable loop points in a sample) (showing the current play position in a sample)
Some things can be really hard (realtime filters, lfos and envelopes)

but don't be put off there are ways around everything ;)
Title: Re: My Music Editor
Post by: Hardcoal on July 22, 2021, 17:51:03
Thanks Adam very kind words
All Im doing including my game editor I never gave up on anything..
I use stuff I made from all the projects and also always planning to continue everything I started..
I building projects In a way that it is easy to Drop them and return to them rather quickly and understand what's going on their.

About this music Editor.. I do Hold Great respect to anyone who put of from his time to help me..
So ill never complain and mention anyone who helped me in Credits And If I made money out of something Ill also wont forget people who helped me.
Im doing things on my own pace and mental state.
Ye Im pleased with my achievements and midi master especially helped me so much, I owe him a lot.

(My mood does not always allow me to be stable at one thing)

What Im doing is Tools..
Tools that I can later Clone from one place to another ..
And see what I can make of it.

I planning on small projects , not like before when I tried to take over the world..

Like an Audio Recorder..
and those sort of things.

Im continuing on this sample editing manipulation up to a point.
than ill drop it and move on to other thing

Hope ill keep getting help here.
cause its for the benefit of all




Title: Re: My Music Editor
Post by: Hardcoal on July 22, 2021, 19:53:50
What is the best way to Free a TaudioSample..?
And Reconstruct it

Cheers
Title: Re: My Music Editor
Post by: iWasAdam on July 22, 2021, 20:14:11
I'm not at my machine to check things. But usually (if it's a ng class) you just set it to null

Class = null

This will set the internal garbage collector to automatically free thing for you. Derron can probably give you the exact details...
Title: Re: My Music Editor
Post by: iWasAdam on July 23, 2021, 07:53:05
No problem.

The TSample (PlaySampleFromMIddle function) - without running it looks generally right.

my general process is similar:
1. create new sample of the size you need
2. copy, modify (etc) from the old sample to the new sample

:)

Title: Re: My Music Editor
Post by: Hardcoal on July 24, 2021, 23:45:02
Ok Im now adding more Tracks.. so one track is kinda working ok, So progress is not bad
Title: Re: My Music Editor
Post by: Hardcoal on July 25, 2021, 23:22:54
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
Title: Re: My Music Editor
Post by: Hardcoal on July 26, 2021, 09:53:07
Ok, i see helped has seized.
No worries i promise ill manage myself.

Ive also finally read all this thing between Adam and mmaster

Title: Re: My Music Editor
Post by: Midimaster on July 26, 2021, 10:09:08
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...



Title: Re: My Music Editor
Post by: Hardcoal on July 26, 2021, 10:48:56
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
Title: Re: My Music Editor
Post by: Hardcoal on July 29, 2021, 17:39:30
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
Title: Re: My Music Editor
Post by: Midimaster on July 30, 2021, 01:02:42
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.




Title: Re: My Music Editor
Post by: Hardcoal on July 30, 2021, 05:20:43
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


Title: Re: My Music Editor
Post by: Midimaster on July 30, 2021, 10:49:48
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!






Title: Re: My Music Editor
Post by: Hardcoal on July 30, 2021, 11:34:51
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..
Title: Re: My Music Editor
Post by: Midimaster on July 30, 2021, 12:12:45
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!
Title: Re: My Music Editor
Post by: iWasAdam on July 30, 2021, 15:02:54
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 :)


Title: Re: My Music Editor
Post by: Hardcoal on July 30, 2021, 16:45:41
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
Title: Re: My Music Editor
Post by: iWasAdam on July 31, 2021, 06:18:16
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
Title: Re: My Music Editor
Post by: Midimaster on July 31, 2021, 07:32:06
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...


Title: Re: My Music Editor
Post by: Hardcoal on July 31, 2021, 07:53:35
I did not experiment with looping at all yet.
But im not trying to loop something from the middle of a sample.
I will reach loop testing only in the future.
Im still working on the basic track editing process.
Soon ill finish with it and release a demo.
Than ill move on to looping tests.
And see the related issues you mentioned
And see what to do.

I think im back on track..
Most of the things seem to work,
And im making rather complicated things.
It's interesting because im not planning to do what's been done before, im going to try new stuff.
Like sample prefabs, which means when you change one sample, it Chang its clones too.

Now one important question..
How difficult is to add vst plug-ins to my app?
Like delay echo etc..?

Cheers and good day guys
Title: Re: My Music Editor
Post by: iWasAdam on July 31, 2021, 08:51:06
my advice would be to do a simple tiny test loop app - get it working and then move the code into your main app.

let's assume you are recording a sound - all you do is overwrite the data in a looping sample that is already playing. a good tip would be to take the sample you are overwriting decrease the volume and then add the new mic sample

Adding vst effect would mean you need to rewrite EVERYTHING to be based on the vst core code.

Adding simple effects would also mean you would need to use your own (as midimaster said) ringbuffer and the code to support effects.

My own approach is to have send busses - these send selected audio into seperate effects units. the result from the units can be mixed back into your output.

I currently use 2 in QasarBeach. But in its predecessor I used 8 programmable ones. the programmables ones used fixed units but with control inputs:
(https://vjointeractive.files.wordpress.com/2021/07/screen-shot-2013-05-25-at-11.40.44.png)
The internal units were a crossover - left/right delay and reverb - reverb crossover and final crossover

these inputs were then collected and given names and graphics:
(https://img.itch.zone/aW1hZ2UvNjU4OTMvMjk4MjIyLmpwZw==/original/veZt4%2B.jpg)

It was hugely powerful, but I only ever really used 4 at any one time.

But the principal remains the same:
1. take the audio and split it into your dry (the original) mix and a feed into your bus system
2. do whatever you need to do with the bus - this has to be coded to be as fast as possible. so multiple playheads, simple mixing and big buffers are what you need
3. take you dry mix and the wet mix (this is the result from your busses) and mix them into your ringbuffer as the audio out.

an example multi-tap echo could be something along these lines:
Very short simple fast code. using multiple buffers (you can also use a single buffer, but mutiple ones allows you to do strange things with echos and audio)

for l = 0 Until bufferLength-1 Step 2
outBuffer10[l] += outBuffer13[l]
outBuffer13[l] = outBuffer12[l]
outBuffer12[l] = outBuffer11[l]
outBuffer11[l] = fxBusEcho[l] * outDelay

r = l + 1
outBuffer10[r] += outBuffer13[r]
outBuffer13[r] = outBuffer12[r]
outBuffer12[r] = outBuffer11[r]
outBuffer11[r] = fxBusEcho[r] * outDelay2

fxBusEcho[l] = 0
fxBusEcho[r] = 0
Next


Title: Re: My Music Editor
Post by: iWasAdam on July 31, 2021, 11:32:44
here are a couple of nuggets for you:

1. it's irrelevant how you do something. it's the result that matters
2. thinking of sound as the result you want - then trying to figure out how to get it can be very rewarding - particularly when you come up with a solution that goes against general thinking
3. thinking of a sound as the result from many buffers can give different concepts of what make a sound...

OK.

I think it is also time for you to step back and look at how far you have come.
Decide what has worked and what hasn't. But also what you would like to happen...

If you are thinking that effects is what you really want - then you might have to rethink (code) things - this is good as you can choose a system approach that you think will work for you.

Above all - everything you have learnt so far is now in your toolbox and you can use this knowledge again and again :)
Title: Re: My Music Editor
Post by: Hardcoal on August 01, 2021, 20:35:37
It is really easy to me to do things..
I mean one of the reasons I dont code too much is because its G damn boring sometimes..
as soon as you find a system of work, it becomes generally easy.. (besides the screen headaches crap)
I force myself to program just to make a progress..
I only begin to enjoy when I pass the basic level.. (Im generally speaking off course)
than when like in my game editor.. its becoming fun when youre adding things beyond the basic structure of the concept..

this music thing was very fun at the start.. now its a bit boring..

anyway enough whining..

If i dont respond to your pervious posts, its just because Its not the time.. Adam and Midi..
Im a very OCD guy.. lots of flooding in my system
I do many things.. Like Welding building coding.. its all one confusion in my life
and I stress myself too much.. because of MY OCD I keep building stuff that i shouldn't

anyway.. again to what ive mentioned long ago..
The Code IDe's are so god damn primitive..
If I knew how to make a proper IDe I would ..
but thats not for now..

I need lots of money and people to do what I ask :) but thats probably on my next life

anyway.. now in my music Editor let me tell you what my problem is ...
I have a track section .. and i can stretch it from one side and from the other side..
so only a section of it can be heard..
but the problem is.. you cant begin playing a track from the middle..
you only have the command playsound..
unless i copy it to a new sample.. which is slow..
I sure wish there was a way to do it without copying..

thats very essential..

And about the VST adding , Adam I didnt see any mentioning on how to do it if at all..

you showed me how to code an effect.. not how to add an external VST.

but this is also Awesome adam. and ill test it soon.


midi master , im gonna experiment with recording while playbacking .. and let you know..

Ill post an image soon.. or even a demo.

Btw this track editing is seperate of my main program that is with the notation and piano keyboard
I will mix them together maybe in the future..

Its too heavy as it is

I made too many editors so things became repetitive..
I should actually make an editor maker..
its so boring making again and again same things..
Like Undo.. Copy, Paste..
We need a system for this ..

(https://i.imgur.com/WSAGn67.jpg)

Midi master At the moment im recording samples.. and theirs not latency issues with that.
maybe you mean playing midi tools..
yet well.. in that case ill deal with it later..





Title: Re: My Music Editor
Post by: Midimaster on August 02, 2021, 01:58:03
Quotebut the problem is.. you cant begin playing a track from the middle..
you only have the command playsound..
unless i copy it to a new sample.. which is slow..
I sure wish there was a way to do it without copying..

I cannot confirm that copying the middle of an TAudioSample into a new one does last so long.

Here is a example, that copies 30sec (16bit-Stereo 44.1kHz) from the middle of a TAudioSample into a new one.

I tested three methods.

the classic way runs with 48msec on my computer

The INT_POINTER algo needs only 6msec to do the job, also the BANK algo:

Code (BlitzMax) Select
SuperStrict
' 100 sec of source:
Global A:TAudioSample = CreateAudioSample(44100*100,44100,SF_STEREO16LE)

' 30 sec of new sample
Global B:TAudioSample = CreateAudioSample(44100*30,44100,SF_STEREO16LE)

Global Play:TSound





' now copy 30sec of music from the middle of A to B:
' start at 20th second of A
'


' Method I copy Bytes:
'-------------------------------------------------------
Local time:Int=MilliSecs()
For Local i%=0 To 44100*4*30
  b.Samples[i] = a.Samples[i+44100*20*4]
Next
Play=LoadSound(B)
Print "Method I:............ Copy bytes : " + (MilliSecs()-time) + " msec"



' Method II Copy via INT POINTERs:
'-------------------------------------------------------
time=MilliSecs()
Global A_P:Int Ptr = Int Ptr(A.Samples)
Global B_P:Int Ptr = Int Ptr(B.Samples)

For Local i%=0 To 44100*30
  B_P[i] = A_P[i+44100*20]
Next
Play=LoadSound(B)
Print "Method II: Copy via INT POINTERs : " + (MilliSecs()-time) + " msec"



' Method I copy via BANK:
'-------------------------------------------------------
time=MilliSecs()
Global Bank_A:TBank = CreateStaticBank(A.Samples, A.length*4)
Global Bank_B:TBank = CreateStaticBank(B.Samples, B.length*4)

CopyBank Bank_A , 44100*20*4 , Bank_B ,0 , 44100*4*30
Play=LoadSound(B)
Print "Method III:....... copy via BANK : " + (MilliSecs()-time) + " msec"





Latency

QuoteMidi master At the moment im recording samples.. and theirs not latency issues with that...

You won't run into latency problems until you try to record a second ttrack while playback the first track. Try the following:

1. Record a first take with a simple rhythm on a snare.
2. Now playback the snare track and try to record  the same rhythm on a bell synchron to the snare you listen.
3. Now playback both tracks. You can listen: The bell will not longer be synchron to the snare.
Title: Re: My Music Editor
Post by: iWasAdam on August 02, 2021, 06:11:18
OK 2 questions with 2 similar but different answers
QuoteI sure wish there was a way to do it without copying..
thats very essential..
You need a custom sound core that is built on top of the ring buffer.
in essence you throw out everything you already have and write the system from the ground up to do this.

When you read in your samples from disk - they become a sound store. You then have direct control over how you interact with this sound source, how it is played and accessed.

It becomes a realtime system. the pitch the volume, how the samples is played is all completely written by you and controlled by you.

It's not as hard as it sounds - but you will need help.

Have a look around QasarBeach for more ideas on that front:
https://adamstrange.itch.io/qasarbeach (https://adamstrange.itch.io/qasarbeach)

QuoteAnd about the VST adding , Adam I didnt see any mentioning on how to do it if at all..
There is quite a lot of info about vst writing. BUT (and it's a huge one). You HAVE to use their sound frame work from the start. you can't just plug it in and it's done.
It is related to the first question - if you want vst support you will have to build a system to access the vst sound framework and then build your sound system on top of that. you then have access to vst support. - to my knowledge no one in the blitz community has attempted this. my advice would be tentatively not do this.

As said before that is all very low level stuff...

I know it's not possibly the answers you want to hear - but it is the only ones I can give ;(

You are now at the level where you want direct control over sound - without writing something custom, the base blitz/monkey products become unusable.
Title: Re: My Music Editor
Post by: Hardcoal on August 02, 2021, 06:51:09
Thanks Adam.
I made a nice progress today.
Soon ill be approaching the latency issue and then ill approach this playing sample from middle issue.
See what i can come up with.
Ill try first using what i got.
I think it's better than rewriting things if i can spare it.
I've already came up with solution as i type.
Soon ill release a little demo.
If you really want to know what im trying to achieve,
Take a look at Mixcraft demo.
That's exactly what im trying to do on the principle level
Title: Re: My Music Editor
Post by: iWasAdam on August 02, 2021, 08:45:41
QuoteSee what i can come up with.
Ill try first using what i got.
I think it's better than rewriting things if i can spare it.
Absolutely ;)

Just have in the back of your mind that you are hovering around the limits of what can reasonably be achieved.

The next (big) step is to look at rewriting, etc
Title: Re: My Music Editor
Post by: Hardcoal on August 03, 2021, 23:45:46
It appear like I pulled it off.

All working.. 
off course their are still bugs
but the core idea is working..

(https://i.imgur.com/EDm9aqd.jpg)

Ive managed to do what i planned exactly.. and no.. it was not simple!

now you can take any recorded sample and short it from left or right and it will play it as if its only a section..
and you can always get it back to its original size. so you dont lose any data..

I didnt do Zoom for samples.. and many other things..
but im not sure how far im planing to go with it..

but what  i achieved is already good for me.

ill put a demo after ill fix some errors



Title: Re: My Music Editor
Post by: Midimaster on August 03, 2021, 23:52:53
can we see the code how you did it?
Title: Re: My Music Editor
Post by: Hardcoal on August 04, 2021, 03:15:52
Sure man..
The thing is the code is spreded over many files..
but ill post some of it and explain what i done..

anyway here is demo..
https://drive.google.com/file/d/1VxmK4YwjIBoaSsG44ReYr_tDaFjuCX5G/view?usp=sharing

Their might be some glitches, one time it created a weird noise.. i dont know why

Atm it displays 10 seconds sample length..


OK.. about how it works..

Well first the sample window is made of two parts the inner part is the image of the sample
and the outer part is a frame that can be shrinked but the sample image remain the same size , but not fully visible.

when I shrink the Sample from the Left side, I cut the sample after shrinking is over and save it on another TAudioSample
but when I shrink from the Right side theirs no need to cut it, just to stop play the sample when The cursor reaches the end of the visible section.

This is how my track Type looks atm


Type TrackSection_Type

Field TrackName:String = "New Track " + CountList(TrackSections_List)
Field TrackType:Type_Class 'Not Used Yet  Midi/Sample..

Field AudioSample:TAudioSample
Field Sound:TSound
Field ShortedSample:TAudioSample 'For Cases you short from the Left

Field Channel:TChannel
Field Volume:Float 'Not in use yet 'There will be a volume General Control Stripe
Field Filename:String = "NewSample.wav"

   'Not Used yet
Field TrackStartPointInSecs:Float
Field TrackVisibleLengthInSecs:Float

   'Alba
    Field TrackScrollArea = alcreategscrollarea(Tracks_Area, "", 0, 0, TrackManagerWidth, TrackScrollAreaHeight, ALF_DontHideScrolls)
Field TrackImage:TexturedButton_Type = Alba.CreateTextureButton(TrackScrollArea, 0, MovePartHeight, GraphicsWdth, TrackSampleAreaHeight)
   'Buttons
    Field PlayButton
Field MuteButton:SwitchButton_Type
Field DeleteButton

   'Flags
    Field IsPlaying_flg
Field PlayingEnded_flg
Field IsMuted_flg = False
Field IsShorted_flg 'When you short from the left

Method Del()
Alba.FreeElement_Mine(MuteButton.MainHandle)
Alba.FreeElement_Mine(PlayButton)
   'Dont Free Element TrackImage.ALHandle atm
Alba.FreeElement_Mine(TrackScrollArea)
End Method

End Type



The shortedSample is the sample that i Play when I shrink it from the Left side..


This is what I use to cut the sample


Function CutSample:TAudioSample(Sample:TAudioSample, StartInSecs:Float, LengthInSecs:Float)
Local T:TAudioSample, StartInBytes, LengthInBytes

StartInBytes = OA.sampleSecsToBytes (Sample, StartInSecs)
LengthInBytes = OA.sampleSecsToBytes (Sample, LengthInSecs)

T = OA.CreateNewTAudioSample(LengthInSecs / 4,Sample.hertz,Sample.format)

Local Counter
For Local I = StartInBytes To StartInBytes + LengthInBytes
If I >= Sample.length * 4 Then Exit
T.samples[Counter] = Sample.samples[I]
Counter = Counter + 1
Next
Return T
End Function



NOW I have an issue with this function.
I made a volume listener before recording begins..
so the recodring will begin when it hear the first sound...
but the problem sometimes it crashes..
can anyone tell me what makes it crash?
or what should i clean before or what should i do to listen to first audio sound
if theirs a better way..

Method ListenToSample()

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



* This works for 44100 herz samples..
   I didnt make it to work with other loaded samples of different formats ..
so dont expect it to work on other samples..
it havnt been tested


11:10
-------
ok from this point ill start fixing bugs..
Ive found first bug, when i start record, it begins from the wrong place.. fixing...

23:11
-------
Major bug fixed with recording..
but now i got a new weird bug were it creates annoying static noise..
Its probably some error with my copy sample thing that produces white annoying noise from time to time.






Title: Re: My Music Editor
Post by: iWasAdam on August 05, 2021, 08:01:17
well done - an interesting way to take a problem and come up with a workable solution. a brilliant example of lateral thinkimg from you - \o/
Title: Re: My Music Editor
Post by: Hardcoal on August 05, 2021, 09:09:16
Thanks adam.. ^-^  ye i got what i wanted .. for the moment..
from here will see what ill do
Title: Re: My Music Editor
Post by: Hardcoal on August 05, 2021, 22:17:14
Added new feature, "Track Loop"..
this means it will loop by itself.. independent of the whole track playing

(https://i.imgur.com/KrytJea.jpg)

it got its own cursor as can be seen marked by the arrow in the picture
Title: Re: My Music Editor
Post by: Hardcoal on August 07, 2021, 21:44:37
Latest Looks

(https://i.imgur.com/6QOJ5UV.png)
Title: Re: My Music Editor
Post by: Hardcoal on August 08, 2021, 23:33:26
Update..

Added Track zoom in and out.. not simple ..but works..
Title: Re: My Music Editor
Post by: Hardcoal on August 09, 2021, 23:36:51
Added Tracks Scroll

I have a zoom in to Tracks already made, but the problem is that i need to draw all samples again, and its slow..
Unless maybe ill make them high definition to begin with..
any other solution?

(https://i.imgur.com/lrueH7L.jpg)


Title: Re: My Music Editor
Post by: iWasAdam on August 10, 2021, 06:22:34
okydoke....  Here's the best way to visualize any type of time line informations:
read through and go with this

1. First get drawing a line from 0 to 1 working like this
(https://vjointeractive.files.wordpress.com/2021/08/lrueh7l-1.png)

2. get your display to scale move whatever and the line goes from 0 to 1 correctly (clips, scales, etc)

3. what you now have is a base prototype you can fit any data into

lets just say the x and y are -10, 20 and the height is 50 and the width is 300
yhalf = height * 0.5
ymid = y + yhalf
you can draw a midline at x,ymid,x+width,ymid

xpos = x
for fpos = 0 to width
frame = (fpos / width) * samplelength
draw frame at xpos
xpos += 1
next

if you really want to be efficient. the best thing is to create  a store for each sample of 256 positions. and fill this with the waveform. then use this as the drawing data - drawing 256 each time


Title: Re: My Music Editor
Post by: Hardcoal on August 10, 2021, 07:11:26
thanks adam :)
Title: Re: My Music Editor
Post by: iWasAdam on August 10, 2021, 07:53:45
if you think of everything as 0=start and 1=end. it becomes simple math to get positions, etc.

length = 300
end = 1
1*300 = 300

so 0.5 = 0.5 * 300 = 150.

using this we can reverse the concept
lets say a playhead now goes from 0 to 1
for any playhead in a sample. the position/frame is playhead * samplelength

and for any given position say 150 in a a sample of 300. pos/length = playhead = 150/300 = 0.5

I know that this is a small abstraction. but once you work in 0 to 1 and get used to it. there are all sorts of things that 'just' happen and you dont need to think about lengths anymore

lets assume that that your frame position doesn't move in units but in doubles and is moving at 0.5. if your sound generation system plays the frame, you are now playing at half speed.
Title: Re: My Music Editor
Post by: Hardcoal on August 10, 2021, 08:34:07
I kinda get it ;)
Title: Re: My Music Editor
Post by: iWasAdam on August 10, 2021, 10:48:29
ok.
lets say you have an array of 128 floats. with each float going from 0..1

we already have a playhead going from 0 to 1

so for any (playhead * 127) we can get a position (0to 127) which directly amps to the float arrray... with values from 0 to 1

Now... I'm just saying with that sort of data. you could feed the float results into the volume. so if the floats were all 1 then the volume would be at max. all at 0 then the volume would be off.

But... if the float array was a curve, or started at 0 quickly going to 1. staying there for most of the sample. then going back to 0 at the end....
You now have volume fade in/out...
(https://help.apple.com/assets/6008DDC8B269B318B901D62C/6008DDDAB269B318B901D635/en_US/fad7aeb558a513a0012880bf613652a5.png)

so all you now need is to set channel.volume = volfade
Title: Re: My Music Editor
Post by: Hardcoal on August 10, 2021, 13:48:07
Adam thanks for all your effort :)
I think i know how to make volume in and out..doesnt seem to complicated . i mean if i use the channelvolume..
but atm all i plan is to do just volume control to a track.. with no fade in and out..
all im doing is play with the idea of a mutli tracker..
i dont even know what I want..

all i know is i want to make music, and im not pleased with the tools i got..
I mean i cant yet find the right frame for me, but it will happen eventually..

I keep your posts in mind .. and i understand what you are saying.. and will use it when time comes..

you also gave me a great help on understanding math as a side effect
Title: Re: My Music Editor
Post by: Hardcoal on August 13, 2021, 06:53:36
Added Volume control (https://i.imgur.com/RY0fb63.jpg)
Title: Re: My Music Editor
Post by: Hardcoal on August 15, 2021, 17:55:37
Added Split Samples option

(https://i.imgur.com/AMi3rAg.jpg)
Title: Re: My Music Editor
Post by: Hardcoal on August 16, 2021, 03:55:54
Ive managed to make copy Tracks 100 times faster

Still much to work on speed, when considering lots of tracks
Title: Re: My Music Editor
Post by: Hardcoal on December 14, 2021, 15:17:03
Hey. is their any way i can make a descent piano to be played on my app without vst?
or should i just load for each key a sample for its on?
Title: Re: My Music Editor
Post by: Midimaster on December 14, 2021, 18:16:53
This has nothing to do with VST.

All E-pianos are working with build-in samples for each key. High quality E-Pianos have even a dozend or more samples for each key, depending on velocity of the keypress.

Didn't you already sample PIANO sounds for a project some month ago?

Title: Re: My Music Editor
Post by: Hardcoal on December 15, 2021, 03:23:16
 i didnt sample the piano i used to internal midi piano..

i might simply sample my own Synth..

didnt you say once that i can use those sounds because i bought the synth?

anyway.. i remember there was a piano sound pack somewhere
Title: Re: My Music Editor
Post by: Midimaster on December 15, 2021, 08:37:11
Yes, you are free to use "recordings" of your E-piano. Even, when they are only one note long. I told you the idea of creating an app, that sends a sequence of MIDI-NOTE commands to the E-Piano to play each key. The E-Piano is also connected to AUDIO-IN of the computer, and so you are able to record the audio result of the MIDI-NOTE as a single samples.

I send you a ZIP file with 62 piano samples from MIDI-NOTE 23 to MIDI-NOTE 84, They are recorded with 500msec and MIDI-VELOCITY=60. They samples are very small, only 15kB each. Recorded from a Roland E96 at 16bit-Stereo-44.1kHz. You can do whatever you want with it. You have my permission! The sounds are Public Domain now. Everybody can use for free.

Here is the ZIP with 62 Piano-Samples:
Title: Re: My Music Editor
Post by: Hardcoal on December 15, 2021, 09:32:01
Yea you have..
Thanks for the Zip...
I know i can play my piano using midi out and i did it.
but i wanted a stand alone software that plays a piano with out delay and sounds reasonable..
Ill also add samples from my synth..

but can I afterwards can I use it in Free/Commercial  App distribution?
Title: Re: My Music Editor
Post by: Midimaster on December 15, 2021, 09:46:07
I suggested to use your E-Piano once for getting the Piano-Samples. Than include the bundle of OGGs into your app. Now it is stand-alone.

A recorded sample sound as perfect as the source was (+ the recording parameters). So if you recorded with 48kHz, 24bit and a harmless compression, you will get realistic piano sounds in your app. The price is the size of 10-100MB for the bundle of samples.

If you created the sounds in a "recording" way, you are the "creator". It is not longer the work of the E-Piano-factory. So you can use it as you like. No one can hinder you. Also in commercial apps. Be careful not to use the name of the E-Piano. You might get in conflict with trademarks, etc...

The forbitten way would be, accessing the ROMs of a E-Piano directly and copying the content to a own file. Or also forbidden copying files from other software developers with out the permission.
Title: Re: My Music Editor
Post by: Hardcoal on December 15, 2021, 16:23:23
Ok. i made progress..

now..

Is there a way to trick the computer to think an external midi keyboard is connected to it.
so i can play from my app on another app?
Title: Re: My Music Editor
Post by: Midimaster on December 15, 2021, 16:38:56
yes, I remember that (in former times) there were "virtual midi-devices", that enabled to route the MIDI-OUT signal direct to MIDI-IN of another app.

Perhaps the app MIDI-OX can do this? This a swiss-knife for MIDI-Analyses and a must for each MIDI programmer. It looks a little bit "out-of-date" but it still works perfect for several MIDI-problems or Debugging. I use it since 20 years...

Homepage: http://www.midiox.com/

Download: http://www.midiox.com/zip/midioxse.exe


Did you already check again the list of MIDI-IN-DEVICES (in your BlitzMax-app) after you started the third-party- app? Perhaps, if you have luck, the app added its "device" and is now avaiable with a normal entry in the MIDI-IN-DEVICE-list...
Title: Re: My Music Editor
Post by: Hardcoal on December 15, 2021, 20:52:24
Awesome ill check it and see TY.


im almost certain you answered that before. but im not sure, so ill ask again..
is their a way to reduce the latency of the built in midi instruments, or do i have to tolerate the latency as it it?
Title: Re: My Music Editor
Post by: Midimaster on December 15, 2021, 23:44:43
The MIDI communication itself has <10msec latency, the problem on Windows is the audio-player behind it, which loads each sound again and again and send them with a "secure" latency to the audio-out. Windows-typical is 160msec. Or in notes: It is more than a 1/16-note in a 120bpm-Song.This is not acceptable for music purposes.

The only chance you have it to do this one time for only recording the resulting audio, then cut the piano samples and use them for future music. You need to establish your own MIDI-player. With BlitzMax and the FreeAudio-Ringbuffer you will be able to reduce the latency to moderate 40msec, with MINIAUDIO (on BlitzMax NG) you will get something below 25msec. This is the same as people playing 9-12 meters next to you. Or in notes: It is shorter than a 1/64-note in a 120bpm-Song. Acceptable.
Title: Re: My Music Editor
Post by: Hardcoal on January 05, 2022, 05:19:06
Hi..

Im trying to detect how to try opening another midi port when one does not get opened

midiIn.openPort(0)

lets say this does not open . it produce and error..
how can i detect it.. and than try to open another port like  midiIn.openPort(1)

thanks
Title: TimeStretch
Post by: Hardcoal on February 11, 2022, 11:12:13
hi  how can i time stretch a sample without changing the pitch.. so i can change drum bits to a desired RPM ..
anyway im experimenting on it using my logic.. see if it works..
Title: Re: My Music Editor
Post by: Midimaster on February 11, 2022, 12:12:04
If you really only want to slow down the speed of a drum track you could have luck with a simple solution.

The big difference between drum tracks and regular music is, that in drum tracks are (nearly) silent parts between the events.

The method would be to manipulate the TAudioSample:

Scan with a pointer through the source TAudioSample and search for silence. If you find "loudness" then copy alle the sample values as they are into the new target TAudioSample as long as they are "loud". If you find the part where true silence starts,  go ahead until you find again the moment when it changes to loudness. Now calculate from the old pointer the new pointer by multiplying it with your speed changing factor. Start writing the source samples to the new pointer position.


TAudioSamples have values from -32000 to + 32000. A value of ±1000 means -50dB, a value of ±125 means -80dB. You have to decide (define) where silence starts for you. This level is called "Threshold" (±125 is a good value).

As audio wave often cross the Zero-line a simple search for  low values would not lead to success. But if you find low values for a long time this can be a good indication for "silence". A long time can be 100msec (means 4800 sample step ahead) You have to define how long the sequence of low values needs to be to be classifiet as "silence"
maybe 2400 is also good or you . Just explore...

A sudden value above the threshold is immediately a indication for "loudnees". No need to verifiy it for a long time.

You can hear this in my example here:

https://www.syntaxbomb.com/worklogs/miniaudio-wrapper-for-blitzmax-enables-wasapi-playback-and-recording-mp3/msg347049834/#msg347049834

(https://www.syntaxbomb.com/worklogs/miniaudio-wrapper-for-blitzmax-enables-wasapi-playback-and-recording-mp3/?action=dlattach;attach=4536)


There is no need for my MiniAudio-Module or my FreeAudio-Ringbuffer. Without them you can do it  directly with TAudioSamples. but without the possibility of sudden real time speed changes.

If you want to change the speed immediately while it is running you should think about using MiniAudio-Module or my FreeAudio-Ringbuffer.






Title: Re: My Music Editor
Post by: Hardcoal on May 14, 2022, 09:20:49
I had a along break..

I came back to work on it..

this project is the mother project from which Im pulling stuff for smaller ones.