Floppy & HDD Music
- Войдите на сайт для отправки комментариев
Вс, 29/05/2016 - 15:17
Собственно, см. видео https://youtu.be/oq2bXXktJQs
Подробности чуть позже.
Собственно, см. видео https://youtu.be/oq2bXXktJQs
Подробности чуть позже.
Не понял, звук что-ли сами дисководы издают? Или звук от звуковой карты а на дисководах просто "цветомузыка" сделана?
Судя по звукам, шумит механика. По крайней мере, местами. Вот еще.
Нет, Женя, вместо динамиков используются именно дисководы. А вместо звуковой платы - Ардуино, настроенный на частоту прерываний 40 кГц.
Видео промежуточных этапов:
https://www.youtube.com/watch?v=VqNxzM-S5dk&lc=z12kslihsyapfhpxs04cjp5hv...
https://www.youtube.com/watch?v=k1sQ6k0-MXY
Проект представляет, по сути, музыкальную шкатулку, собранную на базе Ardiono Pro Mini, трех Floppy дисководов и двух накопителей на жестких дисках.
Все это располагается в корпусе теперь уже наверное винтажного ПК цвета слоновой кости. Блок питания - оттуда же: стандартный компьютерный.
Каждый floppy может воспроизводить один голос, а HDD - 2. Итого 7 голосов.
Не обошлось и без некоторой "фейковатости": один НЖМД расположен в корпусе от старого пишущего CDD. Индикаторы, задающие режимы работы, заменены индикаторами включения соответствующих голосов. Второй НЖМД находится в стандартном Mobile Rack'е, но индикаторам присвоемы другие функции - как у первомго НЖМД.
FDD взяты без переделки, но используются только три сигнала: "выбор устройства", "направление" и "шаг". Собственно, шаговый двигатель с головкой и излучают звук. В процесе экспериментов выяснилось, что наиболее грмкий и отчетливый звук (понятное дело, разработчики, наоборот, стремятся сделать свои устройства как можно тише) достигается, если разрешить дисководу перемещаться только в пределах двух (а не 40-80) дорожек. Правда, появляется призвук октавой ниже, но для данного проекта это, возможно, даже плюс.
А вот HDD подверглись серьезной переделке:
- удалена плата контроллера,
- удалены блины и мотор,
- удалены некоторые конструктивные элементы, препятствующие свободному перемещению коромысла.
В И-нете аналогичные проекты попадаются не так уж редко, но, почему-то, везде рекомендуется подпаивать подводящие провода непосредственно к контактам катушки коромысла, хотя коромысло уже соединено с неподвижной колодкой гибким шлейфом, который заодно служит упругим элементом, позиционирующим головку примерно в середину диапазона свободного хода. Я подсоединялся к неподвижному концу шлейфа, в одном случае даже удалось использовать съемный контакт, выпаянный из платы контроллера.
Звук в HDD излучает коромысло, катушка которого имеет сопротивление порядка 7-9 Ом, т.е. такое же, как у стандартного динамика. Поэтому и сигнал на нее подавался со стандартного УМЗЧ, собранного на TDA2004, схема которого была слегка доработана, исходя из особенностей применения.
В качестве радиатора УМЗЧ использовался массивный корпус HDD. Естественно, с использованием теплопроводящей пасты. Усилитель питался от 12В, а индикаторы - от 5В, т.к. активному режиму соответствует низкий уровень (это стандартно для цифровой электроники и для FDD в частности, поэтому было решено не отказываться от этого стандарта и при переделке HDD). Такой подход требует подачи на устройство напряжения 5В, чего можно было бы избежать, если бы активный сигнал был высоким. Но т.к. питание все равно осуществлялось через стандартный molex, было решено оставить, как есть.
Для управления переделанным HDD используется 4 контакта: "звук 1-го канала", "выбор 1-го канала", "звук 2-го канала" и "выбор 2-го канала". "Звук" подается на входы усилителя, а катушка коромысла подключена по "мостовой" схеме. "Выбор" используется исключительно для зажигания светодиодов.
Исходники:
Основная программа:
#include <TimerOne.h> #include "Arhat_digOut.c" #include "voice_0.c" #include "voice_1.c" #include "voice_2.c" #include "voice_3.c" #include "voice_4.c" #include "voice_5.c" #include "voice_6.c" // change interrupt period to 25us (40kHz) insteadо 40us (25kHz) // minimum frequence for 3.5" floppy is 440-520 Hz // channel 0, vely low notes: 5.25 floppy - up to () #define pin_sound_0 8 //2 // sound pin, for floppy - step #define pin_dir_0 9 //3 // step direction pin, for floppy only #define pin_active_0 10 //4 // for LED & activate floppy drive // channel 1, low notes: 3.5 floppy - up to C1-F1 (261-347 Hz) #define pin_sound_1 5 // sound pin, for floppy - step #define pin_dir_1 6 // step direction pin, for floppy only #define pin_active_1 7 // for LED & activate floppy drive // channel 2, low notes: 3.5 floppy: up to C1-F1 (261-347 Hz) #define pin_sound_2 2 //8 // sound pin, for floppy - step #define pin_dir_2 3 //9 // step direction pin, for floppy only #define pin_active_2 4 //10 // for LED & activate floppy drive // channel 3, high notes: 1-st HDD 1-st channel - not limited #define pin_sound_3 11 // sound pin, for HDD - sound amplifier #define pin_active_3 12 // for LED only // channel 4, high notes: 1-st HDD 2-nd channel - not limited #define pin_sound_4 13 // sound pin, for HDD - sound amplifier #define pin_active_4 14 // for LED only // channel 5, high notes: 2-nd HDD 1-st channel - not limited #define pin_sound_5 15 // sound pin, for HDD - sound amplifier #define pin_active_5 16 // for LED only // channel 6, high notes: 2-nd HDD 2-nd channel - not limited #define pin_sound_6 17 // sound pin, for HDD - sound amplifier #define pin_active_6 18 // for LED only #define pin_button_0 19 // for button input #define RESOLUTION 25 //Microsecond resolution for notes (interrupt frequency 40 kHz) #define FREQUENCE 1000/RESOLUTION // interrupt frequence in kHz byte MAX_POSITION_0 = 2; //74; // max head position: double number of track without 2, for 5.25 - 40 tracks byte MAX_POSITION_1 = 2; //154; // max head position: double number of track without 2, for 3.5 - 80 tracks byte MAX_POSITION_2 = 2; //154; // max head position: double number of track without 2, for 3.5 - 80 tracks byte currentPosition[] = {0, 0, 0}; // head position (track number) char currentStateAdd_0 = 1; // addition for currentPosition -1 or +1 (track) char currentStateAdd_1 = 1; char currentStateAdd_2 = 1; byte currentStateStep[] = {0,0,0,0,0,0,0}; // positive or negative half period unsigned int currentPeriodA[] = {0,0,0,0,0,0,0}; // positive half period 0 = off. unsigned int currentPeriodB[] = {0,0,0,0,0,0,0}; // negative half period 0 = off. unsigned int currentTick_0 = 1; // sound half-perion counter (from currentPeriod*[] to 0) unsigned int currentTick_1 = 1; unsigned int currentTick_2 = 1; unsigned int currentTick_3 = 1; unsigned int currentTick_4 = 1; unsigned int currentTick_5 = 1; unsigned int currentTick_6 = 1; unsigned long startTime; void setup(){ Serial.begin(115200); for(byte i = 2; i <= 18; i++) pinMode(i, OUTPUT); // 0-1 - Serial, 2-10 - floppy control, 11-18 - HDD control, 19(,A6,A7) - buttons pinMode(A5, INPUT_PULLUP); resetAll(); Timer1.initialize(RESOLUTION); // Set up a timer at the defined resolution Timer1.attachInterrupt(tick); // Attach the tick function resetAll(); delay(2000); startTime = millis(); } void loop(){ unsigned long currTime = millis() - startTime; static unsigned int index[7] = {0,0,0,0,0,0,0}; // array index of the note of each channels static byte melodyOver[7] = {0,0,0,0,0,0,0}; if(!melodyOver[0]) { if(pgm_read_word(&bach_ch_0[index[0] << 1]) <= currTime/tick_0) { // even values in arrays are start time unsigned int pp = pgm_read_word(&bach_ch_0[(index[0] << 1) + 1]); // and odd are note period. 0 - pause, 65535 - end if(pp == 65535){ pp = 0; melodyOver[0] = 1; } currentPeriodA[0] = pp/2; currentPeriodB[0] = pp - pp/2; currentTick_0 = currentPeriodA[0]; if(pp != 0) { digitalWrite(pin_active_0, LOW); } else { digitalWrite(pin_active_0, HIGH); digitalWrite(pin_sound_0, LOW); } index[0]++; } } if(!melodyOver[1]) { if(pgm_read_word(&bach_ch_1[index[1] << 1]) <= currTime/tick_1) { unsigned int pp = pgm_read_word(&bach_ch_1[(index[1] << 1) + 1]); if(pp == 65535){ pp = 0; melodyOver[1] = 1; } currentPeriodA[1] = pp/2; currentPeriodB[1] = pp - pp/2; currentTick_1 = currentPeriodA[1]; if(pp != 0) { digitalWrite(pin_active_1, LOW); } else { digitalWrite(pin_active_1, HIGH); digitalWrite(pin_sound_1, LOW); } index[1]++; } } if(!melodyOver[2]) { if(pgm_read_word(&bach_ch_2[index[2] << 1]) <= currTime/tick_2) { unsigned int pp = pgm_read_word(&bach_ch_2[(index[2] << 1) + 1]); if(pp == 65535){ pp = 0; melodyOver[2] = 1; } currentPeriodA[2] = pp/2; currentPeriodB[2] = pp - pp/2; currentTick_2 = currentPeriodA[2]; if(pp != 0) { digitalWrite(pin_active_2, LOW); } else { digitalWrite(pin_active_2, HIGH); digitalWrite(pin_sound_2, LOW); } index[2]++; } } if(!melodyOver[3]) { if(pgm_read_word(&bach_ch_3[index[3] << 1]) <= currTime/tick_3) { unsigned int pp = pgm_read_word(&bach_ch_3[(index[3] << 1) + 1]); if(pp == 65535){ pp = 0; melodyOver[3] = 1; } currentPeriodA[3] = pp/2; currentPeriodB[3] = pp - pp/2; currentTick_3 = currentPeriodA[3]; if(pp != 0) { digitalWrite(pin_active_3, LOW); } else { digitalWrite(pin_active_3, HIGH); digitalWrite(pin_sound_3, LOW); } index[3]++; } } if(!melodyOver[4]) { if(pgm_read_word(&bach_ch_4[index[4] << 1]) <= currTime/tick_4) { unsigned int pp = pgm_read_word(&bach_ch_4[(index[4] << 1) + 1]); if(pp == 65535){ pp = 0; melodyOver[4] = 1; } currentPeriodA[4] = pp/2; currentPeriodB[4] = pp - pp/2; currentTick_4 = currentPeriodA[4]; if(pp != 0) { digitalWrite(pin_active_4, LOW); } else { digitalWrite(pin_active_4, HIGH); digitalWrite(pin_sound_4, LOW); } index[4]++; } } if(!melodyOver[5]) { if(pgm_read_word(&bach_ch_5[index[5] << 1]) <= currTime/tick_5) { unsigned int pp = pgm_read_word(&bach_ch_5[(index[5] << 1) + 1]); if(pp == 65535){ pp = 0; melodyOver[5] = 1; } currentPeriodA[5] = pp/2; currentPeriodB[5] = pp - pp/2; currentTick_5 = currentPeriodA[5]; if(pp != 0) { digitalWrite(pin_active_5, LOW); } else { digitalWrite(pin_active_5, HIGH); digitalWrite(pin_sound_5, LOW); } index[5]++; } } if(!melodyOver[6]) { if(pgm_read_word(&bach_ch_6[index[6] << 1]) <= currTime/tick_6) { unsigned int pp = pgm_read_word(&bach_ch_6[(index[6] << 1) + 1]); if(pp == 65535){ pp = 0; melodyOver[6] = 1; } currentPeriodA[6] = pp/2; currentPeriodB[6] = pp - pp/2; currentTick_6 = currentPeriodA[6]; if(pp != 0) { digitalWrite(pin_active_6, LOW); } else { digitalWrite(pin_active_6, HIGH); digitalWrite(pin_sound_6, LOW); } index[6]++; } } } /* Called by the timer inturrupt at the specified resolution. */ void tick() // elapsed time ~12us (~50%) { // If there is a period set for control pin 2, count the number of // ticks that pass, and toggle the pin if the current period is reached. ///////////////////////////////////////////////////////////////////////////// if (currentPeriodA[0]){ // if the note is on currentTick_0--; // decrease tick counter if (currentTick_0 <= 0){ // half-period has been finished if (currentPosition[0] >= MAX_POSITION_0) { // Switch head directions if end has been reached currentStateAdd_0 = -1; pinOutHigh(pin_dir_0); } else if (currentPosition[0] <= 0) { currentStateAdd_0 = 1; pinOutLow(pin_dir_0); } currentPosition[0] += currentStateAdd_0; //Update currentPosition if(currentStateStep[0]) { pinOutHigh(pin_sound_0); //Pulse the control pin currentStateStep[0] = LOW; currentTick_0 = currentPeriodA[0]; } else { pinOutLow(pin_sound_0); //Pulse the control pin currentStateStep[0] = HIGH; currentTick_0 = currentPeriodB[0]; } } } if (currentPeriodA[1]){ currentTick_1--; if (currentTick_1 <= 0){ if (currentPosition[1] >= MAX_POSITION_1) { currentStateAdd_1 = -1; pinOutHigh(pin_dir_1); } else if (currentPosition[1] <= 0) { currentStateAdd_1 = 1; pinOutLow(pin_dir_1); } currentPosition[1] += currentStateAdd_1; //Update currentPosition if(currentStateStep[1]) { pinOutHigh(pin_sound_1); currentStateStep[1] = LOW; currentTick_1 = currentPeriodA[1]; } else { pinOutLow(pin_sound_1); //Pulse the control pin currentStateStep[1] = HIGH; currentTick_1 = currentPeriodB[1]; } } } if (currentPeriodA[2]){ currentTick_2--; if (currentTick_2 <= 0){ if (currentPosition[2] >= MAX_POSITION_2) { currentStateAdd_2 = -1; pinOutHigh(pin_dir_2); } else if (currentPosition[2] <= 0) { currentStateAdd_2 = 1; pinOutLow(pin_dir_2); } currentPosition[2] += currentStateAdd_2; //Update currentPosition if(currentStateStep[2]) { pinOutHigh(pin_sound_2); //Pulse the control pin currentStateStep[2] = LOW; currentTick_2 = currentPeriodA[2]; } else { pinOutLow(pin_sound_2); //Pulse the control pin currentStateStep[2] = HIGH; currentTick_2 = currentPeriodB[2]; } } } if (currentPeriodA[3]){ currentTick_3--; if (currentTick_3 <= 0){ if(currentStateStep[3]) { pinOutHigh(pin_sound_3); //Pulse the control pin currentStateStep[3] = LOW; currentTick_3 = currentPeriodA[3]; } else { pinOutLow(pin_sound_3); //Pulse the control pin currentStateStep[3] = HIGH; currentTick_3 = currentPeriodB[3]; } } } if (currentPeriodA[4]){ currentTick_4--; if (currentTick_4 <= 0){ if(currentStateStep[4]) { pinOutHigh(pin_sound_4); //Pulse the control pin currentStateStep[4] = LOW; currentTick_4 = currentPeriodA[4]; } else { pinOutLow(pin_sound_4); //Pulse the control pin currentStateStep[4] = HIGH; currentTick_4 = currentPeriodB[4]; } } } if (currentPeriodA[5]){ currentTick_5--; if (currentTick_5 <= 0){ if(currentStateStep[5]) { pinOutHigh(pin_sound_5); //Pulse the control pin currentStateStep[5] = LOW; currentTick_5 = currentPeriodA[5]; } else { pinOutLow(pin_sound_5); //Pulse the control pin currentStateStep[5] = HIGH; currentTick_5 = currentPeriodB[5]; } } } if (currentPeriodA[6]){ currentTick_6--; if (currentTick_6 <= 0){ if(currentStateStep[6]) { pinOutHigh(pin_sound_6); //Pulse the control pin currentStateStep[6] = LOW; currentTick_6 = currentPeriodA[6]; } else { pinOutLow(pin_sound_6); //Pulse the control pin currentStateStep[6] = HIGH; currentTick_6 = currentPeriodB[6]; } } } } //Resets all the pins void resetAll(){ pinOutHigh(pin_dir_0); // reverse direction pinOutHigh(pin_dir_1); pinOutHigh(pin_dir_2); pinOutLow(pin_active_0); // drive enable pinOutLow(pin_active_1); pinOutLow(pin_active_2); // New all-at-once reset for (byte s=0;s<80;s++){ // For max drive's position pinOutHigh(pin_sound_0); pinOutHigh(pin_sound_1); pinOutHigh(pin_sound_2); delay(3); pinOutLow(pin_sound_0); pinOutLow(pin_sound_1); pinOutLow(pin_sound_2); delay(3); } currentPosition[0] = 0; // We're reset. currentPosition[1] = 0; // We're reset. currentPosition[2] = 0; // We're reset. pinOutLow(pin_dir_0); // forward direction pinOutLow(pin_dir_1); pinOutLow(pin_dir_2); pinOutHigh(pin_active_0); // drive disable pinOutHigh(pin_active_1); pinOutHigh(pin_active_2); pinOutHigh(pin_active_3); pinOutHigh(pin_active_4); pinOutHigh(pin_active_5); pinOutHigh(pin_active_6); // 2 steps forward for (byte s=0;s<2;s++){ // For max drive's position pinOutHigh(pin_sound_0); pinOutHigh(pin_sound_1); pinOutHigh(pin_sound_2); delay(3); pinOutLow(pin_sound_0); pinOutLow(pin_sound_1); pinOutLow(pin_sound_2); delay(3); } }Файл с описанием команд быстрого вывода в порт (спасибо Arhat)
Файлы с данными
#include <avr/pgmspace.h> #define tick_0 2 #define NumNotes_0 2 // Sound Sample Rate: 40000 Hz const unsigned int bach_ch_0[] PROGMEM = { 8640, 545, 14385, 65535 };#include <avr/pgmspace.h> #define tick_1 2 #define NumNotes_1 54 // Sound Sample Rate: 40000 Hz const unsigned int bach_ch_1[] PROGMEM = { 10, 91, 40, 0, 55, 102, 85, 0, 105, 91, 960, 0, 970, 102, 1000, 0, 1025, 115, 1055, 0, 1070, 121, 1100, 0, 1105, 136, 1140, 0, 1160, 144, 1675, 0, 1715, 136, 2015, 0, 2895, 182, 2920, 0, 2935, 204, 2970, 0, 2985, 182, 3780, 0, 3860, 243, 4015, 0, 4040, 229, 4200, 0, 4220, 289, 4375, 0, 4390, 272, 4760, 0, 5775, 364, 5810, 0, 5820, 408, 5855, 0, 5870, 364, 6715, 0, 6740, 408, 6765, 0, 6785, 458, 6805, 0, 6820, 485, 6850, 0, 6870, 545, 6895, 0, 6910, 577, 7440, 0, 7465, 545, 7775, 0, 9135, 289, 12465, 0, 12480, 272, 14385, 65535 };#include <avr/pgmspace.h> #define tick_2 2 #define NumNotes_2 52 // Sound Sample Rate: 40000 Hz const unsigned int bach_ch_2[] PROGMEM = { 10, 45, 50, 0, 65, 51, 95, 0, 105, 45, 955, 0, 980, 51, 1010, 0, 1020, 57, 1055, 0, 1070, 61, 1100, 0, 1105, 68, 1145, 0, 1150, 72, 1680, 0, 1705, 68, 2015, 0, 2900, 91, 2930, 0, 2935, 102, 2975, 0, 2980, 91, 3785, 0, 3860, 121, 4020, 0, 4035, 115, 4205, 0, 4210, 144, 4385, 0, 4400, 136, 4755, 0, 5775, 182, 5810, 0, 5815, 204, 5865, 0, 5870, 182, 6715, 0, 6740, 204, 6780, 0, 6775, 229, 6820, 0, 6830, 243, 6860, 0, 6875, 272, 6905, 0, 6910, 289, 7440, 0, 7465, 272, 7770, 0, 9600, 243, 12465, 65535 };#include <avr/pgmspace.h> #define tick_3 2 #define NumNotes_3 7 // Sound Sample Rate: 40000 Hz const unsigned int bach_ch_3[] PROGMEM = { 10095, 204, 12405, 0, 12480, 204, 12945, 0, 12975, 243, 13440, 216, 14385, 65535 };#include <avr/pgmspace.h> #define tick_4 2 #define NumNotes_4 4 // Sound Sample Rate: 40000 Hz const unsigned int bach_ch_4[] PROGMEM = { 10560, 172, 12465, 0, 12480, 182, 14385, 65535 };#include <avr/pgmspace.h> #define tick_5 2 #define NumNotes_5 54 // Sound Sample Rate: 40000 Hz const unsigned int bach_ch_5[] PROGMEM = { 0, 91, 30, 0, 45, 102, 75, 0, 90, 91, 945, 0, 960, 102, 990, 0, 1005, 115, 1035, 0, 1050, 121, 1080, 0, 1095, 136, 1125, 0, 1140, 144, 1665, 0, 1695, 136, 1995, 0, 2880, 182, 2910, 0, 2925, 204, 2955, 0, 2970, 182, 3765, 0, 3840, 243, 4005, 0, 4020, 229, 4185, 0, 4200, 289, 4365, 0, 4380, 272, 4740, 0, 5760, 364, 5790, 0, 5805, 408, 5835, 0, 5850, 364, 6705, 0, 6720, 408, 6750, 0, 6765, 458, 6795, 0, 6810, 485, 6840, 0, 6855, 545, 6885, 0, 6900, 577, 7425, 0, 7455, 545, 7755, 0, 11055, 144, 12465, 0, 12480, 136, 14385, 65535 };#include <avr/pgmspace.h> #define tick_6 2 #define NumNotes_6 52 // Sound Sample Rate: 40000 Hz const unsigned int bach_ch_6[] PROGMEM = { 0, 45, 30, 0, 45, 51, 85, 0, 90, 45, 945, 0, 960, 51, 1000, 0, 1005, 57, 1045, 0, 1050, 61, 1090, 0, 1095, 68, 1135, 0, 1140, 72, 1665, 0, 1695, 68, 1995, 0, 2880, 91, 2910, 0, 2925, 102, 2965, 0, 2970, 91, 3765, 0, 3840, 121, 4005, 0, 4020, 115, 4185, 0, 4200, 144, 4365, 0, 4380, 136, 4740, 0, 5760, 182, 5790, 0, 5805, 204, 5845, 0, 5850, 182, 6705, 0, 6720, 204, 6760, 0, 6765, 229, 6805, 0, 6810, 243, 6850, 0, 6855, 272, 6895, 0, 6900, 289, 7425, 0, 7455, 272, 7755, 0, 11520, 121, 12480, 65535 };Ахренеть!
Снимаю шляпу!
крутотень! а Ду Хаст можно так сделать?
В принципе можно сделать что угодно - 7 голосов достаточно для вменяемой аранжировки любого произведения. В частности, в том фрагменте, что на видео, у Баха звучит максимум 9 нот, так что кое-где пришлось подсократить октаву.
С другой стороны, звук принципиально без динамики. Так что впечатление от результата может быть делеким от желаемого. Наиболее адекватно звучат органные партии.
понял, спасибо за ответ.
примерно так и подумал (про органные партии).