├── .gitignore ├── serial-setup.sh ├── ym2149.h ├── Makefile ├── README.md ├── main.c ├── ymdump.py ├── streamer.py ├── uart.h ├── ym2149.c ├── uart.c └── ymreader.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__/ 3 | -------------------------------------------------------------------------------- /serial-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | stty -F /dev/ttyUSB0 cs8 57600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts 3 | -------------------------------------------------------------------------------- /ym2149.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void set_ym_clock(void); 5 | void set_bus_ctl(void); 6 | 7 | void send_data(char addr, char data); 8 | char read_data(char addr); 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | baud=57600 2 | avrType=atmega328p 3 | avrFreq=16000000 # 16 Mhz 4 | programmerDev=/dev/ttyUSB0 5 | programmerType=arduino 6 | 7 | cflags=-DF_CPU=$(avrFreq) -mmcu=$(avrType) -Wall -Werror -Wextra -Os 8 | 9 | objects=$(patsubst %.c,%.o,$(wildcard *.c)) 10 | 11 | 12 | .PHONY: clean flash 13 | 14 | all: main.hex 15 | 16 | %.o: %.c 17 | avr-gcc $(cflags) -c $< -o $@ 18 | 19 | main.elf: $(objects) 20 | avr-gcc $(cflags) -o $@ $^ 21 | 22 | main.hex: main.elf 23 | avr-objcopy -j .text -j .data -O ihex $^ $@ 24 | 25 | flash: main.hex 26 | avrdude -p$(avrType) -c$(programmerType) -P$(programmerDev) -b$(baud) -v -U flash:w:$< 27 | 28 | clean: 29 | rm -f main.hex main.elf $(objects) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Title: ym2149-streamer 2 | 3 | The ym2149-streamer allows sending Atari ST YM files to a YM2149 chip 4 | driven by an Arduino. 5 | 6 | Requirements 7 | ------------ 8 | 9 | The following libraries are required: 10 | 11 | * avr-gcc 12 | * avr-libc 13 | * avrdude 14 | 15 | How to do 16 | --------- 17 | 18 | $ make 19 | $ make flash 20 | $ python streamer.py 21 | 22 | Where would typically be `/dev/ttyUSB0`. 23 | 24 | More information 25 | ---------------- 26 | 27 | More information can be found on my blog: 28 | 29 | * [Streaming music to YM2149F][1] 30 | * [Driving YM2149F sound chip with an Arduino][2] 31 | * [Arduino Hello World without IDE][3] 32 | 33 | Besides, a video showing the [YM2149 & Arduino circuit playing a tune][4] is 34 | available. 35 | 36 | 37 | [1]: http://www.florentflament.com/blog/streaming-music-to-ym2149f.html 38 | [2]: http://www.florentflament.com/blog/driving-ym2149f-sound-chip-with-an-arduino.html 39 | [3]: http://www.florentflament.com/blog/arduino-hello-world-without-ide.html 40 | [4]: https://www.youtube.com/watch?v=MTRJdDbY048 41 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "uart.h" 2 | #include "ym2149.h" 3 | 4 | #define LED PORTB5 // Digital port 13 noted the board has an led on pin 13 already(should be yellow and be right below the pin) 5 | void set_led_out(void) { 6 | DDRB |= 1 << LED; 7 | } 8 | 9 | void clear_registers(void) { 10 | int i; 11 | for (i=0; i<14; i++) { 12 | send_data(i, 0); 13 | } 14 | } 15 | 16 | int main() { 17 | unsigned int i; 18 | unsigned char data[16]; 19 | 20 | set_ym_clock(); 21 | set_bus_ctl(); 22 | initUART(); 23 | set_led_out(); 24 | clear_registers(); 25 | 26 | for/*ever*/(;;) { 27 | for (i=0; i<16; i++) { 28 | data[i] = getByte(); 29 | } 30 | 31 | // Working around envelope issue (kind of). When writing on the 32 | // envelope shape register, it resets the envelope. This cannot be 33 | // properly expressed with the YM file format. So not using it. 34 | // Thanks sebdel: https://github.com/sebdel 35 | for (i=0; i<13; i++) { 36 | send_data(i, data[i]); 37 | } 38 | 39 | // Have LED blink with noise (drums) 40 | if (~data[7] & 0x38) { 41 | PORTB |= 1 << LED; 42 | } else { 43 | PORTB &= ~(1 << LED); 44 | } 45 | } 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /ymdump.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sys 3 | 4 | from ymreader import YmReader 5 | 6 | def divide_by2(l): 7 | val = l[0] + l[1]*256 8 | val /= 2 9 | return (val%256, val/256) 10 | 11 | def main(): 12 | if len(sys.argv) not in (2, 3, 4): 13 | print("Dump YM file to z88dk z80asm format.") 14 | print("Syntax is: {} [max_frames_count] [frames_offset]".format(sys.argv[0])) 15 | exit(0) 16 | 17 | 18 | songname = os.path.splitext(os.path.basename(sys.argv[1]))[0] 19 | with open(sys.argv[1]) as fd: 20 | ym = YmReader(fd) 21 | atari_clock = ym.get_header()['chip_clock'] == 2000000 22 | 23 | data = ym.get_data() 24 | # Possibly capping number of frames to dump 25 | if len(sys.argv) >= 3: 26 | max_frames = int(sys.argv[2]) 27 | f_offset = 0 28 | if len(sys.argv) == 4: 29 | f_offset = int(sys.argv[3]) 30 | data = data[f_offset:f_offset+max_frames] 31 | 32 | # Printing out stuff 33 | print("\tPUBLIC _{}".format(songname)) 34 | print("._{}".format(songname)) 35 | for s in data: 36 | d = [ord(c) for c in s] 37 | if atari_clock: # Ajust notes to the 1000000 MHz Amstrad CPC clock 38 | d[0:2] = divide_by2(d[0:2]) 39 | d[2:4] = divide_by2(d[2:4]) 40 | d[4:6] = divide_by2(d[4:6]) 41 | l = ", ".join(["${:02x}".format(x) for x in d[:14]]) 42 | print("\tDEFB " + l) 43 | 44 | main() 45 | -------------------------------------------------------------------------------- /streamer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import time 5 | 6 | from ymreader import YmReader 7 | 8 | def to_minsec(frames, frames_rate): 9 | secs = frames / frames_rate 10 | mins = secs / 60 11 | secs = secs % 60 12 | return (mins, secs) 13 | 14 | def main(): 15 | header = None 16 | data = None 17 | 18 | if len(sys.argv) != 3: 19 | print("Syntax is: {} ".format(sys.argv[0])) 20 | exit(0) 21 | 22 | with open(sys.argv[2]) as fd: 23 | ym = YmReader(fd) 24 | ym.dump_header() 25 | header = ym.get_header() 26 | data = ym.get_data() 27 | 28 | song_min, song_sec = to_minsec(header['nb_frames'], header['frames_rate']) 29 | print("") 30 | with open(sys.argv[1], 'w') as fd: 31 | time.sleep(2) # Wait for Arduino reset 32 | frame_t = time.time() 33 | for i in range(header['nb_frames']): 34 | # Substract time spent in code 35 | time.sleep(1./header['frames_rate'] - (time.time() - frame_t)) 36 | frame_t = time.time() 37 | fd.write(data[i]) 38 | fd.flush() 39 | i+= 1 40 | 41 | # Additionnal processing 42 | cur_min, cur_sec = to_minsec(i, header['frames_rate']) 43 | sys.stdout.write( 44 | "\x1b[2K\rPlaying {0:02}:{1:02} / {2:02}:{3:02}".format( 45 | cur_min, cur_sec, song_min, song_sec)) 46 | sys.stdout.flush() 47 | 48 | # Clear YM2149 registers 49 | fd.write('\x00'*16) 50 | fd.flush() 51 | print("") 52 | 53 | if __name__ == '__main__': 54 | main() 55 | -------------------------------------------------------------------------------- /uart.h: -------------------------------------------------------------------------------- 1 | /* 2 | * uart.h 3 | * 4 | * UART example for ATMega328P clocked at 16 MHz 5 | * 6 | * TODO :- 7 | * - Implement string read function 8 | * - Optimize for size 9 | * - Add helper routines and compile to .a file 10 | * 11 | * Created on: 22-Jan-2014 12 | * Author: Shrikant Giridhar 13 | */ 14 | 15 | #ifndef UART_H_ 16 | #define UART_H_ 17 | 18 | #include 19 | #include 20 | 21 | /* Probably already defined somewhere else. Define here, if isn't. */ 22 | #ifndef FOSC 23 | #define FOSC 16000000UL 24 | #endif 25 | 26 | /* Settings */ 27 | #define _BAUD 57600 // Baud rate (9600 is default) 28 | #define _DATA 0x03 // Number of data bits in frame = byte tranmission 29 | #define _UBRR (FOSC/16)/_BAUD - 1 // Used for UBRRL and UBRRH 30 | 31 | #define RX_BUFF 10 32 | 33 | /* Useful macros */ 34 | #define TX_START() UCSR0B |= _BV(TXEN0) // Enable TX 35 | #define TX_STOP() UCSR0B &= ~_BV(TXEN0) // Disable TX 36 | #define RX_START() UCSR0B |= _BV(RXEN0) // Enable RX 37 | #define RX_STOP() UCSR0B &= ~_BV(RXEN0) // Disable RX 38 | #define COMM_START() TX_START(); RX_START() // Enable communications 39 | 40 | /* Interrupt macros; Remember to set the GIE bit in SREG before using (see datasheet) */ 41 | #define RX_INTEN() UCSR0B |= _BV(RXCIE0) // Enable interrupt on RX complete 42 | #define RX_INTDIS() UCSR0B &= ~_BV(RXCIE0) // Disable RX interrupt 43 | #define TX_INTEN() UCSR0B |= _BV(TXCIE0) // Enable interrupt on TX complete 44 | #define TX_INTDIS() UCSR0B &= ~_BV(TXCIE0) // Disable TX interrupt 45 | 46 | /* Prototypes */ 47 | void initUART(void); 48 | uint8_t getByte(void); 49 | void putByte(unsigned char data); 50 | void writeString(const char *str); 51 | const char* readString(void); 52 | 53 | #endif /* UART_H_ */ 54 | -------------------------------------------------------------------------------- /ym2149.c: -------------------------------------------------------------------------------- 1 | #include "ym2149.h" 2 | 3 | // MSB (PB3) is connected to BDIR 4 | // LSB (PB2) is connected to BC1 5 | // +5V is connected to BC2 6 | #define DATA_READ (0x01 << 2) 7 | #define DATA_WRITE (0x02 << 2) 8 | #define ADDRESS_MODE (0x03 << 2) 9 | 10 | // Sets a 4MHz clock OC2A (PORTB3) 11 | void set_ym_clock(void) { 12 | DDRB |= 0x01 << PORTB3; 13 | // Toggle OC2A on compare match 14 | TCCR2A &= ~(0x01 << COM2A1); 15 | TCCR2A |= 0x01 << COM2A0; 16 | // Clear Timer on Compare match 17 | TCCR2B &= ~(0x01 << WGM22); 18 | TCCR2A |= 0x01 << WGM21; 19 | TCCR2A &= ~(0x01 << WGM20); 20 | // Use CLK I/O without prescaling 21 | TCCR2B &= ~(0x01 << CS22); 22 | TCCR2B &= ~(0x01 << CS21); 23 | TCCR2B |= 0x01 << CS20; 24 | // Divide the 16MHz clock by 8 -> 2MHz 25 | OCR2A = 3; 26 | } 27 | 28 | void set_bus_ctl(void) { 29 | DDRC |= 0x0c; // Bits 2 and 3 30 | } 31 | void set_data_out(void) { 32 | DDRC |= 0x03; // Bits 0 and 1 33 | DDRD |= 0xfc; // Bits 2 to 7 34 | } 35 | void set_data_in(void) { 36 | DDRC &= ~0x03; // Bits 0 and 1 37 | DDRD &= ~0xfc; // Bits 2 to 7 38 | } 39 | 40 | void set_address(char addr) { 41 | set_data_out(); 42 | PORTC = (PORTC & 0xf3) | ADDRESS_MODE; 43 | PORTC = (PORTC & 0xfc) | (addr & 0x03); // 2 first bits ont PORTC 44 | PORTD = (PORTD & 0x02) | (addr & 0xfc); // 6 last bits on PORTD 45 | _delay_us(1.); //tAS = 300ns 46 | PORTC = (PORTC & 0xf3) /*INACTIVE*/ ; 47 | _delay_us(1.); //tAH = 80ns 48 | } 49 | 50 | void set_data(char data) { 51 | set_data_out(); 52 | PORTC = (PORTC & 0xfc) | (data & 0x03); // 2 first bits ont PORTC 53 | PORTD = (PORTD & 0x02) | (data & 0xfc); // 6 last bits on PORTD 54 | PORTC = (PORTC & 0xf3) | DATA_WRITE; 55 | _delay_us(1.); // 300ns < tDW < 10us 56 | PORTC = (PORTC & 0xf3) /*INACTIVE*/ ; // To fit tDW max 57 | _delay_us(1.); // tDH = 80ns 58 | } 59 | 60 | char get_data(void) { 61 | char data; 62 | set_data_in(); 63 | PORTC = (PORTC & 0xf3) | DATA_READ; 64 | _delay_us(1.); // tDA = 400ns 65 | data = (PIND & 0xfc) | (PINB & 0x03); 66 | PORTC = (PORTC & 0xf3) /*INACTIVE*/ ; 67 | _delay_us(1.); // tTS = 100ns 68 | return data; 69 | } 70 | 71 | void send_data(char addr, char data) { 72 | set_address(addr); 73 | set_data(data); 74 | } 75 | 76 | char read_data(char addr) { 77 | set_address(addr); 78 | return get_data(); 79 | } 80 | -------------------------------------------------------------------------------- /uart.c: -------------------------------------------------------------------------------- 1 | /* 2 | * uart.c 3 | * 4 | * Asynchronous UART example tested on ATMega328P (16 MHz) 5 | * 6 | * Toolchain: avr-gcc (4.3.3) 7 | * Editor: Eclipse Kepler (4) 8 | * Usage: 9 | * Perform all settings in uart.h and enable by calling initUART(void) 10 | * Compile: 11 | * make all 12 | * 13 | * Functions: 14 | * - First call initUART() to set up Baud rate and frame format 15 | * - initUART() calls macros TX_START() and RX_START() automatically 16 | * - To enable interrupts on reception, call RX_INTEN() macros 17 | * - Call functions getByte() and putByte(char) for character I/O 18 | * - Call functions writeString(char*) and readString() for string I/O 19 | * 20 | * Created on: 21-Jan-2014 21 | * Author: Shrikant Giridhar 22 | */ 23 | 24 | #include "uart.h" 25 | 26 | // Debug Mode; comment out on Release 27 | //#define _DEBUG 0 28 | 29 | 30 | /*! \brief Configures baud rate (refer datasheet) */ 31 | void initUART(void) 32 | { 33 | // Not necessary; initialize anyway 34 | DDRD |= _BV(PD1); 35 | DDRD &= ~_BV(PD0); 36 | 37 | // Set baud rate; lower byte and top nibble 38 | UBRR0H = ((_UBRR) & 0xF00); 39 | UBRR0L = (uint8_t) ((_UBRR) & 0xFF); 40 | 41 | TX_START(); 42 | RX_START(); 43 | 44 | // Set frame format = 8-N-1 45 | UCSR0C = (_DATA << UCSZ00); 46 | 47 | } 48 | 49 | /*! \brief Returns a byte from the serial buffer 50 | * Use this function if the RX interrupt is not enabled. 51 | * Returns 0 on empty buffer 52 | */ 53 | uint8_t getByte(void) 54 | { 55 | // Check to see if something was received 56 | while (!(UCSR0A & _BV(RXC0))); 57 | return (uint8_t) UDR0; 58 | } 59 | 60 | 61 | /*! \brief Transmits a byte 62 | * Use this function if the TX interrupt is not enabled. 63 | * Blocks the serial port while TX completes 64 | */ 65 | void putByte(unsigned char data) 66 | { 67 | // Stay here until data buffer is empty 68 | while (!(UCSR0A & _BV(UDRE0))); 69 | UDR0 = (unsigned char) data; 70 | 71 | } 72 | 73 | /*! \brief Writes an ASCII string to the TX buffer */ 74 | void writeString(const char *str) 75 | { 76 | while (*str != '\0') 77 | { 78 | putByte(*str); 79 | ++str; 80 | } 81 | } 82 | 83 | const char* readString(void) 84 | { 85 | static char rxstr[RX_BUFF]; 86 | static char* temp; 87 | temp = rxstr; 88 | 89 | while((*temp = getByte()) != '\n') 90 | { 91 | ++temp; 92 | } 93 | 94 | return rxstr; 95 | } 96 | 97 | #if _DEBUG 98 | 99 | int main(void) 100 | { 101 | initUART(); 102 | while(1) 103 | { 104 | writeString(readString()); 105 | putByte('\r'); 106 | putByte('\n'); 107 | } 108 | return 0; 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /ymreader.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import itertools 3 | import logging 4 | import struct 5 | 6 | class YmReader(object): 7 | 8 | def __parse_extra_infos(self): 9 | # Thanks http://stackoverflow.com/questions/32774910/clean-way-to-read-a-null-terminated-c-style-string-from-a-file 10 | toeof = iter(functools.partial(self.__fd.read, 1), '') 11 | def readcstr(): 12 | return ''.join(itertools.takewhile('\0'.__ne__, toeof)) 13 | self.__header['song_name'] = readcstr() 14 | self.__header['author_name'] = readcstr() 15 | self.__header['song_comment'] = readcstr() 16 | 17 | def __parse_header(self): 18 | # See: 19 | # http://leonard.oxg.free.fr/ymformat.html 20 | # ftp://ftp.modland.com/pub/documents/format_documentation/Atari%20ST%20Sound%20Chip%20Emulator%20YM1-6%20(.ay,%20.ym).txt 21 | ym_header = '> 4s 8s I I H I H I H' 22 | s = self.__fd.read(struct.calcsize(ym_header)) 23 | d = {} 24 | (d['id'], 25 | d['check_string'], 26 | d['nb_frames'], 27 | d['song_attributes'], 28 | d['nb_digidrums'], 29 | d['chip_clock'], 30 | d['frames_rate'], 31 | d['loop_frame'], 32 | d['extra_data'], 33 | ) = struct.unpack(ym_header, s) 34 | 35 | if d['check_string'] != 'LeOnArD!': 36 | raise Exception('Unsupported file format: Bad check string: {}'.format(d['check_string'])) 37 | if d['id'] not in ('YM5!', 'YM6!'): 38 | raise Exception('Unsupported file format: Only YM5 supported (got {})'.format(d['id'])) 39 | if d['nb_digidrums'] != 0: 40 | raise Exception('Unsupported file format: Digidrums are not supported') 41 | if d['chip_clock'] not in (1000000, 2000000): 42 | raise Exception('Unsupported file format: Invalid chip clock: {}'.format(d['chip_clock'])) 43 | 44 | d['interleaved'] = d['song_attributes'] & 0x01 != 0 45 | self.__header = d 46 | 47 | self.__parse_extra_infos() 48 | 49 | def __read_data_interleaved(self): 50 | cnt = self.__header['nb_frames'] 51 | regs = [self.__fd.read(cnt) for i in range(16)] 52 | self.__data=[''.join(f) for f in zip(*regs)] 53 | 54 | def __read_data(self): 55 | if not self.__header['interleaved']: 56 | raise Exception( 57 | 'Unsupported file format: Only interleaved data are supported') 58 | self.__read_data_interleaved() 59 | 60 | def __check_eof(self): 61 | if self.__fd.read(4) != 'End!': 62 | logging.warning("*Warning* End! marker not found after frames") 63 | 64 | def __init__(self, fd): 65 | self.__fd = fd 66 | self.__parse_header() 67 | self.__data = [] 68 | 69 | def dump_header(self): 70 | for k in ('id','check_string', 'nb_frames', 'song_attributes', 71 | 'nb_digidrums', 'chip_clock', 'frames_rate', 'loop_frame', 72 | 'extra_data', 'song_name', 'author_name', 'song_comment'): 73 | print("{}: {}".format(k, self.__header[k])) 74 | 75 | def get_header(self): 76 | return self.__header 77 | 78 | def get_data(self): 79 | if not self.__data: 80 | self.__read_data() 81 | self.__check_eof() 82 | return self.__data 83 | --------------------------------------------------------------------------------