C Linux Device Programming — Reading Straight from /Dev
I have been playing with creating sounds using mathematical wave functions in C. The next step in my project is getting user input from a MIDI keyboard controller in order to modulate the waves to different pitches. My first notion was that this would be relatively simple and that Linux, being Linux, would allow me to read the raw data stream from my device like I would any other file. However, research overwhelmingly advises that I write a device driver for the MIDI controller. The general idea is that even though the device file may be present, the kernel will not know what system calls to execute when my application calls functions like read() and write(). Despite these warnings, I did an experiment. I plugged in the MIDI controller and cat’ed the «/dev/midi1» device file. A steady stream of null characters appeared, and when I pressed a key on the MIDI controller several bytes appeared corresponding to the expected Message Chunks that a MIDI device should output. MIDI Protocol Info So my questions are: Why does the cat’ed stream behave this way? Does this mean that there is a plug and play device driver already installed on my system? Should I still go ahead and write a device driver, or can I get away with reading it like a file? Thank you in advanced for sharing your wisdom in these areas.
rather than cat’ting it, I would recommend that you read() the device from a ‘C’ program. You say you’re seeing ‘valid’ data, why not find out for sure? btw, there must already be a device driver there, otherwise you wouldn’t have a device file, nor would there be a way to access it.
3 Answers 3
Why does the cat’ed stream behave this way?
Because that is presumably the raw MIDI data that is being received by the controller. The null bytes are probably some sort of sync tick.
Does this mean that there is a plug and play device driver already installed on my system?
However, research overwhelmingly advises that I write a device driver for the MIDI controller. The general idea is that even though the device file may be present, the kernel will not know what system calls to execute when my application calls functions like read() and write().
Should I still go ahead and write a device driver, or can I get away with reading it like a file?
I’m not sure what you’re reading or how you’re coming to this conclusion, but it’s wrong. 🙂 You’ve already got a perfectly good driver installed for your MIDI controller — go ahead and use it!
Are you sure you are reading NUL bytes? And not 0xf8 bytes? Because 0xf8 is the MIDI time tick status and is usually sent periodically to keep the instruments in sync. Try reading the device using od :
If you’re seeing a bunch of 0xf8, it’s okay. If you don’t need the tempo information sent by your MIDI controller, either disable it on your controller or ignore those 0xf8 status bytes.
Also, for MIDI, keep in mind that the current MIDI status is usually sent once (to save on bytes) and then the payload bytes follow for as long as needed. For example, the pitch bend status is byte 0xeK (where K is the channel number, i.e. 0 to 15) and its payload is 7 bits of the least significant byte followed by 7 bits of the most significant bytes. Thus, maybe you got a weird controller and you’re seeing only repeated payloads of some status, but any controller that’s not stupid won’t repeat what it doesn’t need to.
Now for the driver: have a look at dmesg when you plug in your MIDI controller. Now if your OSS /dev/midi1 appears when you plug in your device (udev is doing this job), and dmesg doesn’t shoot any error, you don’t need anything else. The MIDI protocol is yet-another-serial-protocol that has a fixed baudrate and transmits/receives bytes. There’s nothing complicated about that. just read from or write to the device and you’re done.
The only issue is that queuing at some place could result in bad audio latency (if you’re using the MIDI commands to control live audio, which I believe is what you’re doing). It seems like those devices are mostly made for system exclusive messages, that is, for example, downloading some patch/preset for a synthesizer online and uploading it to the device using MIDI. Latency doesn’t really matter in this situation.
Also have a look at the ALSA way of playing with MIDI on Linux.
How to read a file device in Linux using Qt?
I’m working on a small Qt5-based GUI, which shall display a data stream from a Linux file device. For that I choose a joystick input. With cat /dev/input/js0 it is possible to see the incoming stream on the terminal. Using C you can read this device file using a loop with a blocking read or handle the device signals. But I don’t get this with Qt. What is the typical approach to interact with device files using Qt? Based on the answer of @rodrigo here a new implementation: joystick.h
#ifndef JOYSTICK_H #define JOYSTICK_H #include #include #include class Joystick : public QObject < Q_OBJECT QString fileName = "/dev/input/js0"; QFile *file; QSocketNotifier *notifier; public: explicit Joystick(QObject *parent = nullptr); ~Joystick(); signals: public slots: void handle_readNotification(int socket); >; #endif // JOYSTICK_H
#include "joystick.h" Joystick::Joystick(QObject *parent) : QObject(parent) < file = new QFile(); file->setFileName(fileName); if( !file->exists() ) < qWarning("file does not exist"); return; >if( !file->open(QFile::ReadOnly) ) < qWarning("can not open file"); return; >notifier = new QSocketNotifier( file->handle(), QSocketNotifier::Read, this); connect( notifier, &QSocketNotifier::activated, this, &Joystick::handle_readNotification ); if( !notifier->isEnabled() )< qInfo("enable notifier"); notifier->setEnabled(true); > qInfo("Joystick init ready"); > void Joystick::handle_readNotification(int /*socket*/) < static quint64 cnt=0; qInfo("cnt: %d",cnt++); if( !(file->isOpen()) ) < qWarning("file closed"); return; >char buf[16]; /* tested with different sizes */ if( file->read(buf,sizeof(buf)) ) < qInfo("read: %s",buf); >// QByteArray ba = file->readAll(); // qInfo("Data: %s", ba.data()); >
Then I run this, the last out put is cnt: 0 . It seems, that the read or readAll call now blocks. If I comment out the read calls the counter runs very fast. here a similiar post That is wrong here?
final solution
#ifndef JOYSTICK_H #define JOYSTICK_H #include #include #include class Joystick : public QObject < Q_OBJECT QString fileName = "/dev/input/js0"; QSocketNotifier *notifier; int fd; public: explicit Joystick(QObject *parent = nullptr); ~Joystick(); signals: void buttonPressed(quint8 number, qint16 value); void axisMoved(quint8 number, qint16 value); public slots: void handle_readNotification(int socket); >; #endif // JOYSTICK_H
#include "joystick.h" #include #include #include Joystick::Joystick(QObject *parent) : QObject(parent) < auto file = new QFile(); file->setFileName(fileName); if( !file->exists() ) < qWarning("file does not exist"); return; >fd = open(fileName.toUtf8().data(), O_RDONLY|O_NONBLOCK); if( fd==-1 ) < qWarning("can not open file"); return; >notifier = new QSocketNotifier( fd, QSocketNotifier::Read, this); connect( notifier, &QSocketNotifier::activated, this, &Joystick::handle_readNotification ); > Joystick::~Joystick() < if( fd>=0 ) < close(fd); >> void Joystick::handle_readNotification(int /*socket*/) < struct js_event buf; while( read(fd,&buf,sizeof(buf))>0 ) < switch (buf.type) < case JS_EVENT_BUTTON: emit buttonPressed(buf.number, buf.value); break; case JS_EVENT_AXIS: emit axisMoved(buf.number, buf.value); break; >> >