Page 1 of 1

basic midi sequencer

Posted: Tue Apr 21, 2009 2:58 pm
by Olby
hi,

I'm coding a basic midi sequencer and came toa full stop when I started to think that I cannot implement the ticks (midi pulses) using PureBasics ElapsedMilliseconds().

What I want to do is to have a sequencer with resolution of 96 ticks/pulses per beat. This means (as far as I understand the whole math behind it) that each beat goes through 96 ticks before moving on to the next beat. If thats so how would I make a sequencer that can perfectly play those 96 ticks even at high bpm rates. I tried using the timer but it gave me 15 to 16 milliseconds of time between each call but to play a 200 BPM beat you will need around 3 msec between each tick and this is not achievable using this timer.

This leads me to a conclusion that I am doing it completely wrong. So I wanted to ask you guys which is the best solution to create such perfect timing programs in PB. I know some of the sequencer even use four times and more the resolution than 96. How do they do it? Of course I could skip the whole tick part and just use delay for the appropriate note length but I also would like to implement midi note recording which again means that I will have to use ticks anyway.

thank you,
olby

Posted: Tue Apr 21, 2009 5:21 pm
by bobobo

Posted: Tue Apr 21, 2009 8:11 pm
by eriansa
You can use Multimedia-Timers (mmsystem) or the Midistream api's.

Posted: Tue Apr 21, 2009 9:17 pm
by Olby
thank you. I will look into these.

Posted: Wed Apr 22, 2009 12:07 pm
by bobobo

Posted: Thu Apr 23, 2009 8:57 pm
by Olby
do anyone know how to prevent my sequencer from slowing down when computer is doing something in the background? it will start playing back slower when I for example switch back to purebasic window and click a menu. I guess my computer can handle those tasks simultaneously.

sequencer update is handled in a separate thread so none of the other code gets into its way. it is something to do with the hi performance timers. currently I just simply check if the specific time since the last tick is passed (timer => tick)and if yes move on to the next tick. i guess this is incorrect way of handling this. are there any better solutions ?

thank you once again,
olby

Posted: Thu Apr 23, 2009 11:25 pm
by einander
With threads you can achieve acceptable performance, and do other tasks smultaneously.
May be you can start with this:

Code: Select all

#NoteOn      = $90;
#MSEC=60000.0    ; = millisecs in 1 minute

Global _hMIDIout
Procedure midiinit()
  For nDev=-1 To MidiOutGetNumDevs_()-1
    If MidiOutGetDevCaps_(nDev,@midi.MIDIOUTCAPS,SizeOf(MIDIOUTCAPS))=0
      If midi\wVoices>0
        MidiPort=nDev
      EndIf
    EndIf
  Next
  MidiOutOpen_(@_hMIDIout,MidiPort,0,0,0)
EndProcedure

Macro MMK  ; mouse key state
  Abs(GetAsyncKeyState_(#VK_LBUTTON) +GetAsyncKeyState_(#VK_RBUTTON)*2+GetAsyncKeyState_(#VK_MBUTTON)*3)/$8000   
EndMacro

Macro PlayMIDINote(Chan, Note, Vol=100)
  MidiOutShortMsg_(_hMIDIout, #NoteOn | Chan | Note << 8 | Vol << 16) ;
EndMacro
     
Macro BPM_Msec(n)  ; convert MilliSecs to Beats per Minute and vice versa
  #MSEC/n
EndMacro

Structure Metronome
  BPM.l
  Msecs.l
  Sound.B
  Vol.B
  count.l
EndStructure

Procedure ThreadMetron(*Param.Metronome)
  With *Param
    \Sound=35  ; 35=acoustic bass drum
    Repeat
      PlayMIDINote(9,\Sound,100)  ; 9=perc MIDI channel 
      ; \count+1  ; for beat count
      Delay(\Msecs)
    ForEver
  EndWith 
EndProcedure

 ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
If OpenWindow(0, 100, 100,400,80 ,"Test Metronome",  #WS_OVERLAPPEDWINDOW|#PB_Window_ScreenCentered) 
  StickyWindow(0,1)
  _TB=TrackBarGadget(#PB_Any,40,10,300,30,50,500)
  _InfoTB=TextGadget(#PB_Any,GadgetX(_TB),40,300,20,"",#PB_Text_Border)
  SetGadgetState(_TB,100)
  SetGadgetText(_InfoTB,"Beats per minute : "+Str(GetGadgetState(_TB)))
  
  BPM.Metronome
  
  BPM\Msecs=BPM_Msec(GetGadgetState(_TB))
  
  midiinit()
  
  Thread1=CreateThread(@ThreadMetron(),@BPM)
  ThreadPriority(Thread1,31)
  
  Repeat 
    If GetAsyncKeyState_(27) :  End : EndIf
    
    Ev=WaitWindowEvent() 
    Select Ev
      Case #PB_Event_Gadget
        Select EventGadget()
          Case _TB
            BPM\BPM=GetGadgetState(_TB)          
            BPM\Msecs=BPM_Msec(BPM\BPM)
            SetGadgetText(_InfoTB,"Beats per minute : "+Str(BPM\BPM))
        EndSelect
    EndSelect
    
  Until Ev=#WM_CLOSE 
  End  
EndIf
Cheers

Posted: Fri Apr 24, 2009 7:33 am
by Olby
@einander:

Thanks that was very useful peace of code. I did almost everything the same, I just didn't know about the the thread priority command. Man, PureBasic is extremely powerful language... Gotta study it better.. :wink:

Posted: Fri Apr 24, 2009 4:26 pm
by einander
@ Olby : You're welcome
This code can help as a starter, but with intensive process activity, sometimes falls behind.
Maybe using Mutex the timing can be improved.

Posted: Fri Apr 24, 2009 7:21 pm
by bobobo
don't use delay when you need it accurate

http://www.codeproject.com/KB/system/cp ... play=Print
(take a look at the code)