Chord Displayer

Started by Hardcoal, March 26, 2023, 21:22:09

Previous topic - Next topic

Hardcoal

My method of work is to develop general environment of gadgets, and than decide what to do with them.
so i made gadgets for music apps.. and now im using it.

So this first thing I will probably release is this chord recognizer.

It display chords by its name and how its being played on a piano and a guitar..

all functions are already working.

I want to thank midimaster who helped me alot in all that regarding audio and notes..

I didnt use to care about looks for too much.
but since i got remarks about that, i started putting effort on looks as well.
and i admit.. its nice to see your app looks







Code

Midimaster

I have to tell you, that the notation of the Cm-chord you display in your example screenshot is already wrong.

A Cm-Chord must have a C and a Eb and a G! Writing it with a D# is not allowed!

These Chords must use # (sharps): G D A E B F#  and Em Bm F#m C#m G#m D#m
These Chords must use b (flats): F Bb Eb Ab Db Gb and Dm Gm Cm Fm Bbm Ebm

You could implement a music theory AI to decide which chord uses which sign. But the more convenient way is to simply create a table where the app can look for each chord.

...back from Egypt

Hardcoal

lol midi master.. I still didnt get to that..
That will be the last thing ill do when ill be ready to release the app..
so worry not.. im fully aware of that..

I already create a table for all chords, but i didnt get to that yet..

I just wanted to make first impression

Code

Midimaster

#3
The table could contain a bundle of chord definitions.
Each definition contains 3 or 4 notes.
Each note is a combination of a number for the altitude of the note and a letter for ev. presign:

"Cm =0 |2b|4 "

means Cm has three notes: The 0 represents C, the 2 represents E (two notes higher) and the b says that this E needs a flat presign. The 4 representsthe G.

Here is a stand alone runable example:

SuperStrict
Graphics 600,400
SetColor 255,255,255

' the table:
Global Chord:String[10]
' chord note lines definition: 0=C, 1=D, 2=E, 3=F, 4=G, 5=A, 6=H
' # means "has a sharp", b means "has a flat"
Chord
="Cm =0 |2b|4 "
Chord[1]="Bb =6b|1 |3 "
Chord[2]="F#7=3#|5 |0#|2 "
' the current chord defintion:
Global CurrChord:String
Find("Cm")
Repeat
    Cls
    If KeyHit(KEY_C) Then Find("Cm")
    If KeyHit(KEY_B) Then Find("Bb")
    If KeyHit(KEY_F) Then Find("F#7")
   
    DrawText "Press <C> for Cm-chord, <B> for Bb-Major-chord or <F> for F#7-chord", 30,50
    'the 5 note lines:
    For Local i:Int=1 To 5
        DrawRect 100,200+i*20,300,1
    Next 
   
    DrawActChord   
   
    Flip 1
Until AppTerminate()
Function Find(Chordname:String)
    ' scans the table and return the chord definition
    For Local a:String=EachIn Chord
        Local middle:Int=Instr(a,"=")
        Local key:String=Trim(Left(a,middle-1))
        Print "scan table..." + key
        If key = Chordname
            CurrChord=Mid(a,middle+1,-1)
            Print "found definition: " + CurrChord
            Return
        EndIf
    Next
End Function
Function DrawActChord()
    ' paints the notes depending on the current definition
   
    'splits the defintion in the single notes:
    Local notes:String[]=CurrChord.Split("|")
    Local lastpos:Int  ' cares about octave shifting
   
    For Local a:String= EachIn notes
        Local pos:Int= a.ToInt
       
        ' cares about octave shifting
        If pos<lastpos
            pos=pos+7
        EndIf
        lastpos=pos
       
       
        DrawOval 200,311-pos*10,30,18
       
        SetScale 1,3
        DrawText Right(a,1), 180,300-pos*10    
        SetScale 1,1
    Next
   
End Function



The Forum-Editor has heavy problems with the blitzmax code snipplet so I post it again as attachment:


[url=&quot;https://www.syntaxbomb.com/index.php?action=dlattach;attach=5752;type=preview;file&quot;]ChordDisplayer.bmx[/url]
...back from Egypt

Hardcoal

Awesome midi master..
I will not release this until it gets your approval anyway.
When i began developing it i was very busy with lots of aspects..
Now, im getting closer to the the finer details.

Im building things atm in a way that is accessible to any project so i don't need to repeat my code.
And if i improve one thing, it effects all projects.. For good and sometimes for bad.
But it can be balanced
Code

lucidapogee

Looks neat. Maybe this will be helpful in writing compositions with physical instruments.
Ebox Thin Client with Windows 95
EEE PC 701SD with Windows XP
Atari 1040STFM with GEM/TOS
Playstation 2 with FreeMcBoot Yabasic
Keyboard Famiclones with GBasic and FBasic
Xerox Sunrise 1800 with MSBasic and CP/M

Hardcoal

Ok midiMaster.. Ive fixed it..

off course I will have to pass you the program for tests to know for certain everything is ok.
but this can wait.. i still have enough work around I want to do.

I must also thank Derron that caused me to put more effort and how my apps look.
Ive never considered before the importance of looks. but now i see how effective it is

Code

Hardcoal

I have this error RtMidiIn: message queue limit reached!! and i dont have any idea how to get rid of it..
or how to clean the queue...
Code

Midimaster

MidiIn? Did you connect a MIDI Keyboard to the IN-Port of the computer? Which Keyboard?

A lot of keyboard send ACTIVE SENSING and TIME TICK messages. These are a lot of datas

You can check this with the tool MIDI-OX, which is still the best tool to investigate the MIDI ports

http://www.midiox.com/


In your app you should check all the time the state of the midi in port and fetch away the messages.


Here is a code snipplet how I normaly handle the MIDI-IN-port. The open port sends messages as Byte[]-Arrays. Every 10msec I check, whether there are those messages and copy them all to my own message-list. Later I check this list and remove the messages I do not need, but return the messages of interest.

' function in the main code:

Function TimerTick()
    Global MidiInTimer:Int
   
    If (MidiInTimer<MilliSecs()) And (MIDI.InStarted=True) Then
        MidiInTimer = MilliSecs()+10
        MIDI.Receive
        If MyWindow()=Null
            MIDI.InMessages.clear()
        ElseIf GameMode>0
            GameWindow.FetchMidiMessage
        Else
            MIDI.InMessages.clear()
        EndIf
    EndIf
End Function



Type MidiMessageTyp
    Field Typ:Int, Channel:Int, Note:Int, Volume:Int
End Type



Type MIDI

    Global InMessages:TList = New TList

    Global MidiOut:TRtMidiOut, OutStarted:Int
    Global MidiIn :TRtMidiIn , InStarted:Int

    Function Receive()
        ' has to be called every 10msec!
        Local stamp:Double

        If InStarted=True Then
            message:Byte[] = midiIn.getMessage(stamp)
            nBytes:Int     = message.length

            Local Value:Int
            Local loc:MidiMessageTyp=New MidiMessageTyp  ' my message typ
            If nBytes > 0
                For Local i:Int = 0 Until nBytes
                    Value = message[i]
                    Select i
                        Case 0
                            loc.Typ     = Value/16
                            loc.Channel = Value Mod 16   
                        Case 1
                            loc.Note    = Value
                        Case 2
                            loc.Volume  = Value
                    End Select
                Next   
                InMessages.AddLast loc  ' List for the messages
            EndIf
        EndIf
    End Function
   
   
    Function GetNextMessage:MidiMessageTyp(OnlyThisMessageTyp:Int=0)
        ' this also removes unwanted messages from the list
               
        If InMessages.count()>0 Then
            For Local loc:MidiMessageTyp = EachIn InMessages
                Akt:MidiMessageTyp = loc
                InMessages.Remove loc
               
                If OnlyThisMessageTyp=0
                    Print "fetch all types"
                    Return Akt
                ElseIf akt.Typ = OnlyThisMessageTyp
                    Print "found my type"
                    Return Akt
                EndIf
            Next
        EndIf
        Return Null
    End Function
   
End Type
   
   


...back from Egypt

Hardcoal

This midi ox is cool.. i made something like that .. but not that advanced..

here is the latest image 


Code

Hardcoal

#10
This is so annoying.. in RT midi.. I cant recognize if a midi is occupied by another program..
I tried every way.. including asking isportopen()
but the most annoying thing is that it does show an error message when trying to create new port.
but it does not bother to transmit the error message to the user from the DLL

so when i try opening the midi using  ' MidiIn = New TRtMidiIn.Create()' its and error but does not let you know.. by returning any flag
Method Prepare:String()
Local MSG:String, PortsExists_flg

MidiIn = New TRtMidiIn.Create()
MidiOut = New TRtMidiOut.Create()

   'Midi In

  Local PortsCount = Midi.midiIn.getPortCount()

   'If no Ports can be Open then
If PortsCount = 0 Then
Notify "No Midi Ports"
Return '<--End
Else
Print "Number of Midi Ports " + PortsCount
End If

For Local I:Int = 0 Until PortsCount
Print "  Input port Named " + Midi.MidiIn.getPortName(I)
PortsExists_flg = True
Next

MSG = MSG + "Opening " + Midi.MidiIn.getPortName() + "~n"

    If PortsExists_flg <> False Then Midi.MidiIn.openPort(0)  '< - 'This will not work if Mixcraft is open

   'Don't ignore sysex, timing, or active sensing messages.
Midi.MidiIn.ignoreTypes(False, False, False)

   'Midi Out
   
Return MSG
End Method


Code

Midimaster

Hi Hardcoal,

thats an interesting point. I never tested what happens if a device is there but already used by another app. 

First of all: You can always open a RT-MIDI-instance, also if no port or no device exists or it is used by another app or it would be avaiable for you. So there is never an error massage when opening the RT-MIDI .
MidiOut:TRtMidiOut = New TRtMidiOut.Create()

After you created the instance you have to check whether there are avaiable ports. In my opinion the instance should now report only the not used ports with...
PortsCount = MidiIn.GetPortCount()
For Local i%=0 To PortsCount-1
   Print MidiIn.getPortName(i)
   ....
 
Did you find out, that RT-MIDI now also counts and lists the ports, that are used by another app? This would be a bug, or?

If so...I would expect that in the last step the RT-MIDI report something when trying to open such a port:
Report = MidiOut.openPort(i)
Can you please check if this is possible and if what happens...

There are two people responsible for RT-MIDI. One is Brucey, which wrote the wrapper to BlitzMax and the second is Gary P. Scavone which is the author of RT-MIDI class. Both would be very interested in a bug report, but first we should be 100% sure, that is it a bug and not a forgotten check from us.

Here ist the RT-MIDI-Homepage with also a tutorial:

https://www.music.mcgill.ca/~gary/rtmidi/ 

There I can see that they published Version 5, while Brucey is still on version 4


...back from Egypt

Midimaster

I now opened an issue on the GitHub-Account of RT-MIDI. It has the number #313

https://github.com/thestk/rtmidi/issues/313

Here is the question:
Quotepre-information:
 Hi, I'm working with RT-MIDI via a BlitzMax-Wrapper. At the moment we use V 4.0.0. The BlitzMax-Wrapper is made by Bruce A Henderson (https://github.com/bmx-ng).
Today we come to the problem, that we have a MIDI-IN-Device, but it is used by another app. The question is now: How can we find out, that this Port is not aviable for us. Does RT-MIDI offer a way to detect this? by the way... is there a forum where we can ask qusetions like this to the RT-MIDI community?
we'll see what happens....

Years ago I found a bug on RT-MIDI and reported it to Brucey. He contacted Gary. And both were very helpful and immediately reacted with an update...
...back from Egypt

Derron

#13
I am not sure but the blitzmax glue code is doing this:
void bmx_rtmidiout_openPort(RtMidiOut * m, int portNumber, BBString * portName) {
    char * n = bbStringToUTF8String(portName);
   
    try {
        m->openPort(portNumber, n);
        bbMemFree(n);
    } catch (RtMidiError &error) {
        bbMemFree(n);
        bmx_rtmidi_throw(error);
    }   
}


while rtMidi does this:
void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portName )
{
  if ( connected_ ) {
    errorString_ = "MidiOutCore::openPort: a valid connection already exists!";
    error( RtMidiError::WARNING, errorString_ );
    return;
  }
...

so if something fails in rtmidi it "error()"s . error is defined in RtMidi.h and extends "exceptions". So normally it should throw an exception which is then passed into "bmx_rtmidi_throw"


which itself then does:

void bmx_rtmidi_throw(RtMidiError &error) {
    bbExThrow(CB_PREF(bah_rtmidi_TRtError__create)(bbStringFromCString(error.what()), (int)error.getType()));
}


Maybe you can add some 'printf("bla");' to these portions so to see what actually does "what" and where it does no longer pass along the error.


Edit: the current version of that rtMidi.mod surely needs an update anyways (char conversion casts missing for current NG).

Edit2: I modified the glue.cpp to compile - if you want to check with your changes - https://gist.github.com/GWRon/c8df9f3e1097a149a013ccc07cbd6f14
As I do not have a midi input device it lists "0" here.

Edit3:
try {
m->openPort(portNumber, n);
printf("midi open port OK");
bbMemFree(n);
} catch (RtMidiError &error) {
printf("midi in openport error");
bbMemFree(n);
bmx_rtmidi_throw(error);
}
prints for me the OK part ... but not the caught exception.

I checked what the sample uses on "APIs":
For local i:int = EachIn midiIn.getCompiledApi()
print "api: " + i
Next
only number 5 ... which equals to
Rem
bbdoc: A compilable but non-functional API.
End Rem
Const RTMIDI_API_RTMIDI_DUMMY:Int = 5

So in my case it asks the "dummy" api to open ports ...  which might explain why no error is thrown.

bye
Ron

Midimaster

That would means he has to do something like this?

...
If PortsExists_flg
Try
Midi.MidiIn.openPort(0)
Catch Exception:Object
Print "Device open failed:"
Print Exception.ToString()
End Try

EndIf
...
...back from Egypt