domenica 17 aprile 2016

Flacreader, lettore di commenti (tag metadata) dei file flac

Come un surplus per JMusicMan, il progetto che avevo iniziato in java per la catalogazione della musica sul mio PC e che avevo abbandonato per l'impossibilità di gestire i file .flac, ho sviluppato questa classe, ancora da migliorare, per leggere e modificare i commenti (si chiamano così, più precisamente: vorbis comments) di questi file lossless. 

Però è una classe scritta in C#, dovrei convertirla di nuovo in java, o creare un binding. 

Da questa pagina ho consultato come è formato l'header dei file flac, dunque con delle operazioni sui file binari bit a bit sono riuscito a creare questa classe che attualmente legge bene i file, modifica bene anche i file che hanno già una sezione vorbis comment, ma ancora non ho testato la modifica dei file che non hanno la suddetta sezione, e quindi sarà la caratteristica che implementerò al prossimo commit su git. 

La pagina git è questa: https://github.com/standuptall/Flacreader, con due branch, uno quello principale (la classe è Flacreader.cs) e un'altro branch (grafica) che include una piccola interfaccia grafica. Anche se con git devo prenderci ancora la mano. 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace it.albe
{
    public class FlacReader
    {
        public static string vendor_string;
        public static Int32 user_comment_list_length;
        private string filepath;
        private bool data_written = false;
        private class _metadata {
            public struct _streaminfo
            {
                public bool presente;
                public const int code = 0x0;
                public bool ultimo;
            }
            public struct _padding
            {
                public bool presente;
                public const int code = 0x1;
                public bool ultimo;
            }
            public struct _application
            {
                public bool presente;
                public const int code = 0x2;
                public bool ultimo;
            }
            public struct _seektable
            {
                public bool presente;
                public const int code = 0x3;
                public bool ultimo;
            }
            public struct _vorbis_comment
            {
                public bool presente;
                public const int code = 0x4;
                public bool ultimo;
            }
            public struct _cuesheet
            {
                public bool presente;
                public const int code = 0x5;
                public bool ultimo;
            }
            public struct _picture
            {
                public bool presente;
                public const int code = 0x6;
                public bool ultimo;
            }

            public _streaminfo streaminfo;
            public _padding padding;
            public _application application;
            public _seektable seektable;
            public _vorbis_comment vorbis_comment;
            public _cuesheet cuesheet;
            public _picture picture;
            public const int ending_metadata_code = 0xF0;
            const int NUM_METADATA = 7;
            public _metadata()
            {
                streaminfo.presente = false;
                streaminfo.ultimo = false;
                padding.presente = false;
                padding.ultimo = false;
                application.presente = false;
                application.ultimo = false;
                seektable.presente = false;
                seektable.ultimo = false;
                vorbis_comment.presente = false;
                vorbis_comment.ultimo = false;
                cuesheet.presente = false;
                cuesheet.ultimo = false;
                picture.presente = false;
                picture.ultimo = false;
            }
            public void setMetadata(int code)
            {
                switch ((code<<1)>>1)  //tolgo il bit più significativo
                {
                    case _streaminfo.code: streaminfo.presente = true; break;
                    case _padding.code: padding.presente = true; break;
                    case _application.code: application.presente = true; break;
                    case _seektable.code: seektable.presente = true; break;
                    case _vorbis_comment.code: vorbis_comment.presente = true; break;
                    case _cuesheet.code: cuesheet.presente = true; break;
                    case _picture.code: picture.presente = true; break;
                }
            }
            public int getFlag(int flag)  //devo controllare se è l'ultimo, così setto il bit più significativo a 1
            {
                flag = ((flag << 1) >> 1); //tolgo il bit più significtivo
                if (picture.presente)
                    picture.ultimo = true;
                else if (cuesheet.presente)
                    cuesheet.ultimo = true;
                else if (vorbis_comment.presente)
                    vorbis_comment.ultimo = true;
                else if (seektable.presente)
                    seektable.ultimo = true;
                else if (application.presente)
                    application.ultimo = true;
                else if (padding.presente)
                    padding.ultimo = true;
                else if (streaminfo.presente)
                    streaminfo.ultimo = true;
                switch (flag)
                {
                    case _streaminfo.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _padding.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _application.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _seektable.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _vorbis_comment.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _cuesheet.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                    case _picture.code: if (streaminfo.presente) flag += ending_metadata_code; break;
                }
                return flag;
            }
        } ;
        _metadata metadata;
        private int metadata_comments_length;
        public Dictionary comments;
        public FlacReader(string filepath)
        {
            metadata_comments_length = 0;
            metadata = new _metadata();
            comments = new Dictionary();
            this.filepath = filepath;
            user_comment_list_length = 0;
            FileStream stream;
            stream = File.OpenRead(filepath);
            if (stream == null)
                throw new FileNotFoundException("Il file non esiste");
            byte[] array = {0,0,0,0};
            stream.Read(array,0,4);
            if (!((array[0]==0x66)&&(array[1]==0x4C)&&(array[2]==0x61)&&(array[3]==0x43)))   //fLaC
                throw new Exception("Il file non è un file flac!");
            byte flag;
            do 
            {
                stream.Read(array,0,1);  //mi sposto di 1 byte
                flag = array[0];
                metadata.setMetadata(flag);
                stream.Read(array, 0, 3);  //leggo tre byte per la lunghezza del metadata
                Int32 metadata_length = array[0] * 65536 + array[1] * 256 + array[2];
                if (flag == 4 || flag == 132)   //se è un vorbis comment cioè 00000100 oppure 10000100
                {
                    metadata_comments_length = metadata_length;
                    caricaCommenti(stream);
                }
                else
                    stream.Seek(metadata_length, SeekOrigin.Current); //mi sposto della lunghezza del metadata
            } while (!((flag>>7)==0x1)); //se il bit più significativo è uguale a uno vuol dire ch enon ci sono più metadata
            stream.Close();
        }
        public void setVendor(string vendorName)
        {
            int vendor_string_length_old = vendor_string.Length;  
            metadata_comments_length -= vendor_string_length_old;//tolgo la lunghezza iniziale
            vendor_string = vendorName;
            metadata_comments_length += vendor_string.Length;  //e metto la nuova lunghezza del vendor string
        }
        public string getVendor()
        {
            return vendor_string;
        }
        public void addComment(string field, string value)
        {
            try
            {
                if (comments[field] != null)   //se il commento già esiste
                {
                    string val = comments[field];
                    metadata_comments_length -= (field.Length + val.Length + 1);   //tolgo la lunghezza originaria
                    metadata_comments_length -= 4;
                }
            }
            catch(KeyNotFoundException e)
            {
                comments[field] = value;
                user_comment_list_length++;
                metadata_comments_length += 4; //aggiungo 4 byte per memorizzare la lunghezza del comment sul file
                metadata_comments_length += field.Length + value.Length + 1;  //aggiungo la lunghezza del commento più il simbolo uguale
            }
            
            
        }
        public void writeAll()  //file esistente quindi riverso il contenuto su un file _temp
        {
            FileStream stream,streamWrite;
            stream = File.OpenRead(filepath);
            streamWrite = File.OpenWrite(filepath + "_temp");
            byte[] array = { 0, 0, 0, 0 };
            stream.Read(array, 0, 4);
            streamWrite.Write(array, 0, 4);
            byte flag;
            do
            {
                stream.Read(array, 0, 1);  //mi sposto di 1 byte
                streamWrite.Write(array, 0, 1);  //mi sposto di 1 byte
                flag = array[0];
                stream.Read(array, 0, 3);  //leggo tre byte per la lunghezza del metadata
                Int32 metadata_length = array[0] * 65536 + array[1] * 256 + array[2];
                if (flag == 4 || flag == 132)  //se è un vorbis comment cioè 00000100 oppure 10000100
                {
                    stream.Seek(metadata_length, SeekOrigin.Current); //mi sposto sul lettore della lunghezza del metadata
                    array[0] = (byte) (metadata_comments_length >> 16);
                    array[1] = (byte) (metadata_comments_length >> 8);
                    array[2] = (byte)(metadata_comments_length);
                    streamWrite.Write(array, 0, 3);  //scrivo la nuova lunghezza del metadata
                    scriviCommenti(streamWrite);
                }
                else
                {
                    streamWrite.Write(array, 0, 3);  //scrivo la lunghezza del metadata
                    int i = 0;
                    for (i = 0; (i+4)< metadata_length; i+=4)
                    {
                        stream.Read(array, 0, 4);
                        streamWrite.Write(array, 0, 4);
                    }
                    /* scrivo i byte rimanenti */
                    stream.Read(array, 0, metadata_length-i);
                    streamWrite.Write(array, 0, metadata_length -i);
                }
            } while (!((flag >> 7) == 0x1)); //se il bit più significativo è uguale a uno vuol dire ch enon ci sono più metadata
            /* scrivo tutto il rimanente */
            byte[] chunk = new byte[10240];
            
               
            int num = 0;
            while (true)
            {
                num = stream.Read(chunk, 0, 10240);
                streamWrite.Write(chunk, 0, num);
                if (num < 10240)
                {
                    stream.Close();
                    streamWrite.Close();
                    return;
                }
            }
            
        }
        public void writeAll(String filename)  //nuovo file
        {
        }
        private void caricaCommenti(Stream stream)
        {
            byte[] array = { 0, 0, 0, 0 };
            stream.Read(array, 0, 4);
            uint vendor_length = (uint)((array[0]));  //non capisco perché ma conta solo il primo byte
            byte[] stringa = new byte[1024];
            stream.Read(stringa, 0, (int)vendor_length);
            vendor_string = System.Text.Encoding.UTF8.GetString(stringa,0,(int)vendor_length);
            stream.Read(array, 0, 4); //leggo il numero dei commenti
            uint number_of_comments = (uint)array[0];
            for (uint i = 0; i < number_of_comments; i++)
            {
                stream.Read(array, 0, 4); //leggo il numero di caratteri da leggere
                uint comment_length = (uint)array[0];
                stream.Read(stringa, 0, (int)comment_length);
                String comment = System.Text.Encoding.UTF8.GetString(stringa, 0,(int) comment_length);
                comments[comment.Split('=')[0]] = comment.Split('=')[1];
                Array.Clear(stringa, 0, stringa.Length);
            }
        }
        private void scriviCommenti(Stream stream)
        {
            Int32 length = vendor_string.Length;
            byte[] array = { 0, 0, 0, 0 };
            array[0] = (byte)(length);
            array[1] = 0;
            array[2] = 0;
            array[3] = 0;
            stream.Write(array, 0, 4);
            byte[] arrayName = System.Text.Encoding.UTF8.GetBytes(vendor_string.ToCharArray());
            stream.Write(arrayName, 0, length);
            byte numcommenti = (byte)comments.Count;
            array[0] = (byte)(numcommenti);
            stream.Write(array, 0, 4);
            /* scrivo i commenti */
            foreach (KeyValuePair entry in comments)
            {
                string comment = entry.Key + "=" + entry.Value;
                length = comment.Length;
                arrayName = System.Text.Encoding.UTF8.GetBytes(comment.ToCharArray());
                array[0] = (byte)length;
                array[1] = 0;
                array[2] = 0;
                array[3] = 0;
                stream.Write(array, 0, 4);
                stream.Write(arrayName, 0, length);
            }
            data_written = true;
        }
    }
}

venerdì 26 febbraio 2016

Turbo Vision, la visione dell'era delle interfacce grafiche secondo la Borland

Era il 1990, e la Borland, con un po' di ritardo (diciamo, un ritardo spaventoso) lancia il framework Turbo Vision, quando già la Apple e la Microsoft rivaleggiavano con le interfacce grafiche in VGA e la modalità testuale del DOS era un vecchio ricordo. 

Si tratta di un framework object-oriented (lanciato in concomitanza con l'aggiornamento del linguaggio Turbo Pascal 7.0, con l'implementazione dell'object orienting) che fornisce una interfaccia grafica (o meglio, una imitazione di una interfaccia grafica) in modalità testuale, con l'uso di diversi widget (finestre, tasti, menu, barra di scelta veloce ecc.). Purtroppo non ebbe molto seguito per ovvi motivi, giusto un anno dopo veniva rilasciato Windows 3.1, il primo Microsoft OS completamente grafico. 

Però. Però.
Non nego che ha un fascino innegabile. Quello schermo pieno di pixel e quei movimenti scattosi, gli stratagemmi per le ombreggiature e le animazioni per rendere tridimensionale l'ambiente sono molto belli da vedere, e mi piacciono ancora oggi. E devo dire che non sono l'unico.

L'angolo della nostalgia

Ho iniziato a programmare in Pascal, e tengo sempre un posto particolare nel mio cuore di programmatore per questo linguaggio. Era il 2001 circa, la prima versione che scaricai era quella che il libro su cui studiavo i principi della programmazione consigliava. 
Oggi ho scaricato il simulatore DOS DOSBox e rivisto alcuni dei miei vecchi programmi scritti usando il Turbo Pascal 4.0. Questo è un esempio:

e qui mi viene da sorridere facendo il critico dopo 13 anni. A parte la pixel art che è vergognosa, l'area di calcolo è così piccola che viene a decadere tutta l'utilità del programma. E poi non c'era motivo di mettere una pixel art (Alby III G, la mia classe d'Istituto) che prendesse tutto lo schermo. 
Questa versione è rimasta 1.1. Flash Utility lasciò il posto a un esperimento del 2009 usando appunto Turbo Vision. 

Questa versione (chiamata FXMath o FixMath) è un'applicazione più matura ma comunque, piena zeppa di bug, anche perché non l'ho curata più di tanto. Consiste in un calcolatore di espressioni dove il risultato viene dato analizzando (parsing) la stringa inserita carattere per carattere, con la casella di testo "personalizzata" ad hoc per l'editing. E' un progetto più complesso, che presenta le caratteristiche di ereditarietà e polimorfismo tipiche della programmazione ad oggetti. Questo progetto verrà sviluppato più avanti in ambiente linux usando le gtk+. Più avanti dedicherò il post. 
Riporto solamente la dichiarazione degli oggetti usati nel programma:
TYPE
    TipodiOggetto = (Numero,Operatore);

    PCasellaTesto = ^TCasellaTesto;
    TCasellaTesto = OBJECT (TInputLine)
                      PUBLIC
                      Expression: ARRAY[0..1024] OF PMathObject;{L'espressione.}
                      IndiceOggetto:word;                     {Il numero     }
                                                              {ordinale.     }
                      Counter:Word;                           {Numero oggetti}
                      ame:PCasellaTesto;
                      CONSTRUCTOR Init(TR:TRect;TMaxLen:Word);
                      PROCEDURE Draw;Virtual;                 {Override      }
                      PROCEDURE SetExpression(Oggetto:PMathObject);{Memorizza}
                      PROCEDURE Riordina(Indice:Word;Direzione:boolean;lung:byte);
                      PUBLIC                                     {nell'arra}
                      FUNCTION  IsCursorShown:boolean;
                      FUNCTION  FormattaNumero(n:real):string;
                      FUNCTION  CatturaNumero(dir:boolean;Indice:word;VAR P:word):real;
                      PROCEDURE ToArray(Indice:word;num:real);
                      FUNCTION  CancellaOggetto(Indice:Word):byte;
                      PROCEDURE CalcolaEspressione;           {in una MsgBox }
                      PROCEDURE HandleEvent(var Event:TEvent);VIRTUAL;
                    END;
    PEspresBox = ^TEspresBox;
    TEspresBox = OBJECT (TDialog)                     {La finestra di dialogo}
                     PUBLIC
                     Field:PCasellaTesto;
                     CONSTRUCTOR Init;
                     PROCEDURE HandleEvent(var Event: TEvent); VIRTUAL;
                  END;
    TMyApp = OBJECT (TApplication)
                 EspresBox :PEspresBox;
                 PROCEDURE NuovaFinestra;
                 PROCEDURE HandleEvent(var Event:TEvent); VIRTUAL;
                 PROCEDURE InitMenuBar; VIRTUAL;
                 PROCEDURE ApriFinestraEspressione;
                 FUNCTION GetFileMenuItems(mah: PmenuItem) : PMenuItem;
                 FUNCTION GetEditMenuItems(mah: PMenuitem) : PMenuItem;
                 FUNCTION GetInsertMenuItems(mah: PMenuItem) : PMenuItem;
                 FUNCTION GetHelpMenuItems(mah: PMenuItem) : PMenuItem;
              END;

giovedì 25 febbraio 2016

L'applicativo scritto per la mia tesi di laurea

Per la mia tesi di laurea ho scritto un software in java per automatizzare un calcolo che, anche fatto con un foglio di calcolo, richiedeva della manualità e quindi più tempo. 
Il programma sviluppato non è certo chissà cosa di complicato, ma essendo un programmatore, ho voluto inserire qualcosa che riguardasse la programmazione nella mia tesi di laurea. 

Schermata del software
Piccolo incipit.
La mia tesi di laurea "Produzione, gestione e smaltimento dei rifiuti citotossici e citostatici
nell’AOUP “Paolo Giaccone” di Palermo", redatta con uno stage dove ho raccolto dati, riguarda lo smaltimento dei rifiuti citotossici e citostatici. I dati che avevo raccolto riguardavano le quantità di rifiuti relativi a certi farmaci prodotti ogni giorno, e ogni farmaco è caratteristico di un reparto. Il mio programma non fa altro che calcolare le percentuali del numero di farmaci effettivamente utilizzati per ogni reparto, su scala settimanale. Posso decidere se utilizzare i dati di 1, 2 o 3 settimane, come si nota sullo screenshot. Inseriti i file .csv, formati in questo modo:


poi basta premere sul tasto Elabora, che nel log del programma vengono mostrati i dati in output, che verranno comunque esportati in altrettanti file .csv. 

Dando un'occhiata al codice, ho utilizzato la libreria esterna opencsv versione 3.4 per gestire i file csv in maniera veloce. 

public class Farmaci {

    public static double oncologia=0;
    public static double ematologia=0;
    public static double neurologia=0;
    public static double urologia=0;
    public static double reumatologia=0;
    public static double onco=0;
    public static double ema=0;
    public static double neuro=0;
    public static double uro=0;
    public static double reuma=0;
    public static Integer[] ID;
    public static int primo=0, secondo=0, terzo=0;
    /*numero di farmaci*/
    public static int count;
    public static Frame frame = new Frame();
    
    
    public static void main(String[] args) {
        ID = new Integer[67];
        for (int i=0;i<67;i++)
            ID[i] = new Integer(0);
        frame.setVisible(true);
    }
}

Questa è la classe principale dove dichiaro le variabili principali. Nel metodo main inizializzo l'array di Integer di 67 elementi, tanti quanto il numero di farmaci. 
Riporto dalla mia tesi:

Il seguente codice scritto in linguaggio Java è la funzione principale del software che ho scritto. Essa riceve in input un intero da 1 a 3 che denota la casella contenente il percorso del file .csv da elaborare, quindi apre il file, legge iteratamente ogni record ed effettua le opportune operazioni, per poi infine scrivere le percentuali nelle 5 label relative a ogni reparto, e riportare altre informazioni nella casella di testo “sum”.
private voidelaboraFile(intindiceCasellaDiTesto){
try{
CSVReadercsvReader;
/*  Controllo quale casella del file esaminare */
if (indiceCasellaDiTesto==1)
csvReader = new CSVReader(new java.io.FileReader(new File(this.file1textfield.getText())),';');
else if (indiceCasellaDiTesto==2)
csvReader = new CSVReader(new java.io.FileReader(new File(this.file2textfield.getText())),';');
else if (indiceCasellaDiTesto==3)
csvReader = new CSVReader(new java.io.FileReader(new File(this.file3textfield.getText())),';');
else //DevoinizializzareobbligatoriamentecsvReader
csvReader = new CSVReader(new java.io.FileReader(new File(this.file1textfield.getText())),';');
String[] row=null;
/* Leggo dal file .csv */
while ((row = csvReader.readNext())!=null){
if (!row[0].equals("0")){ //controllo se è la riga terminale
Farmaci.ID[Integer.parseInt(row[0])-1]=Farmaci.ID[Integer.parseInt(row[0])-1]+Integer.parseInt(row[2]);      //memorizzo il farmaco con il numero delle volte che è stato utilizzato
/* Da qui in poi verifico a quale reparto “appartiene” ed eseguo le dovute operazioni
if (row[1].equals("oncologia"))
Farmaci.onco=Farmaci.onco+Integer.parseInt(row[2]);
else if (row[1].equals("oncoema")){
Farmaci.onco=Farmaci.onco+0.5*Integer.parseInt(row[2]);
Farmaci.ema=Farmaci.ema+0.5*Integer.parseInt(row[2]);
}
else if (row[1].equals("oncoemaneuo")){
Farmaci.onco=Farmaci.onco+0.3333*Integer.parseInt(row[2]);
Farmaci.ema=Farmaci.ema+0.3333*Integer.parseInt(row[2]);
Farmaci.neuro=Farmaci.neuro+0.3333*Integer.parseInt(row[2]);
}
else if (row[1].equals("ema")){
Farmaci.ema = Farmaci.ema+Integer.parseInt(row[2]);
}
else if (row[1].equals("oncoemaurol ")){
Farmaci.onco=Farmaci.onco+0.3333*Integer.parseInt(row[2]);
Farmaci.ema=Farmaci.ema+0.3333*Integer.parseInt(row[2]);
Farmaci.uro=Farmaci.uro+0.3333*Integer.parseInt(row[2]);
}
else if (row[1].equals("oncoemaurol")){
Farmaci.onco=Farmaci.onco+0.3333*Integer.parseInt(row[2]);
Farmaci.ema=Farmaci.ema+0.3333*Integer.parseInt(row[2]);
Farmaci.uro=Farmaci.uro+0.3333*Integer.parseInt(row[2]);
}
else if (row[1].equals("reumatologia")){
Farmaci.reuma=Farmaci.reuma+Integer.parseInt(row[2]);
}
/* Aumento il contatore del numero di flaconi utilizzati */
Farmaci.count = Farmaci.count+Integer.parseInt(row[2]);
}
else{
/* Operazioni da effettuare quando si arriva a fine giornata.
   ovvero il calcolo delle percentuali */
Farmaci.oncologia = (Farmaci.onco/Farmaci.count)*100;
Farmaci.ematologia = (Farmaci.ema/Farmaci.count)*100;
Farmaci.urologia = (Farmaci.uro/Farmaci.count)*100;
Farmaci.neurologia = (Farmaci.neuro/Farmaci.count)*100;
Farmaci.reumatologia = (Farmaci.reuma/Farmaci.count)*100;
oncologia.setText(String.format("%.2f",Farmaci.oncologia)+"%");
ematologia.setText(String.format("%.2f",Farmaci.ematologia)+"%");
urologia.setText(String.format("%.2f",Farmaci.urologia)+"%");
neurologia.setText(String.format("%.2f",Farmaci.neurologia)+"%");
reumatologia.setText(String.format("%.2f",Farmaci.reumatologia)+"%");
}
}
/* Trovo i tre farmaci più utilizzati */
int primo=0, secondo=0, terzo=0;
for (int i=0;i<67;i++){
if (Farmaci.ID[i]>Farmaci.ID[primo])
primo = i;
}
for (inti=0;i<67;i++){
if ((Farmaci.ID[i]>Farmaci.ID[secondo])&&(Farmaci.ID[i]<Farmaci.ID[primo]))
secondo = i;
}
for (inti=0;i<67;i++){
if ((Farmaci.ID[i]>Farmaci.ID[terzo])&&(Farmaci.ID[i]<Farmaci.ID[secondo]))
terzo = i;
}
/* Scrivo il tutto nella casella di testo per mostrare i dati. */
sum.append("Numero di farmaci utilizzati: "+Farmaci.count+"\n");
sum.append("I tre farmaci più utilizzati (per ID): \n");
sum.append("#"+Integer.toString(primo)+": "+Integer.toString(Farmaci.ID[primo])+" usi\n");
sum.append("#"+Integer.toString(secondo)+": "+Integer.toString(Farmaci.ID[secondo])+" usi\n");
sum.append("#"+Integer.toString(terzo)+": "+Integer.toString(Farmaci.ID[terzo])+" usi\n");
}
/* Controllo degli errori */
catch(java.io.IOException e){
sum.append("File non trovato\n");
}
catch(Exception e){
sum.append("Si è verificato un errore: "+e.getMessage()+”\n”);
}
}

giovedì 28 gennaio 2016

Come collegare il gamepad della prima console xbox sul PC

Avevo in cantina una vecchia xbox, la prima serie. Essendo obsoleta, ho pensato di recuperarne almeno il sempre valido joystick, o gamepad che dir si voglia. 
Ha 2 analogici, 2 triggers, 1 d-pad e 8 tasti: il gamepad della xbox 360 (nativamente supportato su windows, e con presa USB) ha solo un tasto in più.
Il modello in questione è il controller s, il secondo gamepad usato per questa console

Questo gamepad ha una presa proprietaria della Microsoft e credo che non appartenga a nessun standard, almeno stando alle informazioni che sono riuscito a racimolare sul web. 
Il vantaggio è che questa presa è assolutamente analoga a quella USB: in poche parole, non basta essere un elettrotecnico per trasformare questa presa in una USB e quindi, collegarla al PC.
Infatti sia la presa USB che la presa del gamepad hanno lo stesso numero di fili aventi lo stesso colore  (4 fili, in verità il gamepad ne ha uno in
La presa del gamepad
più, il giallo, che non viene utilizzato però). In fin dei conti, basta prendere un vecchio cavo USB e recidere in due sia quest'ultimo che il filo del gamepad, quindi collegare i fili; se si è bravi si può saldare ogni singolo filo e isolare singolarmente i quattro fili. 

Fatto questo, collegare il gamepad al PC e Windows riconoscerà una nuova periferica, ma fallirà nel trovare i driver appropriati. Allora ecco che un nerd della rete è venuto in aiuto: ha sviluppato i driver necessari per far funzionare e riconoscere il gamepad al PC come una
I fili collegati
periferica di gioco. Se avete un sistema a 32 bit potete scaricare da qui l'installer (scegliete l'ultima versione) che installerà tutto automaticamente, riavviate e il gioco è fatto. 
Se invece avete un sistema a 64bit questi driver, compilati per 32bit, non funzioneranno. Ma ecco che un altro bravo nerd della rete è venuto di nuovo in aiuto e ha compilato i driver anche per i sistemi a 64bit. Vi illustro in alcuni passaggi quello che ho fatto:
  1. Da questa pagina ho copiato sugli appunti i caratteri strani che stanno tra "begin" e "end" (compresi), dunque ho aperto Notepad++ (dovrebbe andar bene anche il blocco note, ma se usate ancora quest'ultimo siete sfigati), incollato il tutto e salvato come file avente estensione .7z
  2. Ho scaricato un encoder/decoder per decodificare il tutto da qui ed estratto il file UUDECODE.EXE sulla stessa cartella del file .7z
  3. Ho aperto il prompt (dal menù Start, scrivere cmd e premere invio), mi sono spostato sulla cartella (cd "percorso cartella) e scritto UUDECODE <nomefile.7z>  (con il nome del file .7z che avete salvato al punto 1) a premuto invio
  4. Poi ho aperto il file xbcd_108.7z che si è creato (l'ho aperto con 7zip, ma va bene anche winrar) ed estratto il contenuto in una cartella.
  5. Sono andato nel Pannello di Controllo->Sistema->Gestione dispositivi e individuato la periferica non riconosciuta (contrassegnata da un punto esclamativo su sfondo giallo), dunque cliccato con tasto destro->Proprietà, nella finestra sono andato nella sezione "Driver" e cliccato su "Aggiorna driver..." e poi "Cerca il software del driver nel computer", dunque ho scelto la sottocartella "x64" della cartella dove ho scompattato xbcd_108.7z
  6. Dopo avere atteso un po', Windows ci chiede se siamo sicuri di voler installare un software senza firma; scegliamo si. 
I sistemi Windows a 64bit controllano a ogni avvio se ci sono driver senza firma digitale, in tal caso li blocca e ne impedisce l'esecuzione. In poche parole,allo stato attuale, il gamepad ancora non vi funziona. Se voi avete un modo di mettere la firma digitale bene, altrimenti dovrete eludere il succitato controllo. All'avvio di Windows, premete continuamente F8 e scegliere l'ultima opzione, quella relativa al controllo dei driver non firmati. Questo farà si che all'avvio non verranno controllati i driver e finalmente il nostro gamepad funzionerà. Andate nel pannello di controllo e cercate "gioco", dovreste trovare "Configura dispositivi di gioco USB". Da lì dovreste vedere se il gamepad funziona bene. 

In aggiuntiva, dovrete installare il software che gestisce le periferiche di gioco, cioè un software che sfrutta al meglio le potenzialità di questo gamepad con cui possiamo associare le funzioni di gioco a ogni tasto del gamepad; all'inizio io ho usato il molto valido Pinnacle Game Profiler, ma vista la sua natura shareware (ormai mi è scaduto, comunque il prezzo è molto basso) sono passato a una valida alternativa freeware: Xpadder. Potrete seguire le istruzioni di questo video per la configurazione.

lunedì 31 agosto 2015

Un programma per aggiungere lo "staccato" agli spartiti in MusicXML

Prima
Dopo
Ne approfitto per rispolverare questo blog su cui non scrivo da molto.

Ho sviluppato un programma in java che in principio doveva essere un plugin interno a MuseScore (un software libero di notazione musicale), ma viste alcune complicazioni, ho deciso di creare un programma in java con cui si può fare tutto.

In pratica esso agisce su un file MusicXML (file standard per la notazione musicale che MuseScore può esportare facilmente), e aggiunge lo "staccato" alle note seguite da una pausa avente lo stesso valore. Rende lo spartito più leggibile.

L'ho sviluppato per mettere fine una volta per tutte alla modifica manuale degli spartiti. 

Il prossimo passo sarà quello di aggiungere il punto a quelle note legate a una stessa nota ma con la metà del loro valore.

Per sviluppare il programma (sviluppato e lasciato in linea di comando) mi sono servito della libreria JDOM per manipolare i file XML.

Da qui potete scaricare sorgenti e compilati del programma. 

Per il funzionamento è necessario estrarre in una cartella AddStaccato.jar e le directory DTD e lib.
Per lanciare il programma devi avere java sul tuo pc e le variabili d'ambiente impostate.
Quindi lanci il comando:

java -jar <percorso_del_programma_addstaccato> <opzioni> <valori> nomefile.xml

le opzioni sono:

-r seguito da un numero, specifica il numero della parte (=rigo musicale) da modificare
-i seguito da due numeri che rappresentato la prima e l'ultima battuta (incluse) da modificare

Esempio:


java -jar <percorso_del_programma_addstaccato> -r 4 -i 15 74 nomefile.xml

modifica le battute comprese tra 15 e 74 (inclusi) del rigo numero 4 di nomefile.xml

bye


giovedì 19 giugno 2014

Aggiornamento: JMusicMan

Dopo tanto tempo, ho deciso di scrivere un programma per gestire la musica sul mio Android. Quando avevo l'iPod non avevo molto problemi, perché iTunes è un gran software, fa tutto a dovere, anche se ha un grandissimo difetto: se non hai un Mac (cioè se sei su Windows) ti gira come una palla pesantissima...

Oggi ho Android, nello scorso post avevo mostrato il mio sistema per sincronizzare Rhythmbox con il dispositivo Android. Perfetto, anch'esso funzionale, ma il sistema di plugin di Rhythmbox è odioso, tanto che quando formatto il pc (quando esce una LTS ubuntu è d'obbligo) devo reinstallare tutto, aggiungendo che qualche plugin è datato/non funziona più / è incompatibile con la nuova versione, perdo delle caratteristiche importanti.


La verità è che sono veramente un maniaco, basterebbe solo collegare il cellulare al PC e copiare la musica; a me no, non basta, finché non ho un sistema davvero efficiente per avere una copia della mia libreria sul PC fedele a quella del cellulare.


Ecco che ho sviluppato JMusicMan, ancora in versione alpha, con lo scopo  ben preciso di adempiere le seguenti funzionalità:

  1. Le canzoni devono essere organizzate in cartelle in  questo modo: /artista/album/file.mp3, e ogni file mp3 deve avere il titolo xx - TitoloCanzone.mp3, dove xx è il numero della traccia. Il tutto ovviamente preso dal tag ID3 del file mp3.
  2. La libreria sul PC deve essere uguale a quella sul cellulare.
  3. Quando sincronizzo con il cellulare, il programma deve controllare quali file copiare sul dispositivo e quali file eliminare dal dispositivo
Devo dire che queste tre caratteristiche sono state perfettamente implementate (soltanto la lettura dei tag ID3, resa possibile tramite una libreria di terze parti, ogni tanto fa i capricci).

Dopo aver corretto alcuni bugs, e dopo aver implementato altre nuove funzionalità (attualmente il software sa richiamare il player precedentemente impostato per riprodurre il file musicale), poi rilascierò il software. 

La prossima commissione sarà anche quella di organizzare i files .ogg e .flac (per me che sono un maniaco della qualità audio, il supporto a questo formato è indispensabile).

Questo è il repository git su github: https://github.com/standuptall/JMusicMan.git

giovedì 13 marzo 2014

Rhythmbox e la sincronizzazione dei dispositivi portatili

Ho sempre trovato Rhythmbox per GNU/Linux, un ottimo player, perfettamente integrato all'ambiente GNOME e derivati. Il suo punto di forza sta nell'usabilità, nella semplicità di utilizzo e nella sua espansione tramite plugin; proprio con quest'ultimo punto deriva il suo adattarsi alle più svariate esigenze dei musicofili più incalliti (come me), e la possibilità di curare ogni aspetto del proprio music entertainment.

Rhythmbox è molto simile per certi versi ad iTunes, ma col tempo è diventato un programma a se stante, anche perché lo considero più stabile di iTunes.

Ma parliamo di una caratteristica che credo ogni amante della musica richieda: la possibilità di sincronizzare la propria musica con la libreria del PC, in modo da avere la stessa musica organizzata alla stessa maniera sia su PC che sul dispositivo portatile: è una situazione molto comoda che evita di fare confusione in mezzo alla mole di musica che ognuno possiede, e soprattutto evita di gestire manualmente la musica direttamente dalla memoria di massa del dispositivo (organizzare la musica in cartelle, cancellare quella che non asdcoltiamo mai e che occupa memoria inutilmente, aggiungere altra musica ecc.). Rhythmbox sa gestire di default i dispositivi portatili tramite due plugin: Supporto per dispositivi Apple iPod e Supporto per dispositivi MTP. Il primo non necessita di chiarimenti: iPhone, iPod e derivati possono essere gestiti con Rhythmbox. Un dispositivo MTP è invece un dispositivo che supporta il protocollo MTP: fra questi ricordiamo molti lettori Creative, Zune, dispositivi con Windows Phone, Symbian ecc.

Ma soprattutto Rythmbox sa gestire i dispositivi mobili anche senza nessun protocollo, semplicemente tenendo traccia della musica presente sulla memoria di massa del dispositivo e la propria libreria. In tal senso anche una semplice chiavetta USB o un hard disk possono essere trattati come dei dispositivi portatili che riproducono musica (ma che effettivamente non fanno). E allora? Qual'è l'utilità?
Android: un OS oggi molto diffuso fra i dispositivi mobili. Se proviamo a collegare un Android al PC, il cellulare ci chiede se vogliamo attivare l'archivio USB, se lo facciamo, allora il nostro PC riconoscerà il dispositivo Android come una semplice memoria di massa (una chiavetta USB); se apriamo Rhythmbox però non vediamo nessun dispositivo collegato. Ma c'è un modo per far riconoscere un dispositivo a Rhythmbox come un dispositivo multimediale: dobbiamo semplicemente creare un file nella directory principale della memoria di massa del dispositivo. Semplice no?

Quindi, andiamo nella directory principale del dispositivo e creiamo un file di nome ".is_audio_player" e come contenuto il seguente:

name="My Portable Audio Player" (Nome che viene dato al dispositivo)
audio_folders=Music/, Sounds/   (Le cartelle che verranno riconosciute come contenenti musica)
folder_depth=2                  (Il numero di sottocartella che la cartella Music/ può contenere)
output_formats=audio/mpeg,audio/x-ms-wma,application/ogg        (Tipi MIME per i nostri file audio)

Fatto ciò salviamo il file, andiamo su rhythmbox, clicchiamo su "+" in basso a sinistra, "Controlla nuovi dispositivi" (solo la prima volta, successivamente quando colleghiamo il dispositivo automaticamente Rhythmbox lo riconoscerà). Ed ecco che Rhythmbox ha riconosciuto il nostro dispositivo Android come lettore musicale. Adesso basta cliccare sul nome del dispositivo e scegliere "Sincronizza", che tutta la nostra libreria del PC verrà sincronizzata col nostro lettore portatile.

A tal proposito consiglio vivamente un plugin per Rhythmbox di nome File Organizer, che ci consente mantenere organizzata la nostra cartella muica sia su PC che su dispositivo mobile, in particolare di default esso controlla i tag id3 del file audio, crea una cartella col nome dell'artista della canzone, una sottocartella col nome dell'album della canzone e posiziona il file in questa sottocartella, rinominandolo con "XX-Titolo" dove XX è il numero della traccia e Titolo è il titolo della canzone. Purtroppo questo plugin non fa ciò automaticamente, ma ci basta selezionare la musica che vogliamo tenere organizzata, tasto destro->Organize selection. In questo modo si ha una libreria omogenea, ordinata e organizzata secondo gli stessi criteri per tutti i file. Per installare il plugin File Organizer:

1)Scarichiamo il file .tar.gz da questa pagina
2)Estraiamo il contenuto su una cartella
3)Col terminale, posizioniamoci sulla cartella contenente i file estratti e scriviamo:
chmod a+x ./INSTALL
per dare i permessi necessari al file INSTALL
4)Lanciamo sudo ./INSTALL
5)Andiamo su Rhythmbox e attiviamo il plugin dal menù Plugins