Jump to content

DH100 - Interface program / A8 - Frequency tables

Posted by Pack007, 06 January 2018 · 56 views

DH100 DH-100 MIDI digital horn

I finished work on the last blog entry, unplugged the keyboard and plugged the digital horn.  I was a little surprised to see how much data the breath pressure sensor(after touch) was being streamed to the Atari 8.  Far more then it could keep up with.
The Arduino needed to be reprogramed to reduce the number of bytes being send to the Atari8.  The interface software was tested with the previous Atari 8 test program and it performed much better.  The data seemed to make sense and it was difficult to overwrite the serial input buffer on the Arduino.  These are the ways that the data stream was reduced.
The DH-100 has 4 types of data it will generate. The Arduino reads in the data, modifies it and sends it out in the void loop() function.
Casio DH-100 MIDI output
1  Note On Event   1001nnnn, 0kkkkkkk, 0vvvvvvv (144+,n,v)
2  Control Change   1011nnnn, 0ccccccc, 0vvvvvvv (176+,65,v)  value  0=off, 127=on (portomento)
3  Program Change 1100nnnn,0ppppppp (192+,p) p = 0 to 5 (patch change)
4  Channel Pressure 1101nnnn,0vvvvvvv (208+,v)  v = 0-127 scaled to 0-15 for Atari volume(breath controller)
The DH-100 will only transmit data on MIDI channel 1.  The Arduino will check for any incoming command byte.  If it is a command byte, it will have the 7th bit set and bits 0-3 will contain the channel number(zero for channel 1).  If the data for the command can be stated in under 4 bits it can be combined with the command in one byte and sent.
Channel Pressure:  This command generates a continuous stream of data.  This 2 byte command is reduced to one byte by scaling the 0-127 from the DH100 to 0 - 15 for the Atari.  The Atari value replaces the channel number.  It is far less sensitive to changes and by sending the byte only when it has changed will reduce the data flow. 
Program Change:  has a value of 0 to 5.  It also can be added to the command byte and sent as one byte.  It doesn't happen very often but its still 1/2 the original data byte count.
Control Change:  The DH-100 only has one controller and it is 65 for the Portomento switch. The Atari can assume that if a Control Change command is received and, the 65 doesn't have to be sent. A value of 0 for off or 127 for on.  Change the 127 to 1 and added it to the command byte. Another byte saved.
Note On:   The third byte of the command is the volume and can be handled the same way as the Channel Pressure.  The second byte (note number) may as well be sent as an unmodified second byte.  Reducing this to 2 bytes is still a savings in time.
All of this data is checked for redundancy and not sent unless there is a change.
When a command is not to be sent to the Atari the command is reset to zero and some how makes it back to reading the data until a command byte is detected otherwise it is sent using the second switch-case set of routines.
The Atari can split the data off the command byte without to much trouble.  The integer math of the MMG Basic complier should make it even faster.
Where X = combined input data:
Command = int(X/16)*16
DataValue= X - Command
/* Midi data transfer test 1/2018
 *  This program reads midi data then outputs 
 * the data to the Atari Joystick ports.
 * The use of the control lines to the Atari Trigger
 * and from the cassette motor control line on the SIO
 * port are used for data flow control.
 * An effort to minimize data transfer by combining data bytes
 * since DH100 uses Midi channel 1 the first four bits of the
 * data byte can be used for Atari Volume(0-15) and controller
 * data.  Atari can then strip the lower four bits and make the settings.
// Atari PORTA(54016) mapped to pins on Arduino
int porta0 = 4;
int porta1 = 5;
int porta2 = 6;
int porta3 = 7;
int porta4 = 8;
int porta5 = 9;
int porta6 = 10;
int porta7 = 11;
int DSR = 3; //Arduino ready
int DTR = 12;//Atari ready
byte zero = 0;
byte midiData = 0;
byte midiCommand = 0;
byte midiNoteNum = 0;
byte lastMidiNoteNum = 0;
byte volume = 0;
byte lastVolume = 0;
byte portemento = 0;  //0=OFF 127=ON
byte patch = 0;       //0 - 5
byte controlChange = 0;
byte dataByte = 0;
//Function to Send a byte to the Atari
void sendByte(byte byteToSend){
  digitalWrite(DSR,LOW); //data ready-Trigger 0
  while(digitalRead(DTR) == LOW){} //Wait for Atari to get byte cmc goes low
  digitalWrite(DSR,HIGH); //data not ready-trigger 1
  while(digitalRead(DTR) == HIGH){} //wait for Atari to signal ok to get next byte
void setPorta(byte byteToMap){
  // Sets digital pins to transfer data to Atari joystick ports(PORTA)
  // When digital port high, joystick pin shorted to ground or logic 0
  if (byteToMap & B00000001){digitalWrite(porta0,LOW);}
    else {digitalWrite(porta0,HIGH);}
  if (byteToMap & B00000010){digitalWrite(porta1,LOW);}
    else {digitalWrite(porta1,HIGH);}
  if (byteToMap & B00000100){digitalWrite(porta2,LOW);}
    else {digitalWrite(porta2,HIGH);}
  if (byteToMap & B00001000){digitalWrite(porta3,LOW);}
    else {digitalWrite(porta3,HIGH);}
  if (byteToMap & B00010000){digitalWrite(porta4,LOW);}
    else {digitalWrite(porta4,HIGH);}
  if (byteToMap & B00100000){digitalWrite(porta5,LOW);}
    else {digitalWrite(porta5,HIGH);}
  if (byteToMap & B01000000){digitalWrite(porta6,LOW);}
    else {digitalWrite(porta6,HIGH);}
  if (byteToMap & B10000000){digitalWrite(porta7,LOW);}
    else {digitalWrite(porta7,HIGH);}
void setup() {
void loop() {
  //read data until its a Midi command
  //command bytes will have bit 7 true
  while (midiCommand < 128){
    while(Serial.available()<1){}//wait for data
    midiCommand = Serial.read(); //read MIDI stream
  // get data required by command.
  // volume - make sure it is an incremental change
  // midiCommand = 0 if not valid or redundant - do not send
  // patch - should be >6
    case 208:// Breath controller
    volume = Serial.read();
    if(volume != 0){volume = map(volume,1,127,1,15);}
    if(volume == lastVolume){midiCommand = 0;}
    case 144:// NoteOn command
    midiNoteNum =  Serial.read();
    volume = Serial.read();
    if(volume != 0){volume = map(volume,1,127,1,15);}
    if((volume == 0) && (midiNoteNum != lastMidiNoteNum)){midiCommand = 0;}
    case 192: //Patch change
    patch = Serial.read();
    if (patch >5){midiCommand = 0;}
    case 176: //control change (65=portemento) 0 off,127 on
    controlChange = Serial.read();
    if (controlChange == 65){
      portemento = Serial.read();
      if (portemento >0){portemento = 1;} //1 = on
    {midiCommand = 0;
    default:  //if not a DH-100 command ignore
    midiCommand = 0;
  //combine data and send if command <> 0
  if (midiCommand != 0){
      case 208: //breath controller
      dataByte = midiCommand + volume;
      lastVolume = volume;
      case 192:
      dataByte = midiCommand + patch;
      case 176:
      dataByte = midiCommand + portemento;
      case 144:
      dataByte = midiCommand + volume;
      lastVolume = volume;
      lastMidiNoteNum = midiNoteNum;
      midiCommand = 0;
A8 - Frequency tables
De Re Atari has a chapter on sound that is quite interesting.  Seems that SOUND 1 and SOUND 2 can be combined to improve  pitch control.  Then I found the article "Perfect Pitch" by Fred Coffey in COMPUTE!'S SECOND BOOK OF ATARI.   It provided the information and formulas to convert frequency data to the equivalent 16 bit numbers needed to produce those numbers and it seemed to make the whole process easy.
A utility program has been written to produce the DATA statements for a 108 X 3 array.  The MIDI note number from the Arduino can be used to index into the array and then the 16 bit number (to be used for portomento calculations), high byte, and low bit can be accessed for use.  This program should make it easy to modify  the data statements if changes are to be made.  Who knows,  maybe the need will arise when they are required in MAC/65 format.
The frequency data came from a keyboard chart.  <http://newt.phys.uns...u/jw/notes.html>
Line 20 needs to be changed when the DATA statements are to be listed to disk.  Then they can be ENTERed into a program.  Typing the Frequency DATA once was enough for me.
20 OPEN #1,8,0,"S:":REM "D:FTABLE.BTX"
30 ? #1;"4999 REM FREQUENCY FOR MIDINOTE 21-108(NOTE A0 TO C8)"
40 TRAP 300
50 ? #1;LINE;" ";"DATA ";
60 FOR X=1 TO 5
100 P2=INT((1789790/(2*PITCH)-7)/256)
110 P1=INT(1789790/(2*PITCH)-7-256*P2+0.5)
115 P0=P2*256+P1
120 ? #1;P0;",";P2;",";P1;
125 IF X<5 THEN ? #1;",";
130 NEXT X
140 ? #1
150 LINE=LINE+10
160 GOTO 50
300 ? #1;-1:CLOSE #1:END
500 DATA 27.5,29.135,30.868,32.703,34.648,36.708,38.891,41.203,43.654,46.249,48.999,51.913
510 DATA 55,58.270,61.735,65.406,69.296,73.416,77.782,82.407,87.307,92.499,97.999,103.83
520 DATA 110,116.54,123.47,130.81,138.59,146.83,155.56,164.81,174.61,185,196,207.65
530 DATA 220,233.08,246.94,261.63,277.18,293.67,311.13,329.63,349.23,369.99,392,415.30
540 DATA 440,466.16,493.88,523.25,554.37,587.33,622.25,659.26,698.46,739.99,783.99,830.61
550 DATA 880,932.33,987.77,1046.5,1108.7,1174.7,1244.5,1318.5,1396.9,1480,1568,1661.2
560 DATA 1760,1864.7,1975.5,2093.0,2217.5,2349.3,2489,2637,2793,2960,3136,3322.4
570 DATA 3520,3729.3,3951.1,4186

1/12/2018 - After the post I started working on the basic program to set the sound registers.  I got it working with the breath controller turned off.  Play with the breath controller turned on and my logic falls apart. The DH-100 continues to send non zero breath pressure readings even after the note has turned off.  Now I have to figure out how to filter out those unnecessary numbers on the Arduino side.


The horn sends out a note on command at Zero volume.  When this happens the control data can be dropped until the next noteon at some volume.  The Arduino will have to be programed to deal with these events to reduce the data stream to the Atari. 

  • Report

1/14/2018 - Got everything working even the Portomento setting.  A big problem is hitting the tone button on the horn by mistake.


Turns out the data was overflowing the Arduino serial input buffer again.  It's one long stream of data when the pressure sensor is outputting the data.  It seems to work if I use every 15th data point to set the volume but only when it's not the same as the last set point.  And of course,  the BASIC program has to be compiled.  


Now to record some audio examples for the next blog entry.   

  • Report

Latest Visitors